metheus-governance-mcp-cli 0.2.262 → 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/selftest-runner-scenarios.mjs +130 -5
- package/lib/selftest-telegram-e2e.mjs +63 -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, {
|
|
@@ -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;
|