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 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
- waitForBotRunnerStateLockRelease(filePath);
2206
- try {
2207
- if (!fs.existsSync(filePath)) {
2208
- return {
2209
- filePath,
2210
- routes: {},
2211
- sharedInboxes: {},
2212
- excludedComments: {},
2213
- requests: {},
2214
- consumedComments: {},
2215
- migrated: false,
2216
- migratedKeys: [],
2217
- remainingAnonymousKeys: [],
2218
- };
2219
- }
2220
- const parsed = tryJsonParse(fs.readFileSync(filePath, "utf8"));
2221
- const runnerConfig = loadBotRunnerConfig({ persistIfNeeded: true });
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
- filePath,
2251
- routes: {},
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
- return withBotRunnerStateFileLock(filePath, () => {
2414
- let current = {};
2415
- try {
2416
- current = safeObject(tryJsonParse(fs.readFileSync(filePath, "utf8")));
2417
- } catch {}
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
- writeTextFileAtomic(filePath, `${JSON.stringify(payload, null, 2)}\n`);
2590
- return filePath;
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 = {};
@@ -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 safeObject(localMatch.candidates).archiveEnvelope || {};
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
- ensureArray(importOutcome?.importedCommentIDs)
6311
- .map((commentID) => orderedComments.find((record) => record.id === commentID))
6312
- .filter(Boolean),
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,
@@ -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: formatTelegramInboundArchiveComment({
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 selected = selectProjectChatDestination(
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-"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metheus-governance-mcp-cli",
3
- "version": "0.2.263",
3
+ "version": "0.2.265",
4
4
  "description": "Metheus Governance MCP CLI (setup + stdio proxy)",
5
5
  "type": "module",
6
6
  "files": [