metheus-governance-mcp-cli 0.2.172 → 0.2.178
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 +177 -24
- package/lib/local-ai-adapters.mjs +294 -35
- package/lib/runner-data.mjs +9 -2
- package/lib/runner-helpers.mjs +46 -11
- package/lib/runner-orchestration.mjs +321 -137
- package/lib/runner-trigger.mjs +62 -7
- package/lib/selftest-runner-scenarios.mjs +528 -25
- package/lib/selftest-telegram-e2e.mjs +622 -620
- package/package.json +1 -1
package/cli.mjs
CHANGED
|
@@ -11,6 +11,7 @@ import http from "node:http";
|
|
|
11
11
|
import https from "node:https";
|
|
12
12
|
import {
|
|
13
13
|
DEFAULT_LOCAL_AI_CLIENT,
|
|
14
|
+
adjudicateRunnerRespondersWithAI,
|
|
14
15
|
analyzeHumanConversationIntentWithAI,
|
|
15
16
|
auditRoleExecutionPlanWithAI,
|
|
16
17
|
auditDirectHumanReplyWithAI,
|
|
@@ -126,6 +127,7 @@ import {
|
|
|
126
127
|
withWorkspaceDirArg,
|
|
127
128
|
} from "./lib/client-registration.mjs";
|
|
128
129
|
import {
|
|
130
|
+
applyPendingAgeSelection,
|
|
129
131
|
buildRunnerRouteStateFromComment,
|
|
130
132
|
isInboundArchiveKind,
|
|
131
133
|
normalizeArchiveCommentRecord,
|
|
@@ -165,6 +167,7 @@ import {
|
|
|
165
167
|
} from "./lib/runner-execution.mjs";
|
|
166
168
|
import {
|
|
167
169
|
processRunnerSelectedRecord,
|
|
170
|
+
resolveRunnerResponderAdjudication,
|
|
168
171
|
selectRunnerPendingWork,
|
|
169
172
|
} from "./lib/runner-orchestration.mjs";
|
|
170
173
|
import {
|
|
@@ -196,6 +199,7 @@ const BOT_RUNNER_ROUTE_LOCK_STALE_MS = 30 * 60 * 1000;
|
|
|
196
199
|
const BOT_RUNNER_ACTIVE_EXECUTION_STUCK_WARNING_MS = 20 * 60 * 1000;
|
|
197
200
|
const BOT_RUNNER_ACTIVE_EXECUTION_HEARTBEAT_INTERVAL_MS = 5000;
|
|
198
201
|
const BOT_RUNNER_ACTIVE_EXECUTION_HEARTBEAT_STALE_MS = 60 * 1000;
|
|
202
|
+
const BOT_RUNNER_PENDING_COMMENT_MAX_AGE_MS = 15 * 60 * 1000;
|
|
199
203
|
const BOT_RUNNER_DEFAULT_CONCURRENCY = 2;
|
|
200
204
|
const BOT_RUNNER_MAX_CONCURRENCY = 8;
|
|
201
205
|
const TELEGRAM_ROOT_LEGACY_ENV_RELATIVE_PATH = path.join(".metheus", "telegram.env");
|
|
@@ -3989,6 +3993,7 @@ async function resolveInformationalQueryReply({
|
|
|
3989
3993
|
|
|
3990
3994
|
function buildRunnerExecutionDeps() {
|
|
3991
3995
|
return {
|
|
3996
|
+
adjudicateRunnerRespondersWithAI,
|
|
3992
3997
|
analyzeHumanConversationIntentWithAI,
|
|
3993
3998
|
auditRoleExecutionPlanWithAI,
|
|
3994
3999
|
auditDirectHumanReplyWithAI,
|
|
@@ -4031,6 +4036,7 @@ function buildRunnerRuntimeDeps() {
|
|
|
4031
4036
|
getTelegramUpdates,
|
|
4032
4037
|
normalizeLocalTelegramUpdate,
|
|
4033
4038
|
listThreadComments,
|
|
4039
|
+
applyPendingAgeSelection,
|
|
4034
4040
|
normalizeArchiveCommentRecord,
|
|
4035
4041
|
isInboundArchiveKind,
|
|
4036
4042
|
buildArchivedInboundMessageKey,
|
|
@@ -4223,6 +4229,7 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
4223
4229
|
timeoutSeconds: runtime.timeoutSeconds,
|
|
4224
4230
|
});
|
|
4225
4231
|
const bot = selectRunnerBot(bots, normalizedRoute);
|
|
4232
|
+
const managedConversationBots = resolveRunnerConversationManagedBots(normalizedRoute, bots);
|
|
4226
4233
|
const executionPlan = resolveRunnerExecutionPlan(
|
|
4227
4234
|
normalizedRoute,
|
|
4228
4235
|
bot,
|
|
@@ -4293,11 +4300,21 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
4293
4300
|
refreshedState,
|
|
4294
4301
|
mode,
|
|
4295
4302
|
parseArchivedChatComment,
|
|
4303
|
+
pendingSelectionOptions: {
|
|
4304
|
+
maxPendingAgeMs: BOT_RUNNER_PENDING_COMMENT_MAX_AGE_MS,
|
|
4305
|
+
},
|
|
4296
4306
|
deps: {
|
|
4307
|
+
applyPendingAgeSelection,
|
|
4297
4308
|
normalizeArchiveCommentRecord,
|
|
4298
4309
|
},
|
|
4299
4310
|
});
|
|
4300
4311
|
const pending = pendingWork.pending;
|
|
4312
|
+
if (pending.staleSkippedLatest) {
|
|
4313
|
+
saveRunnerRouteState(routeKey, buildRunnerRouteStateFromComment(pending.staleSkippedLatest, {
|
|
4314
|
+
last_reason: `skipped ${ensureArray(pending.staleSkipped).length} stale archive message(s) older than ${Math.floor(BOT_RUNNER_PENDING_COMMENT_MAX_AGE_MS / 60000)} minutes`,
|
|
4315
|
+
}));
|
|
4316
|
+
refreshedState = safeObject(loadBotRunnerState().routes[routeKey]);
|
|
4317
|
+
}
|
|
4301
4318
|
if (pending.shouldPrime && pending.latest) {
|
|
4302
4319
|
saveRunnerRouteState(routeKey, buildRunnerRouteStateFromComment(pending.latest, { primed: true }));
|
|
4303
4320
|
return {
|
|
@@ -4318,7 +4335,9 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
4318
4335
|
route_name: normalizedRoute.name,
|
|
4319
4336
|
logical_signature: runnerRouteLogicalSignature(normalizedRoute),
|
|
4320
4337
|
outcome: "idle",
|
|
4321
|
-
detail:
|
|
4338
|
+
detail: pending.staleSkippedLatest
|
|
4339
|
+
? `skipped ${ensureArray(pending.staleSkipped).length} stale archive message(s)`
|
|
4340
|
+
: importOutcome.importedCount > 0
|
|
4322
4341
|
? "local telegram updates imported but no pending archive comments were selected"
|
|
4323
4342
|
: "no new local telegram messages or archived inbound messages",
|
|
4324
4343
|
archive_source: String(archiveThread.source || "").trim() || "-",
|
|
@@ -4344,33 +4363,85 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
4344
4363
|
};
|
|
4345
4364
|
}
|
|
4346
4365
|
if (deferExecution) {
|
|
4347
|
-
const
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
route_key: routeKey,
|
|
4351
|
-
route_name: normalizedRoute.name,
|
|
4352
|
-
logical_signature: runnerRouteLogicalSignature(normalizedRoute),
|
|
4353
|
-
outcome: "accepted",
|
|
4354
|
-
detail: `accepted comment ${selectedRecord.id} for background execution`,
|
|
4355
|
-
archive_source: String(archiveThread.source || "").trim() || "-",
|
|
4356
|
-
archive_work_item_id: String(archiveThread.workItemID || "").trim() || "",
|
|
4357
|
-
thread_id: archiveThread.threadID,
|
|
4358
|
-
comment_id: selectedRecord.id,
|
|
4359
|
-
execution_mode: executionPlan.mode,
|
|
4360
|
-
role_profile: executionPlan.roleProfileName,
|
|
4361
|
-
deferred_execution: {
|
|
4362
|
-
routeKey,
|
|
4363
|
-
normalizedRoute,
|
|
4364
|
-
routeState: refreshedState,
|
|
4366
|
+
const skippedRecords = [];
|
|
4367
|
+
for (const selectedRecord of pending.pending) {
|
|
4368
|
+
const adjudication = await resolveRunnerResponderAdjudication({
|
|
4365
4369
|
selectedRecord,
|
|
4366
4370
|
pendingOrdered: pending.ordered,
|
|
4371
|
+
normalizedRoute,
|
|
4367
4372
|
bot,
|
|
4368
|
-
destination,
|
|
4369
|
-
archiveThread,
|
|
4370
4373
|
executionPlan,
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
+
deps: {
|
|
4375
|
+
...buildRunnerExecutionDeps(),
|
|
4376
|
+
managedConversationBots,
|
|
4377
|
+
resolveConversationPeerBots: resolveRunnerConversationPeers,
|
|
4378
|
+
},
|
|
4379
|
+
});
|
|
4380
|
+
const currentBotSelector = String(bot?.username || bot?.name || "").trim().replace(/^@+/, "").toLowerCase();
|
|
4381
|
+
const currentBotSelected = ensureArray(adjudication.selected_bot_usernames)
|
|
4382
|
+
.map((value) => String(value || "").trim().replace(/^@+/, "").toLowerCase())
|
|
4383
|
+
.includes(currentBotSelector);
|
|
4384
|
+
if (!currentBotSelected) {
|
|
4385
|
+
saveRunnerRouteState(
|
|
4386
|
+
routeKey,
|
|
4387
|
+
buildRunnerRouteStateFromComment(selectedRecord, {
|
|
4388
|
+
last_action: "adjudication_skipped",
|
|
4389
|
+
last_reason: String(adjudication.reason_code || "").trim() || "not_selected_by_adjudicator",
|
|
4390
|
+
last_trigger: "responder_adjudication",
|
|
4391
|
+
}),
|
|
4392
|
+
);
|
|
4393
|
+
skippedRecords.push({
|
|
4394
|
+
id: selectedRecord.id,
|
|
4395
|
+
reason: String(adjudication.reason_code || "").trim() || "not_selected_by_adjudicator",
|
|
4396
|
+
});
|
|
4397
|
+
continue;
|
|
4398
|
+
}
|
|
4399
|
+
saveRunnerRouteState(routeKey, buildRunnerActiveExecutionPatch(selectedRecord));
|
|
4400
|
+
return {
|
|
4401
|
+
route_key: routeKey,
|
|
4402
|
+
route_name: normalizedRoute.name,
|
|
4403
|
+
logical_signature: runnerRouteLogicalSignature(normalizedRoute),
|
|
4404
|
+
outcome: "accepted",
|
|
4405
|
+
detail: `accepted comment ${selectedRecord.id} for background execution`,
|
|
4406
|
+
archive_source: String(archiveThread.source || "").trim() || "-",
|
|
4407
|
+
archive_work_item_id: String(archiveThread.workItemID || "").trim() || "",
|
|
4408
|
+
thread_id: archiveThread.threadID,
|
|
4409
|
+
comment_id: selectedRecord.id,
|
|
4410
|
+
execution_mode: executionPlan.mode,
|
|
4411
|
+
role_profile: executionPlan.roleProfileName,
|
|
4412
|
+
deferred_execution: {
|
|
4413
|
+
routeKey,
|
|
4414
|
+
normalizedRoute,
|
|
4415
|
+
routeState: refreshedState,
|
|
4416
|
+
selectedRecord,
|
|
4417
|
+
pendingOrdered: pending.ordered,
|
|
4418
|
+
bot,
|
|
4419
|
+
destination,
|
|
4420
|
+
archiveThread,
|
|
4421
|
+
executionPlan,
|
|
4422
|
+
runtime,
|
|
4423
|
+
managedConversationBots,
|
|
4424
|
+
},
|
|
4425
|
+
};
|
|
4426
|
+
}
|
|
4427
|
+
if (skippedRecords.length > 0) {
|
|
4428
|
+
const distinctReasons = Array.from(new Set(skippedRecords.map((item) => item.reason).filter(Boolean)));
|
|
4429
|
+
const lastSkipped = skippedRecords[skippedRecords.length - 1];
|
|
4430
|
+
return {
|
|
4431
|
+
route_key: routeKey,
|
|
4432
|
+
route_name: normalizedRoute.name,
|
|
4433
|
+
logical_signature: runnerRouteLogicalSignature(normalizedRoute),
|
|
4434
|
+
outcome: "skipped",
|
|
4435
|
+
detail: distinctReasons.join("; ") || "all pending messages were skipped",
|
|
4436
|
+
archive_source: String(archiveThread.source || "").trim() || "-",
|
|
4437
|
+
archive_work_item_id: String(archiveThread.workItemID || "").trim() || "",
|
|
4438
|
+
thread_id: archiveThread.threadID,
|
|
4439
|
+
comment_id: lastSkipped.id,
|
|
4440
|
+
skipped_count: skippedRecords.length,
|
|
4441
|
+
execution_mode: executionPlan.mode,
|
|
4442
|
+
role_profile: executionPlan.roleProfileName,
|
|
4443
|
+
};
|
|
4444
|
+
}
|
|
4374
4445
|
}
|
|
4375
4446
|
const skippedRecords = [];
|
|
4376
4447
|
for (const selectedRecord of pending.pending) {
|
|
@@ -4396,6 +4467,7 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
4396
4467
|
buildRunnerExecutionDeps,
|
|
4397
4468
|
buildRunnerDeliveryDeps,
|
|
4398
4469
|
buildRunnerRuntimeDeps,
|
|
4470
|
+
managedConversationBots,
|
|
4399
4471
|
resolveConversationPeerBots: resolveRunnerConversationPeers,
|
|
4400
4472
|
},
|
|
4401
4473
|
});
|
|
@@ -5184,6 +5256,45 @@ function resolveRunnerConversationPeers(routeRaw) {
|
|
|
5184
5256
|
return peers;
|
|
5185
5257
|
}
|
|
5186
5258
|
|
|
5259
|
+
function resolveRunnerConversationManagedBots(routeRaw, availableBots = []) {
|
|
5260
|
+
const normalizedRoute = normalizeRunnerRoute(routeRaw);
|
|
5261
|
+
const config = loadBotRunnerConfig({ persistIfNeeded: true });
|
|
5262
|
+
const seen = new Set();
|
|
5263
|
+
const managed = [];
|
|
5264
|
+
ensureArray(config.routes)
|
|
5265
|
+
.map((rawRoute) => normalizeRunnerRoute(rawRoute))
|
|
5266
|
+
.filter((route) => route.enabled)
|
|
5267
|
+
.filter((route) => String(route.projectID || "").trim() === String(normalizedRoute.projectID || "").trim())
|
|
5268
|
+
.filter((route) => String(route.provider || "").trim() === String(normalizedRoute.provider || "").trim())
|
|
5269
|
+
.filter((route) => {
|
|
5270
|
+
const routeDestinationID = String(route.destinationID || "").trim();
|
|
5271
|
+
const targetDestinationID = String(normalizedRoute.destinationID || "").trim();
|
|
5272
|
+
const routeDestinationLabel = normalizeRunnerRouteIdentityText(route.destinationLabel);
|
|
5273
|
+
const targetDestinationLabel = normalizeRunnerRouteIdentityText(normalizedRoute.destinationLabel);
|
|
5274
|
+
return targetDestinationID
|
|
5275
|
+
? (routeDestinationID === targetDestinationID || (!routeDestinationID && routeDestinationLabel === targetDestinationLabel))
|
|
5276
|
+
: routeDestinationLabel === targetDestinationLabel;
|
|
5277
|
+
})
|
|
5278
|
+
.forEach((route) => {
|
|
5279
|
+
try {
|
|
5280
|
+
const bot = selectRunnerBot(availableBots, route);
|
|
5281
|
+
const username = String(bot?.username || bot?.name || route.botName || "").trim().replace(/^@+/, "").toLowerCase();
|
|
5282
|
+
if (!username || seen.has(username)) {
|
|
5283
|
+
return;
|
|
5284
|
+
}
|
|
5285
|
+
seen.add(username);
|
|
5286
|
+
managed.push({
|
|
5287
|
+
username,
|
|
5288
|
+
display_name: String(bot?.name || bot?.username || route.botName || username).trim(),
|
|
5289
|
+
route_name: String(route.name || "").trim(),
|
|
5290
|
+
route,
|
|
5291
|
+
bot,
|
|
5292
|
+
});
|
|
5293
|
+
} catch {}
|
|
5294
|
+
});
|
|
5295
|
+
return managed;
|
|
5296
|
+
}
|
|
5297
|
+
|
|
5187
5298
|
function applyRunnerProjectUpRouteSuggestions(routeSuggestions) {
|
|
5188
5299
|
const config = loadBotRunnerConfig({ persistIfNeeded: true });
|
|
5189
5300
|
let nextConfig = config;
|
|
@@ -6591,6 +6702,7 @@ async function runRunnerStartResolvedRoutes(routes, flags, options = {}) {
|
|
|
6591
6702
|
buildRunnerExecutionDeps,
|
|
6592
6703
|
buildRunnerDeliveryDeps,
|
|
6593
6704
|
buildRunnerRuntimeDeps,
|
|
6705
|
+
managedConversationBots: deferredExecution.managedConversationBots,
|
|
6594
6706
|
resolveConversationPeerBots: resolveRunnerConversationPeers,
|
|
6595
6707
|
reportRunnerStage: (stage) => {
|
|
6596
6708
|
runnerLogger?.append("execution_stage", {
|
|
@@ -11760,6 +11872,47 @@ TELEGRAM_BOT_REVIEW_TOKEN=review-token
|
|
|
11760
11872
|
push("runner_active_execution_heartbeat_marks_live_execution_stale_when_expired", false, String(err?.message || err));
|
|
11761
11873
|
}
|
|
11762
11874
|
|
|
11875
|
+
try {
|
|
11876
|
+
const nowMs = Date.now();
|
|
11877
|
+
const pendingSelection = selectPendingArchiveComments(
|
|
11878
|
+
[
|
|
11879
|
+
{
|
|
11880
|
+
id: "comment-seen",
|
|
11881
|
+
created_at: new Date(nowMs - (BOT_RUNNER_PENDING_COMMENT_MAX_AGE_MS + 5 * 60_000)).toISOString(),
|
|
11882
|
+
body: "[Telegram message]\nmessage_id: 100\n\nseen pending mention",
|
|
11883
|
+
},
|
|
11884
|
+
{
|
|
11885
|
+
id: "comment-old",
|
|
11886
|
+
created_at: new Date(nowMs - (BOT_RUNNER_PENDING_COMMENT_MAX_AGE_MS + 60_000)).toISOString(),
|
|
11887
|
+
body: "[Telegram message]\nmessage_id: 101\n\nold pending mention",
|
|
11888
|
+
},
|
|
11889
|
+
{
|
|
11890
|
+
id: "comment-fresh",
|
|
11891
|
+
created_at: new Date(nowMs - 60_000).toISOString(),
|
|
11892
|
+
body: "[Telegram message]\nmessage_id: 102\n\nfresh pending mention",
|
|
11893
|
+
},
|
|
11894
|
+
],
|
|
11895
|
+
{
|
|
11896
|
+
last_processed_comment_id: "comment-seen",
|
|
11897
|
+
},
|
|
11898
|
+
"start",
|
|
11899
|
+
(record) => normalizeArchiveCommentRecord(record, parseArchivedChatComment),
|
|
11900
|
+
{
|
|
11901
|
+
maxPendingAgeMs: BOT_RUNNER_PENDING_COMMENT_MAX_AGE_MS,
|
|
11902
|
+
nowMs,
|
|
11903
|
+
},
|
|
11904
|
+
);
|
|
11905
|
+
push(
|
|
11906
|
+
"runner_pending_selection_skips_stale_archive_backlog",
|
|
11907
|
+
ensureArray(pendingSelection.pending).length === 1
|
|
11908
|
+
&& String(ensureArray(pendingSelection.pending)[0]?.id || "").trim() === "comment-fresh"
|
|
11909
|
+
&& String(safeObject(pendingSelection.staleSkippedLatest).id || "").trim() === "comment-old",
|
|
11910
|
+
`pending=${ensureArray(pendingSelection.pending).map((item) => String(item?.id || "").trim()).join(",") || "-"} stale=${String(safeObject(pendingSelection.staleSkippedLatest).id || "").trim() || "-"}`,
|
|
11911
|
+
);
|
|
11912
|
+
} catch (err) {
|
|
11913
|
+
push("runner_pending_selection_skips_stale_archive_backlog", false, String(err?.message || err));
|
|
11914
|
+
}
|
|
11915
|
+
|
|
11763
11916
|
try {
|
|
11764
11917
|
const startResponse = await handleLocalProjectToolDispatchImpl(
|
|
11765
11918
|
{
|