metheus-governance-mcp-cli 0.2.263 → 0.2.265
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 +88 -57
- package/lib/runner-helpers.mjs +1 -1
- package/lib/runner-orchestration.mjs +32 -5
- package/lib/runner-runtime.mjs +23 -4
- package/lib/selftest-runner-scenarios.mjs +165 -15
- 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 = {};
|
package/lib/runner-helpers.mjs
CHANGED
|
@@ -478,7 +478,7 @@ export function dedupeProcessableArchiveComments(records) {
|
|
|
478
478
|
return deduped;
|
|
479
479
|
}
|
|
480
480
|
|
|
481
|
-
function isArchiveRecordAfterState(record, routeState) {
|
|
481
|
+
export function isArchiveRecordAfterState(record, routeState) {
|
|
482
482
|
const state = safeObject(routeState);
|
|
483
483
|
const lastCommentID = String(state.last_processed_comment_id || "").trim();
|
|
484
484
|
const lastCreatedAt = String(state.last_processed_created_at || "").trim();
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
compareArchiveCommentRecords,
|
|
9
9
|
dedupeProcessableArchiveComments,
|
|
10
10
|
findRecentTelegramMessageEnvelope,
|
|
11
|
+
isArchiveRecordAfterState,
|
|
11
12
|
isTelegramLocalInboundEnvelopeForRoute,
|
|
12
13
|
isInboundArchiveKind,
|
|
13
14
|
normalizeTelegramMessageEnvelope,
|
|
@@ -1809,7 +1810,7 @@ export async function resolveRunnerPrecomputedSelectedRecordExecutionContext({
|
|
|
1809
1810
|
};
|
|
1810
1811
|
}
|
|
1811
1812
|
|
|
1812
|
-
function resolveRunnerDeliverySourceMessageEnvelope({
|
|
1813
|
+
export function resolveRunnerDeliverySourceMessageEnvelope({
|
|
1813
1814
|
routeState,
|
|
1814
1815
|
persistedRequest,
|
|
1815
1816
|
selectedRecord,
|
|
@@ -1826,7 +1827,7 @@ function resolveRunnerDeliverySourceMessageEnvelope({
|
|
|
1826
1827
|
if (Object.keys(safeObject(localMatch.envelope)).length > 0) {
|
|
1827
1828
|
return localMatch.envelope;
|
|
1828
1829
|
}
|
|
1829
|
-
return
|
|
1830
|
+
return {};
|
|
1830
1831
|
}
|
|
1831
1832
|
|
|
1832
1833
|
function escapeRegExp(text) {
|
|
@@ -6305,11 +6306,18 @@ export function selectRunnerPendingWork({
|
|
|
6305
6306
|
.map((record) => normalizeArchiveCommentRecord(record, parseArchivedChatComment))
|
|
6306
6307
|
.filter((record) => record.id && record.parsedArchive)
|
|
6307
6308
|
.sort(compareArchiveCommentRecords);
|
|
6309
|
+
const importedCommentRecords = ensureArray(importOutcome?.importedComments)
|
|
6310
|
+
.map((record) => normalizeArchiveCommentRecord(record, parseArchivedChatComment))
|
|
6311
|
+
.filter((record) => record.id && record.parsedArchive)
|
|
6312
|
+
.sort(compareArchiveCommentRecords);
|
|
6308
6313
|
const inboundComments = orderedComments.filter((record) => isInboundArchiveKind(record.parsedArchive.kind));
|
|
6309
6314
|
const importedRecords = dedupeProcessableArchiveComments(
|
|
6310
|
-
|
|
6311
|
-
|
|
6312
|
-
|
|
6315
|
+
[
|
|
6316
|
+
...ensureArray(importOutcome?.importedCommentIDs)
|
|
6317
|
+
.map((commentID) => orderedComments.find((record) => record.id === commentID))
|
|
6318
|
+
.filter(Boolean),
|
|
6319
|
+
...importedCommentRecords,
|
|
6320
|
+
],
|
|
6313
6321
|
);
|
|
6314
6322
|
const hasCursor = Boolean(
|
|
6315
6323
|
String(safeObject(refreshedState).active_comment_id || "").trim()
|
|
@@ -6324,6 +6332,18 @@ export function selectRunnerPendingWork({
|
|
|
6324
6332
|
(record) => normalizeArchiveCommentRecord(record, parseArchivedChatComment),
|
|
6325
6333
|
pendingSelectionOptions,
|
|
6326
6334
|
);
|
|
6335
|
+
const laggingImportedRecords = importedRecords.filter((record) => (
|
|
6336
|
+
!orderedComments.some((orderedRecord) => String(orderedRecord.id || "").trim() === String(record.id || "").trim())
|
|
6337
|
+
));
|
|
6338
|
+
const importedPendingAfterCursor = dedupeProcessableArchiveComments(
|
|
6339
|
+
laggingImportedRecords.filter((record) => isArchiveRecordAfterState(record, {
|
|
6340
|
+
...safeObject(refreshedState),
|
|
6341
|
+
last_processed_comment_id: String(safeObject(refreshedState).active_comment_id || "").trim()
|
|
6342
|
+
|| safeObject(refreshedState).last_processed_comment_id,
|
|
6343
|
+
last_processed_created_at: String(safeObject(refreshedState).active_comment_created_at || "").trim()
|
|
6344
|
+
|| safeObject(refreshedState).last_processed_created_at,
|
|
6345
|
+
})),
|
|
6346
|
+
);
|
|
6327
6347
|
const pending = importedRecords.length > 0 && !hasCursor
|
|
6328
6348
|
? applyPendingAgeSelection({
|
|
6329
6349
|
ordered: orderedComments,
|
|
@@ -6331,6 +6351,13 @@ export function selectRunnerPendingWork({
|
|
|
6331
6351
|
shouldPrime: false,
|
|
6332
6352
|
pending: importedRecords,
|
|
6333
6353
|
}, pendingSelectionOptions)
|
|
6354
|
+
: importedPendingAfterCursor.length > 0 && ensureArray(fullPending.pending).length === 0
|
|
6355
|
+
? applyPendingAgeSelection({
|
|
6356
|
+
ordered: orderedComments,
|
|
6357
|
+
latest: inboundComments.length ? inboundComments[inboundComments.length - 1] : null,
|
|
6358
|
+
shouldPrime: false,
|
|
6359
|
+
pending: importedPendingAfterCursor,
|
|
6360
|
+
}, pendingSelectionOptions)
|
|
6334
6361
|
: fullPending;
|
|
6335
6362
|
return {
|
|
6336
6363
|
orderedComments,
|
package/lib/runner-runtime.mjs
CHANGED
|
@@ -767,6 +767,7 @@ async function archiveRunnerTelegramInboundUpdates({
|
|
|
767
767
|
}) {
|
|
768
768
|
let handledUpdateID = 0;
|
|
769
769
|
const importedCommentIDs = [];
|
|
770
|
+
const importedComments = [];
|
|
770
771
|
const retainedSharedUpdates = [];
|
|
771
772
|
for (let index = 0; index < mergedSharedUpdates.length; index += 1) {
|
|
772
773
|
const update = mergedSharedUpdates[index];
|
|
@@ -799,17 +800,19 @@ async function archiveRunnerTelegramInboundUpdates({
|
|
|
799
800
|
continue;
|
|
800
801
|
}
|
|
801
802
|
let createdComment;
|
|
803
|
+
let archiveBody = "";
|
|
802
804
|
try {
|
|
805
|
+
archiveBody = formatTelegramInboundArchiveComment({
|
|
806
|
+
...update,
|
|
807
|
+
archiveSourceOrigin: "telegram_archive_context",
|
|
808
|
+
});
|
|
803
809
|
createdComment = await createThreadComment({
|
|
804
810
|
siteBaseURL: runtime.baseURL,
|
|
805
811
|
token: runtime.token,
|
|
806
812
|
timeoutSeconds: runtime.timeoutSeconds,
|
|
807
813
|
threadID: archiveThread.threadID,
|
|
808
814
|
actorUserID: runtime.actor.user_id,
|
|
809
|
-
body:
|
|
810
|
-
...update,
|
|
811
|
-
archiveSourceOrigin: "telegram_archive_context",
|
|
812
|
-
}),
|
|
815
|
+
body: archiveBody,
|
|
813
816
|
});
|
|
814
817
|
} catch (err) {
|
|
815
818
|
releaseRunnerInboundArchiveMessageReservation(reservation.reservationKey);
|
|
@@ -822,6 +825,19 @@ async function archiveRunnerTelegramInboundUpdates({
|
|
|
822
825
|
if (String(createdComment.id || "").trim()) {
|
|
823
826
|
importedCommentIDs.push(String(createdComment.id || "").trim());
|
|
824
827
|
}
|
|
828
|
+
importedComments.push({
|
|
829
|
+
...safeObject(createdComment),
|
|
830
|
+
id: String(createdComment?.id || "").trim(),
|
|
831
|
+
body: archiveBody,
|
|
832
|
+
created_at: String(createdComment?.created_at || createdComment?.createdAt || new Date().toISOString()).trim(),
|
|
833
|
+
updated_at: String(
|
|
834
|
+
createdComment?.updated_at
|
|
835
|
+
|| createdComment?.updatedAt
|
|
836
|
+
|| createdComment?.created_at
|
|
837
|
+
|| createdComment?.createdAt
|
|
838
|
+
|| new Date().toISOString()
|
|
839
|
+
).trim(),
|
|
840
|
+
});
|
|
825
841
|
if (boolFromRaw(archivePolicy.dedupeInbound, true)) {
|
|
826
842
|
existingKeys.add(dedupeKey);
|
|
827
843
|
}
|
|
@@ -829,6 +845,7 @@ async function archiveRunnerTelegramInboundUpdates({
|
|
|
829
845
|
return {
|
|
830
846
|
handledUpdateID,
|
|
831
847
|
importedCommentIDs,
|
|
848
|
+
importedComments,
|
|
832
849
|
retainedSharedUpdates,
|
|
833
850
|
};
|
|
834
851
|
}
|
|
@@ -1168,6 +1185,7 @@ export async function archiveLocalTelegramMessagesForRoute({
|
|
|
1168
1185
|
const {
|
|
1169
1186
|
handledUpdateID,
|
|
1170
1187
|
importedCommentIDs,
|
|
1188
|
+
importedComments,
|
|
1171
1189
|
retainedSharedUpdates,
|
|
1172
1190
|
} = await archiveRunnerTelegramInboundUpdates({
|
|
1173
1191
|
mergedSharedUpdates,
|
|
@@ -1191,6 +1209,7 @@ export async function archiveLocalTelegramMessagesForRoute({
|
|
|
1191
1209
|
|
|
1192
1210
|
return {
|
|
1193
1211
|
importedCommentIDs,
|
|
1212
|
+
importedComments,
|
|
1194
1213
|
importedCount: importedCommentIDs.length,
|
|
1195
1214
|
lastUpdateID: Math.max(lastUpdateID, handledUpdateID),
|
|
1196
1215
|
};
|
|
@@ -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];
|
|
@@ -595,8 +598,8 @@ export async function runSelftestRunnerScenarios(push, deps) {
|
|
|
595
598
|
);
|
|
596
599
|
}
|
|
597
600
|
|
|
598
|
-
try {
|
|
599
|
-
const duplicateComments = [
|
|
601
|
+
try {
|
|
602
|
+
const duplicateComments = [
|
|
600
603
|
{
|
|
601
604
|
id: "dup-comment-earliest",
|
|
602
605
|
createdAt: "2026-03-18T00:00:01.000Z",
|
|
@@ -633,15 +636,68 @@ export async function runSelftestRunnerScenarios(push, deps) {
|
|
|
633
636
|
`pending=${pendingWork.pending.pending.map((item) => item.id).join(",") || "(none)"}`,
|
|
634
637
|
);
|
|
635
638
|
} catch (err) {
|
|
636
|
-
push(
|
|
637
|
-
"runner_pending_selection_does_not_requeue_imported_duplicate_after_cursor",
|
|
638
|
-
false,
|
|
639
|
-
String(err?.message || err),
|
|
640
|
-
);
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
try {
|
|
644
|
-
const
|
|
639
|
+
push(
|
|
640
|
+
"runner_pending_selection_does_not_requeue_imported_duplicate_after_cursor",
|
|
641
|
+
false,
|
|
642
|
+
String(err?.message || err),
|
|
643
|
+
);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
try {
|
|
647
|
+
const pendingWork = selectRunnerPendingWork({
|
|
648
|
+
comments: [
|
|
649
|
+
{
|
|
650
|
+
id: "cursor-comment",
|
|
651
|
+
createdAt: "2026-03-18T00:00:01.000Z",
|
|
652
|
+
updatedAt: "2026-03-18T00:00:01.000Z",
|
|
653
|
+
parsedArchive: { kind: "telegram_message", chatID: "-1001", messageID: 353, body: "@RyoAI_bot first copy" },
|
|
654
|
+
},
|
|
655
|
+
],
|
|
656
|
+
importOutcome: {
|
|
657
|
+
importedCommentIDs: ["fresh-import-comment"],
|
|
658
|
+
importedComments: [
|
|
659
|
+
{
|
|
660
|
+
id: "fresh-import-comment",
|
|
661
|
+
created_at: "2026-03-18T00:00:05.000Z",
|
|
662
|
+
updated_at: "2026-03-18T00:00:05.000Z",
|
|
663
|
+
body: "[Telegram message]\nchat_id: -1001\nmessage_id: 354\n\nfresh imported mention",
|
|
664
|
+
},
|
|
665
|
+
],
|
|
666
|
+
},
|
|
667
|
+
refreshedState: {
|
|
668
|
+
last_processed_comment_id: "cursor-comment",
|
|
669
|
+
last_processed_created_at: "2026-03-18T00:00:01.000Z",
|
|
670
|
+
},
|
|
671
|
+
mode: "start",
|
|
672
|
+
parseArchivedChatComment,
|
|
673
|
+
deps: {
|
|
674
|
+
normalizeArchiveCommentRecord: (record, parser) => ({
|
|
675
|
+
id: String(record?.id || "").trim(),
|
|
676
|
+
body: String(record?.body || "").trim(),
|
|
677
|
+
createdAt: String(record?.created_at || record?.createdAt || record?.updated_at || record?.updatedAt || "").trim(),
|
|
678
|
+
updatedAt: String(record?.updated_at || record?.updatedAt || "").trim(),
|
|
679
|
+
parsedArchive: typeof parser === "function" ? parser(String(record?.body || "").trim()) : null,
|
|
680
|
+
}),
|
|
681
|
+
applyPendingAgeSelection: (selection) => selection,
|
|
682
|
+
},
|
|
683
|
+
pendingSelectionOptions: {},
|
|
684
|
+
});
|
|
685
|
+
push(
|
|
686
|
+
"runner_pending_selection_uses_fresh_imported_comment_when_thread_read_lags",
|
|
687
|
+
pendingWork.pending.pending.length === 1
|
|
688
|
+
&& String(pendingWork.pending.pending[0]?.id || "").trim() === "fresh-import-comment",
|
|
689
|
+
`pending=${pendingWork.pending.pending.map((item) => item.id).join(",") || "(none)"}`,
|
|
690
|
+
);
|
|
691
|
+
} catch (err) {
|
|
692
|
+
push(
|
|
693
|
+
"runner_pending_selection_uses_fresh_imported_comment_when_thread_read_lags",
|
|
694
|
+
false,
|
|
695
|
+
String(err?.message || err),
|
|
696
|
+
);
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
try {
|
|
700
|
+
const selected = selectProjectChatDestination(
|
|
645
701
|
[
|
|
646
702
|
{ id: "dest-1", provider: "telegram", label: "Main Room", chat_id: "-1001", is_active: true },
|
|
647
703
|
{ id: "dest-2", provider: "slack", label: "Slack", chat_id: "C123", is_active: true },
|
|
@@ -2745,6 +2801,66 @@ export async function runSelftestRunnerScenarios(push, deps) {
|
|
|
2745
2801
|
}
|
|
2746
2802
|
}
|
|
2747
2803
|
|
|
2804
|
+
const originalStateBackupHome = process.env.HOME;
|
|
2805
|
+
const originalStateBackupUserProfile = process.env.USERPROFILE;
|
|
2806
|
+
let stateBackupTempRoot = "";
|
|
2807
|
+
try {
|
|
2808
|
+
stateBackupTempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "metheus-runner-selftest-state-backup-"));
|
|
2809
|
+
process.env.HOME = stateBackupTempRoot;
|
|
2810
|
+
process.env.USERPROFILE = stateBackupTempRoot;
|
|
2811
|
+
const metheusDir = path.join(stateBackupTempRoot, ".metheus");
|
|
2812
|
+
fs.mkdirSync(metheusDir, { recursive: true });
|
|
2813
|
+
const statePath = path.join(metheusDir, "bot-runner-state.json");
|
|
2814
|
+
const backupPath = `${statePath}.bak`;
|
|
2815
|
+
fs.writeFileSync(
|
|
2816
|
+
backupPath,
|
|
2817
|
+
`${JSON.stringify({
|
|
2818
|
+
version: 1,
|
|
2819
|
+
updated_at: "2026-03-29T00:00:00.000Z",
|
|
2820
|
+
routes: {
|
|
2821
|
+
"telegram-monitor-state-backup::project::telegram::monitor::dest::actor": {
|
|
2822
|
+
last_action: "idle",
|
|
2823
|
+
},
|
|
2824
|
+
},
|
|
2825
|
+
shared_inboxes: {},
|
|
2826
|
+
excluded_comments: {},
|
|
2827
|
+
requests: {},
|
|
2828
|
+
consumed_comments: {},
|
|
2829
|
+
}, null, 2)}\n`,
|
|
2830
|
+
"utf8",
|
|
2831
|
+
);
|
|
2832
|
+
fs.writeFileSync(statePath, "{ malformed json", "utf8");
|
|
2833
|
+
const recoveredState = loadBotRunnerState();
|
|
2834
|
+
const recoveredRouteState = safeObject(
|
|
2835
|
+
safeObject(recoveredState.routes)["telegram-monitor-state-backup::project::telegram::monitor::dest::actor"],
|
|
2836
|
+
);
|
|
2837
|
+
const repairedPrimary = safeObject(tryJsonParse(fs.readFileSync(statePath, "utf8")));
|
|
2838
|
+
push(
|
|
2839
|
+
"runner_state_load_restores_primary_from_backup_when_primary_is_malformed",
|
|
2840
|
+
String(recoveredRouteState.last_action || "") === "idle"
|
|
2841
|
+
&& String(safeObject(safeObject(repairedPrimary.routes)["telegram-monitor-state-backup::project::telegram::monitor::dest::actor"]).last_action || "") === "idle",
|
|
2842
|
+
`recovered=${String(recoveredRouteState.last_action || "(none)")} repaired=${String(safeObject(safeObject(repairedPrimary.routes)["telegram-monitor-state-backup::project::telegram::monitor::dest::actor"]).last_action || "(none)")}`,
|
|
2843
|
+
);
|
|
2844
|
+
} catch (err) {
|
|
2845
|
+
push("runner_state_load_restores_primary_from_backup_when_primary_is_malformed", false, String(err?.message || err));
|
|
2846
|
+
} finally {
|
|
2847
|
+
if (typeof originalStateBackupHome === "string") {
|
|
2848
|
+
process.env.HOME = originalStateBackupHome;
|
|
2849
|
+
} else {
|
|
2850
|
+
delete process.env.HOME;
|
|
2851
|
+
}
|
|
2852
|
+
if (typeof originalStateBackupUserProfile === "string") {
|
|
2853
|
+
process.env.USERPROFILE = originalStateBackupUserProfile;
|
|
2854
|
+
} else {
|
|
2855
|
+
delete process.env.USERPROFILE;
|
|
2856
|
+
}
|
|
2857
|
+
if (stateBackupTempRoot) {
|
|
2858
|
+
try {
|
|
2859
|
+
fs.rmSync(stateBackupTempRoot, { recursive: true, force: true });
|
|
2860
|
+
} catch {}
|
|
2861
|
+
}
|
|
2862
|
+
}
|
|
2863
|
+
|
|
2748
2864
|
const defaultMonitorTriggerPolicy = normalizeRunnerTriggerPolicy({}, { role: "monitor" });
|
|
2749
2865
|
push(
|
|
2750
2866
|
"bot_runner_default_monitor_trigger_policy",
|
|
@@ -15200,9 +15316,43 @@ export async function runSelftestRunnerScenarios(push, deps) {
|
|
|
15200
15316
|
&& String(capturedSourceMessageEnvelope.source_route_key || "") === "telegram-monitor-ryoai2-bot-2::project::telegram::monitor::dest::actor",
|
|
15201
15317
|
`kind=${String(processed.kind || "(none)")} reply_to=${capturedReplyToMessageID} origin=${String(capturedSourceMessageEnvelope.source_origin || "(none)")} route=${String(capturedSourceMessageEnvelope.source_route_key || "(none)")}`,
|
|
15202
15318
|
);
|
|
15203
|
-
} catch (err) {
|
|
15204
|
-
push("runner_delivery_accepts_archived_local_route_provenance", false, String(err?.message || err));
|
|
15205
|
-
}
|
|
15319
|
+
} catch (err) {
|
|
15320
|
+
push("runner_delivery_accepts_archived_local_route_provenance", false, String(err?.message || err));
|
|
15321
|
+
}
|
|
15322
|
+
|
|
15323
|
+
try {
|
|
15324
|
+
const envelope = resolveRunnerDeliverySourceMessageEnvelope({
|
|
15325
|
+
routeState: {
|
|
15326
|
+
recent_local_inbound_receipts: {},
|
|
15327
|
+
recent_local_inbound_envelopes: {},
|
|
15328
|
+
},
|
|
15329
|
+
persistedRequest: {},
|
|
15330
|
+
selectedRecord: {
|
|
15331
|
+
parsedArchive: {
|
|
15332
|
+
kind: "telegram_message",
|
|
15333
|
+
chatID: "-100123",
|
|
15334
|
+
chatType: "supergroup",
|
|
15335
|
+
body: "@RyoAI2_bot hi without local provenance",
|
|
15336
|
+
messageID: 331,
|
|
15337
|
+
sender: "human",
|
|
15338
|
+
senderIsBot: false,
|
|
15339
|
+
mentionUsernames: ["ryoai2_bot"],
|
|
15340
|
+
sourceOrigin: "archive_reconstructed",
|
|
15341
|
+
sourceRouteKey: "telegram-monitor-ryoai2-bot-2::project::telegram::monitor::dest::actor",
|
|
15342
|
+
sourceBotUsername: "ryoai2_bot",
|
|
15343
|
+
},
|
|
15344
|
+
},
|
|
15345
|
+
routeKey: "telegram-monitor-ryoai2-bot-2::project::telegram::monitor::dest::actor",
|
|
15346
|
+
currentBotSelector: "ryoai2_bot",
|
|
15347
|
+
});
|
|
15348
|
+
push(
|
|
15349
|
+
"runner_delivery_drops_archive_reconstructed_reply_anchor_before_delivery",
|
|
15350
|
+
Object.keys(safeObject(envelope)).length === 0,
|
|
15351
|
+
`origin=${String(safeObject(envelope).source_origin || "(none)")} message=${String(safeObject(envelope).message_id || "(none)")}`,
|
|
15352
|
+
);
|
|
15353
|
+
} catch (err) {
|
|
15354
|
+
push("runner_delivery_drops_archive_reconstructed_reply_anchor_before_delivery", false, String(err?.message || err));
|
|
15355
|
+
}
|
|
15206
15356
|
|
|
15207
15357
|
try {
|
|
15208
15358
|
const provenanceTempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "metheus-runner-provenance-selftest-"));
|