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 +88 -57
- package/lib/runner-orchestration.mjs +2 -2
- package/lib/selftest-runner-scenarios.mjs +101 -4
- 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 = {};
|
|
@@ -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) {
|
|
@@ -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-"));
|