metheus-governance-mcp-cli 0.2.72 → 0.2.74
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/README.md +5 -3
- package/lib/bot-commands.mjs +342 -71
- package/lib/selftest-bot-commands.mjs +86 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -247,8 +247,8 @@ Behavior:
|
|
|
247
247
|
- `AI_PERMISSION_MODE`
|
|
248
248
|
- `AI_REASONING_EFFORT`
|
|
249
249
|
- Slack and KakaoTalk currently use a single local token entry per provider in this command flow.
|
|
250
|
-
- `bot verify` checks the configured local token
|
|
251
|
-
- `bot show` prints one local bot entry in detail.
|
|
250
|
+
- `bot verify` checks the configured local token, cross-checks the server bot binding, prints the effective runtime role profile summary that the runner will use, and shows which local runner routes currently point at this bot entry.
|
|
251
|
+
- `bot show` prints one local bot entry in detail, including grouped role summaries when one server bot name expands to multiple roles and any linked local runner routes.
|
|
252
252
|
- `bot global` edits Telegram-wide local settings such as API base URL, allowed updates, and default bot key.
|
|
253
253
|
- `bot set-default` updates `TELEGRAM_DEFAULT_BOT_KEY`.
|
|
254
254
|
- `bot migrate` moves legacy `TELEGRAM_BOT_TOKEN` into a named Telegram bot entry.
|
|
@@ -259,7 +259,7 @@ Non-interactive examples:
|
|
|
259
259
|
metheus-governance-mcp-cli bot global --provider telegram --non-interactive true --api-base-url http://127.0.0.1:8999/telegram --auto-clear-webhook false --allowed-updates message,edited_message,channel_post
|
|
260
260
|
metheus-governance-mcp-cli bot add --provider telegram --non-interactive true --server-bot-id <server_bot_uuid> --token <telegram_bot_token> --default true
|
|
261
261
|
metheus-governance-mcp-cli bot add --provider telegram --non-interactive true --server-bot-id <server_bot_uuid> --token <telegram_bot_token> --ai-client codex --ai-model gpt-5-codex --ai-permission-mode read_only --ai-reasoning-effort low
|
|
262
|
-
metheus-governance-mcp-cli bot edit --provider telegram --bot-key
|
|
262
|
+
metheus-governance-mcp-cli bot edit --provider telegram --bot-key ryoai_bot --non-interactive true --ai-client claude --ai-model claude-sonnet-4 --ai-permission-mode danger_full_access --ai-reasoning-effort high
|
|
263
263
|
metheus-governance-mcp-cli bot set-default --provider telegram --bot-key main --non-interactive true
|
|
264
264
|
metheus-governance-mcp-cli bot migrate --provider telegram --bot-key main --server-bot-id <server_bot_uuid> --bot-name <telegram_username> --role-profile monitor --ai-client codex --ai-permission-mode read_only --ai-reasoning-effort low --non-interactive true
|
|
265
265
|
metheus-governance-mcp-cli bot remove --provider telegram --bot-key main --non-interactive true
|
|
@@ -268,6 +268,8 @@ metheus-governance-mcp-cli bot verify --provider telegram --bot-key main --json
|
|
|
268
268
|
|
|
269
269
|
For direct Telegram adds, the CLI can derive the local entry key from the matched server bot name. You no longer need `--bot-key` or `--username` in the normal server-bound path. Treat `--bot-key` as an advanced override only when you intentionally want a different local suffix.
|
|
270
270
|
|
|
271
|
+
For direct Telegram edits, `--bot-key` still identifies which saved local entry to update. If one server bot name expands to multiple roles such as `approval / worker / review / monitor`, prefer the guided `bot edit` flow so you can keep the current grouped settings, edit one role only, or walk every role in sequence instead of forcing one entry-level AI override.
|
|
272
|
+
|
|
271
273
|
Current support status:
|
|
272
274
|
|
|
273
275
|
- Telegram: full local bot entry management, token verification, bot-to-AI binding, inbound runner support
|
package/lib/bot-commands.mjs
CHANGED
|
@@ -809,6 +809,16 @@ function renderBotListPayload(provider, state, deps) {
|
|
|
809
809
|
};
|
|
810
810
|
}
|
|
811
811
|
|
|
812
|
+
function formatRoleProfileOutputLine(profile) {
|
|
813
|
+
const current = safeObject(profile);
|
|
814
|
+
return [
|
|
815
|
+
current.client ? `client=${current.client}` : "client=(blank)",
|
|
816
|
+
current.model ? `model=${current.model}` : "model=(blank)",
|
|
817
|
+
current.permissionMode ? `permission=${current.permissionMode}` : "permission=(blank)",
|
|
818
|
+
current.reasoningEffort ? `reasoning=${current.reasoningEffort}` : "reasoning=(blank)",
|
|
819
|
+
].join(" | ");
|
|
820
|
+
}
|
|
821
|
+
|
|
812
822
|
function buildBotShowPayload(provider, state, entry, deps, extras = {}) {
|
|
813
823
|
if (provider === "telegram") {
|
|
814
824
|
const selectedEntry = entry || null;
|
|
@@ -829,6 +839,8 @@ function buildBotShowPayload(provider, state, entry, deps, extras = {}) {
|
|
|
829
839
|
permissionMode: selectedEntry.permissionMode,
|
|
830
840
|
reasoningEffort: selectedEntry.reasoningEffort,
|
|
831
841
|
} : null,
|
|
842
|
+
serverBinding: safeObject(extras.serverBinding),
|
|
843
|
+
routeLinks: safeObject(extras.routeLinks),
|
|
832
844
|
...safeObject(extras),
|
|
833
845
|
};
|
|
834
846
|
}
|
|
@@ -846,6 +858,60 @@ function buildBotShowPayload(provider, state, entry, deps, extras = {}) {
|
|
|
846
858
|
};
|
|
847
859
|
}
|
|
848
860
|
|
|
861
|
+
function describeRouteRuntimeProfile(routeRole, serverBinding) {
|
|
862
|
+
const binding = safeObject(serverBinding);
|
|
863
|
+
if (binding.mode === "group") {
|
|
864
|
+
return safeObject(safeObject(binding.effectiveRoleProfiles)[String(routeRole || "").trim()]);
|
|
865
|
+
}
|
|
866
|
+
return safeObject(binding.effectiveRoleProfile);
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
function summarizeTelegramRouteLinks(parsedEnv, entry, serverBinding, deps) {
|
|
870
|
+
const selectedEntry = safeObject(entry);
|
|
871
|
+
const binding = safeObject(serverBinding);
|
|
872
|
+
const config = safeObject(requireDependency(deps, "loadBotRunnerConfig")({ persistIfNeeded: true }));
|
|
873
|
+
const routes = ensureArray(config.routes);
|
|
874
|
+
const entryKey = String(selectedEntry.key || "").trim();
|
|
875
|
+
const defaultBotKey = String(safeObject(parsedEnv).TELEGRAM_DEFAULT_BOT_KEY || "").trim();
|
|
876
|
+
const serverBotID = String(binding.serverBotID || selectedEntry.serverBotID || "").trim();
|
|
877
|
+
const normalizedNames = new Set(
|
|
878
|
+
[
|
|
879
|
+
normalizeServerBotIdentityText(selectedEntry.username),
|
|
880
|
+
normalizeServerBotIdentityText(binding.name),
|
|
881
|
+
normalizeServerBotIdentityText(entryKey),
|
|
882
|
+
].filter(Boolean),
|
|
883
|
+
);
|
|
884
|
+
const linkedRoutes = [];
|
|
885
|
+
routes.forEach((rawRoute, index) => {
|
|
886
|
+
const route = safeObject(rawRoute);
|
|
887
|
+
if (route.enabled === false) return;
|
|
888
|
+
if (String(route.provider || "").trim().toLowerCase() !== "telegram") return;
|
|
889
|
+
const routeName = String(route.name || route.route_name || `telegram-route-${index + 1}`).trim();
|
|
890
|
+
const routeBotID = String(route.bot_id || route.botID || "").trim();
|
|
891
|
+
const routeBotName = normalizeServerBotIdentityText(route.bot_name || route.botName || "");
|
|
892
|
+
const routeRole = String(route.role_profile || route.roleProfile || route.role || "").trim();
|
|
893
|
+
let matchedBy = "";
|
|
894
|
+
if (serverBotID && routeBotID === serverBotID) {
|
|
895
|
+
matchedBy = "bot_id";
|
|
896
|
+
} else if (routeBotName && normalizedNames.has(routeBotName)) {
|
|
897
|
+
matchedBy = "bot_name";
|
|
898
|
+
} else if (!routeBotID && !routeBotName && entryKey && defaultBotKey && entryKey === defaultBotKey) {
|
|
899
|
+
matchedBy = "default_bot";
|
|
900
|
+
}
|
|
901
|
+
if (!matchedBy) return;
|
|
902
|
+
linkedRoutes.push({
|
|
903
|
+
routeName,
|
|
904
|
+
matchedBy,
|
|
905
|
+
routeRole,
|
|
906
|
+
runtimeRoleProfile: describeRouteRuntimeProfile(routeRole, binding),
|
|
907
|
+
});
|
|
908
|
+
});
|
|
909
|
+
return {
|
|
910
|
+
total: linkedRoutes.length,
|
|
911
|
+
routes: linkedRoutes,
|
|
912
|
+
};
|
|
913
|
+
}
|
|
914
|
+
|
|
849
915
|
function printBotList(provider, state, deps) {
|
|
850
916
|
process.stdout.write(`${providerLabel(provider, deps)}\n`);
|
|
851
917
|
process.stdout.write(` file: ${state.filePath}\n`);
|
|
@@ -883,6 +949,8 @@ function printBotList(provider, state, deps) {
|
|
|
883
949
|
function printBotShow(provider, state, entry, deps, extras = {}) {
|
|
884
950
|
if (provider === "telegram") {
|
|
885
951
|
const selectedEntry = entry || {};
|
|
952
|
+
const serverBinding = safeObject(extras.serverBinding);
|
|
953
|
+
const routeLinks = safeObject(extras.routeLinks);
|
|
886
954
|
process.stdout.write(`${providerLabel(provider, deps)} bot\n`);
|
|
887
955
|
process.stdout.write(` file: ${state.filePath}\n`);
|
|
888
956
|
process.stdout.write(` name: ${telegramEntryDisplayName(selectedEntry)}\n`);
|
|
@@ -896,9 +964,32 @@ function printBotShow(provider, state, entry, deps, extras = {}) {
|
|
|
896
964
|
process.stdout.write(` ai_model: ${selectedEntry.model || "-"}\n`);
|
|
897
965
|
process.stdout.write(` permission_mode: ${selectedEntry.permissionMode || "-"}\n`);
|
|
898
966
|
process.stdout.write(` reasoning_effort: ${selectedEntry.reasoningEffort || "-"}\n`);
|
|
899
|
-
if (
|
|
900
|
-
process.stdout.write(` server_binding: ${
|
|
967
|
+
if (Object.keys(serverBinding).length) {
|
|
968
|
+
process.stdout.write(` server_binding: ${serverBinding.ok ? "OK" : "FAIL"}${serverBinding.detail ? ` (${serverBinding.detail})` : ""}\n`);
|
|
969
|
+
process.stdout.write(` server_binding_mode: ${serverBinding.mode || "-"}\n`);
|
|
970
|
+
process.stdout.write(` server_bot_name: ${serverBinding.name || "-"}\n`);
|
|
971
|
+
process.stdout.write(` server_bot_id: ${serverBinding.serverBotID || selectedEntry.serverBotID || "-" }\n`);
|
|
972
|
+
if (serverBinding.mode === "group") {
|
|
973
|
+
process.stdout.write(` server_roles: ${ensureArray(serverBinding.roles).join(", ") || "-"}\n`);
|
|
974
|
+
const groupedProfiles = safeObject(serverBinding.effectiveRoleProfiles);
|
|
975
|
+
Object.keys(groupedProfiles).forEach((role) => {
|
|
976
|
+
process.stdout.write(` runtime_role_profile[${role}]: ${formatRoleProfileOutputLine(groupedProfiles[role])}\n`);
|
|
977
|
+
});
|
|
978
|
+
} else if (serverBinding.effectiveRoleProfile) {
|
|
979
|
+
process.stdout.write(` server_role: ${serverBinding.role || "-"}\n`);
|
|
980
|
+
process.stdout.write(` runtime_role_profile: ${formatRoleProfileOutputLine(serverBinding.effectiveRoleProfile)}\n`);
|
|
981
|
+
}
|
|
901
982
|
}
|
|
983
|
+
process.stdout.write(` route_links: ${intFromRaw(routeLinks.total, 0)}\n`);
|
|
984
|
+
ensureArray(routeLinks.routes).forEach((route) => {
|
|
985
|
+
const routeRuntime = safeObject(route.runtimeRoleProfile);
|
|
986
|
+
const routeDetail = [
|
|
987
|
+
`matched_by=${String(route.matchedBy || "-")}`,
|
|
988
|
+
route.routeRole ? `route_role=${String(route.routeRole || "").trim()}` : "",
|
|
989
|
+
Object.keys(routeRuntime).length ? `runtime=${formatRoleProfileOutputLine(routeRuntime)}` : "",
|
|
990
|
+
].filter(Boolean).join(" ");
|
|
991
|
+
process.stdout.write(` linked_route[${String(route.routeName || "-")}]: ${routeDetail || "-"}\n`);
|
|
992
|
+
});
|
|
902
993
|
return;
|
|
903
994
|
}
|
|
904
995
|
const tokenKey = providerTokenKey(provider, deps);
|
|
@@ -1348,6 +1439,31 @@ function formatRoleProfileSummary(profile) {
|
|
|
1348
1439
|
].join(" | ");
|
|
1349
1440
|
}
|
|
1350
1441
|
|
|
1442
|
+
function serializeRoleProfile(profile) {
|
|
1443
|
+
const current = safeObject(profile);
|
|
1444
|
+
return {
|
|
1445
|
+
role: String(current.role || "").trim(),
|
|
1446
|
+
client: String(current.client || "").trim(),
|
|
1447
|
+
model: String(current.model || "").trim(),
|
|
1448
|
+
permissionMode: String(current.permissionMode || "").trim(),
|
|
1449
|
+
reasoningEffort: String(current.reasoningEffort || "").trim(),
|
|
1450
|
+
};
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
function buildRoleProfileOutput(roleName, deps) {
|
|
1454
|
+
return serializeRoleProfile(currentRoleProfileState(roleName, deps));
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
function buildGroupedRoleProfileOutput(roles, deps) {
|
|
1458
|
+
const output = {};
|
|
1459
|
+
preferredRoleSort(roles).forEach((role) => {
|
|
1460
|
+
const normalizedRole = String(role || "").trim();
|
|
1461
|
+
if (!normalizedRole) return;
|
|
1462
|
+
output[normalizedRole] = buildRoleProfileOutput(normalizedRole, deps);
|
|
1463
|
+
});
|
|
1464
|
+
return output;
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1351
1467
|
function persistRoleProfileState(profile, deps) {
|
|
1352
1468
|
const current = safeObject(profile);
|
|
1353
1469
|
const role = requireDependency(deps, "normalizeRunnerRoleProfileName")(current.role || "");
|
|
@@ -1416,9 +1532,13 @@ async function maybePromptGroupedServerRoleProfiles(ui, serverBot, deps) {
|
|
|
1416
1532
|
if (roles.length <= 1) {
|
|
1417
1533
|
return false;
|
|
1418
1534
|
}
|
|
1535
|
+
process.stdout.write(`Current grouped role execution profiles for "${String(serverBot?.name || "").trim() || "telegram"}":\n`);
|
|
1536
|
+
roles.forEach((role) => {
|
|
1537
|
+
process.stdout.write(` - ${role}: ${formatRoleProfileSummary(currentRoleProfileState(role, deps))}\n`);
|
|
1538
|
+
});
|
|
1419
1539
|
const editChoice = await promptChoice(
|
|
1420
1540
|
ui,
|
|
1421
|
-
|
|
1541
|
+
"Grouped role settings",
|
|
1422
1542
|
[
|
|
1423
1543
|
{
|
|
1424
1544
|
value: "keep",
|
|
@@ -1426,22 +1546,173 @@ async function maybePromptGroupedServerRoleProfiles(ui, serverBot, deps) {
|
|
|
1426
1546
|
description: "use the existing role_profiles defaults as-is",
|
|
1427
1547
|
},
|
|
1428
1548
|
{
|
|
1429
|
-
value: "
|
|
1430
|
-
label: "
|
|
1431
|
-
description: "
|
|
1549
|
+
value: "one",
|
|
1550
|
+
label: "Edit one role",
|
|
1551
|
+
description: "change only the role that needs an update",
|
|
1552
|
+
},
|
|
1553
|
+
{
|
|
1554
|
+
value: "all",
|
|
1555
|
+
label: "Edit all roles",
|
|
1556
|
+
description: "review each role in sequence",
|
|
1432
1557
|
},
|
|
1433
1558
|
],
|
|
1434
1559
|
{ defaultIndex: 0 },
|
|
1435
1560
|
);
|
|
1436
|
-
if (editChoice?.value
|
|
1561
|
+
if (editChoice?.value === "keep") {
|
|
1437
1562
|
return false;
|
|
1438
1563
|
}
|
|
1439
|
-
|
|
1440
|
-
|
|
1564
|
+
if (editChoice?.value === "all") {
|
|
1565
|
+
for (const role of roles) {
|
|
1566
|
+
await promptRoleExecutionProfile(ui, role, deps);
|
|
1567
|
+
}
|
|
1568
|
+
return true;
|
|
1569
|
+
}
|
|
1570
|
+
const remaining = new Set(roles);
|
|
1571
|
+
while (remaining.size) {
|
|
1572
|
+
const roleChoice = await promptChoice(
|
|
1573
|
+
ui,
|
|
1574
|
+
"Select role to edit",
|
|
1575
|
+
[
|
|
1576
|
+
...Array.from(remaining).map((role) => ({
|
|
1577
|
+
value: role,
|
|
1578
|
+
label: role,
|
|
1579
|
+
description: formatRoleProfileSummary(currentRoleProfileState(role, deps)),
|
|
1580
|
+
})),
|
|
1581
|
+
{
|
|
1582
|
+
value: "__done__",
|
|
1583
|
+
label: "Done",
|
|
1584
|
+
description: "finish grouped role editing",
|
|
1585
|
+
},
|
|
1586
|
+
],
|
|
1587
|
+
{ defaultIndex: 0 },
|
|
1588
|
+
);
|
|
1589
|
+
if (!roleChoice || roleChoice.value === "__done__") {
|
|
1590
|
+
break;
|
|
1591
|
+
}
|
|
1592
|
+
await promptRoleExecutionProfile(ui, roleChoice.value, deps);
|
|
1593
|
+
remaining.delete(roleChoice.value);
|
|
1594
|
+
if (!remaining.size) {
|
|
1595
|
+
break;
|
|
1596
|
+
}
|
|
1597
|
+
if (!await promptYesNo(ui, "Edit another role?", false)) {
|
|
1598
|
+
break;
|
|
1599
|
+
}
|
|
1441
1600
|
}
|
|
1442
1601
|
return true;
|
|
1443
1602
|
}
|
|
1444
1603
|
|
|
1604
|
+
async function resolveTelegramServerBindingDetails(envConfig, flags, deps) {
|
|
1605
|
+
const current = safeObject(envConfig);
|
|
1606
|
+
if (!String(current.serverBotID || "").trim() && !String(current.botUsername || "").trim() && !String(current.botKey || "").trim()) {
|
|
1607
|
+
return null;
|
|
1608
|
+
}
|
|
1609
|
+
const lookup = await requireDependency(deps, "listServerBots")({
|
|
1610
|
+
provider: "telegram",
|
|
1611
|
+
baseURL: flags["base-url"] || deps.defaultSiteURL,
|
|
1612
|
+
timeoutSeconds: intFromRaw(flags["timeout-seconds"], 15) || 15,
|
|
1613
|
+
});
|
|
1614
|
+
if (!lookup?.ok) {
|
|
1615
|
+
return {
|
|
1616
|
+
ok: false,
|
|
1617
|
+
mode: "lookup_error",
|
|
1618
|
+
matchedBy: "",
|
|
1619
|
+
name: "",
|
|
1620
|
+
role: "",
|
|
1621
|
+
roles: [],
|
|
1622
|
+
effectiveRoleProfile: null,
|
|
1623
|
+
effectiveRoleProfiles: {},
|
|
1624
|
+
detail: `server bot lookup unavailable: ${lookup?.error || "unknown error"}`,
|
|
1625
|
+
};
|
|
1626
|
+
}
|
|
1627
|
+
const bots = ensureArray(lookup.bots).map((bot) => ({
|
|
1628
|
+
id: String(bot?.id || "").trim(),
|
|
1629
|
+
role: String(bot?.role || bot?.bot_role || "").trim(),
|
|
1630
|
+
name: String(bot?.name || "").trim(),
|
|
1631
|
+
})).filter((bot) => bot.id);
|
|
1632
|
+
const resolveGroupedPayload = (matches, matchedBy) => {
|
|
1633
|
+
const roles = preferredRoleSort(summarizeServerBotRoles(matches));
|
|
1634
|
+
return {
|
|
1635
|
+
ok: true,
|
|
1636
|
+
mode: "group",
|
|
1637
|
+
matchedBy,
|
|
1638
|
+
name: String(matches[0]?.name || "").trim(),
|
|
1639
|
+
role: "",
|
|
1640
|
+
roles,
|
|
1641
|
+
serverBotID: String(current.serverBotID || "").trim(),
|
|
1642
|
+
effectiveRoleProfile: null,
|
|
1643
|
+
effectiveRoleProfiles: buildGroupedRoleProfileOutput(roles, deps),
|
|
1644
|
+
detail: `${String(matches[0]?.name || "").trim() || "(unnamed)"} roles: ${roles.join(", ") || "-"}`,
|
|
1645
|
+
};
|
|
1646
|
+
};
|
|
1647
|
+
if (String(current.serverBotID || "").trim()) {
|
|
1648
|
+
const match = bots.find((bot) => bot.id === String(current.serverBotID || "").trim());
|
|
1649
|
+
if (!match) {
|
|
1650
|
+
return {
|
|
1651
|
+
ok: false,
|
|
1652
|
+
mode: "missing",
|
|
1653
|
+
matchedBy: "server_bot_id",
|
|
1654
|
+
name: "",
|
|
1655
|
+
role: "",
|
|
1656
|
+
roles: [],
|
|
1657
|
+
serverBotID: String(current.serverBotID || "").trim(),
|
|
1658
|
+
effectiveRoleProfile: null,
|
|
1659
|
+
effectiveRoleProfiles: {},
|
|
1660
|
+
detail: `server bot ${current.serverBotID} not found`,
|
|
1661
|
+
};
|
|
1662
|
+
}
|
|
1663
|
+
const sameNameMatches = bots.filter((bot) => normalizeServerBotIdentityText(bot.name) === normalizeServerBotIdentityText(match.name));
|
|
1664
|
+
if (sameNameMatches.length > 1) {
|
|
1665
|
+
return resolveGroupedPayload(sameNameMatches, "server_bot_id");
|
|
1666
|
+
}
|
|
1667
|
+
const effectiveRoleProfile = buildRoleProfileOutput(match.role || current.roleProfile || "", deps);
|
|
1668
|
+
return {
|
|
1669
|
+
ok: true,
|
|
1670
|
+
mode: "single",
|
|
1671
|
+
matchedBy: "server_bot_id",
|
|
1672
|
+
name: match.name,
|
|
1673
|
+
role: match.role,
|
|
1674
|
+
roles: summarizeServerBotRoles([match]),
|
|
1675
|
+
serverBotID: match.id,
|
|
1676
|
+
effectiveRoleProfile,
|
|
1677
|
+
effectiveRoleProfiles: {},
|
|
1678
|
+
detail: `${String(match.name || "").trim() || "(unnamed)"} [${String(match.role || "").trim() || "-"}]`,
|
|
1679
|
+
};
|
|
1680
|
+
}
|
|
1681
|
+
const normalizedServerIdentity = normalizeServerBotIdentityText(current.botUsername || current.botKey);
|
|
1682
|
+
const matches = bots.filter((bot) => normalizeServerBotIdentityText(bot.name) === normalizedServerIdentity);
|
|
1683
|
+
if (!matches.length) {
|
|
1684
|
+
return {
|
|
1685
|
+
ok: false,
|
|
1686
|
+
mode: "missing",
|
|
1687
|
+
matchedBy: "server_bot_name",
|
|
1688
|
+
name: "",
|
|
1689
|
+
role: "",
|
|
1690
|
+
roles: [],
|
|
1691
|
+
serverBotID: "",
|
|
1692
|
+
effectiveRoleProfile: null,
|
|
1693
|
+
effectiveRoleProfiles: {},
|
|
1694
|
+
detail: `no server bot matched ${current.botUsername ? `@${current.botUsername}` : current.botKey}`,
|
|
1695
|
+
};
|
|
1696
|
+
}
|
|
1697
|
+
if (matches.length > 1) {
|
|
1698
|
+
return resolveGroupedPayload(matches, "server_bot_name");
|
|
1699
|
+
}
|
|
1700
|
+
const match = matches[0] || {};
|
|
1701
|
+
const effectiveRoleProfile = buildRoleProfileOutput(match.role || current.roleProfile || "", deps);
|
|
1702
|
+
return {
|
|
1703
|
+
ok: true,
|
|
1704
|
+
mode: "single",
|
|
1705
|
+
matchedBy: "server_bot_name",
|
|
1706
|
+
name: String(match.name || "").trim(),
|
|
1707
|
+
role: String(match.role || "").trim(),
|
|
1708
|
+
roles: summarizeServerBotRoles([match]),
|
|
1709
|
+
serverBotID: String(match.id || "").trim(),
|
|
1710
|
+
effectiveRoleProfile,
|
|
1711
|
+
effectiveRoleProfiles: {},
|
|
1712
|
+
detail: `${String(match.name || "").trim() || "(unnamed)"} [${String(match.role || "").trim() || "-"}]`,
|
|
1713
|
+
};
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1445
1716
|
function buildTemporaryTelegramEnvConfig({ token, apiBaseURL }) {
|
|
1446
1717
|
return {
|
|
1447
1718
|
ok: true,
|
|
@@ -1840,54 +2111,19 @@ async function verifyProviderEntry(ui, provider, flags, deps) {
|
|
|
1840
2111
|
envConfig,
|
|
1841
2112
|
intFromRaw(flags["timeout-seconds"], 15) || 15,
|
|
1842
2113
|
);
|
|
1843
|
-
let serverBinding = null;
|
|
1844
2114
|
let overallOK = Boolean(result.ok);
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
});
|
|
1851
|
-
if (!lookup?.ok) {
|
|
1852
|
-
serverBinding = {
|
|
1853
|
-
ok: false,
|
|
1854
|
-
detail: `server bot lookup unavailable: ${lookup?.error || "unknown error"}`,
|
|
1855
|
-
};
|
|
2115
|
+
let serverBinding = null;
|
|
2116
|
+
let routeLinks = null;
|
|
2117
|
+
if (provider === "telegram") {
|
|
2118
|
+
serverBinding = await resolveTelegramServerBindingDetails(envConfig, flags, deps);
|
|
2119
|
+
if (serverBinding && !serverBinding.ok) {
|
|
1856
2120
|
overallOK = false;
|
|
1857
|
-
} else {
|
|
1858
|
-
const normalizedServerIdentity = normalizeServerBotIdentityText(envConfig.botUsername || envConfig.botKey);
|
|
1859
|
-
if (String(envConfig.serverBotID || "").trim()) {
|
|
1860
|
-
const match = ensureArray(lookup.bots).find((bot) => String(bot.id || "").trim() === String(envConfig.serverBotID || "").trim());
|
|
1861
|
-
if (!match) {
|
|
1862
|
-
serverBinding = {
|
|
1863
|
-
ok: false,
|
|
1864
|
-
detail: `server bot ${envConfig.serverBotID} not found`,
|
|
1865
|
-
};
|
|
1866
|
-
overallOK = false;
|
|
1867
|
-
} else {
|
|
1868
|
-
serverBinding = {
|
|
1869
|
-
ok: true,
|
|
1870
|
-
detail: `${String(match.name || "").trim() || "(unnamed)"} [${String(match.role || "").trim() || "-"}]`,
|
|
1871
|
-
};
|
|
1872
|
-
}
|
|
1873
|
-
} else {
|
|
1874
|
-
const matches = ensureArray(lookup.bots).filter(
|
|
1875
|
-
(bot) => normalizeServerBotIdentityText(bot?.name) === normalizedServerIdentity,
|
|
1876
|
-
);
|
|
1877
|
-
if (!matches.length) {
|
|
1878
|
-
serverBinding = {
|
|
1879
|
-
ok: false,
|
|
1880
|
-
detail: `no server bot matched ${envConfig.botUsername ? `@${envConfig.botUsername}` : envConfig.botKey}`,
|
|
1881
|
-
};
|
|
1882
|
-
overallOK = false;
|
|
1883
|
-
} else {
|
|
1884
|
-
serverBinding = {
|
|
1885
|
-
ok: true,
|
|
1886
|
-
detail: `${String(matches[0]?.name || "").trim() || "(unnamed)"} roles: ${summarizeServerBotRoles(matches).join(", ") || "-"}`,
|
|
1887
|
-
};
|
|
1888
|
-
}
|
|
1889
|
-
}
|
|
1890
2121
|
}
|
|
2122
|
+
routeLinks = summarizeTelegramRouteLinks(state.parsed, {
|
|
2123
|
+
key: envConfig.botKey,
|
|
2124
|
+
serverBotID: envConfig.serverBotID,
|
|
2125
|
+
username: envConfig.botUsername,
|
|
2126
|
+
}, serverBinding, deps);
|
|
1891
2127
|
}
|
|
1892
2128
|
const interactiveJsonChoice = (
|
|
1893
2129
|
!boolFromRaw(flags.json, false)
|
|
@@ -1920,24 +2156,49 @@ async function verifyProviderEntry(ui, provider, flags, deps) {
|
|
|
1920
2156
|
permissionMode: envConfig.permissionMode || "",
|
|
1921
2157
|
reasoningEffort: envConfig.reasoningEffort || "",
|
|
1922
2158
|
serverBinding,
|
|
2159
|
+
routeLinks,
|
|
1923
2160
|
}, null, 2)}\n`,
|
|
1924
2161
|
);
|
|
1925
2162
|
} else {
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
)
|
|
2163
|
+
const lines = [
|
|
2164
|
+
`${providerLabel(provider, deps)} verify: ${overallOK ? "OK" : "FAIL"}`,
|
|
2165
|
+
`file: ${envConfig.filePath}`,
|
|
2166
|
+
provider === "telegram" ? `bot_key: ${envConfig.botKey || "-"}` : "",
|
|
2167
|
+
provider === "telegram" ? `server_bot_id: ${envConfig.serverBotID || "-"}` : "",
|
|
2168
|
+
provider === "telegram" ? `role_profile: ${envConfig.roleProfile || "-"}` : "",
|
|
2169
|
+
provider === "telegram" ? `ai_client: ${envConfig.client || "-"}` : "",
|
|
2170
|
+
provider === "telegram" ? `ai_model: ${envConfig.model || "-"}` : "",
|
|
2171
|
+
provider === "telegram" ? `permission_mode: ${envConfig.permissionMode || "-"}` : "",
|
|
2172
|
+
provider === "telegram" ? `reasoning_effort: ${envConfig.reasoningEffort || "-"}` : "",
|
|
2173
|
+
`detail: ${result.detail || "-"}`,
|
|
2174
|
+
serverBinding ? `server_binding: ${serverBinding.ok ? "OK" : "FAIL"}${serverBinding.detail ? ` (${serverBinding.detail})` : ""}` : "",
|
|
2175
|
+
routeLinks ? `route_links: ${intFromRaw(routeLinks.total, 0)}` : "",
|
|
2176
|
+
].filter(Boolean);
|
|
2177
|
+
if (provider === "telegram" && serverBinding) {
|
|
2178
|
+
lines.push(`server_binding_mode: ${serverBinding.mode || "-"}`);
|
|
2179
|
+
lines.push(`server_bot_name: ${serverBinding.name || "-"}`);
|
|
2180
|
+
lines.push(`server_roles: ${ensureArray(serverBinding.roles).join(", ") || "-"}`);
|
|
2181
|
+
if (serverBinding.mode === "group") {
|
|
2182
|
+
const groupedProfiles = safeObject(serverBinding.effectiveRoleProfiles);
|
|
2183
|
+
Object.keys(groupedProfiles).forEach((role) => {
|
|
2184
|
+
lines.push(`runtime_role_profile[${role}]: ${formatRoleProfileOutputLine(groupedProfiles[role])}`);
|
|
2185
|
+
});
|
|
2186
|
+
} else if (serverBinding.effectiveRoleProfile) {
|
|
2187
|
+
lines.push(`runtime_role_profile: ${formatRoleProfileOutputLine(serverBinding.effectiveRoleProfile)}`);
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2190
|
+
if (provider === "telegram" && routeLinks) {
|
|
2191
|
+
ensureArray(routeLinks.routes).forEach((route) => {
|
|
2192
|
+
const routeRuntime = safeObject(route.runtimeRoleProfile);
|
|
2193
|
+
const routeDetail = [
|
|
2194
|
+
`matched_by=${String(route.matchedBy || "-")}`,
|
|
2195
|
+
route.routeRole ? `route_role=${String(route.routeRole || "").trim()}` : "",
|
|
2196
|
+
Object.keys(routeRuntime).length ? `runtime=${formatRoleProfileOutputLine(routeRuntime)}` : "",
|
|
2197
|
+
].filter(Boolean).join(" ");
|
|
2198
|
+
lines.push(`linked_route[${String(route.routeName || "-")}]: ${routeDetail || "-"}`);
|
|
2199
|
+
});
|
|
2200
|
+
}
|
|
2201
|
+
process.stdout.write(`${lines.join("\n")}\n`);
|
|
1941
2202
|
}
|
|
1942
2203
|
if (!overallOK) {
|
|
1943
2204
|
process.exitCode = 1;
|
|
@@ -2096,12 +2357,22 @@ async function runBotShow(ui, flags, deps, explicitProvider = "") {
|
|
|
2096
2357
|
const state = loadProviderEnvState(provider, deps);
|
|
2097
2358
|
if (provider === "telegram") {
|
|
2098
2359
|
const entry = await resolveTelegramEntryForShow(ui, state.parsed, flags, deps);
|
|
2099
|
-
const
|
|
2360
|
+
const serverBinding = await resolveTelegramServerBindingDetails(
|
|
2361
|
+
requireDependency(deps, "loadProviderEnvConfig")(provider, {
|
|
2362
|
+
botKey: entry.key,
|
|
2363
|
+
botID: entry.serverBotID,
|
|
2364
|
+
botName: entry.username || entry.key,
|
|
2365
|
+
}),
|
|
2366
|
+
flags,
|
|
2367
|
+
deps,
|
|
2368
|
+
);
|
|
2369
|
+
const routeLinks = summarizeTelegramRouteLinks(state.parsed, entry, serverBinding, deps);
|
|
2370
|
+
const payload = buildBotShowPayload(provider, state, entry, deps, { serverBinding, routeLinks });
|
|
2100
2371
|
if (boolFromRaw(flags.json, false)) {
|
|
2101
2372
|
process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
2102
2373
|
return;
|
|
2103
2374
|
}
|
|
2104
|
-
printBotShow(provider, state, entry, deps);
|
|
2375
|
+
printBotShow(provider, state, entry, deps, { serverBinding, routeLinks });
|
|
2105
2376
|
return;
|
|
2106
2377
|
}
|
|
2107
2378
|
const payload = buildBotShowPayload(provider, state, null, deps);
|
|
@@ -84,6 +84,11 @@ function readTrailingJSON(rawText) {
|
|
|
84
84
|
return readJSON(text.slice(start));
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
function intFromRaw(rawValue, fallback = 0) {
|
|
88
|
+
const parsed = Number.parseInt(String(rawValue ?? "").trim(), 10);
|
|
89
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
90
|
+
}
|
|
91
|
+
|
|
87
92
|
function createMockServer(options = {}) {
|
|
88
93
|
const serverBots = ensureArray(options.serverBots).length
|
|
89
94
|
? ensureArray(options.serverBots)
|
|
@@ -356,19 +361,21 @@ export async function runSelftestBotCommands(push, deps) {
|
|
|
356
361
|
"2", // bot entry: ryoai_bot
|
|
357
362
|
"1", // keep username
|
|
358
363
|
"1", // keep token
|
|
359
|
-
"2", //
|
|
360
|
-
"
|
|
361
|
-
"1", // review: keep current role profile settings
|
|
364
|
+
"2", // grouped role settings: edit one role
|
|
365
|
+
"3", // select role to edit: worker
|
|
362
366
|
"2", // worker: edit settings
|
|
363
367
|
"3", // worker AI client: claude
|
|
364
368
|
"worker-sonnet-4",
|
|
365
369
|
"4", // worker permission: danger_full_access
|
|
366
370
|
"4", // worker reasoning: high
|
|
371
|
+
"y", // edit another role
|
|
372
|
+
"3", // select role to edit: approval
|
|
367
373
|
"2", // approval: edit settings
|
|
368
374
|
"4", // approval AI client: gemini
|
|
369
375
|
"approval-pro-2",
|
|
370
376
|
"4", // approval permission: danger_full_access
|
|
371
377
|
"4", // approval reasoning: high
|
|
378
|
+
"n", // stop editing roles
|
|
372
379
|
"1", // keep current default setting
|
|
373
380
|
"y", // save
|
|
374
381
|
]),
|
|
@@ -386,6 +393,26 @@ export async function runSelftestBotCommands(push, deps) {
|
|
|
386
393
|
&& String(safeObject(safeObject(groupedRunnerConfig.role_profiles || {}).approval).model || "") === "approval-pro-2",
|
|
387
394
|
`worker=${JSON.stringify(safeObject(safeObject(groupedRunnerConfig.role_profiles || {}).worker))} approval=${JSON.stringify(safeObject(safeObject(groupedRunnerConfig.role_profiles || {}).approval))}`,
|
|
388
395
|
);
|
|
396
|
+
|
|
397
|
+
const groupedShowResult = await runCLI({
|
|
398
|
+
cliPath,
|
|
399
|
+
args: [
|
|
400
|
+
"bot", "show",
|
|
401
|
+
"--provider", "telegram",
|
|
402
|
+
"--bot-key", "ryoai_bot",
|
|
403
|
+
"--base-url", `http://127.0.0.1:${groupedMock.port}`,
|
|
404
|
+
"--json", "true",
|
|
405
|
+
],
|
|
406
|
+
env,
|
|
407
|
+
});
|
|
408
|
+
const groupedShowPayload = readJSON(groupedShowResult.stdout);
|
|
409
|
+
push(
|
|
410
|
+
"bot_show_reports_grouped_server_roles",
|
|
411
|
+
safeObject(groupedShowPayload.serverBinding).mode === "group"
|
|
412
|
+
&& safeObject(safeObject(groupedShowPayload.serverBinding).effectiveRoleProfiles).worker?.client === "claude"
|
|
413
|
+
&& safeObject(safeObject(groupedShowPayload.serverBinding).effectiveRoleProfiles).approval?.client === "gemini",
|
|
414
|
+
`mode=${String(safeObject(groupedShowPayload.serverBinding).mode || "")} worker=${String(safeObject(safeObject(groupedShowPayload.serverBinding).effectiveRoleProfiles).worker?.client || "")} approval=${String(safeObject(safeObject(groupedShowPayload.serverBinding).effectiveRoleProfiles).approval?.client || "")}`,
|
|
415
|
+
);
|
|
389
416
|
} finally {
|
|
390
417
|
await groupedMock.close();
|
|
391
418
|
await runCLI({
|
|
@@ -406,16 +433,58 @@ export async function runSelftestBotCommands(push, deps) {
|
|
|
406
433
|
"bot", "show",
|
|
407
434
|
"--provider", "telegram",
|
|
408
435
|
"--bot-key", "monitorselftestbot",
|
|
436
|
+
"--base-url", baseURL,
|
|
409
437
|
"--json", "true",
|
|
410
438
|
],
|
|
411
439
|
env,
|
|
412
440
|
});
|
|
413
441
|
const showPayload = readJSON(showResult.stdout);
|
|
442
|
+
const runnerConfigPath = path.join(tempHome, ".metheus", "bot-runner.json");
|
|
443
|
+
const runnerConfig = readJSON(fs.readFileSync(runnerConfigPath, "utf8"));
|
|
444
|
+
runnerConfig.routes = [
|
|
445
|
+
{
|
|
446
|
+
name: "telegram-monitor",
|
|
447
|
+
enabled: true,
|
|
448
|
+
project_id: "03c586a2-006d-4051-83b4-f353a5813176",
|
|
449
|
+
provider: "telegram",
|
|
450
|
+
bot_id: mock.bots[0].id,
|
|
451
|
+
role_profile: "monitor",
|
|
452
|
+
},
|
|
453
|
+
{
|
|
454
|
+
name: "telegram-default",
|
|
455
|
+
enabled: true,
|
|
456
|
+
project_id: "03c586a2-006d-4051-83b4-f353a5813176",
|
|
457
|
+
provider: "telegram",
|
|
458
|
+
},
|
|
459
|
+
];
|
|
460
|
+
fs.writeFileSync(runnerConfigPath, `${JSON.stringify(runnerConfig, null, 2)}\n`, "utf8");
|
|
461
|
+
const showWithRoutesResult = await runCLI({
|
|
462
|
+
cliPath,
|
|
463
|
+
args: [
|
|
464
|
+
"bot", "show",
|
|
465
|
+
"--provider", "telegram",
|
|
466
|
+
"--bot-key", "monitorselftestbot",
|
|
467
|
+
"--base-url", baseURL,
|
|
468
|
+
"--json", "true",
|
|
469
|
+
],
|
|
470
|
+
env,
|
|
471
|
+
});
|
|
472
|
+
const showWithRoutesPayload = readJSON(showWithRoutesResult.stdout);
|
|
414
473
|
push(
|
|
415
474
|
"bot_show_returns_selected_telegram_entry",
|
|
416
475
|
safeObject(showPayload.entry).key === "monitorselftestbot"
|
|
417
|
-
&& safeObject(showPayload.entry).client === "codex"
|
|
418
|
-
|
|
476
|
+
&& safeObject(showPayload.entry).client === "codex"
|
|
477
|
+
&& safeObject(showPayload.serverBinding).mode === "single",
|
|
478
|
+
`key=${String(safeObject(showPayload.entry).key || "")} client=${String(safeObject(showPayload.entry).client || "")} mode=${String(safeObject(showPayload.serverBinding).mode || "")}`,
|
|
479
|
+
);
|
|
480
|
+
push(
|
|
481
|
+
"bot_show_reports_linked_runner_routes",
|
|
482
|
+
intFromRaw(safeObject(showWithRoutesPayload.routeLinks).total, 0) >= 1
|
|
483
|
+
&& ensureArray(safeObject(showWithRoutesPayload.routeLinks).routes).some((route) => (
|
|
484
|
+
String(safeObject(route).routeName || "") === "telegram-monitor"
|
|
485
|
+
&& String(safeObject(route).matchedBy || "") === "bot_id"
|
|
486
|
+
)),
|
|
487
|
+
`routes=${JSON.stringify(safeObject(showWithRoutesPayload.routeLinks))}`,
|
|
419
488
|
);
|
|
420
489
|
|
|
421
490
|
await runCLI({
|
|
@@ -436,7 +505,6 @@ export async function runSelftestBotCommands(push, deps) {
|
|
|
436
505
|
"3", // workspace_write
|
|
437
506
|
"2", // change reasoning effort
|
|
438
507
|
"3", // medium
|
|
439
|
-
"1", // keep bot key
|
|
440
508
|
"1", // keep default setting
|
|
441
509
|
"y", // save
|
|
442
510
|
]),
|
|
@@ -518,8 +586,9 @@ export async function runSelftestBotCommands(push, deps) {
|
|
|
518
586
|
"bot_verify_guided_can_emit_json",
|
|
519
587
|
guidedVerifyPayload.ok === true
|
|
520
588
|
&& safeObject(guidedVerifyPayload.serverBinding).ok === true
|
|
589
|
+
&& safeObject(guidedVerifyPayload.serverBinding).mode === "single"
|
|
521
590
|
&& String(guidedVerifyPayload.client || "") === "claude",
|
|
522
|
-
`verify=${String(guidedVerifyPayload.ok)} server=${String(safeObject(guidedVerifyPayload.serverBinding).detail || "")}`,
|
|
591
|
+
`verify=${String(guidedVerifyPayload.ok)} mode=${String(safeObject(guidedVerifyPayload.serverBinding).mode || "")} server=${String(safeObject(guidedVerifyPayload.serverBinding).detail || "")}`,
|
|
523
592
|
);
|
|
524
593
|
|
|
525
594
|
await runCLI({
|
|
@@ -556,8 +625,17 @@ export async function runSelftestBotCommands(push, deps) {
|
|
|
556
625
|
"bot_verify_cross_checks_server_bot_binding",
|
|
557
626
|
verifyPayload.ok === true
|
|
558
627
|
&& safeObject(verifyPayload.serverBinding).ok === true
|
|
628
|
+
&& safeObject(verifyPayload.serverBinding).mode === "single"
|
|
559
629
|
&& String(verifyPayload.client || "") === "claude",
|
|
560
|
-
`verify=${String(verifyPayload.ok)} server=${String(safeObject(verifyPayload.serverBinding).detail || "")}`,
|
|
630
|
+
`verify=${String(verifyPayload.ok)} mode=${String(safeObject(verifyPayload.serverBinding).mode || "")} server=${String(safeObject(verifyPayload.serverBinding).detail || "")}`,
|
|
631
|
+
);
|
|
632
|
+
push(
|
|
633
|
+
"bot_verify_reports_linked_runner_routes",
|
|
634
|
+
intFromRaw(safeObject(verifyPayload.routeLinks).total, 0) >= 1
|
|
635
|
+
&& ensureArray(safeObject(verifyPayload.routeLinks).routes).some((route) => (
|
|
636
|
+
String(safeObject(route).routeName || "") === "telegram-monitor"
|
|
637
|
+
)),
|
|
638
|
+
`routes=${JSON.stringify(safeObject(verifyPayload.routeLinks))}`,
|
|
561
639
|
);
|
|
562
640
|
|
|
563
641
|
await runCLI({
|