metheus-governance-mcp-cli 0.2.263 → 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 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 = {};
@@ -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 safeObject(localMatch.candidates).archiveEnvelope || {};
1829
+ return {};
1830
1830
  }
1831
1831
 
1832
1832
  function escapeRegExp(text) {
@@ -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",
@@ -15200,9 +15263,43 @@ export async function runSelftestRunnerScenarios(push, deps) {
15200
15263
  && String(capturedSourceMessageEnvelope.source_route_key || "") === "telegram-monitor-ryoai2-bot-2::project::telegram::monitor::dest::actor",
15201
15264
  `kind=${String(processed.kind || "(none)")} reply_to=${capturedReplyToMessageID} origin=${String(capturedSourceMessageEnvelope.source_origin || "(none)")} route=${String(capturedSourceMessageEnvelope.source_route_key || "(none)")}`,
15202
15265
  );
15203
- } catch (err) {
15204
- push("runner_delivery_accepts_archived_local_route_provenance", false, String(err?.message || err));
15205
- }
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
+ }
15206
15303
 
15207
15304
  try {
15208
15305
  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.264",
4
4
  "description": "Metheus Governance MCP CLI (setup + stdio proxy)",
5
5
  "type": "module",
6
6
  "files": [