metheus-governance-mcp-cli 0.2.262 → 0.2.264
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 +120 -83
- package/lib/runner-delivery.mjs +22 -0
- package/lib/runner-orchestration.mjs +87 -2
- package/lib/selftest-runner-scenarios.mjs +231 -9
- package/lib/selftest-telegram-e2e.mjs +63 -8
- package/package.json +1 -1
package/cli.mjs
CHANGED
|
@@ -831,9 +831,17 @@ function botRunnerWorkspaceRegistryFilePath() {
|
|
|
831
831
|
return resolveHomeFilePath(BOT_RUNNER_WORKSPACE_REGISTRY_RELATIVE_PATH);
|
|
832
832
|
}
|
|
833
833
|
|
|
834
|
-
function botRunnerStateFilePath() {
|
|
835
|
-
return resolveHomeFilePath(BOT_RUNNER_STATE_RELATIVE_PATH);
|
|
836
|
-
}
|
|
834
|
+
function botRunnerStateFilePath() {
|
|
835
|
+
return resolveHomeFilePath(BOT_RUNNER_STATE_RELATIVE_PATH);
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
function botRunnerStateBackupFilePath(filePath = botRunnerStateFilePath()) {
|
|
839
|
+
const normalizedPath = String(filePath || "").trim();
|
|
840
|
+
if (!normalizedPath) {
|
|
841
|
+
return "";
|
|
842
|
+
}
|
|
843
|
+
return `${normalizedPath}.bak`;
|
|
844
|
+
}
|
|
837
845
|
|
|
838
846
|
function botRunnerProcessesFilePath() {
|
|
839
847
|
return resolveHomeFilePath(BOT_RUNNER_PROCESSES_RELATIVE_PATH);
|
|
@@ -2200,25 +2208,37 @@ function migrateBotRunnerStateRoutes(routes, runnerConfig) {
|
|
|
2200
2208
|
};
|
|
2201
2209
|
}
|
|
2202
2210
|
|
|
2203
|
-
function loadBotRunnerState() {
|
|
2204
|
-
const filePath = botRunnerStateFilePath();
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2211
|
+
function loadBotRunnerState() {
|
|
2212
|
+
const filePath = botRunnerStateFilePath();
|
|
2213
|
+
const backupPath = botRunnerStateBackupFilePath(filePath);
|
|
2214
|
+
waitForBotRunnerStateLockRelease(filePath);
|
|
2215
|
+
const emptyState = {
|
|
2216
|
+
filePath,
|
|
2217
|
+
routes: {},
|
|
2218
|
+
sharedInboxes: {},
|
|
2219
|
+
excludedComments: {},
|
|
2220
|
+
requests: {},
|
|
2221
|
+
consumedComments: {},
|
|
2222
|
+
migrated: false,
|
|
2223
|
+
migratedKeys: [],
|
|
2224
|
+
remainingAnonymousKeys: [],
|
|
2225
|
+
};
|
|
2226
|
+
try {
|
|
2227
|
+
if (!fs.existsSync(filePath)) {
|
|
2228
|
+
return emptyState;
|
|
2229
|
+
}
|
|
2230
|
+
let parsed = tryJsonParse(fs.readFileSync(filePath, "utf8"));
|
|
2231
|
+
if ((!parsed || typeof parsed !== "object" || Array.isArray(parsed)) && backupPath && fs.existsSync(backupPath)) {
|
|
2232
|
+
const backupParsed = tryJsonParse(fs.readFileSync(backupPath, "utf8"));
|
|
2233
|
+
if (backupParsed && typeof backupParsed === "object" && !Array.isArray(backupParsed)) {
|
|
2234
|
+
parsed = backupParsed;
|
|
2235
|
+
writeTextFileAtomic(filePath, `${JSON.stringify(backupParsed, null, 2)}\n`);
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2238
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
2239
|
+
return emptyState;
|
|
2240
|
+
}
|
|
2241
|
+
const runnerConfig = loadBotRunnerConfig({ persistIfNeeded: true });
|
|
2222
2242
|
const migratedState = migrateBotRunnerStateRoutes(safeObject(parsed?.routes), runnerConfig);
|
|
2223
2243
|
if (migratedState.changed) {
|
|
2224
2244
|
saveBotRunnerState({
|
|
@@ -2241,24 +2261,14 @@ function loadBotRunnerState() {
|
|
|
2241
2261
|
excludedComments: normalizeBotRunnerExcludedComments(parsed?.excluded_comments || parsed?.excludedComments),
|
|
2242
2262
|
requests: normalizeBotRunnerRequests(parsed?.requests),
|
|
2243
2263
|
consumedComments: normalizeBotRunnerConsumedComments(parsed?.consumed_comments || parsed?.consumedComments),
|
|
2244
|
-
migrated: migratedState.changed,
|
|
2245
|
-
migratedKeys: migratedState.migratedKeys,
|
|
2246
|
-
remainingAnonymousKeys: migratedState.remainingAnonymousKeys,
|
|
2247
|
-
};
|
|
2248
|
-
} catch {
|
|
2249
|
-
return
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
sharedInboxes: {},
|
|
2253
|
-
excludedComments: {},
|
|
2254
|
-
requests: {},
|
|
2255
|
-
consumedComments: {},
|
|
2256
|
-
migrated: false,
|
|
2257
|
-
migratedKeys: [],
|
|
2258
|
-
remainingAnonymousKeys: [],
|
|
2259
|
-
};
|
|
2260
|
-
}
|
|
2261
|
-
}
|
|
2264
|
+
migrated: migratedState.changed,
|
|
2265
|
+
migratedKeys: migratedState.migratedKeys,
|
|
2266
|
+
remainingAnonymousKeys: migratedState.remainingAnonymousKeys,
|
|
2267
|
+
};
|
|
2268
|
+
} catch {
|
|
2269
|
+
return emptyState;
|
|
2270
|
+
}
|
|
2271
|
+
}
|
|
2262
2272
|
|
|
2263
2273
|
function sleepSyncMs(delayMs) {
|
|
2264
2274
|
const ms = Number(delayMs) || 0;
|
|
@@ -2408,13 +2418,19 @@ function writeTextFileAtomic(filePath, text) {
|
|
|
2408
2418
|
}
|
|
2409
2419
|
}
|
|
2410
2420
|
|
|
2411
|
-
function saveBotRunnerState(nextState) {
|
|
2412
|
-
const filePath = botRunnerStateFilePath();
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2421
|
+
function saveBotRunnerState(nextState) {
|
|
2422
|
+
const filePath = botRunnerStateFilePath();
|
|
2423
|
+
const backupPath = botRunnerStateBackupFilePath(filePath);
|
|
2424
|
+
return withBotRunnerStateFileLock(filePath, () => {
|
|
2425
|
+
let current = {};
|
|
2426
|
+
try {
|
|
2427
|
+
current = safeObject(tryJsonParse(fs.readFileSync(filePath, "utf8")));
|
|
2428
|
+
} catch {}
|
|
2429
|
+
if (!Object.keys(current).length && backupPath && fs.existsSync(backupPath)) {
|
|
2430
|
+
try {
|
|
2431
|
+
current = safeObject(tryJsonParse(fs.readFileSync(backupPath, "utf8")));
|
|
2432
|
+
} catch {}
|
|
2433
|
+
}
|
|
2418
2434
|
const stateEntryTimestampMs = (...values) => {
|
|
2419
2435
|
for (const value of values) {
|
|
2420
2436
|
const ms = Date.parse(String(value || "").trim());
|
|
@@ -2562,7 +2578,7 @@ function saveBotRunnerState(nextState) {
|
|
|
2562
2578
|
}
|
|
2563
2579
|
return normalizeBotRunnerConsumedComments(merged);
|
|
2564
2580
|
};
|
|
2565
|
-
const payload = {
|
|
2581
|
+
const payload = {
|
|
2566
2582
|
version: 1,
|
|
2567
2583
|
updated_at: new Date().toISOString(),
|
|
2568
2584
|
routes: mergeRunnerStateRoutes(
|
|
@@ -2581,15 +2597,30 @@ function saveBotRunnerState(nextState) {
|
|
|
2581
2597
|
current.requests,
|
|
2582
2598
|
nextState?.requests ?? current.requests,
|
|
2583
2599
|
),
|
|
2584
|
-
consumed_comments: mergeRunnerStateConsumedComments(
|
|
2585
|
-
current.consumed_comments ?? current.consumedComments,
|
|
2586
|
-
nextState?.consumedComments ?? nextState?.consumed_comments ?? current.consumed_comments ?? current.consumedComments,
|
|
2587
|
-
),
|
|
2588
|
-
};
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2600
|
+
consumed_comments: mergeRunnerStateConsumedComments(
|
|
2601
|
+
current.consumed_comments ?? current.consumedComments,
|
|
2602
|
+
nextState?.consumedComments ?? nextState?.consumed_comments ?? current.consumed_comments ?? current.consumedComments,
|
|
2603
|
+
),
|
|
2604
|
+
};
|
|
2605
|
+
const serialized = `${JSON.stringify(payload, null, 2)}\n`;
|
|
2606
|
+
writeTextFileAtomic(filePath, serialized);
|
|
2607
|
+
const verifiedPayload = tryJsonParse(fs.readFileSync(filePath, "utf8"));
|
|
2608
|
+
if (!verifiedPayload || typeof verifiedPayload !== "object" || Array.isArray(verifiedPayload)) {
|
|
2609
|
+
if (backupPath && fs.existsSync(backupPath)) {
|
|
2610
|
+
const backupRaw = fs.readFileSync(backupPath, "utf8");
|
|
2611
|
+
const backupParsed = tryJsonParse(backupRaw);
|
|
2612
|
+
if (backupParsed && typeof backupParsed === "object" && !Array.isArray(backupParsed)) {
|
|
2613
|
+
writeTextFileAtomic(filePath, backupRaw);
|
|
2614
|
+
}
|
|
2615
|
+
}
|
|
2616
|
+
throw new Error("bot runner state write verification failed");
|
|
2617
|
+
}
|
|
2618
|
+
if (backupPath) {
|
|
2619
|
+
writeTextFileAtomic(backupPath, serialized);
|
|
2620
|
+
}
|
|
2621
|
+
return filePath;
|
|
2622
|
+
});
|
|
2623
|
+
}
|
|
2593
2624
|
|
|
2594
2625
|
function normalizeBotRunnerExcludedComments(rawExcluded, nowMs = Date.now()) {
|
|
2595
2626
|
const normalized = {};
|
|
@@ -13302,21 +13333,27 @@ async function maybeDeliverRunnerExecutionFailureAfterRecord({
|
|
|
13302
13333
|
|
|
13303
13334
|
async function finalizeRunnerDeferredExecutionProcessed(deferredExecution, processed) {
|
|
13304
13335
|
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
|
-
|
|
13336
|
+
const resolvedIntentType = String(
|
|
13337
|
+
safeObject(loadBotRunnerState().routes[deferredExecution.routeKey]).last_intent_type || "",
|
|
13338
|
+
).trim();
|
|
13339
|
+
const normalizedDeferredOutcome = processed.kind === "delivery_failed"
|
|
13340
|
+
? "delivery_failed_after_generation"
|
|
13341
|
+
: processed.kind === "skipped"
|
|
13342
|
+
? "skipped"
|
|
13343
|
+
: String(processed.result?.outcome || "replied").trim().toLowerCase();
|
|
13344
|
+
markRunnerRequestLifecycle({
|
|
13345
|
+
normalizedRoute: deferredExecution.normalizedRoute,
|
|
13346
|
+
requestKey: deferredExecution.requestKey,
|
|
13347
|
+
selectedRecord: deferredExecution.selectedRecord,
|
|
13348
|
+
routeKey: deferredExecution.routeKey,
|
|
13349
|
+
outcome: normalizedDeferredOutcome,
|
|
13350
|
+
closedReason: processed.kind === "skipped"
|
|
13351
|
+
? String(processed.skippedRecord?.reason || processed.result?.detail || "skipped").trim()
|
|
13352
|
+
: "",
|
|
13353
|
+
conversationIDRaw: String(processed.result?.conversation_id || "").trim(),
|
|
13354
|
+
conversationParticipants: ensureArray(processed.result?.conversation_participants),
|
|
13355
|
+
conversationInitialResponders: ensureArray(processed.result?.conversation_initial_responders),
|
|
13356
|
+
allowedResponders: ensureArray(processed.result?.conversation_allowed_responders),
|
|
13320
13357
|
conversationLeadBot: String(processed.result?.conversation_lead_bot || "").trim(),
|
|
13321
13358
|
conversationSummaryBot: String(processed.result?.conversation_summary_bot || "").trim(),
|
|
13322
13359
|
conversationAllowBotToBot: processed.result?.conversation_allow_bot_to_bot === true,
|
|
@@ -13349,17 +13386,17 @@ async function finalizeRunnerDeferredExecutionProcessed(deferredExecution, proce
|
|
|
13349
13386
|
lastReplyMessageID: intFromRawAllowZero(processed.result?.last_reply_message_id, 0),
|
|
13350
13387
|
lastReplyMessageThreadID: intFromRawAllowZero(processed.result?.last_reply_message_thread_id, 0),
|
|
13351
13388
|
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,
|
|
13389
|
+
replyFallbackUsed: processed.result?.reply_fallback_used === true,
|
|
13390
|
+
authoritativeSourceMessageEnvelope: safeObject(
|
|
13391
|
+
safeObject(deferredExecution.humanInboundVisibility).sourceMessageEnvelope,
|
|
13392
|
+
),
|
|
13393
|
+
});
|
|
13394
|
+
if (processed.kind !== "delivery_failed" && processed.kind !== "skipped") {
|
|
13395
|
+
await ensureRunnerRootWorkItemForRequest({
|
|
13396
|
+
normalizedRoute: deferredExecution.normalizedRoute,
|
|
13397
|
+
routeKey: deferredExecution.routeKey,
|
|
13398
|
+
selectedRecord: deferredExecution.selectedRecord,
|
|
13399
|
+
runtime: deferredExecution.runtime,
|
|
13363
13400
|
requestKey: deferredExecution.requestKey,
|
|
13364
13401
|
});
|
|
13365
13402
|
}
|
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}`,
|
|
@@ -1809,7 +1809,7 @@ export async function resolveRunnerPrecomputedSelectedRecordExecutionContext({
|
|
|
1809
1809
|
};
|
|
1810
1810
|
}
|
|
1811
1811
|
|
|
1812
|
-
function resolveRunnerDeliverySourceMessageEnvelope({
|
|
1812
|
+
export function resolveRunnerDeliverySourceMessageEnvelope({
|
|
1813
1813
|
routeState,
|
|
1814
1814
|
persistedRequest,
|
|
1815
1815
|
selectedRecord,
|
|
@@ -1826,7 +1826,7 @@ function resolveRunnerDeliverySourceMessageEnvelope({
|
|
|
1826
1826
|
if (Object.keys(safeObject(localMatch.envelope)).length > 0) {
|
|
1827
1827
|
return localMatch.envelope;
|
|
1828
1828
|
}
|
|
1829
|
-
return
|
|
1829
|
+
return {};
|
|
1830
1830
|
}
|
|
1831
1831
|
|
|
1832
1832
|
function escapeRegExp(text) {
|
|
@@ -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, {
|
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
import http from "node:http";
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
|
-
import process from "node:process";
|
|
5
|
+
import process from "node:process";
|
|
6
|
+
import {
|
|
7
|
+
resolveRunnerDeliverySourceMessageEnvelope,
|
|
8
|
+
} from "./runner-orchestration.mjs";
|
|
6
9
|
|
|
7
10
|
function requireDependency(deps, key) {
|
|
8
11
|
const candidate = deps?.[key];
|
|
@@ -2745,6 +2748,66 @@ export async function runSelftestRunnerScenarios(push, deps) {
|
|
|
2745
2748
|
}
|
|
2746
2749
|
}
|
|
2747
2750
|
|
|
2751
|
+
const originalStateBackupHome = process.env.HOME;
|
|
2752
|
+
const originalStateBackupUserProfile = process.env.USERPROFILE;
|
|
2753
|
+
let stateBackupTempRoot = "";
|
|
2754
|
+
try {
|
|
2755
|
+
stateBackupTempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "metheus-runner-selftest-state-backup-"));
|
|
2756
|
+
process.env.HOME = stateBackupTempRoot;
|
|
2757
|
+
process.env.USERPROFILE = stateBackupTempRoot;
|
|
2758
|
+
const metheusDir = path.join(stateBackupTempRoot, ".metheus");
|
|
2759
|
+
fs.mkdirSync(metheusDir, { recursive: true });
|
|
2760
|
+
const statePath = path.join(metheusDir, "bot-runner-state.json");
|
|
2761
|
+
const backupPath = `${statePath}.bak`;
|
|
2762
|
+
fs.writeFileSync(
|
|
2763
|
+
backupPath,
|
|
2764
|
+
`${JSON.stringify({
|
|
2765
|
+
version: 1,
|
|
2766
|
+
updated_at: "2026-03-29T00:00:00.000Z",
|
|
2767
|
+
routes: {
|
|
2768
|
+
"telegram-monitor-state-backup::project::telegram::monitor::dest::actor": {
|
|
2769
|
+
last_action: "idle",
|
|
2770
|
+
},
|
|
2771
|
+
},
|
|
2772
|
+
shared_inboxes: {},
|
|
2773
|
+
excluded_comments: {},
|
|
2774
|
+
requests: {},
|
|
2775
|
+
consumed_comments: {},
|
|
2776
|
+
}, null, 2)}\n`,
|
|
2777
|
+
"utf8",
|
|
2778
|
+
);
|
|
2779
|
+
fs.writeFileSync(statePath, "{ malformed json", "utf8");
|
|
2780
|
+
const recoveredState = loadBotRunnerState();
|
|
2781
|
+
const recoveredRouteState = safeObject(
|
|
2782
|
+
safeObject(recoveredState.routes)["telegram-monitor-state-backup::project::telegram::monitor::dest::actor"],
|
|
2783
|
+
);
|
|
2784
|
+
const repairedPrimary = safeObject(tryJsonParse(fs.readFileSync(statePath, "utf8")));
|
|
2785
|
+
push(
|
|
2786
|
+
"runner_state_load_restores_primary_from_backup_when_primary_is_malformed",
|
|
2787
|
+
String(recoveredRouteState.last_action || "") === "idle"
|
|
2788
|
+
&& String(safeObject(safeObject(repairedPrimary.routes)["telegram-monitor-state-backup::project::telegram::monitor::dest::actor"]).last_action || "") === "idle",
|
|
2789
|
+
`recovered=${String(recoveredRouteState.last_action || "(none)")} repaired=${String(safeObject(safeObject(repairedPrimary.routes)["telegram-monitor-state-backup::project::telegram::monitor::dest::actor"]).last_action || "(none)")}`,
|
|
2790
|
+
);
|
|
2791
|
+
} catch (err) {
|
|
2792
|
+
push("runner_state_load_restores_primary_from_backup_when_primary_is_malformed", false, String(err?.message || err));
|
|
2793
|
+
} finally {
|
|
2794
|
+
if (typeof originalStateBackupHome === "string") {
|
|
2795
|
+
process.env.HOME = originalStateBackupHome;
|
|
2796
|
+
} else {
|
|
2797
|
+
delete process.env.HOME;
|
|
2798
|
+
}
|
|
2799
|
+
if (typeof originalStateBackupUserProfile === "string") {
|
|
2800
|
+
process.env.USERPROFILE = originalStateBackupUserProfile;
|
|
2801
|
+
} else {
|
|
2802
|
+
delete process.env.USERPROFILE;
|
|
2803
|
+
}
|
|
2804
|
+
if (stateBackupTempRoot) {
|
|
2805
|
+
try {
|
|
2806
|
+
fs.rmSync(stateBackupTempRoot, { recursive: true, force: true });
|
|
2807
|
+
} catch {}
|
|
2808
|
+
}
|
|
2809
|
+
}
|
|
2810
|
+
|
|
2748
2811
|
const defaultMonitorTriggerPolicy = normalizeRunnerTriggerPolicy({}, { role: "monitor" });
|
|
2749
2812
|
push(
|
|
2750
2813
|
"bot_runner_default_monitor_trigger_policy",
|
|
@@ -13246,14 +13309,139 @@ export async function runSelftestRunnerScenarios(push, deps) {
|
|
|
13246
13309
|
&& /ECONNRESET/i.test(String(processed.result?.transport_error || "")),
|
|
13247
13310
|
`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
13311
|
);
|
|
13249
|
-
} catch (err) {
|
|
13250
|
-
push("runner_delivery_failure_after_generation_records_ai_state_without_execution_error", false, String(err?.message || err));
|
|
13251
|
-
}
|
|
13252
|
-
|
|
13312
|
+
} catch (err) {
|
|
13313
|
+
push("runner_delivery_failure_after_generation_records_ai_state_without_execution_error", false, String(err?.message || err));
|
|
13314
|
+
}
|
|
13315
|
+
|
|
13316
|
+
try {
|
|
13317
|
+
const processed = await processRunnerSelectedRecord({
|
|
13318
|
+
routeKey: "delivery-stale-reply-anchor-key",
|
|
13319
|
+
normalizedRoute: normalizeRunnerRoute({
|
|
13320
|
+
name: "telegram-monitor-delivery-stale-reply-anchor",
|
|
13321
|
+
project_id: selftestProjectID,
|
|
13322
|
+
provider: "telegram",
|
|
13323
|
+
role: "monitor",
|
|
13324
|
+
role_profile: "monitor",
|
|
13325
|
+
destination_id: "dest-1",
|
|
13326
|
+
destination_label: "Main Room",
|
|
13327
|
+
server_bot_name: "RyoAI_bot",
|
|
13328
|
+
server_bot_id: "bot-1",
|
|
13329
|
+
trigger_policy: {
|
|
13330
|
+
mentions_only: true,
|
|
13331
|
+
direct_messages: true,
|
|
13332
|
+
reply_to_bot_messages: true,
|
|
13333
|
+
},
|
|
13334
|
+
archive_policy: {
|
|
13335
|
+
mirror_replies: true,
|
|
13336
|
+
dedupe_inbound: true,
|
|
13337
|
+
dedupe_outbound: true,
|
|
13338
|
+
skip_bot_messages: true,
|
|
13339
|
+
},
|
|
13340
|
+
dry_run_delivery: false,
|
|
13341
|
+
}),
|
|
13342
|
+
selectedRecord: {
|
|
13343
|
+
id: "comment-delivery-stale-reply-anchor",
|
|
13344
|
+
createdAt: "2026-03-17T00:00:22.000Z",
|
|
13345
|
+
parsedArchive: {
|
|
13346
|
+
kind: "telegram_message",
|
|
13347
|
+
chatID: "-100123",
|
|
13348
|
+
chatType: "supergroup",
|
|
13349
|
+
body: "@RyoAI_bot hi",
|
|
13350
|
+
messageID: 127,
|
|
13351
|
+
sender: "human",
|
|
13352
|
+
senderIsBot: false,
|
|
13353
|
+
mentionUsernames: ["ryoai_bot"],
|
|
13354
|
+
},
|
|
13355
|
+
},
|
|
13356
|
+
pendingOrdered: [],
|
|
13357
|
+
bot: {
|
|
13358
|
+
id: "bot-1",
|
|
13359
|
+
name: "RyoAI_bot",
|
|
13360
|
+
username: "RyoAI_bot",
|
|
13361
|
+
role: "monitor",
|
|
13362
|
+
provider: "telegram",
|
|
13363
|
+
},
|
|
13364
|
+
destination: {
|
|
13365
|
+
id: "dest-1",
|
|
13366
|
+
label: "Main Room",
|
|
13367
|
+
provider: "telegram",
|
|
13368
|
+
chatID: "-100123",
|
|
13369
|
+
},
|
|
13370
|
+
archiveThread: {
|
|
13371
|
+
threadID: "thread-1",
|
|
13372
|
+
workItemID: "work-item-1",
|
|
13373
|
+
},
|
|
13374
|
+
executionPlan: {
|
|
13375
|
+
mode: "role_profile",
|
|
13376
|
+
roleProfileName: "monitor",
|
|
13377
|
+
roleProfile: {
|
|
13378
|
+
client: "sample",
|
|
13379
|
+
model: "",
|
|
13380
|
+
permissionMode: "read_only",
|
|
13381
|
+
reasoningEffort: "low",
|
|
13382
|
+
},
|
|
13383
|
+
workspaceDir: "",
|
|
13384
|
+
workspaceSource: "selftest",
|
|
13385
|
+
usedCommandFallback: false,
|
|
13386
|
+
},
|
|
13387
|
+
runtime: {
|
|
13388
|
+
baseURL: "https://example.test",
|
|
13389
|
+
token: "selftest-token",
|
|
13390
|
+
timeoutSeconds: 30,
|
|
13391
|
+
actor: { user_id: "user-1" },
|
|
13392
|
+
},
|
|
13393
|
+
deps: {
|
|
13394
|
+
saveRunnerRouteState: () => {},
|
|
13395
|
+
startRunnerTypingHeartbeat: () => ({ async stop() {} }),
|
|
13396
|
+
runRunnerAIExecution: async () => ({
|
|
13397
|
+
skip: false,
|
|
13398
|
+
reply: "Hello from RyoAI_bot.",
|
|
13399
|
+
}),
|
|
13400
|
+
performLocalBotDelivery: async () => {
|
|
13401
|
+
const err = new Error("local telegram delivery skipped stale reply anchor (Bad Request: message to be replied not found)");
|
|
13402
|
+
err.code = "TELEGRAM_STALE_REPLY_ANCHOR";
|
|
13403
|
+
err.staleReplyAnchor = true;
|
|
13404
|
+
throw err;
|
|
13405
|
+
},
|
|
13406
|
+
serializeRunnerTriggerPolicy: (value) => value,
|
|
13407
|
+
serializeRunnerArchivePolicy: (value) => value,
|
|
13408
|
+
buildRunnerExecutionDeps: () => ({
|
|
13409
|
+
validateWorkspaceArtifacts,
|
|
13410
|
+
analyzeHumanConversationIntentWithAI: async () => ({
|
|
13411
|
+
mode: "single_bot",
|
|
13412
|
+
lead_bot: "ryoai_bot",
|
|
13413
|
+
participants: ["ryoai_bot"],
|
|
13414
|
+
initial_responders: ["ryoai_bot"],
|
|
13415
|
+
allowed_responders: ["ryoai_bot"],
|
|
13416
|
+
summary_bot: "",
|
|
13417
|
+
allow_bot_to_bot: false,
|
|
13418
|
+
reply_expectation: "informational",
|
|
13419
|
+
intent_type: "small_talk",
|
|
13420
|
+
}),
|
|
13421
|
+
}),
|
|
13422
|
+
buildRunnerDeliveryDeps: () => ({}),
|
|
13423
|
+
buildRunnerRuntimeDeps: () => ({}),
|
|
13424
|
+
resolveConversationPeerBots: () => [],
|
|
13425
|
+
},
|
|
13426
|
+
});
|
|
13427
|
+
push(
|
|
13428
|
+
"runner_stale_reply_anchor_skips_generated_reply_without_delivery_failure",
|
|
13429
|
+
processed.kind === "skipped"
|
|
13430
|
+
&& String(processed.skippedRecord?.reason || "") === "stale_reply_anchor"
|
|
13431
|
+
&& String(processed.result?.outcome || "") === "skipped"
|
|
13432
|
+
&& processed.result?.ai_reply_generated === true
|
|
13433
|
+
&& String(processed.result?.delivery_status || "") === "skipped_stale_reply_anchor"
|
|
13434
|
+
&& /message to be replied not found/i.test(String(processed.result?.transport_error || "")),
|
|
13435
|
+
`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)")}`,
|
|
13436
|
+
);
|
|
13437
|
+
} catch (err) {
|
|
13438
|
+
push("runner_stale_reply_anchor_skips_generated_reply_without_delivery_failure", false, String(err?.message || err));
|
|
13439
|
+
}
|
|
13440
|
+
|
|
13253
13441
|
try {
|
|
13254
13442
|
let capturedReplyToMessageID = 0;
|
|
13255
13443
|
let capturedMessageThreadID = 0;
|
|
13256
|
-
let capturedSourceMessageEnvelope = {};
|
|
13444
|
+
let capturedSourceMessageEnvelope = {};
|
|
13257
13445
|
const processed = await processRunnerSelectedRecord({
|
|
13258
13446
|
routeKey: "delivery-prefers-route-local-inbound-envelope-key",
|
|
13259
13447
|
normalizedRoute: normalizeRunnerRoute({
|
|
@@ -15075,9 +15263,43 @@ export async function runSelftestRunnerScenarios(push, deps) {
|
|
|
15075
15263
|
&& String(capturedSourceMessageEnvelope.source_route_key || "") === "telegram-monitor-ryoai2-bot-2::project::telegram::monitor::dest::actor",
|
|
15076
15264
|
`kind=${String(processed.kind || "(none)")} reply_to=${capturedReplyToMessageID} origin=${String(capturedSourceMessageEnvelope.source_origin || "(none)")} route=${String(capturedSourceMessageEnvelope.source_route_key || "(none)")}`,
|
|
15077
15265
|
);
|
|
15078
|
-
} catch (err) {
|
|
15079
|
-
push("runner_delivery_accepts_archived_local_route_provenance", false, String(err?.message || err));
|
|
15080
|
-
}
|
|
15266
|
+
} catch (err) {
|
|
15267
|
+
push("runner_delivery_accepts_archived_local_route_provenance", false, String(err?.message || err));
|
|
15268
|
+
}
|
|
15269
|
+
|
|
15270
|
+
try {
|
|
15271
|
+
const envelope = resolveRunnerDeliverySourceMessageEnvelope({
|
|
15272
|
+
routeState: {
|
|
15273
|
+
recent_local_inbound_receipts: {},
|
|
15274
|
+
recent_local_inbound_envelopes: {},
|
|
15275
|
+
},
|
|
15276
|
+
persistedRequest: {},
|
|
15277
|
+
selectedRecord: {
|
|
15278
|
+
parsedArchive: {
|
|
15279
|
+
kind: "telegram_message",
|
|
15280
|
+
chatID: "-100123",
|
|
15281
|
+
chatType: "supergroup",
|
|
15282
|
+
body: "@RyoAI2_bot hi without local provenance",
|
|
15283
|
+
messageID: 331,
|
|
15284
|
+
sender: "human",
|
|
15285
|
+
senderIsBot: false,
|
|
15286
|
+
mentionUsernames: ["ryoai2_bot"],
|
|
15287
|
+
sourceOrigin: "archive_reconstructed",
|
|
15288
|
+
sourceRouteKey: "telegram-monitor-ryoai2-bot-2::project::telegram::monitor::dest::actor",
|
|
15289
|
+
sourceBotUsername: "ryoai2_bot",
|
|
15290
|
+
},
|
|
15291
|
+
},
|
|
15292
|
+
routeKey: "telegram-monitor-ryoai2-bot-2::project::telegram::monitor::dest::actor",
|
|
15293
|
+
currentBotSelector: "ryoai2_bot",
|
|
15294
|
+
});
|
|
15295
|
+
push(
|
|
15296
|
+
"runner_delivery_drops_archive_reconstructed_reply_anchor_before_delivery",
|
|
15297
|
+
Object.keys(safeObject(envelope)).length === 0,
|
|
15298
|
+
`origin=${String(safeObject(envelope).source_origin || "(none)")} message=${String(safeObject(envelope).message_id || "(none)")}`,
|
|
15299
|
+
);
|
|
15300
|
+
} catch (err) {
|
|
15301
|
+
push("runner_delivery_drops_archive_reconstructed_reply_anchor_before_delivery", false, String(err?.message || err));
|
|
15302
|
+
}
|
|
15081
15303
|
|
|
15082
15304
|
try {
|
|
15083
15305
|
const provenanceTempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "metheus-runner-provenance-selftest-"));
|
|
@@ -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;
|