metheus-governance-mcp-cli 0.2.261 → 0.2.263
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 +32 -26
- package/lib/runner-delivery.mjs +22 -0
- package/lib/runner-orchestration.mjs +85 -0
- package/lib/runner-runtime.mjs +244 -39
- package/lib/selftest-runner-scenarios.mjs +130 -5
- package/lib/selftest-telegram-e2e.mjs +170 -8
- package/package.json +1 -1
package/cli.mjs
CHANGED
|
@@ -13302,21 +13302,27 @@ async function maybeDeliverRunnerExecutionFailureAfterRecord({
|
|
|
13302
13302
|
|
|
13303
13303
|
async function finalizeRunnerDeferredExecutionProcessed(deferredExecution, processed) {
|
|
13304
13304
|
if (String(deferredExecution?.requestKey || "").trim()) {
|
|
13305
|
-
const resolvedIntentType = String(
|
|
13306
|
-
safeObject(loadBotRunnerState().routes[deferredExecution.routeKey]).last_intent_type || "",
|
|
13307
|
-
).trim();
|
|
13308
|
-
|
|
13309
|
-
|
|
13310
|
-
|
|
13311
|
-
|
|
13312
|
-
|
|
13313
|
-
|
|
13314
|
-
|
|
13315
|
-
|
|
13316
|
-
|
|
13317
|
-
|
|
13318
|
-
|
|
13319
|
-
|
|
13305
|
+
const resolvedIntentType = String(
|
|
13306
|
+
safeObject(loadBotRunnerState().routes[deferredExecution.routeKey]).last_intent_type || "",
|
|
13307
|
+
).trim();
|
|
13308
|
+
const normalizedDeferredOutcome = processed.kind === "delivery_failed"
|
|
13309
|
+
? "delivery_failed_after_generation"
|
|
13310
|
+
: processed.kind === "skipped"
|
|
13311
|
+
? "skipped"
|
|
13312
|
+
: String(processed.result?.outcome || "replied").trim().toLowerCase();
|
|
13313
|
+
markRunnerRequestLifecycle({
|
|
13314
|
+
normalizedRoute: deferredExecution.normalizedRoute,
|
|
13315
|
+
requestKey: deferredExecution.requestKey,
|
|
13316
|
+
selectedRecord: deferredExecution.selectedRecord,
|
|
13317
|
+
routeKey: deferredExecution.routeKey,
|
|
13318
|
+
outcome: normalizedDeferredOutcome,
|
|
13319
|
+
closedReason: processed.kind === "skipped"
|
|
13320
|
+
? String(processed.skippedRecord?.reason || processed.result?.detail || "skipped").trim()
|
|
13321
|
+
: "",
|
|
13322
|
+
conversationIDRaw: String(processed.result?.conversation_id || "").trim(),
|
|
13323
|
+
conversationParticipants: ensureArray(processed.result?.conversation_participants),
|
|
13324
|
+
conversationInitialResponders: ensureArray(processed.result?.conversation_initial_responders),
|
|
13325
|
+
allowedResponders: ensureArray(processed.result?.conversation_allowed_responders),
|
|
13320
13326
|
conversationLeadBot: String(processed.result?.conversation_lead_bot || "").trim(),
|
|
13321
13327
|
conversationSummaryBot: String(processed.result?.conversation_summary_bot || "").trim(),
|
|
13322
13328
|
conversationAllowBotToBot: processed.result?.conversation_allow_bot_to_bot === true,
|
|
@@ -13349,17 +13355,17 @@ async function finalizeRunnerDeferredExecutionProcessed(deferredExecution, proce
|
|
|
13349
13355
|
lastReplyMessageID: intFromRawAllowZero(processed.result?.last_reply_message_id, 0),
|
|
13350
13356
|
lastReplyMessageThreadID: intFromRawAllowZero(processed.result?.last_reply_message_thread_id, 0),
|
|
13351
13357
|
replyToMessageID: intFromRawAllowZero(processed.result?.reply_to_message_id, 0),
|
|
13352
|
-
replyFallbackUsed: processed.result?.reply_fallback_used === true,
|
|
13353
|
-
authoritativeSourceMessageEnvelope: safeObject(
|
|
13354
|
-
safeObject(deferredExecution.humanInboundVisibility).sourceMessageEnvelope,
|
|
13355
|
-
),
|
|
13356
|
-
});
|
|
13357
|
-
if (processed.kind !== "delivery_failed") {
|
|
13358
|
-
await ensureRunnerRootWorkItemForRequest({
|
|
13359
|
-
normalizedRoute: deferredExecution.normalizedRoute,
|
|
13360
|
-
routeKey: deferredExecution.routeKey,
|
|
13361
|
-
selectedRecord: deferredExecution.selectedRecord,
|
|
13362
|
-
runtime: deferredExecution.runtime,
|
|
13358
|
+
replyFallbackUsed: processed.result?.reply_fallback_used === true,
|
|
13359
|
+
authoritativeSourceMessageEnvelope: safeObject(
|
|
13360
|
+
safeObject(deferredExecution.humanInboundVisibility).sourceMessageEnvelope,
|
|
13361
|
+
),
|
|
13362
|
+
});
|
|
13363
|
+
if (processed.kind !== "delivery_failed" && processed.kind !== "skipped") {
|
|
13364
|
+
await ensureRunnerRootWorkItemForRequest({
|
|
13365
|
+
normalizedRoute: deferredExecution.normalizedRoute,
|
|
13366
|
+
routeKey: deferredExecution.routeKey,
|
|
13367
|
+
selectedRecord: deferredExecution.selectedRecord,
|
|
13368
|
+
runtime: deferredExecution.runtime,
|
|
13363
13369
|
requestKey: deferredExecution.requestKey,
|
|
13364
13370
|
});
|
|
13365
13371
|
}
|
package/lib/runner-delivery.mjs
CHANGED
|
@@ -187,6 +187,17 @@ function assertLocalTelegramReplyAnchor({
|
|
|
187
187
|
}
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
+
function buildLocalDeliveryError(message, code, extra = {}) {
|
|
191
|
+
const error = new Error(message);
|
|
192
|
+
error.code = String(code || "").trim();
|
|
193
|
+
Object.assign(error, safeObject(extra));
|
|
194
|
+
return error;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function isTelegramReplyTargetMissingError(detail) {
|
|
198
|
+
return /message to be replied not found/i.test(String(detail || ""));
|
|
199
|
+
}
|
|
200
|
+
|
|
190
201
|
function throwLocalDeliveryFailure({
|
|
191
202
|
provider,
|
|
192
203
|
delivery,
|
|
@@ -198,6 +209,17 @@ function throwLocalDeliveryFailure({
|
|
|
198
209
|
const errorDetail =
|
|
199
210
|
String(responseJSON.description || responseJSON.error || JSON.stringify(delivery.body || "")).trim()
|
|
200
211
|
|| `${normalizedProvider} api status ${delivery.statusCode}`;
|
|
212
|
+
if (normalizedProvider === "telegram" && isTelegramReplyTargetMissingError(errorDetail)) {
|
|
213
|
+
throw buildLocalDeliveryError(
|
|
214
|
+
`local ${normalizedProvider} delivery skipped stale reply anchor (${errorDetail})`,
|
|
215
|
+
"TELEGRAM_STALE_REPLY_ANCHOR",
|
|
216
|
+
{
|
|
217
|
+
staleReplyAnchor: true,
|
|
218
|
+
provider: normalizedProvider,
|
|
219
|
+
detail: errorDetail,
|
|
220
|
+
},
|
|
221
|
+
);
|
|
222
|
+
}
|
|
201
223
|
if (delivery.statusCode === 401 || /unauthorized|invalid_auth/i.test(errorDetail)) {
|
|
202
224
|
throw new Error(
|
|
203
225
|
`local ${normalizedProvider} delivery failed (Unauthorized). Check ${String(providerEnv.tokenKey || providerEnvConfig(normalizedProvider).tokenKey)} in ${providerEnv.filePath}`,
|
|
@@ -7362,6 +7362,91 @@ export async function processRunnerSelectedRecord({
|
|
|
7362
7362
|
});
|
|
7363
7363
|
} catch (err) {
|
|
7364
7364
|
const transportError = String(err?.message || err).trim() || "delivery_failed";
|
|
7365
|
+
const staleReplyAnchor = err?.staleReplyAnchor === true
|
|
7366
|
+
|| String(err?.code || "").trim() === "TELEGRAM_STALE_REPLY_ANCHOR";
|
|
7367
|
+
if (staleReplyAnchor) {
|
|
7368
|
+
saveRunnerRouteState(
|
|
7369
|
+
routeKey,
|
|
7370
|
+
buildRunnerRouteStateFromComment(selectedRecord, {
|
|
7371
|
+
last_action: "stale_reply_anchor_skipped",
|
|
7372
|
+
last_reason: transportError,
|
|
7373
|
+
last_trigger: String(effectiveTriggerDecision.trigger || "").trim(),
|
|
7374
|
+
last_conversation_id: String(effectiveConversationContext?.id || "").trim(),
|
|
7375
|
+
last_conversation_stage: String(effectiveConversationContext?.stage || "").trim(),
|
|
7376
|
+
last_contract_validation_status: String(responseContractValidation.status || "").trim(),
|
|
7377
|
+
last_contract_validation_reason: String(responseContractValidation.reason || "").trim(),
|
|
7378
|
+
last_contract_validation_targets: ensureArray(responseContractValidation.targets),
|
|
7379
|
+
last_assignment_validation_status: String(assignmentExecutionValidation.status || "").trim(),
|
|
7380
|
+
last_assignment_validation_reason: String(assignmentExecutionValidation.reason || "").trim(),
|
|
7381
|
+
last_speaker_bot_username: normalizeMentionSelector(bot?.username || bot?.name),
|
|
7382
|
+
last_workspace_dir: String(effectiveExecutionPlan.workspaceDir || "").trim(),
|
|
7383
|
+
...responderStatePatch,
|
|
7384
|
+
...intentStatePatch,
|
|
7385
|
+
...visibilityStatePatch,
|
|
7386
|
+
}),
|
|
7387
|
+
);
|
|
7388
|
+
return {
|
|
7389
|
+
kind: "skipped",
|
|
7390
|
+
skippedRecord: {
|
|
7391
|
+
id: selectedRecord.id,
|
|
7392
|
+
reason: "stale_reply_anchor",
|
|
7393
|
+
messageID: intFromRawAllowZero(selectedRecord?.parsedArchive?.messageID, 0),
|
|
7394
|
+
diagnosticType: "delivery",
|
|
7395
|
+
},
|
|
7396
|
+
result: {
|
|
7397
|
+
route_key: routeKey,
|
|
7398
|
+
route_name: normalizedRoute.name,
|
|
7399
|
+
outcome: "skipped",
|
|
7400
|
+
detail: `skipped stale reply anchor (${transportError})`,
|
|
7401
|
+
thread_id: archiveThread.threadID,
|
|
7402
|
+
comment_id: selectedRecord.id,
|
|
7403
|
+
trigger_kind: String(effectiveTriggerDecision.trigger || "").trim(),
|
|
7404
|
+
conversation_id: String(effectiveConversationContext?.id || "").trim(),
|
|
7405
|
+
conversation_stage: String(effectiveConversationContext?.stage || "").trim(),
|
|
7406
|
+
conversation_intent_mode: String(effectiveConversationContext?.intentMode || "").trim(),
|
|
7407
|
+
conversation_lead_bot: String(effectiveConversationContext?.leadBotUsername || "").trim(),
|
|
7408
|
+
conversation_summary_bot: String(effectiveConversationContext?.summaryBotUsername || "").trim(),
|
|
7409
|
+
conversation_participants: ensureArray(effectiveConversationContext?.participantSelectors),
|
|
7410
|
+
conversation_initial_responders: ensureArray(effectiveConversationContext?.initialResponderSelectors),
|
|
7411
|
+
conversation_allowed_responders: ensureArray(effectiveConversationContext?.allowedResponderSelectors),
|
|
7412
|
+
conversation_allow_bot_to_bot: effectiveConversationContext?.allowBotToBot === true,
|
|
7413
|
+
conversation_reply_expectation: String(effectiveConversationContext?.replyExpectation || "").trim(),
|
|
7414
|
+
execution_contract_type: String(executionContract?.type || "").trim(),
|
|
7415
|
+
execution_contract_actionable: executionContract?.actionable === true,
|
|
7416
|
+
execution_contract_targets: ensureArray(executionContract?.assignments).map((item) => normalizeMentionSelector(item.targetBot)).filter(Boolean),
|
|
7417
|
+
next_expected_responders: collectExecutionContractNextResponders(executionContract),
|
|
7418
|
+
normalized_execution_contract_type: normalizedExecutionContractType,
|
|
7419
|
+
normalized_execution_contract_targets: normalizedExecutionTargets,
|
|
7420
|
+
normalized_execution_next_responders: normalizedExecutionNextResponders,
|
|
7421
|
+
artifact_validation: String(artifactValidation.status || "").trim() || "none",
|
|
7422
|
+
artifact_paths: summarizeValidatedArtifactPaths(artifactValidation),
|
|
7423
|
+
ai_reply_generated: true,
|
|
7424
|
+
ai_reply_generated_at: aiReplyGeneratedAt,
|
|
7425
|
+
ai_reply_preview: aiReplyPreview,
|
|
7426
|
+
source_message_envelope: sourceMessageEnvelope,
|
|
7427
|
+
attempted_delivery_envelope: attemptedDeliveryEnvelope,
|
|
7428
|
+
visibility_status: String(humanInboundVisibility.visibilityStatus || "").trim(),
|
|
7429
|
+
visibility_reason: String(humanInboundVisibility.visibilityReason || "").trim(),
|
|
7430
|
+
visibility_source: String(humanInboundVisibility.visibilitySource || "").trim(),
|
|
7431
|
+
reply_to_message_id: replyToMessageID,
|
|
7432
|
+
reply_message_thread_id: replyMessageThreadID,
|
|
7433
|
+
delivery_status: "skipped_stale_reply_anchor",
|
|
7434
|
+
archive_status: "not_attempted",
|
|
7435
|
+
transport_error: transportError,
|
|
7436
|
+
archive_error: "",
|
|
7437
|
+
response_contract_validation_status: String(responseContractValidation.status || "").trim(),
|
|
7438
|
+
response_contract_validation_reason: String(responseContractValidation.reason || "").trim(),
|
|
7439
|
+
response_contract_validation_targets: ensureArray(responseContractValidation.targets),
|
|
7440
|
+
assignment_validation_status: String(assignmentExecutionValidation.status || "").trim(),
|
|
7441
|
+
assignment_validation_reason: String(assignmentExecutionValidation.reason || "").trim(),
|
|
7442
|
+
assignment_validation_modes: ensureArray(assignmentExecutionValidation.assignmentModes),
|
|
7443
|
+
reply_chars: String(sanitizedReplyText || "").length,
|
|
7444
|
+
execution_mode: effectiveExecutionPlan.mode,
|
|
7445
|
+
role_profile: effectiveExecutionPlan.roleProfileName,
|
|
7446
|
+
executed_role_plan: executedRolePlan && Object.keys(executedRolePlan).length > 0 ? executedRolePlan : undefined,
|
|
7447
|
+
},
|
|
7448
|
+
};
|
|
7449
|
+
}
|
|
7365
7450
|
saveRunnerRouteState(
|
|
7366
7451
|
routeKey,
|
|
7367
7452
|
buildRunnerRouteStateFromComment(selectedRecord, {
|
package/lib/runner-runtime.mjs
CHANGED
|
@@ -51,6 +51,38 @@ function normalizeMentionSelector(rawValue) {
|
|
|
51
51
|
return String(rawValue || "").trim().replace(/^@+/, "").toLowerCase();
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
function buildRunnerRouteOwnershipKey(routeRaw) {
|
|
55
|
+
const route = safeObject(routeRaw);
|
|
56
|
+
const name = String(route.name || "").trim() || "-";
|
|
57
|
+
const projectID = String(route.projectID || route.project_id || "").trim() || "-";
|
|
58
|
+
const provider = String(route.provider || "").trim().toLowerCase() || "-";
|
|
59
|
+
const role = String(route.role || route.bot_role || route.botRole || "").trim().toLowerCase() || "-";
|
|
60
|
+
const botID = String(
|
|
61
|
+
route.botID
|
|
62
|
+
|| route.bot_id
|
|
63
|
+
|| route.server_bot_id
|
|
64
|
+
|| route.serverBotID
|
|
65
|
+
|| "",
|
|
66
|
+
).trim();
|
|
67
|
+
const botName = String(
|
|
68
|
+
route.botName
|
|
69
|
+
|| route.bot_name
|
|
70
|
+
|| route.server_bot_name
|
|
71
|
+
|| route.serverBotName
|
|
72
|
+
|| "",
|
|
73
|
+
).trim();
|
|
74
|
+
const destinationID = String(route.destinationID || route.destination_id || "").trim();
|
|
75
|
+
const destinationLabel = String(route.destinationLabel || route.destination_label || "").trim();
|
|
76
|
+
return [
|
|
77
|
+
name,
|
|
78
|
+
projectID,
|
|
79
|
+
provider,
|
|
80
|
+
role,
|
|
81
|
+
botID || botName || "-",
|
|
82
|
+
destinationID || destinationLabel || "-",
|
|
83
|
+
].join("::");
|
|
84
|
+
}
|
|
85
|
+
|
|
54
86
|
function normalizeRunnerSharedInboxKey({ provider, bot, route }) {
|
|
55
87
|
const normalizedProvider = String(provider || route?.provider || "").trim().toLowerCase() || "telegram";
|
|
56
88
|
const botSelector = firstNonEmptyString([
|
|
@@ -98,6 +130,130 @@ function buildManagedConversationBotSelectors(managedConversationBots) {
|
|
|
98
130
|
);
|
|
99
131
|
}
|
|
100
132
|
|
|
133
|
+
function buildRunnerLocalInboundOwnerMap({
|
|
134
|
+
routeKey,
|
|
135
|
+
route,
|
|
136
|
+
bot,
|
|
137
|
+
managedConversationBots,
|
|
138
|
+
}) {
|
|
139
|
+
const owners = new Map();
|
|
140
|
+
const appendOwner = (selectorRaw, ownerRaw = {}) => {
|
|
141
|
+
const selector = normalizeMentionSelector(selectorRaw);
|
|
142
|
+
const ownerRouteKey = String(
|
|
143
|
+
ownerRaw.routeKey
|
|
144
|
+
|| ownerRaw.route_key
|
|
145
|
+
|| buildRunnerRouteOwnershipKey(ownerRaw.route),
|
|
146
|
+
).trim();
|
|
147
|
+
const ownerBotUsername = normalizeMentionSelector(
|
|
148
|
+
ownerRaw.botUsername
|
|
149
|
+
|| ownerRaw.bot_username
|
|
150
|
+
|| ownerRaw.username
|
|
151
|
+
|| safeObject(ownerRaw.bot).username
|
|
152
|
+
|| safeObject(ownerRaw.bot).name
|
|
153
|
+
|| safeObject(ownerRaw.route).botName
|
|
154
|
+
|| safeObject(ownerRaw.route).bot_name
|
|
155
|
+
|| safeObject(ownerRaw.route).serverBotName
|
|
156
|
+
|| safeObject(ownerRaw.route).server_bot_name
|
|
157
|
+
|| selector,
|
|
158
|
+
);
|
|
159
|
+
if (!selector || !ownerRouteKey) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
owners.set(selector, {
|
|
163
|
+
selector,
|
|
164
|
+
routeKey: ownerRouteKey,
|
|
165
|
+
botUsername: ownerBotUsername || selector,
|
|
166
|
+
});
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
appendOwner(
|
|
170
|
+
bot?.username
|
|
171
|
+
|| bot?.name
|
|
172
|
+
|| route?.botName
|
|
173
|
+
|| route?.bot_name
|
|
174
|
+
|| route?.serverBotName
|
|
175
|
+
|| route?.server_bot_name,
|
|
176
|
+
{
|
|
177
|
+
routeKey,
|
|
178
|
+
botUsername:
|
|
179
|
+
bot?.username
|
|
180
|
+
|| bot?.name
|
|
181
|
+
|| route?.botName
|
|
182
|
+
|| route?.bot_name
|
|
183
|
+
|| route?.serverBotName
|
|
184
|
+
|| route?.server_bot_name,
|
|
185
|
+
},
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
for (const entryRaw of ensureArray(managedConversationBots)) {
|
|
189
|
+
const entry = safeObject(entryRaw);
|
|
190
|
+
appendOwner(
|
|
191
|
+
entry.username
|
|
192
|
+
|| entry.display_name
|
|
193
|
+
|| safeObject(entry.bot).username
|
|
194
|
+
|| safeObject(entry.bot).name
|
|
195
|
+
|| safeObject(entry.route).botName
|
|
196
|
+
|| safeObject(entry.route).bot_name
|
|
197
|
+
|| safeObject(entry.route).serverBotName
|
|
198
|
+
|| safeObject(entry.route).server_bot_name,
|
|
199
|
+
entry,
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return owners;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function resolveRunnerLocalInboundArtifactOwners({
|
|
207
|
+
update,
|
|
208
|
+
routeKey,
|
|
209
|
+
route,
|
|
210
|
+
bot,
|
|
211
|
+
managedConversationBots,
|
|
212
|
+
}) {
|
|
213
|
+
const normalizedUpdate = safeObject(update);
|
|
214
|
+
const ownersBySelector = buildRunnerLocalInboundOwnerMap({
|
|
215
|
+
routeKey,
|
|
216
|
+
route,
|
|
217
|
+
bot,
|
|
218
|
+
managedConversationBots,
|
|
219
|
+
});
|
|
220
|
+
const currentBotSelector = normalizeMentionSelector(
|
|
221
|
+
bot?.username
|
|
222
|
+
|| bot?.name
|
|
223
|
+
|| route?.botName
|
|
224
|
+
|| route?.bot_name
|
|
225
|
+
|| route?.serverBotName
|
|
226
|
+
|| route?.server_bot_name,
|
|
227
|
+
);
|
|
228
|
+
const currentOwner = currentBotSelector ? ownersBySelector.get(currentBotSelector) : null;
|
|
229
|
+
|
|
230
|
+
const explicitMentions = Array.from(new Set(
|
|
231
|
+
ensureArray(normalizedUpdate.mentionUsernames)
|
|
232
|
+
.map((value) => normalizeMentionSelector(value))
|
|
233
|
+
.filter(Boolean),
|
|
234
|
+
));
|
|
235
|
+
if (explicitMentions.length > 0) {
|
|
236
|
+
const explicitOwners = explicitMentions
|
|
237
|
+
.map((selector) => ownersBySelector.get(selector))
|
|
238
|
+
.filter((owner) => owner && owner.routeKey);
|
|
239
|
+
if (explicitOwners.length > 0) {
|
|
240
|
+
return explicitOwners;
|
|
241
|
+
}
|
|
242
|
+
return [];
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const replyTargetSelector = normalizeMentionSelector(normalizedUpdate.replyToFromUsername);
|
|
246
|
+
if (
|
|
247
|
+
normalizedUpdate.replyToFromIsBot === true
|
|
248
|
+
&& replyTargetSelector
|
|
249
|
+
&& ownersBySelector.has(replyTargetSelector)
|
|
250
|
+
) {
|
|
251
|
+
return [ownersBySelector.get(replyTargetSelector)];
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return currentOwner ? [currentOwner] : [];
|
|
255
|
+
}
|
|
256
|
+
|
|
101
257
|
function managedConversationBotTargetsCurrentRoute({
|
|
102
258
|
update,
|
|
103
259
|
bot,
|
|
@@ -327,49 +483,72 @@ function buildRunnerLocalInboundEnvelopeFromReceipt(rawReceipt) {
|
|
|
327
483
|
});
|
|
328
484
|
}
|
|
329
485
|
|
|
330
|
-
function buildRunnerLocalInboundArtifacts(updates, routeKey, bot, destination) {
|
|
331
|
-
const currentBotSelector = normalizeMentionSelector(
|
|
332
|
-
bot?.username
|
|
333
|
-
|| bot?.name
|
|
334
|
-
|| "",
|
|
335
|
-
);
|
|
486
|
+
function buildRunnerLocalInboundArtifacts(updates, routeKey, route, bot, destination, managedConversationBots = []) {
|
|
336
487
|
const normalizedRouteKey = String(routeKey || "").trim();
|
|
337
488
|
const destinationChatID = String(destination?.chatID || "").trim();
|
|
338
489
|
return ensureArray(updates)
|
|
339
490
|
.filter((update) => String(update.chatID || "").trim() === destinationChatID)
|
|
340
491
|
.filter((update) => String(update.text || "").trim())
|
|
341
|
-
.
|
|
342
|
-
const
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
492
|
+
.flatMap((update) => {
|
|
493
|
+
const owners = resolveRunnerLocalInboundArtifactOwners({
|
|
494
|
+
update,
|
|
495
|
+
routeKey: normalizedRouteKey,
|
|
496
|
+
route,
|
|
497
|
+
bot,
|
|
498
|
+
managedConversationBots,
|
|
499
|
+
});
|
|
500
|
+
return owners.map((owner) => {
|
|
501
|
+
const chatID = String(update.chatID || "").trim();
|
|
502
|
+
const messageID = intFromRawAllowZero(update.messageID, 0);
|
|
503
|
+
const receiptKey = buildRunnerRecentLocalInboundReceiptKey(chatID, messageID);
|
|
504
|
+
const receiptEntry = normalizeRunnerRecentLocalInboundReceipt({
|
|
505
|
+
update_id: intFromRawAllowZero(update.updateID, 0),
|
|
506
|
+
chat_id: chatID,
|
|
507
|
+
chat_type: update.chatType,
|
|
508
|
+
chat_title: update.chatTitle,
|
|
509
|
+
message_id: messageID,
|
|
510
|
+
message_thread_id: intFromRawAllowZero(update.messageThreadID, 0),
|
|
511
|
+
reply_to_message_id: intFromRawAllowZero(update.replyToMessageID, 0),
|
|
512
|
+
kind: update.fromIsBot ? "bot_reply" : "telegram_message",
|
|
513
|
+
sender_id: update.fromID,
|
|
514
|
+
sender: update.fromName,
|
|
515
|
+
sender_username: update.fromUsername,
|
|
516
|
+
sender_is_bot: update.fromIsBot === true,
|
|
517
|
+
body: update.text,
|
|
518
|
+
occurred_at: update.occurredAt,
|
|
519
|
+
receipt_origin: "local_telegram_inbound",
|
|
520
|
+
receipt_route_key: owner.routeKey,
|
|
521
|
+
receipt_bot_username: owner.botUsername,
|
|
522
|
+
received_at: firstNonEmptyString([update.occurredAt, new Date().toISOString()]),
|
|
523
|
+
}, receiptKey);
|
|
524
|
+
return {
|
|
525
|
+
ownerRouteKey: String(owner.routeKey || "").trim(),
|
|
526
|
+
receiptEntry,
|
|
527
|
+
};
|
|
528
|
+
});
|
|
366
529
|
})
|
|
367
530
|
.filter((artifact) => {
|
|
368
531
|
const normalizedEntry = ensureArray(safeObject(artifact).receiptEntry);
|
|
369
|
-
return normalizedEntry.length === 2;
|
|
532
|
+
return normalizedEntry.length === 2 && String(safeObject(artifact).ownerRouteKey || "").trim();
|
|
370
533
|
});
|
|
371
534
|
}
|
|
372
535
|
|
|
536
|
+
function groupRunnerLocalInboundArtifactsByRoute(localInboundArtifacts) {
|
|
537
|
+
const grouped = new Map();
|
|
538
|
+
for (const artifactRaw of ensureArray(localInboundArtifacts)) {
|
|
539
|
+
const artifact = safeObject(artifactRaw);
|
|
540
|
+
const ownerRouteKey = String(artifact.ownerRouteKey || "").trim();
|
|
541
|
+
const normalizedEntry = ensureArray(artifact.receiptEntry);
|
|
542
|
+
if (!ownerRouteKey || normalizedEntry.length !== 2) {
|
|
543
|
+
continue;
|
|
544
|
+
}
|
|
545
|
+
const routeArtifacts = grouped.get(ownerRouteKey) || [];
|
|
546
|
+
routeArtifacts.push({ receiptEntry: normalizedEntry });
|
|
547
|
+
grouped.set(ownerRouteKey, routeArtifacts);
|
|
548
|
+
}
|
|
549
|
+
return Object.fromEntries(grouped.entries());
|
|
550
|
+
}
|
|
551
|
+
|
|
373
552
|
function buildRunnerRecentLocalInboundEnvelopes(routeStateRaw, recentLocalInboundReceipts) {
|
|
374
553
|
const routeState = safeObject(routeStateRaw);
|
|
375
554
|
const relevantEnvelopes = Object.values(safeObject(recentLocalInboundReceipts))
|
|
@@ -904,12 +1083,15 @@ export async function archiveLocalTelegramMessagesForRoute({
|
|
|
904
1083
|
const localInboundArtifacts = buildRunnerLocalInboundArtifacts(
|
|
905
1084
|
updates,
|
|
906
1085
|
routeKey,
|
|
1086
|
+
route,
|
|
907
1087
|
bot,
|
|
908
1088
|
destination,
|
|
1089
|
+
managedConversationBots,
|
|
909
1090
|
);
|
|
1091
|
+
const localInboundArtifactsByRoute = groupRunnerLocalInboundArtifactsByRoute(localInboundArtifacts);
|
|
910
1092
|
const recentLocalInboundReceipts = buildRunnerRecentLocalInboundReceipts(
|
|
911
1093
|
routeState,
|
|
912
|
-
|
|
1094
|
+
ensureArray(localInboundArtifactsByRoute[routeKey]),
|
|
913
1095
|
);
|
|
914
1096
|
const recentLocalInboundEnvelopes = buildRunnerRecentLocalInboundEnvelopes(
|
|
915
1097
|
routeState,
|
|
@@ -926,13 +1108,36 @@ export async function archiveLocalTelegramMessagesForRoute({
|
|
|
926
1108
|
if (sharedInboxPatch && saveBotRunnerState) {
|
|
927
1109
|
saveBotRunnerState(sharedInboxPatch);
|
|
928
1110
|
}
|
|
929
|
-
|
|
1111
|
+
const inputRouteKeys = Array.from(new Set([
|
|
930
1112
|
routeKey,
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
}
|
|
935
|
-
|
|
1113
|
+
...Object.keys(safeObject(localInboundArtifactsByRoute)),
|
|
1114
|
+
]));
|
|
1115
|
+
for (const targetRouteKey of inputRouteKeys) {
|
|
1116
|
+
const fullState = loadBotRunnerState ? safeObject(loadBotRunnerState()) : {};
|
|
1117
|
+
const targetRouteState = safeObject(
|
|
1118
|
+
safeObject(fullState.routes)[targetRouteKey]
|
|
1119
|
+
|| (targetRouteKey === routeKey ? routeState : {}),
|
|
1120
|
+
);
|
|
1121
|
+
const targetRecentLocalInboundReceipts = targetRouteKey === routeKey
|
|
1122
|
+
? recentLocalInboundReceipts
|
|
1123
|
+
: buildRunnerRecentLocalInboundReceipts(
|
|
1124
|
+
targetRouteState,
|
|
1125
|
+
ensureArray(localInboundArtifactsByRoute[targetRouteKey]),
|
|
1126
|
+
);
|
|
1127
|
+
const targetRecentLocalInboundEnvelopes = targetRouteKey === routeKey
|
|
1128
|
+
? recentLocalInboundEnvelopes
|
|
1129
|
+
: buildRunnerRecentLocalInboundEnvelopes(
|
|
1130
|
+
targetRouteState,
|
|
1131
|
+
targetRecentLocalInboundReceipts,
|
|
1132
|
+
);
|
|
1133
|
+
saveRunnerRouteState(
|
|
1134
|
+
targetRouteKey,
|
|
1135
|
+
buildRunnerRouteInputStatePatch({
|
|
1136
|
+
recentLocalInboundEnvelopes: targetRecentLocalInboundEnvelopes,
|
|
1137
|
+
recentLocalInboundReceipts: targetRecentLocalInboundReceipts,
|
|
1138
|
+
}),
|
|
1139
|
+
);
|
|
1140
|
+
}
|
|
936
1141
|
saveRunnerRouteState(
|
|
937
1142
|
routeKey,
|
|
938
1143
|
buildRunnerRoutePollingStatePatch({
|
|
@@ -13246,14 +13246,139 @@ export async function runSelftestRunnerScenarios(push, deps) {
|
|
|
13246
13246
|
&& /ECONNRESET/i.test(String(processed.result?.transport_error || "")),
|
|
13247
13247
|
`kind=${String(processed.kind || "(none)")} outcome=${String(processed.result?.outcome || "(none)")} ai_generated=${String(processed.result?.ai_reply_generated || false)} delivery=${String(processed.result?.delivery_status || "(none)")} transport=${String(processed.result?.transport_error || "(none)")}`,
|
|
13248
13248
|
);
|
|
13249
|
-
} catch (err) {
|
|
13250
|
-
push("runner_delivery_failure_after_generation_records_ai_state_without_execution_error", false, String(err?.message || err));
|
|
13251
|
-
}
|
|
13252
|
-
|
|
13249
|
+
} catch (err) {
|
|
13250
|
+
push("runner_delivery_failure_after_generation_records_ai_state_without_execution_error", false, String(err?.message || err));
|
|
13251
|
+
}
|
|
13252
|
+
|
|
13253
|
+
try {
|
|
13254
|
+
const processed = await processRunnerSelectedRecord({
|
|
13255
|
+
routeKey: "delivery-stale-reply-anchor-key",
|
|
13256
|
+
normalizedRoute: normalizeRunnerRoute({
|
|
13257
|
+
name: "telegram-monitor-delivery-stale-reply-anchor",
|
|
13258
|
+
project_id: selftestProjectID,
|
|
13259
|
+
provider: "telegram",
|
|
13260
|
+
role: "monitor",
|
|
13261
|
+
role_profile: "monitor",
|
|
13262
|
+
destination_id: "dest-1",
|
|
13263
|
+
destination_label: "Main Room",
|
|
13264
|
+
server_bot_name: "RyoAI_bot",
|
|
13265
|
+
server_bot_id: "bot-1",
|
|
13266
|
+
trigger_policy: {
|
|
13267
|
+
mentions_only: true,
|
|
13268
|
+
direct_messages: true,
|
|
13269
|
+
reply_to_bot_messages: true,
|
|
13270
|
+
},
|
|
13271
|
+
archive_policy: {
|
|
13272
|
+
mirror_replies: true,
|
|
13273
|
+
dedupe_inbound: true,
|
|
13274
|
+
dedupe_outbound: true,
|
|
13275
|
+
skip_bot_messages: true,
|
|
13276
|
+
},
|
|
13277
|
+
dry_run_delivery: false,
|
|
13278
|
+
}),
|
|
13279
|
+
selectedRecord: {
|
|
13280
|
+
id: "comment-delivery-stale-reply-anchor",
|
|
13281
|
+
createdAt: "2026-03-17T00:00:22.000Z",
|
|
13282
|
+
parsedArchive: {
|
|
13283
|
+
kind: "telegram_message",
|
|
13284
|
+
chatID: "-100123",
|
|
13285
|
+
chatType: "supergroup",
|
|
13286
|
+
body: "@RyoAI_bot hi",
|
|
13287
|
+
messageID: 127,
|
|
13288
|
+
sender: "human",
|
|
13289
|
+
senderIsBot: false,
|
|
13290
|
+
mentionUsernames: ["ryoai_bot"],
|
|
13291
|
+
},
|
|
13292
|
+
},
|
|
13293
|
+
pendingOrdered: [],
|
|
13294
|
+
bot: {
|
|
13295
|
+
id: "bot-1",
|
|
13296
|
+
name: "RyoAI_bot",
|
|
13297
|
+
username: "RyoAI_bot",
|
|
13298
|
+
role: "monitor",
|
|
13299
|
+
provider: "telegram",
|
|
13300
|
+
},
|
|
13301
|
+
destination: {
|
|
13302
|
+
id: "dest-1",
|
|
13303
|
+
label: "Main Room",
|
|
13304
|
+
provider: "telegram",
|
|
13305
|
+
chatID: "-100123",
|
|
13306
|
+
},
|
|
13307
|
+
archiveThread: {
|
|
13308
|
+
threadID: "thread-1",
|
|
13309
|
+
workItemID: "work-item-1",
|
|
13310
|
+
},
|
|
13311
|
+
executionPlan: {
|
|
13312
|
+
mode: "role_profile",
|
|
13313
|
+
roleProfileName: "monitor",
|
|
13314
|
+
roleProfile: {
|
|
13315
|
+
client: "sample",
|
|
13316
|
+
model: "",
|
|
13317
|
+
permissionMode: "read_only",
|
|
13318
|
+
reasoningEffort: "low",
|
|
13319
|
+
},
|
|
13320
|
+
workspaceDir: "",
|
|
13321
|
+
workspaceSource: "selftest",
|
|
13322
|
+
usedCommandFallback: false,
|
|
13323
|
+
},
|
|
13324
|
+
runtime: {
|
|
13325
|
+
baseURL: "https://example.test",
|
|
13326
|
+
token: "selftest-token",
|
|
13327
|
+
timeoutSeconds: 30,
|
|
13328
|
+
actor: { user_id: "user-1" },
|
|
13329
|
+
},
|
|
13330
|
+
deps: {
|
|
13331
|
+
saveRunnerRouteState: () => {},
|
|
13332
|
+
startRunnerTypingHeartbeat: () => ({ async stop() {} }),
|
|
13333
|
+
runRunnerAIExecution: async () => ({
|
|
13334
|
+
skip: false,
|
|
13335
|
+
reply: "Hello from RyoAI_bot.",
|
|
13336
|
+
}),
|
|
13337
|
+
performLocalBotDelivery: async () => {
|
|
13338
|
+
const err = new Error("local telegram delivery skipped stale reply anchor (Bad Request: message to be replied not found)");
|
|
13339
|
+
err.code = "TELEGRAM_STALE_REPLY_ANCHOR";
|
|
13340
|
+
err.staleReplyAnchor = true;
|
|
13341
|
+
throw err;
|
|
13342
|
+
},
|
|
13343
|
+
serializeRunnerTriggerPolicy: (value) => value,
|
|
13344
|
+
serializeRunnerArchivePolicy: (value) => value,
|
|
13345
|
+
buildRunnerExecutionDeps: () => ({
|
|
13346
|
+
validateWorkspaceArtifacts,
|
|
13347
|
+
analyzeHumanConversationIntentWithAI: async () => ({
|
|
13348
|
+
mode: "single_bot",
|
|
13349
|
+
lead_bot: "ryoai_bot",
|
|
13350
|
+
participants: ["ryoai_bot"],
|
|
13351
|
+
initial_responders: ["ryoai_bot"],
|
|
13352
|
+
allowed_responders: ["ryoai_bot"],
|
|
13353
|
+
summary_bot: "",
|
|
13354
|
+
allow_bot_to_bot: false,
|
|
13355
|
+
reply_expectation: "informational",
|
|
13356
|
+
intent_type: "small_talk",
|
|
13357
|
+
}),
|
|
13358
|
+
}),
|
|
13359
|
+
buildRunnerDeliveryDeps: () => ({}),
|
|
13360
|
+
buildRunnerRuntimeDeps: () => ({}),
|
|
13361
|
+
resolveConversationPeerBots: () => [],
|
|
13362
|
+
},
|
|
13363
|
+
});
|
|
13364
|
+
push(
|
|
13365
|
+
"runner_stale_reply_anchor_skips_generated_reply_without_delivery_failure",
|
|
13366
|
+
processed.kind === "skipped"
|
|
13367
|
+
&& String(processed.skippedRecord?.reason || "") === "stale_reply_anchor"
|
|
13368
|
+
&& String(processed.result?.outcome || "") === "skipped"
|
|
13369
|
+
&& processed.result?.ai_reply_generated === true
|
|
13370
|
+
&& String(processed.result?.delivery_status || "") === "skipped_stale_reply_anchor"
|
|
13371
|
+
&& /message to be replied not found/i.test(String(processed.result?.transport_error || "")),
|
|
13372
|
+
`kind=${String(processed.kind || "(none)")} reason=${String(processed.skippedRecord?.reason || "(none)")} outcome=${String(processed.result?.outcome || "(none)")} delivery=${String(processed.result?.delivery_status || "(none)")} transport=${String(processed.result?.transport_error || "(none)")}`,
|
|
13373
|
+
);
|
|
13374
|
+
} catch (err) {
|
|
13375
|
+
push("runner_stale_reply_anchor_skips_generated_reply_without_delivery_failure", false, String(err?.message || err));
|
|
13376
|
+
}
|
|
13377
|
+
|
|
13253
13378
|
try {
|
|
13254
13379
|
let capturedReplyToMessageID = 0;
|
|
13255
13380
|
let capturedMessageThreadID = 0;
|
|
13256
|
-
let capturedSourceMessageEnvelope = {};
|
|
13381
|
+
let capturedSourceMessageEnvelope = {};
|
|
13257
13382
|
const processed = await processRunnerSelectedRecord({
|
|
13258
13383
|
routeKey: "delivery-prefers-route-local-inbound-envelope-key",
|
|
13259
13384
|
normalizedRoute: normalizeRunnerRoute({
|
|
@@ -17,6 +17,10 @@ function safeObject(value) {
|
|
|
17
17
|
return value;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
function ensureArray(value) {
|
|
21
|
+
return Array.isArray(value) ? value : [];
|
|
22
|
+
}
|
|
23
|
+
|
|
20
24
|
function buildTelegramMentionEntities(text) {
|
|
21
25
|
const entities = [];
|
|
22
26
|
const source = String(text || "");
|
|
@@ -58,7 +62,8 @@ async function startLocalTelegramRunnerSelftestServer({
|
|
|
58
62
|
// on Windows CI shells.
|
|
59
63
|
sendMessageTransientFailuresRemaining: 0,
|
|
60
64
|
sendChatActionTransientFailuresRemaining: 0,
|
|
61
|
-
|
|
65
|
+
missingReplyTargetMessageIDs: [],
|
|
66
|
+
updates: [
|
|
62
67
|
{
|
|
63
68
|
update_id: 11,
|
|
64
69
|
message: {
|
|
@@ -294,13 +299,24 @@ async function startLocalTelegramRunnerSelftestServer({
|
|
|
294
299
|
state.sendMessageTransientFailuresRemaining -= 1;
|
|
295
300
|
req.socket.destroy(new Error("socket hang up"));
|
|
296
301
|
return;
|
|
297
|
-
}
|
|
298
|
-
const payload = await readJSONBody(req);
|
|
299
|
-
const
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
302
|
+
}
|
|
303
|
+
const payload = await readJSONBody(req);
|
|
304
|
+
const replyToMessageID = intFromRawAllowZero(payload.reply_to_message_id, 0);
|
|
305
|
+
if (
|
|
306
|
+
replyToMessageID > 0
|
|
307
|
+
&& ensureArray(state.missingReplyTargetMessageIDs).includes(replyToMessageID)
|
|
308
|
+
) {
|
|
309
|
+
writeJSON(res, 400, {
|
|
310
|
+
ok: false,
|
|
311
|
+
description: "Bad Request: message to be replied not found",
|
|
312
|
+
});
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
const messageID = state.nextTelegramMessageID++;
|
|
316
|
+
state.sentMessages.push({
|
|
317
|
+
...payload,
|
|
318
|
+
message_id: messageID,
|
|
319
|
+
});
|
|
304
320
|
writeJSON(res, 200, {
|
|
305
321
|
ok: true,
|
|
306
322
|
result: {
|
|
@@ -581,6 +597,45 @@ export async function runSelftestTelegramE2E(push, deps) {
|
|
|
581
597
|
`error=${unsafeAnchorError || "(none)"} sent=${telegramE2EServer.state.sentMessages.length}`,
|
|
582
598
|
);
|
|
583
599
|
|
|
600
|
+
telegramE2EServer.state.missingReplyTargetMessageIDs = [999];
|
|
601
|
+
const sentCountBeforeStaleAnchor = telegramE2EServer.state.sentMessages.length;
|
|
602
|
+
let staleAnchorError = "";
|
|
603
|
+
try {
|
|
604
|
+
await performLocalBotDelivery({
|
|
605
|
+
siteBaseURL: telegramE2EServer.baseURL,
|
|
606
|
+
token: e2eToken,
|
|
607
|
+
timeoutSeconds: 10,
|
|
608
|
+
actorUserID: e2eActorUserID,
|
|
609
|
+
bot: e2eBot,
|
|
610
|
+
projectID: selftestProjectID,
|
|
611
|
+
provider: "telegram",
|
|
612
|
+
destinationSelectors: {
|
|
613
|
+
destinationLabel: e2eDestination.label,
|
|
614
|
+
},
|
|
615
|
+
text: "stale reply anchor",
|
|
616
|
+
replyToMessageID: 999,
|
|
617
|
+
sourceMessageEnvelope: {
|
|
618
|
+
chat_id: e2eDestination.chat_id,
|
|
619
|
+
message_id: 999,
|
|
620
|
+
source_origin: "local_telegram_inbound",
|
|
621
|
+
},
|
|
622
|
+
archiveReplies: false,
|
|
623
|
+
dryRun: false,
|
|
624
|
+
deps: buildRunnerDeliveryDeps(),
|
|
625
|
+
});
|
|
626
|
+
} catch (err) {
|
|
627
|
+
staleAnchorError = String(err?.message || err);
|
|
628
|
+
} finally {
|
|
629
|
+
telegramE2EServer.state.missingReplyTargetMessageIDs = [];
|
|
630
|
+
}
|
|
631
|
+
push(
|
|
632
|
+
"telegram_delivery_classifies_missing_reply_target_as_stale_anchor",
|
|
633
|
+
/stale reply anchor/i.test(staleAnchorError)
|
|
634
|
+
&& /message to be replied not found/i.test(staleAnchorError)
|
|
635
|
+
&& telegramE2EServer.state.sentMessages.length === sentCountBeforeStaleAnchor,
|
|
636
|
+
`error=${staleAnchorError || "(none)"} sent=${telegramE2EServer.state.sentMessages.length}`,
|
|
637
|
+
);
|
|
638
|
+
|
|
584
639
|
const sentCountBeforeDirectDelivery = telegramE2EServer.state.sentMessages.length;
|
|
585
640
|
const commentCountBeforeDirectDelivery = telegramE2EServer.state.comments.length;
|
|
586
641
|
const requestCountBeforeDirectDelivery = telegramE2EServer.state.runnerRequests.length;
|
|
@@ -1436,6 +1491,113 @@ export async function runSelftestTelegramE2E(push, deps) {
|
|
|
1436
1491
|
telegramE2EServer.state.comments.length === 0,
|
|
1437
1492
|
`comments=${telegramE2EServer.state.comments.length}`,
|
|
1438
1493
|
);
|
|
1494
|
+
|
|
1495
|
+
telegramE2EServer.state.comments = [];
|
|
1496
|
+
telegramE2EServer.state.updates = [
|
|
1497
|
+
{
|
|
1498
|
+
update_id: 403,
|
|
1499
|
+
message: {
|
|
1500
|
+
message_id: 83,
|
|
1501
|
+
date: Math.floor(Date.now() / 1000),
|
|
1502
|
+
chat: {
|
|
1503
|
+
id: Number(e2eDestination.chat_id),
|
|
1504
|
+
type: "supergroup",
|
|
1505
|
+
title: e2eDestination.label,
|
|
1506
|
+
},
|
|
1507
|
+
from: {
|
|
1508
|
+
id: 7001,
|
|
1509
|
+
is_bot: false,
|
|
1510
|
+
first_name: "Operator",
|
|
1511
|
+
username: "operator_user",
|
|
1512
|
+
},
|
|
1513
|
+
text: "@RyoAI2_bot 하이",
|
|
1514
|
+
entities: buildTelegramMentionEntities("@RyoAI2_bot 하이"),
|
|
1515
|
+
},
|
|
1516
|
+
},
|
|
1517
|
+
];
|
|
1518
|
+
const routeRyoai1 = normalizeRunnerRoute({
|
|
1519
|
+
...e2eRoute,
|
|
1520
|
+
name: "selftest-runner-e2e-ownership-ryoai1",
|
|
1521
|
+
server_bot_name: "RyoAI_bot",
|
|
1522
|
+
server_bot_id: "88888888-8888-4888-8888-888888888881",
|
|
1523
|
+
destination_id: "dest-telegram",
|
|
1524
|
+
destination_label: e2eDestination.label,
|
|
1525
|
+
});
|
|
1526
|
+
const routeRyoai2 = normalizeRunnerRoute({
|
|
1527
|
+
...e2eRoute,
|
|
1528
|
+
name: "selftest-runner-e2e-ownership-ryoai2",
|
|
1529
|
+
server_bot_name: "RyoAI2_bot",
|
|
1530
|
+
server_bot_id: "88888888-8888-4888-8888-888888888882",
|
|
1531
|
+
destination_id: "dest-telegram",
|
|
1532
|
+
destination_label: e2eDestination.label,
|
|
1533
|
+
});
|
|
1534
|
+
const routeRyoai3 = normalizeRunnerRoute({
|
|
1535
|
+
...e2eRoute,
|
|
1536
|
+
name: "selftest-runner-e2e-ownership-ryoai3",
|
|
1537
|
+
server_bot_name: "RyoAI3_bot",
|
|
1538
|
+
server_bot_id: "88888888-8888-4888-8888-888888888883",
|
|
1539
|
+
destination_id: "dest-telegram",
|
|
1540
|
+
destination_label: e2eDestination.label,
|
|
1541
|
+
});
|
|
1542
|
+
const routeRyoai2Key = runnerRouteKey(routeRyoai2);
|
|
1543
|
+
const routeRyoai3Key = runnerRouteKey(routeRyoai3);
|
|
1544
|
+
await archiveLocalTelegramMessagesForRoute({
|
|
1545
|
+
routeKey: routeRyoai3Key,
|
|
1546
|
+
route: routeRyoai3,
|
|
1547
|
+
routeState: {},
|
|
1548
|
+
runtime: {
|
|
1549
|
+
baseURL: telegramE2EServer.baseURL,
|
|
1550
|
+
timeoutSeconds: 10,
|
|
1551
|
+
token: e2eToken,
|
|
1552
|
+
actor: {
|
|
1553
|
+
user_id: e2eActorUserID,
|
|
1554
|
+
},
|
|
1555
|
+
},
|
|
1556
|
+
bot: {
|
|
1557
|
+
id: "88888888-8888-4888-8888-888888888883",
|
|
1558
|
+
name: "RyoAI3_bot",
|
|
1559
|
+
username: "RyoAI3_bot",
|
|
1560
|
+
role: "monitor",
|
|
1561
|
+
},
|
|
1562
|
+
destination: {
|
|
1563
|
+
chatID: e2eDestination.chat_id,
|
|
1564
|
+
},
|
|
1565
|
+
archiveThread: {
|
|
1566
|
+
threadID: e2eThreadID,
|
|
1567
|
+
},
|
|
1568
|
+
managedConversationBots: [
|
|
1569
|
+
{
|
|
1570
|
+
username: "RyoAI_bot",
|
|
1571
|
+
route: routeRyoai1,
|
|
1572
|
+
bot: { username: "RyoAI_bot", name: "RyoAI_bot" },
|
|
1573
|
+
},
|
|
1574
|
+
{
|
|
1575
|
+
username: "RyoAI2_bot",
|
|
1576
|
+
route: routeRyoai2,
|
|
1577
|
+
bot: { username: "RyoAI2_bot", name: "RyoAI2_bot" },
|
|
1578
|
+
},
|
|
1579
|
+
{
|
|
1580
|
+
username: "RyoAI3_bot",
|
|
1581
|
+
route: routeRyoai3,
|
|
1582
|
+
bot: { username: "RyoAI3_bot", name: "RyoAI3_bot" },
|
|
1583
|
+
},
|
|
1584
|
+
],
|
|
1585
|
+
deps: buildRunnerRuntimeDeps(),
|
|
1586
|
+
});
|
|
1587
|
+
const ownershipState = loadBotRunnerState();
|
|
1588
|
+
const ryoai2Receipt = safeObject(
|
|
1589
|
+
safeObject(safeObject(ownershipState.routes)[routeRyoai2Key]).recent_local_inbound_receipts?.[`${String(e2eDestination.chat_id)}:83`],
|
|
1590
|
+
);
|
|
1591
|
+
const ryoai3Receipt = safeObject(
|
|
1592
|
+
safeObject(safeObject(ownershipState.routes)[routeRyoai3Key]).recent_local_inbound_receipts?.[`${String(e2eDestination.chat_id)}:83`],
|
|
1593
|
+
);
|
|
1594
|
+
push(
|
|
1595
|
+
"telegram_local_inbound_receipt_is_attributed_to_explicit_mention_owner_route",
|
|
1596
|
+
String(ryoai2Receipt.receipt_route_key || "") === routeRyoai2Key
|
|
1597
|
+
&& String(ryoai2Receipt.receipt_bot_username || "") === "ryoai2_bot"
|
|
1598
|
+
&& !String(ryoai3Receipt.receipt_route_key || "").trim(),
|
|
1599
|
+
`owner=${JSON.stringify(ryoai2Receipt)} foreign=${JSON.stringify(ryoai3Receipt)}`,
|
|
1600
|
+
);
|
|
1439
1601
|
} catch (err) {
|
|
1440
1602
|
push("telegram_runner_e2e_local_mock", false, String(err?.message || err));
|
|
1441
1603
|
} finally {
|