metheus-governance-mcp-cli 0.2.105 → 0.2.107

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 CHANGED
@@ -58,9 +58,9 @@ TELEGRAM_BOT_TOKEN=
58
58
  ```
59
59
 
60
60
  ```env
61
- # ~/.metheus/telegram-bots/RyoAI_bot.env
61
+ # ~/.metheus/telegram-bots/<ServerBotName>.env
62
62
  TELEGRAM_BOT_SERVER_BOT_ID=<server_bot_uuid>
63
- TELEGRAM_BOT_SERVER_NAME=RyoAI_bot
63
+ TELEGRAM_BOT_SERVER_NAME=<ServerBotName>
64
64
  TELEGRAM_BOT_SERVER_ROLES=monitor,review,worker,approval
65
65
  TELEGRAM_BOT_SERVER_ROLE_IDS=monitor:<uuid>,review:<uuid>,worker:<uuid>,approval:<uuid>
66
66
  # Optional fallback only when server bot binding is unavailable
@@ -233,22 +233,22 @@ Direct commands:
233
233
 
234
234
  ```bash
235
235
  metheus-governance-mcp-cli bot list
236
- metheus-governance-mcp-cli bot show --provider telegram --bot-name RyoAI_bot
236
+ metheus-governance-mcp-cli bot show --provider telegram --bot-name <server_bot_name>
237
237
  metheus-governance-mcp-cli bot add --provider telegram
238
238
  metheus-governance-mcp-cli bot edit
239
239
  metheus-governance-mcp-cli bot edit --provider telegram
240
240
  metheus-governance-mcp-cli bot remove --provider telegram
241
- metheus-governance-mcp-cli bot set-default --provider telegram --bot-name RyoAI_bot
242
- metheus-governance-mcp-cli bot migrate --provider telegram --bot-name RyoAI_bot
241
+ metheus-governance-mcp-cli bot set-default --provider telegram --bot-name <server_bot_name>
242
+ metheus-governance-mcp-cli bot migrate --provider telegram --bot-name <server_bot_name>
243
243
  metheus-governance-mcp-cli bot global --provider telegram
244
- metheus-governance-mcp-cli bot verify --provider telegram --bot-name RyoAI_bot
244
+ metheus-governance-mcp-cli bot verify --provider telegram --bot-name <server_bot_name>
245
245
  ```
246
246
 
247
247
  Behavior:
248
248
 
249
249
  - `bot setup` asks for `Telegram / Slack / KakaoTalk` first, then prompts with numbered actions.
250
250
  - `bot add` without flags now uses the shortest practical guided flow: provider -> token -> verify -> optional save-anyway only when verify fails -> optional username fallback only when verify cannot discover it -> optional AI model selection when the resolved defaults leave it blank -> optional default bot.
251
- - In the normal Telegram path, `bot add` does not ask for a local bot key, a server bot UUID, or an approval / worker / review / monitor choice.
251
+ - In the normal Telegram path, `bot add` does not ask for a local selector, a server bot UUID, or an approval / worker / review / monitor choice.
252
252
  - For Telegram, the local bot file stem is auto-generated from the matched server bot name or verified username, so you do not have to invent a separate local nickname first.
253
253
  - For Telegram, the CLI tries to match the verified bot identity against the server `me/bots` list first. If the server exposes one logical bot name with multiple roles such as `approval`, `worker`, `review`, and `monitor`, the CLI does not ask you to choose one UUID. It binds by server bot name and keeps the local role/AI fields empty so runtime can use the server bot role for each route.
254
254
  - When the Telegram username matches exactly one server bot role, the CLI still auto-fills the local `role_profile` and blank AI defaults from your local `bot-runner.json` `role_profiles` mapping.
@@ -284,8 +284,8 @@ Behavior:
284
284
  - `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.
285
285
  - `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.
286
286
  - In `bot show` and `bot verify`, `saved_local_file` means the values already stored on disk under `~/.metheus/telegram-bots/*.env`, while `current_server_protocol` means the fresh `/api/v1/me/bots` response from the server right now.
287
- - `bot global` edits Telegram-wide local settings such as API base URL, allowed updates, and default bot key.
288
- - `bot set-default` updates `TELEGRAM_DEFAULT_BOT_KEY`.
287
+ - `bot global` edits Telegram-wide local settings such as API base URL, allowed updates, and the default Telegram bot selector.
288
+ - `bot set-default` changes the default Telegram bot selection.
289
289
  - `bot migrate` moves legacy `TELEGRAM_BOT_TOKEN` into a named Telegram bot entry.
290
290
 
291
291
  Non-interactive examples:
@@ -294,16 +294,16 @@ Non-interactive examples:
294
294
  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
295
295
  metheus-governance-mcp-cli bot add --provider telegram --non-interactive true --server-bot-id <server_bot_uuid> --token <telegram_bot_token> --default true
296
296
  metheus-governance-mcp-cli bot add --provider telegram --non-interactive true --server-bot-id <server_bot_uuid> --token <telegram_bot_token> --ai-client gpt --ai-model "gpt-5.4" --ai-permission-mode read_only --ai-reasoning-effort low
297
- metheus-governance-mcp-cli bot edit --provider telegram --bot-name RyoAI_bot --non-interactive true --ai-client claude --ai-model "Sonnet 4.6r" --ai-permission-mode danger_full_access --ai-reasoning-effort high
298
- metheus-governance-mcp-cli bot set-default --provider telegram --bot-name RyoAI_bot --non-interactive true
299
- metheus-governance-mcp-cli bot migrate --provider telegram --bot-name RyoAI_bot --server-bot-id <server_bot_uuid> --role-profile monitor --ai-client gpt --ai-permission-mode read_only --ai-reasoning-effort low --non-interactive true
300
- metheus-governance-mcp-cli bot remove --provider telegram --bot-name RyoAI_bot --non-interactive true
301
- metheus-governance-mcp-cli bot verify --provider telegram --bot-name RyoAI_bot --json true
297
+ metheus-governance-mcp-cli bot edit --provider telegram --bot-name <server_bot_name> --non-interactive true --ai-client claude --ai-model "Sonnet 4.6r" --ai-permission-mode danger_full_access --ai-reasoning-effort high
298
+ metheus-governance-mcp-cli bot set-default --provider telegram --bot-name <server_bot_name> --non-interactive true
299
+ metheus-governance-mcp-cli bot migrate --provider telegram --bot-name <server_bot_name> --server-bot-id <server_bot_uuid> --role-profile monitor --ai-client gpt --ai-permission-mode read_only --ai-reasoning-effort low --non-interactive true
300
+ metheus-governance-mcp-cli bot remove --provider telegram --bot-name <server_bot_name> --non-interactive true
301
+ metheus-governance-mcp-cli bot verify --provider telegram --bot-name <server_bot_name> --json true
302
302
  ```
303
303
 
304
- For direct Telegram adds, the CLI derives the local file name from the matched server bot name. You normally do not need `--bot-key` or `--username`. Treat `--bot-key` as an advanced compatibility selector only when you intentionally need to target an older local selector directly.
304
+ For direct Telegram adds, the CLI derives the local file name from the matched server bot name. You normally do not need `--bot-key` or `--username`. Treat `--bot-key` as a legacy compatibility selector only when you intentionally need to target an older local selector directly.
305
305
 
306
- For direct Telegram edits, prefer `--bot-name` or `--bot-id` because server bot identity is the source of truth. Use `--bot-key` only when you intentionally need the advanced local selector. 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.
306
+ For direct Telegram edits, prefer `--bot-name` or `--bot-id` because server bot identity is the source of truth. Use `--bot-key` only when you intentionally need the legacy local selector. 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.
307
307
 
308
308
  For runner commands, you can still use `--route-name` directly, but normal operator workflows can also use `--bot-name` or `--bot-id` when those identify one enabled route uniquely. Use `runner list` first if you are not sure which route belongs to which server bot.
309
309
 
package/cli.mjs CHANGED
@@ -249,7 +249,7 @@ function printUsage() {
249
249
  ` ${cmd} bot set-default --provider telegram [--bot-name <server_name>] [--bot-id <uuid>]`,
250
250
  ` ${cmd} bot migrate --provider telegram [--bot-name <server_name>] [--bot-id <uuid>]`,
251
251
  ` ${cmd} bot verify [--provider <telegram|slack|kakaotalk>] [--bot-name <server_name>] [--bot-id <uuid>] [--timeout-seconds <n>] [--json <true|false>]`,
252
- ` ${cmd} bot global --provider telegram [--api-base-url <url>] [--auto-clear-webhook <true|false>] [--allowed-updates <csv>] [--default-bot-key <key>]`,
252
+ ` ${cmd} bot global --provider telegram [--api-base-url <url>] [--auto-clear-webhook <true|false>] [--allowed-updates <csv>] [--default-bot-name <server_name>] [--default-bot-id <uuid>]`,
253
253
  ` ${cmd} doctor [--project-id <uuid>] [--ctxpack-key <key>] [--base-url <url>] [--timeout-seconds <n>] [--strict <true|false>]`,
254
254
  ` ${cmd} proxy [--project-id <uuid>] [--ctxpack-key <key>] [--base-url <url>] [--workspace-dir <path|auto>] [--include-drafts <true|false>] [--auto-pull-on-conflict <true|false>] [--timeout-seconds <n>]`,
255
255
  ` ${cmd} selftest [--json <true|false>]`,
@@ -223,18 +223,18 @@ function printBotUsage(deps) {
223
223
  ` ${cliName} bot set-default --provider telegram [--bot-name <server_name>] [--bot-id <uuid>] [--non-interactive <true|false>]`,
224
224
  ` ${cliName} bot migrate --provider telegram [--bot-name <server_name>] [--bot-id <uuid>] [--keep-legacy-token <true|false>] [--non-interactive <true|false>]`,
225
225
  ` ${cliName} bot verify [--provider <telegram|slack|kakaotalk>] [--bot-name <server_name>] [--bot-id <uuid>] [--timeout-seconds <n>] [--json <true|false>]`,
226
- ` ${cliName} bot global --provider telegram [--non-interactive <true|false>] [--api-base-url <url>] [--auto-clear-webhook <true|false>] [--allowed-updates <csv>] [--default-bot-key <advanced_key>]`,
226
+ ` ${cliName} bot global --provider telegram [--non-interactive <true|false>] [--api-base-url <url>] [--auto-clear-webhook <true|false>] [--allowed-updates <csv>] [--default-bot-name <server_name>] [--default-bot-id <uuid>]`,
227
227
  "",
228
228
  `Behavior:`,
229
229
  ` - bot setup asks for provider first, then shows a numbered action menu.`,
230
230
  ` - bot add without flags uses the shortest practical guided flow: provider -> token -> verify -> optional username fallback -> optional default bot.`,
231
- ` - in the normal Telegram path, bot add does not ask for a local bot key or a server role/profile choice.`,
232
- ` - server bot name/UUID is the source of truth; --bot-key is an advanced compatibility selector only.`,
231
+ ` - in the normal Telegram path, bot add does not ask for a local selector or a server role/profile choice.`,
232
+ ` - server bot name/UUID is the source of truth; --bot-key remains only as a legacy compatibility selector.`,
233
233
  ` - runner commands can auto-select by --bot-name or --bot-id when one enabled route matches. Use runner list first if you are unsure.`,
234
234
  ` - bot edit without flags asks for provider, existing entry, then walks field-by-field in a numbered flow.`,
235
235
  ` - in the normal Telegram edit path, the CLI keeps or re-resolves the server bot binding automatically instead of asking you to pick a server role/profile UUID first.`,
236
236
  ` - bot set-default / bot verify / bot remove also support guided numbered selection when run without flags.`,
237
- ` - advanced selectors: --bot-key is still supported for compatibility, but prefer --bot-name or --bot-id in normal operator workflows.`,
237
+ ` - legacy compatibility: prefer --bot-name or --bot-id in normal operator workflows; use --bot-key only when you must target an older local selector directly.`,
238
238
  "",
239
239
  ].join("\n"),
240
240
  );
@@ -858,7 +858,7 @@ function findTelegramEntryByFlags(parsedEnv, flags, deps) {
858
858
  if (requestedKey) {
859
859
  const match = entries.find((entry) => entry.key === requestedKey);
860
860
  if (!match) {
861
- throw new Error(`Telegram bot entry "${requestedKey}" was not found`);
861
+ throw new Error(`Telegram bot entry with legacy local selector "${requestedKey}" was not found`);
862
862
  }
863
863
  return match;
864
864
  }
@@ -895,7 +895,7 @@ function saveTelegramBotEdit(parsed, selected, current, deps) {
895
895
  nextParsed.TELEGRAM_DEFAULT_BOT_KEY = current.key;
896
896
  }
897
897
  const filePath = writeProviderEnvState("telegram", nextParsed, deps);
898
- process.stdout.write(`Saved Telegram bot entry "${current.key}" to ${filePath}\n`);
898
+ process.stdout.write(`Saved Telegram bot file for "${telegramEntryDisplayName(current)}" to ${filePath}\n`);
899
899
  return filePath;
900
900
  }
901
901
 
@@ -1129,7 +1129,7 @@ async function editTelegramBotGuided(ui, parsed, selected, current, flags, deps)
1129
1129
  parsed.TELEGRAM_DEFAULT_BOT_KEY = "";
1130
1130
  }
1131
1131
 
1132
- if (!await promptYesNo(ui, `Save changes for "${current.key}" now?`, true)) {
1132
+ if (!await promptYesNo(ui, `Save changes for "${telegramEntryDisplayName(current)}" now?`, true)) {
1133
1133
  process.stdout.write("Cancelled.\n");
1134
1134
  return;
1135
1135
  }
@@ -1306,11 +1306,9 @@ function printBotList(provider, state, deps) {
1306
1306
  process.stdout.write(" entries: none\n");
1307
1307
  return;
1308
1308
  }
1309
- process.stdout.write(` default: ${String(state.parsed.TELEGRAM_DEFAULT_BOT_KEY || "").trim() || "-"}\n`);
1309
+ const defaultEntry = entries.find((entry) => entry.isDefault) || null;
1310
+ process.stdout.write(` default: ${defaultEntry ? telegramEntryDisplayName(defaultEntry) : "-"}\n`);
1310
1311
  entries.forEach((entry) => {
1311
- const advancedSelectorLine = entry.key
1312
- ? ` advanced_local_selector: ${entry.key} (compatibility only)`
1313
- : "";
1314
1312
  process.stdout.write(
1315
1313
  [
1316
1314
  ` - ${telegramEntryDisplayName(entry)}${entry.isDefault ? " [default]" : ""}`,
@@ -1325,7 +1323,6 @@ function printBotList(provider, state, deps) {
1325
1323
  ` model: ${entry.model || "-"}`,
1326
1324
  ` permission_mode: ${entry.permissionMode || "-"}`,
1327
1325
  ` reasoning_effort: ${entry.reasoningEffort || "-"}`,
1328
- advancedSelectorLine,
1329
1326
  ].join("\n") + "\n",
1330
1327
  );
1331
1328
  });
@@ -1361,7 +1358,6 @@ function printBotShow(provider, state, entry, deps, extras = {}) {
1361
1358
  `ai_model: ${selectedEntry.model || "-"}`,
1362
1359
  `permission_mode: ${selectedEntry.permissionMode || "-"}`,
1363
1360
  `reasoning_effort: ${selectedEntry.reasoningEffort || "-"}`,
1364
- selectedEntry.key ? `advanced_local_selector: ${selectedEntry.key} (compatibility only)` : "",
1365
1361
  ]);
1366
1362
  if (Object.keys(serverBinding).length) {
1367
1363
  const liveLines = [
@@ -1441,7 +1437,7 @@ async function resolveTelegramEntryForShow(ui, parsedEnv, flags, deps) {
1441
1437
  }
1442
1438
  const nonInteractive = boolFromRaw(flags["non-interactive"] ?? flags.yes ?? flags.json, false);
1443
1439
  if (nonInteractive) {
1444
- throw new Error("Telegram bot selector is required when multiple entries exist; pass --bot-key, --bot-id, or --bot-name");
1440
+ throw new Error("Telegram bot selector is required when multiple entries exist; pass --bot-name or --bot-id (legacy --bot-key is still supported)");
1445
1441
  }
1446
1442
  return chooseTelegramEntry(ui, parsedEnv, deps, "Select Telegram bot entry", flags);
1447
1443
  }
@@ -1568,12 +1564,12 @@ async function chooseServerBot(ui, provider, baseURL, timeoutSeconds, deps, opti
1568
1564
  {
1569
1565
  value: "__manual__",
1570
1566
  label: "Manual entry",
1571
- description: "type the server bot UUID yourself",
1567
+ description: "enter a server bot UUID manually",
1572
1568
  },
1573
1569
  {
1574
1570
  value: "__blank__",
1575
1571
  label: "Leave empty",
1576
- description: "skip server bot UUID binding",
1572
+ description: "continue without a server bot binding",
1577
1573
  },
1578
1574
  ],
1579
1575
  { defaultIndex: 0 },
@@ -1618,12 +1614,12 @@ async function chooseServerBot(ui, provider, baseURL, timeoutSeconds, deps, opti
1618
1614
  {
1619
1615
  value: "__manual__",
1620
1616
  label: "Manual entry",
1621
- description: "type the server bot UUID yourself",
1617
+ description: "enter a server bot UUID manually",
1622
1618
  },
1623
1619
  {
1624
1620
  value: "__blank__",
1625
1621
  label: "Leave empty",
1626
- description: "skip server bot UUID binding",
1622
+ description: "continue without a server bot binding",
1627
1623
  },
1628
1624
  ],
1629
1625
  { defaultIndex: 0 },
@@ -1710,7 +1706,7 @@ async function resolveServerBotForNonInteractive(provider, flags, deps, options
1710
1706
  (bot) => normalizeServerBotIdentityText(bot.name) === requestedName,
1711
1707
  );
1712
1708
  if (!matchedByName.length) {
1713
- throw new Error(`no server bot matched ${requestedName}`);
1709
+ throw new Error(`no server bot matched the requested server bot name "${requestedName}"`);
1714
1710
  }
1715
1711
  if (matchedByName.length === 1) {
1716
1712
  const match = matchedByName[0] || {};
@@ -1789,23 +1785,26 @@ async function promptAIModel(ui, deps, clientName, defaultValue = "", title = "S
1789
1785
  const normalizedClient = requireDependency(deps, "normalizeLocalAIClientName")(clientName || "", "");
1790
1786
  const currentValue = String(defaultValue || "").trim();
1791
1787
  const suggestions = suggestedAIModelsForClient(normalizedClient);
1788
+ const currentMatchesSuggestions = currentValue && suggestions.some((item) => item.value === currentValue);
1792
1789
  const options = [
1793
1790
  { value: "", label: "(blank)", description: "leave AI model empty" },
1794
1791
  ];
1795
- if (currentValue && !suggestions.some((item) => item.value === currentValue)) {
1792
+ options.push(...suggestions);
1793
+ if (currentValue && !currentMatchesSuggestions) {
1796
1794
  options.push({
1797
1795
  value: currentValue,
1798
- label: currentValue,
1799
- description: "current saved model",
1796
+ label: `Keep current custom model (${currentValue})`,
1797
+ description: "reuse the model already saved on disk",
1800
1798
  });
1801
1799
  }
1802
- options.push(...suggestions);
1803
1800
  options.push({
1804
1801
  value: "__manual__",
1805
1802
  label: "Manual entry",
1806
1803
  description: "type the model name yourself",
1807
1804
  });
1808
- const selectedIndex = Math.max(0, options.findIndex((item) => item.value === currentValue));
1805
+ const selectedIndex = currentMatchesSuggestions
1806
+ ? Math.max(0, options.findIndex((item) => item.value === currentValue))
1807
+ : (suggestions.length ? 1 : Math.max(0, options.findIndex((item) => item.value === currentValue)));
1809
1808
  const selected = await promptChoice(ui, title, options, { defaultIndex: selectedIndex >= 0 ? selectedIndex : 0 });
1810
1809
  if (String(selected?.value || "") === "__manual__") {
1811
1810
  return String(await promptRequiredLine(ui, "AI model", currentValue)).trim();
@@ -2154,6 +2153,11 @@ function resolveTelegramServerBindingDetailsFromBots(envConfig, bots, deps) {
2154
2153
  const normalizedServerIdentity = normalizeServerBotIdentityText(current.serverBotName || current.botUsername || current.botKey);
2155
2154
  const matches = bots.filter((bot) => normalizeServerBotIdentityText(bot.name) === normalizedServerIdentity);
2156
2155
  if (!matches.length) {
2156
+ const unmatchedIdentity = String(current.botUsername || "").trim()
2157
+ ? `@${String(current.botUsername || "").trim()}`
2158
+ : String(current.serverBotName || "").trim()
2159
+ ? `"${String(current.serverBotName || "").trim()}"`
2160
+ : "this local Telegram bot";
2157
2161
  return {
2158
2162
  ok: false,
2159
2163
  mode: "missing",
@@ -2165,7 +2169,7 @@ function resolveTelegramServerBindingDetailsFromBots(envConfig, bots, deps) {
2165
2169
  serverBotID: "",
2166
2170
  effectiveRoleProfile: null,
2167
2171
  effectiveRoleProfiles: {},
2168
- detail: `no server bot matched ${current.botUsername ? `@${current.botUsername}` : current.botKey}`,
2172
+ detail: `no server bot matched ${unmatchedIdentity}`,
2169
2173
  };
2170
2174
  }
2171
2175
  if (matches.length > 1) {
@@ -2262,7 +2266,6 @@ async function buildTelegramEntrySelectionRows(entries, flags, deps) {
2262
2266
  const descriptionParts = [
2263
2267
  current.serverBotID ? `stored_server_bot_id:${current.serverBotID}` : "",
2264
2268
  current.client ? `ai_client:${displayLocalAIClientName(current.client)}` : "",
2265
- current.key ? `advanced_selector:${current.key}` : "",
2266
2269
  ].filter(Boolean);
2267
2270
  return {
2268
2271
  value: current.key,
@@ -2422,12 +2425,12 @@ async function addTelegramBot(ui, flags, deps) {
2422
2425
  defaultKeyHint,
2423
2426
  );
2424
2427
  if (nonInteractive && existingKeys.has(botKey)) {
2425
- throw new Error(`Telegram bot key "${botKey}" already exists`);
2428
+ throw new Error(`Telegram legacy local selector "${botKey}" already exists`);
2426
2429
  }
2427
2430
  const serverGroupMatched = serverBot.matchMode === "group";
2428
2431
  if (serverGroupMatched && !nonInteractive) {
2429
2432
  process.stdout.write(
2430
- `Matched server Telegram bot "${serverBot.name || username || botKey}" with roles: ${ensureArray(serverBot.roles).join(", ")}. Runtime will use the server role, so local role/AI fields can stay empty.\n`,
2433
+ `Matched server Telegram bot "${serverBot.name || username || "current Telegram bot"}" with roles: ${ensureArray(serverBot.roles).join(", ")}. Runtime will use the server role, so local role/AI fields can stay empty.\n`,
2431
2434
  );
2432
2435
  }
2433
2436
  const aiFlagOverrides = hasOwnFlag(flags, "client")
@@ -2522,19 +2525,25 @@ async function addTelegramBot(ui, flags, deps) {
2522
2525
  if (
2523
2526
  boolFromRaw(flags.default, false)
2524
2527
  || (nonInteractive && !hasCurrentDefault)
2525
- || (!nonInteractive && !hasCurrentDefault && await promptYesNo(ui, "Set this bot as TELEGRAM_DEFAULT_BOT_KEY?", true))
2528
+ || (!nonInteractive && !hasCurrentDefault && await promptYesNo(ui, "Set this bot as the default Telegram bot?", true))
2526
2529
  ) {
2527
2530
  nextParsed.TELEGRAM_DEFAULT_BOT_KEY = botKey;
2528
2531
  }
2529
2532
 
2530
2533
  const filePath = writeProviderEnvState("telegram", nextParsed, deps);
2531
- process.stdout.write(`Saved Telegram bot entry "${botKey}" to ${filePath}\n`);
2534
+ process.stdout.write(`Saved Telegram bot file for "${String(serverBot.name || username || botKey).trim() || "telegram"}" to ${filePath}\n`);
2532
2535
  }
2533
2536
 
2534
2537
  async function editTelegramGlobalSettings(ui, flags, deps) {
2535
2538
  const state = loadProviderEnvState("telegram", deps);
2536
2539
  const parsed = { ...state.parsed };
2537
2540
  const nonInteractive = boolFromRaw(flags["non-interactive"] ?? flags.yes, false);
2541
+ const defaultSelectorFlags = {
2542
+ ...flags,
2543
+ "bot-name": firstNonEmptyString([flags["default-bot-name"], flags["bot-name"]]),
2544
+ "bot-id": firstNonEmptyString([flags["default-bot-id"], flags["bot-id"]]),
2545
+ "bot-key": firstNonEmptyString([flags["default-bot-key"], flags["bot-key"]]),
2546
+ };
2538
2547
  if (nonInteractive) {
2539
2548
  if (Object.prototype.hasOwnProperty.call(flags, "api-base-url")) {
2540
2549
  parsed.TELEGRAM_API_BASE_URL = String(flags["api-base-url"] || "").trim();
@@ -2545,8 +2554,16 @@ async function editTelegramGlobalSettings(ui, flags, deps) {
2545
2554
  if (Object.prototype.hasOwnProperty.call(flags, "allowed-updates")) {
2546
2555
  parsed.TELEGRAM_ALLOWED_UPDATES = String(flags["allowed-updates"] || "").trim();
2547
2556
  }
2548
- if (Object.prototype.hasOwnProperty.call(flags, "default-bot-key")) {
2549
- parsed.TELEGRAM_DEFAULT_BOT_KEY = String(flags["default-bot-key"] || "").trim();
2557
+ if (
2558
+ Object.prototype.hasOwnProperty.call(flags, "default-bot-name")
2559
+ || Object.prototype.hasOwnProperty.call(flags, "default-bot-id")
2560
+ || Object.prototype.hasOwnProperty.call(flags, "default-bot-key")
2561
+ ) {
2562
+ const selectedDefault = findTelegramEntryByFlags(parsed, defaultSelectorFlags, deps);
2563
+ if (!selectedDefault) {
2564
+ throw new Error("Telegram default bot selector was not found");
2565
+ }
2566
+ parsed.TELEGRAM_DEFAULT_BOT_KEY = selectedDefault.key;
2550
2567
  }
2551
2568
  const filePath = writeProviderEnvState("telegram", parsed, deps);
2552
2569
  process.stdout.write(`Saved Telegram global settings to ${filePath}\n`);
@@ -2559,7 +2576,13 @@ async function editTelegramGlobalSettings(ui, flags, deps) {
2559
2576
  { value: "api", label: "API base URL", description: String(parsed.TELEGRAM_API_BASE_URL || "").trim() || "(empty)" },
2560
2577
  { value: "webhook", label: "Auto clear webhook", description: boolFromRaw(parsed.TELEGRAM_AUTO_CLEAR_WEBHOOK, true) ? "true" : "false" },
2561
2578
  { value: "updates", label: "Allowed updates", description: String(parsed.TELEGRAM_ALLOWED_UPDATES || "message,edited_message").trim() },
2562
- { value: "default", label: "Default bot key", description: String(parsed.TELEGRAM_DEFAULT_BOT_KEY || "").trim() || "(empty)" },
2579
+ {
2580
+ value: "default",
2581
+ label: "Default Telegram bot",
2582
+ description: telegramEntryDisplayName(
2583
+ telegramEntriesForDisplay(parsed, deps).find((entry) => entry.isDefault) || {},
2584
+ ) || "(empty)",
2585
+ },
2563
2586
  ],
2564
2587
  { defaultIndex: 0 },
2565
2588
  );
@@ -2591,7 +2614,7 @@ async function editTelegramBot(ui, flags, deps) {
2591
2614
  ? findTelegramEntryByFlags(parsed, flags, deps)
2592
2615
  : await chooseTelegramEntry(ui, parsed, deps, "Select Telegram bot entry", flags);
2593
2616
  if (!selected) {
2594
- throw new Error("Telegram bot selector is required for non-interactive edit");
2617
+ throw new Error("Telegram bot selector is required for non-interactive edit; pass --bot-name or --bot-id (legacy --bot-key is still supported)");
2595
2618
  }
2596
2619
  let current = { ...selected };
2597
2620
  if (nonInteractive) {
@@ -2653,11 +2676,11 @@ async function removeTelegramBot(ui, flags, deps) {
2653
2676
  const parsed = { ...state.parsed };
2654
2677
  const selected = await chooseTelegramEntry(ui, parsed, deps, "Select Telegram bot entry to remove", flags);
2655
2678
  if (!selected) return;
2656
- if (!await promptConfirmChoice(ui, `Remove Telegram bot "${selected.key}"?`, {
2679
+ if (!await promptConfirmChoice(ui, `Remove Telegram bot "${telegramEntryDisplayName(selected)}"?`, {
2657
2680
  confirmLabel: "Remove bot",
2658
- confirmDescription: "delete this local Telegram bot entry",
2681
+ confirmDescription: "delete this local Telegram bot file",
2659
2682
  cancelLabel: "Cancel",
2660
- cancelDescription: "keep the current local bot entry",
2683
+ cancelDescription: "keep the current local Telegram bot file",
2661
2684
  defaultValue: "cancel",
2662
2685
  })) {
2663
2686
  process.stdout.write("Cancelled.\n");
@@ -2669,7 +2692,7 @@ async function removeTelegramBot(ui, flags, deps) {
2669
2692
  nextParsed.TELEGRAM_DEFAULT_BOT_KEY = remaining[0]?.key || "";
2670
2693
  }
2671
2694
  const filePath = writeProviderEnvState("telegram", nextParsed, deps);
2672
- process.stdout.write(`Removed Telegram bot "${selected.key}" from ${filePath}\n`);
2695
+ process.stdout.write(`Removed Telegram bot file for "${String(selected.serverBotName || selected.username || selected.key).trim() || "telegram"}" from ${filePath}\n`);
2673
2696
  }
2674
2697
 
2675
2698
  async function verifyProviderEntry(ui, provider, flags, deps) {
@@ -2690,7 +2713,7 @@ async function verifyProviderEntry(ui, provider, flags, deps) {
2690
2713
  selectors = {
2691
2714
  botKey: selected.key,
2692
2715
  botID: selected.serverBotID,
2693
- botName: selected.username,
2716
+ botName: selected.serverBotName || selected.username,
2694
2717
  };
2695
2718
  }
2696
2719
  }
@@ -2840,7 +2863,6 @@ async function verifyProviderEntry(ui, provider, flags, deps) {
2840
2863
  `ai_model: ${envConfig.model || "-"}`,
2841
2864
  `permission_mode: ${envConfig.permissionMode || "-"}`,
2842
2865
  `reasoning_effort: ${envConfig.reasoningEffort || "-"}`,
2843
- envConfig.botKey ? `advanced_local_selector: ${envConfig.botKey} (compatibility only)` : "",
2844
2866
  ]);
2845
2867
  }
2846
2868
  if (provider === "telegram" && serverBinding) {
@@ -3097,7 +3119,7 @@ async function runBotRemove(ui, flags, deps) {
3097
3119
  const state = loadProviderEnvState("telegram", deps);
3098
3120
  const selected = findTelegramEntryByFlags(state.parsed, flags, deps);
3099
3121
  if (!selected) {
3100
- throw new Error("Telegram bot selector is required for non-interactive remove");
3122
+ throw new Error("Telegram bot selector is required for non-interactive remove; pass --bot-name or --bot-id (legacy --bot-key is still supported)");
3101
3123
  }
3102
3124
  const nextParsed = removeTelegramEntry(state.parsed, selected.key);
3103
3125
  if (String(state.parsed.TELEGRAM_DEFAULT_BOT_KEY || "").trim() === selected.key) {
@@ -3105,7 +3127,7 @@ async function runBotRemove(ui, flags, deps) {
3105
3127
  nextParsed.TELEGRAM_DEFAULT_BOT_KEY = remaining[0]?.key || "";
3106
3128
  }
3107
3129
  const filePath = writeProviderEnvState("telegram", nextParsed, deps);
3108
- process.stdout.write(`Removed Telegram bot "${selected.key}" from ${filePath}\n`);
3130
+ process.stdout.write(`Removed Telegram bot file for "${String(selected.serverBotName || selected.username || selected.key).trim() || "telegram"}" from ${filePath}\n`);
3109
3131
  return;
3110
3132
  }
3111
3133
  await removeTelegramBot(ui, flags, deps);
@@ -3137,11 +3159,11 @@ async function runBotSetDefault(ui, flags, deps) {
3137
3159
  ? findTelegramEntryByFlags(parsed, flags, deps)
3138
3160
  : await chooseTelegramEntry(ui, parsed, deps, "Select default Telegram bot", flags);
3139
3161
  if (!selected) {
3140
- throw new Error("Telegram bot selector is required for set-default");
3162
+ throw new Error("Telegram bot selector is required for set-default; pass --bot-name or --bot-id (legacy --bot-key is still supported)");
3141
3163
  }
3142
- if (!nonInteractive && !await promptConfirmChoice(ui, `Set "${selected.key}" as TELEGRAM_DEFAULT_BOT_KEY?`, {
3164
+ if (!nonInteractive && !await promptConfirmChoice(ui, `Set "${telegramEntryDisplayName(selected)}" as the default Telegram bot?`, {
3143
3165
  confirmLabel: "Set default bot",
3144
- confirmDescription: "make this the default local Telegram bot entry",
3166
+ confirmDescription: "make this the default Telegram bot for local delivery and fallback selection",
3145
3167
  cancelLabel: "Cancel",
3146
3168
  cancelDescription: "leave the current default unchanged",
3147
3169
  defaultValue: "confirm",
@@ -3151,7 +3173,7 @@ async function runBotSetDefault(ui, flags, deps) {
3151
3173
  }
3152
3174
  parsed.TELEGRAM_DEFAULT_BOT_KEY = selected.key;
3153
3175
  const filePath = writeProviderEnvState("telegram", parsed, deps);
3154
- process.stdout.write(`Set TELEGRAM_DEFAULT_BOT_KEY=${selected.key} in ${filePath}\n`);
3176
+ process.stdout.write(`Set default Telegram bot to "${telegramEntryDisplayName(selected)}" in ${filePath}\n`);
3155
3177
  }
3156
3178
 
3157
3179
  async function runBotMigrate(ui, flags, deps) {
@@ -3178,11 +3200,11 @@ async function runBotMigrate(ui, flags, deps) {
3178
3200
  const botKey = normalizeBotKey(
3179
3201
  nonInteractive
3180
3202
  ? String(flags["bot-key"] || defaultKeyHint).trim()
3181
- : await promptRequiredLine(ui, "Local Telegram bot key for migrated entry", defaultKeyHint),
3203
+ : await promptRequiredLine(ui, "Legacy local selector for migrated entry", defaultKeyHint),
3182
3204
  defaultKeyHint,
3183
3205
  );
3184
3206
  if (existingKeys.has(botKey)) {
3185
- throw new Error(`Telegram bot key "${botKey}" already exists`);
3207
+ throw new Error(`Telegram legacy local selector "${botKey}" already exists`);
3186
3208
  }
3187
3209
  const nextParsed = upsertTelegramEntry(parsed, {
3188
3210
  key: botKey,
@@ -3203,7 +3225,7 @@ async function runBotMigrate(ui, flags, deps) {
3203
3225
  }
3204
3226
  const filePath = writeProviderEnvState("telegram", nextParsed, deps);
3205
3227
  process.stdout.write(
3206
- `Migrated TELEGRAM_BOT_TOKEN to named entry "${botKey}" in ${filePath}${boolFromRaw(flags["keep-legacy-token"], false) ? " (legacy token preserved)" : ""}\n`,
3228
+ `Migrated TELEGRAM_BOT_TOKEN to named entry selector "${botKey}" in ${filePath}${boolFromRaw(flags["keep-legacy-token"], false) ? " (legacy token preserved)" : ""}\n`,
3207
3229
  );
3208
3230
  }
3209
3231
 
@@ -63,7 +63,7 @@ export async function runDoctorProjectDestinationChecks({
63
63
  }
64
64
  const tokenDetailParts = [`configured (${envConfig.filePath})`];
65
65
  if (envConfig.botKey) {
66
- tokenDetailParts.push(`bot=${envConfig.botKey}`);
66
+ tokenDetailParts.push(`local_selector=${envConfig.botKey}`);
67
67
  }
68
68
  if (envConfig.botUsername) {
69
69
  tokenDetailParts.push(`@${envConfig.botUsername}`);
@@ -95,7 +95,7 @@ function createMockServer(options = {}) {
95
95
  : [
96
96
  {
97
97
  id: "11111111-2222-3333-4444-555555555555",
98
- name: "MonitorSelftestBot",
98
+ name: "ServerProtocolMonitorBot",
99
99
  provider: "telegram",
100
100
  bot_role: "monitor",
101
101
  is_active: true,
@@ -105,8 +105,8 @@ function createMockServer(options = {}) {
105
105
  ensureArray(options.telegramUsersByToken).length
106
106
  ? ensureArray(options.telegramUsersByToken)
107
107
  : [
108
- ["selftest-main-token", "MonitorSelftestBot"],
109
- ["selftest-edited-token", "MonitorSelftestBot"],
108
+ ["selftest-main-token", "ServerProtocolMonitorBot"],
109
+ ["selftest-edited-token", "ServerProtocolMonitorBot"],
110
110
  ],
111
111
  );
112
112
 
@@ -290,6 +290,7 @@ export async function runSelftestBotCommands(push, deps) {
290
290
  const telegramApiBaseURL = `${baseURL}/telegram`;
291
291
  const telegramBotEntriesDir = path.join(tempHome, ".metheus", "telegram-bots");
292
292
  const telegramEnvPath = path.join(telegramBotEntriesDir, "global.env");
293
+ const botRunnerConfigPath = path.join(tempHome, ".metheus", "bot-runner.json");
293
294
  const readTelegramGlobals = () => parseSimpleEnvText(fs.readFileSync(telegramEnvPath, "utf8"));
294
295
  const readTelegramBotEntry = (selector) => {
295
296
  const requested = String(selector || "").trim().toLowerCase();
@@ -309,6 +310,42 @@ export async function runSelftestBotCommands(push, deps) {
309
310
  }
310
311
  return parseSimpleEnvText(fs.readFileSync(filePath, "utf8"));
311
312
  };
313
+ fs.mkdirSync(path.join(tempHome, ".metheus"), { recursive: true });
314
+ fs.writeFileSync(
315
+ botRunnerConfigPath,
316
+ `${JSON.stringify({
317
+ version: 2,
318
+ project_mappings: {},
319
+ role_profiles: {
320
+ monitor: {
321
+ client: "gpt",
322
+ model: "",
323
+ permission_mode: "read_only",
324
+ reasoning_effort: "low",
325
+ },
326
+ review: {
327
+ client: "claude",
328
+ model: "Sonnet 4.6r",
329
+ permission_mode: "danger_full_access",
330
+ reasoning_effort: "high",
331
+ },
332
+ worker: {
333
+ client: "gpt",
334
+ model: "gpt-5.4",
335
+ permission_mode: "danger_full_access",
336
+ reasoning_effort: "high",
337
+ },
338
+ approval: {
339
+ client: "claude",
340
+ model: "Sonnet 4.6r",
341
+ permission_mode: "danger_full_access",
342
+ reasoning_effort: "high",
343
+ },
344
+ },
345
+ routes: [],
346
+ }, null, 2)}\n`,
347
+ "utf8",
348
+ );
312
349
 
313
350
  const setupResult = await runCLI({
314
351
  cliPath,
@@ -359,29 +396,29 @@ export async function runSelftestBotCommands(push, deps) {
359
396
  ]),
360
397
  },
361
398
  });
362
- const addState = readTelegramBotEntry("monitorselftestbot");
399
+ const addState = readTelegramBotEntry("serverprotocolmonitorbot");
363
400
  const addGlobals = readTelegramGlobals();
364
401
  push(
365
402
  "bot_add_guided_creates_named_telegram_entry",
366
- String(addState.TELEGRAM_BOT_SERVER_NAME || "") === "MonitorSelftestBot"
403
+ String(addState.TELEGRAM_BOT_SERVER_NAME || "") === "ServerProtocolMonitorBot"
367
404
  && String(addState.TELEGRAM_BOT_SERVER_ROLES || "") === "monitor"
368
405
  && String(addState.TELEGRAM_BOT_TOKEN || "") === "selftest-main-token"
369
406
  && String(addState.TELEGRAM_BOT_ROLE_PROFILE || "") === "monitor"
370
407
  && String(addState.TELEGRAM_BOT_AI_CLIENT || "") === "gpt"
371
408
  && String(addState.TELEGRAM_BOT_AI_PERMISSION_MODE || "") === "read_only"
372
409
  && String(addState.TELEGRAM_BOT_AI_REASONING_EFFORT || "") === "low"
373
- && String(addGlobals.TELEGRAM_DEFAULT_BOT_KEY || "") === "monitorselftestbot",
410
+ && String(addGlobals.TELEGRAM_DEFAULT_BOT_KEY || "") === "serverprotocolmonitorbot",
374
411
  `default=${String(addGlobals.TELEGRAM_DEFAULT_BOT_KEY || "")} token=${String(addState.TELEGRAM_BOT_TOKEN || "")} server_name=${String(addState.TELEGRAM_BOT_SERVER_NAME || "")} roles=${String(addState.TELEGRAM_BOT_SERVER_ROLES || "")} role=${String(addState.TELEGRAM_BOT_ROLE_PROFILE || "")} client=${String(addState.TELEGRAM_BOT_AI_CLIENT || "")}`,
375
412
  );
376
413
 
377
414
  const groupedMock = await createMockServer({
378
415
  serverBots: [
379
- { id: "977ef999-c40b-4cf6-a142-ade246f1b9cf", name: "RyoAI_bot", provider: "telegram", bot_role: "approval", is_active: true },
380
- { id: "d74b57de-a635-4bac-a20f-f8ada188cf97", name: "RyoAI_bot", provider: "telegram", bot_role: "worker", is_active: true },
381
- { id: "bbae9450-4236-487c-9a05-61dc4a626215", name: "RyoAI_bot", provider: "telegram", bot_role: "review", is_active: true },
382
- { id: "353896c5-3b6b-422f-9eeb-5dc9354f77b3", name: "RyoAI_bot", provider: "telegram", bot_role: "monitor", is_active: true },
416
+ { id: "977ef999-c40b-4cf6-a142-ade246f1b9cf", name: "GroupedServerProtocolBot", provider: "telegram", bot_role: "approval", is_active: true },
417
+ { id: "d74b57de-a635-4bac-a20f-f8ada188cf97", name: "GroupedServerProtocolBot", provider: "telegram", bot_role: "worker", is_active: true },
418
+ { id: "bbae9450-4236-487c-9a05-61dc4a626215", name: "GroupedServerProtocolBot", provider: "telegram", bot_role: "review", is_active: true },
419
+ { id: "353896c5-3b6b-422f-9eeb-5dc9354f77b3", name: "GroupedServerProtocolBot", provider: "telegram", bot_role: "monitor", is_active: true },
383
420
  ],
384
- telegramUsersByToken: [["selftest-group-token", "RyoAI_bot"]],
421
+ telegramUsersByToken: [["selftest-group-token", "GroupedServerProtocolBot"]],
385
422
  }).listen();
386
423
  try {
387
424
  await runCLI({
@@ -410,11 +447,11 @@ export async function runSelftestBotCommands(push, deps) {
410
447
  ]),
411
448
  },
412
449
  });
413
- const groupedState = readTelegramBotEntry("ryoai_bot");
450
+ const groupedState = readTelegramBotEntry("groupedserverprotocolbot");
414
451
  push(
415
452
  "bot_add_guided_autoresolves_server_bot_group_without_role_prompt",
416
453
  String(groupedState.TELEGRAM_BOT_SERVER_BOT_ID || "") === ""
417
- && String(groupedState.TELEGRAM_BOT_SERVER_NAME || "") === "RyoAI_bot"
454
+ && String(groupedState.TELEGRAM_BOT_SERVER_NAME || "") === "GroupedServerProtocolBot"
418
455
  && String(groupedState.TELEGRAM_BOT_SERVER_ROLES || "") === "monitor,review,worker,approval"
419
456
  && String(groupedState.TELEGRAM_BOT_ROLE_PROFILE || "") === ""
420
457
  && String(groupedState.TELEGRAM_BOT_AI_CLIENT || "") === ""
@@ -433,13 +470,13 @@ export async function runSelftestBotCommands(push, deps) {
433
470
  ...env,
434
471
  METHEUS_SCRIPTED_PROMPT_ANSWERS: JSON.stringify([
435
472
  "1", // provider: telegram
436
- "2", // bot entry: ryoai_bot
473
+ "1", // bot entry: GroupedServerProtocolBot
437
474
  "1", // keep token
438
475
  "2", // grouped role settings: edit one role
439
476
  "3", // select role to edit: worker
440
477
  "2", // worker: edit settings
441
478
  "3", // worker AI client: claude
442
- "3", // worker AI model: Sonnet 4.6r
479
+ "2", // worker AI model: Sonnet 4.6r
443
480
  "4", // worker permission: danger_full_access
444
481
  "4", // worker reasoning: high
445
482
  "y", // edit another role
@@ -462,15 +499,16 @@ export async function runSelftestBotCommands(push, deps) {
462
499
  );
463
500
  const groupedRunnerConfigPath = path.join(tempHome, ".metheus", "bot-runner.json");
464
501
  const groupedRunnerConfig = readJSON(fs.readFileSync(groupedRunnerConfigPath, "utf8"));
502
+ const groupedRoleProfiles = safeObject(groupedRunnerConfig.role_profiles || groupedRunnerConfig.roleProfiles);
465
503
  push(
466
504
  "bot_edit_grouped_server_roles_updates_role_profiles",
467
- String(safeObject(safeObject(groupedRunnerConfig.role_profiles || {}).worker).client || "") === "claude"
468
- && String(safeObject(safeObject(groupedRunnerConfig.role_profiles || {}).worker).model || "") === "Sonnet 4.6r"
469
- && String(safeObject(safeObject(groupedRunnerConfig.role_profiles || {}).worker).permission_mode || "") === "danger_full_access"
470
- && String(safeObject(safeObject(groupedRunnerConfig.role_profiles || {}).worker).reasoning_effort || "") === "high"
471
- && String(safeObject(safeObject(groupedRunnerConfig.role_profiles || {}).approval).client || "") === "gemini"
472
- && String(safeObject(safeObject(groupedRunnerConfig.role_profiles || {}).approval).model || "") === "gemini-3.1-pro",
473
- `worker=${JSON.stringify(safeObject(safeObject(groupedRunnerConfig.role_profiles || {}).worker))} approval=${JSON.stringify(safeObject(safeObject(groupedRunnerConfig.role_profiles || {}).approval))}`,
505
+ String(safeObject(safeObject(groupedRoleProfiles).worker).client || "") === "claude"
506
+ && String(safeObject(safeObject(groupedRoleProfiles).worker).model || "") === "Sonnet 4.6r"
507
+ && String(safeObject(safeObject(groupedRoleProfiles).worker).permission_mode || "") === "danger_full_access"
508
+ && String(safeObject(safeObject(groupedRoleProfiles).worker).reasoning_effort || "") === "high"
509
+ && String(safeObject(safeObject(groupedRoleProfiles).approval).client || "") === "gemini"
510
+ && String(safeObject(safeObject(groupedRoleProfiles).approval).model || "") === "gemini-3.1-pro",
511
+ `worker=${JSON.stringify(safeObject(safeObject(groupedRoleProfiles).worker))} approval=${JSON.stringify(safeObject(safeObject(groupedRoleProfiles).approval))}`,
474
512
  );
475
513
 
476
514
  const groupedShowResult = await runCLI({
@@ -478,7 +516,7 @@ export async function runSelftestBotCommands(push, deps) {
478
516
  args: [
479
517
  "bot", "show",
480
518
  "--provider", "telegram",
481
- "--bot-key", "ryoai_bot",
519
+ "--bot-key", "groupedserverprotocolbot",
482
520
  "--base-url", `http://127.0.0.1:${groupedMock.port}`,
483
521
  "--json", "true",
484
522
  ],
@@ -512,7 +550,7 @@ export async function runSelftestBotCommands(push, deps) {
512
550
  args: [
513
551
  "bot", "show",
514
552
  "--provider", "telegram",
515
- "--bot-key", "monitorselftestbot",
553
+ "--bot-key", "serverprotocolmonitorbot",
516
554
  "--base-url", baseURL,
517
555
  "--json", "true",
518
556
  ],
@@ -543,7 +581,7 @@ export async function runSelftestBotCommands(push, deps) {
543
581
  args: [
544
582
  "bot", "show",
545
583
  "--provider", "telegram",
546
- "--bot-key", "monitorselftestbot",
584
+ "--bot-key", "serverprotocolmonitorbot",
547
585
  "--base-url", baseURL,
548
586
  "--json", "true",
549
587
  ],
@@ -552,7 +590,7 @@ export async function runSelftestBotCommands(push, deps) {
552
590
  const showWithRoutesPayload = readJSON(showWithRoutesResult.stdout);
553
591
  push(
554
592
  "bot_show_returns_selected_telegram_entry",
555
- safeObject(showPayload.entry).key === "monitorselftestbot"
593
+ safeObject(showPayload.entry).key === "serverprotocolmonitorbot"
556
594
  && safeObject(showPayload.entry).client === "gpt"
557
595
  && safeObject(showPayload.serverBinding).mode === "single",
558
596
  `key=${String(safeObject(showPayload.entry).key || "")} client=${String(safeObject(showPayload.entry).client || "")} mode=${String(safeObject(showPayload.serverBinding).mode || "")}`,
@@ -574,12 +612,12 @@ export async function runSelftestBotCommands(push, deps) {
574
612
  ...env,
575
613
  METHEUS_SCRIPTED_PROMPT_ANSWERS: JSON.stringify([
576
614
  "1", // provider: telegram
577
- "1", // bot entry: @monitorselftestbot
615
+ "2", // bot entry: @serverprotocolmonitorbot
578
616
  "1", // keep token
579
617
  "2", // change AI client
580
618
  "4", // gemini
581
619
  "2", // change AI model
582
- "3", // gemini model: gemini-3.1-pro
620
+ "2", // gemini model: gemini-3.1-pro
583
621
  "2", // change permission mode
584
622
  "3", // workspace_write
585
623
  "2", // change reasoning effort
@@ -594,7 +632,7 @@ export async function runSelftestBotCommands(push, deps) {
594
632
  !String(guidedEditResult.stdout || "").includes("Telegram username (without @)"),
595
633
  String(guidedEditResult.stdout || "").split(/\r?\n/).filter((line) => line.includes("Telegram username")).join(" | ") || "username prompt skipped",
596
634
  );
597
- const guidedState = readTelegramBotEntry("monitorselftestbot");
635
+ const guidedState = readTelegramBotEntry("serverprotocolmonitorbot");
598
636
  push(
599
637
  "bot_edit_guided_prompts_update_ai_binding_fields",
600
638
  String(guidedState.TELEGRAM_BOT_AI_CLIENT || "") === "gemini"
@@ -609,7 +647,7 @@ export async function runSelftestBotCommands(push, deps) {
609
647
  args: [
610
648
  "bot", "edit",
611
649
  "--provider", "telegram",
612
- "--bot-key", "monitorselftestbot",
650
+ "--bot-key", "serverprotocolmonitorbot",
613
651
  "--non-interactive", "true",
614
652
  "--token", "selftest-edited-token",
615
653
  "--client", "claude",
@@ -619,7 +657,7 @@ export async function runSelftestBotCommands(push, deps) {
619
657
  ],
620
658
  env,
621
659
  });
622
- const editedState = readTelegramBotEntry("monitorselftestbot");
660
+ const editedState = readTelegramBotEntry("serverprotocolmonitorbot");
623
661
  push(
624
662
  "bot_edit_updates_ai_binding_fields",
625
663
  String(editedState.TELEGRAM_BOT_TOKEN || "") === "selftest-edited-token"
@@ -637,7 +675,7 @@ export async function runSelftestBotCommands(push, deps) {
637
675
  ...env,
638
676
  METHEUS_SCRIPTED_PROMPT_ANSWERS: JSON.stringify([
639
677
  "1", // provider: telegram
640
- "1", // bot entry: @monitorselftestbot
678
+ "2", // bot entry: @serverprotocolmonitorbot
641
679
  "1", // confirm set default
642
680
  ]),
643
681
  },
@@ -645,7 +683,7 @@ export async function runSelftestBotCommands(push, deps) {
645
683
  const guidedDefaultState = readTelegramGlobals();
646
684
  push(
647
685
  "bot_set_default_guided_selects_entry",
648
- String(guidedDefaultState.TELEGRAM_DEFAULT_BOT_KEY || "") === "monitorselftestbot",
686
+ String(guidedDefaultState.TELEGRAM_DEFAULT_BOT_KEY || "") === "serverprotocolmonitorbot",
649
687
  `default=${String(guidedDefaultState.TELEGRAM_DEFAULT_BOT_KEY || "")}`,
650
688
  );
651
689
 
@@ -660,7 +698,7 @@ export async function runSelftestBotCommands(push, deps) {
660
698
  ...env,
661
699
  METHEUS_SCRIPTED_PROMPT_ANSWERS: JSON.stringify([
662
700
  "1", // provider: telegram
663
- "1", // bot entry: @monitorselftestbot
701
+ "2", // bot entry: @serverprotocolmonitorbot
664
702
  "2", // output format: json
665
703
  ]),
666
704
  },
@@ -680,7 +718,7 @@ export async function runSelftestBotCommands(push, deps) {
680
718
  args: [
681
719
  "bot", "set-default",
682
720
  "--provider", "telegram",
683
- "--bot-key", "monitorselftestbot",
721
+ "--bot-key", "serverprotocolmonitorbot",
684
722
  "--non-interactive", "true",
685
723
  ],
686
724
  env,
@@ -688,7 +726,7 @@ export async function runSelftestBotCommands(push, deps) {
688
726
  const defaultState = readTelegramGlobals();
689
727
  push(
690
728
  "bot_set_default_updates_default_bot_key",
691
- String(defaultState.TELEGRAM_DEFAULT_BOT_KEY || "") === "monitorselftestbot",
729
+ String(defaultState.TELEGRAM_DEFAULT_BOT_KEY || "") === "serverprotocolmonitorbot",
692
730
  `default=${String(defaultState.TELEGRAM_DEFAULT_BOT_KEY || "")}`,
693
731
  );
694
732
 
@@ -697,7 +735,7 @@ export async function runSelftestBotCommands(push, deps) {
697
735
  args: [
698
736
  "bot", "verify",
699
737
  "--provider", "telegram",
700
- "--bot-key", "monitorselftestbot",
738
+ "--bot-key", "serverprotocolmonitorbot",
701
739
  "--base-url", baseURL,
702
740
  "--timeout-seconds", "5",
703
741
  "--json", "true",
@@ -727,7 +765,7 @@ export async function runSelftestBotCommands(push, deps) {
727
765
  args: [
728
766
  "bot", "remove",
729
767
  "--provider", "telegram",
730
- "--bot-key", "monitorselftestbot",
768
+ "--bot-key", "serverprotocolmonitorbot",
731
769
  "--non-interactive", "true",
732
770
  ],
733
771
  env,
@@ -745,7 +783,7 @@ export async function runSelftestBotCommands(push, deps) {
745
783
  const telegramEntry = safeObject(listPayload[0]);
746
784
  push(
747
785
  "bot_remove_deletes_named_telegram_entry",
748
- !ensureArray(telegramEntry.entries).some((entry) => String(safeObject(entry).key || "") === "monitorselftestbot"),
786
+ !ensureArray(telegramEntry.entries).some((entry) => String(safeObject(entry).key || "") === "serverprotocolmonitorbot"),
749
787
  `entries=${String(ensureArray(telegramEntry.entries).length)}`,
750
788
  );
751
789
 
@@ -767,11 +805,11 @@ export async function runSelftestBotCommands(push, deps) {
767
805
  ],
768
806
  env,
769
807
  });
770
- const aliasAddState = readTelegramBotEntry("monitorselftestbot");
808
+ const aliasAddState = readTelegramBotEntry("serverprotocolmonitorbot");
771
809
  push(
772
810
  "bot_add_accepts_ai_prefixed_option_aliases",
773
811
  String(aliasAddState.TELEGRAM_BOT_SERVER_BOT_ID || "") === mock.bots[0].id
774
- && String(aliasAddState.TELEGRAM_BOT_SERVER_NAME || "") === "MonitorSelftestBot"
812
+ && String(aliasAddState.TELEGRAM_BOT_SERVER_NAME || "") === "ServerProtocolMonitorBot"
775
813
  && String(aliasAddState.TELEGRAM_BOT_SERVER_ROLES || "") === "monitor"
776
814
  && String(aliasAddState.TELEGRAM_BOT_AI_CLIENT || "") === "gpt"
777
815
  && String(aliasAddState.TELEGRAM_BOT_AI_MODEL || "") === "gpt-5.4"
@@ -792,7 +830,7 @@ export async function runSelftestBotCommands(push, deps) {
792
830
  const guidedRemoveBeforePayload = ensureArray(readJSON(guidedRemoveBeforeList.stdout));
793
831
  const guidedRemoveBeforeEntry = safeObject(guidedRemoveBeforePayload[0]);
794
832
  const guidedRemoveEntries = ensureArray(guidedRemoveBeforeEntry.entries);
795
- const explicitEntryIndex = guidedRemoveEntries.findIndex((entry) => String(safeObject(entry).key || "") === "monitorselftestbot");
833
+ const explicitEntryIndex = guidedRemoveEntries.findIndex((entry) => String(safeObject(entry).key || "") === "serverprotocolmonitorbot");
796
834
 
797
835
  await runCLI({
798
836
  cliPath,
@@ -838,7 +876,7 @@ export async function runSelftestBotCommands(push, deps) {
838
876
  "--provider", "telegram",
839
877
  "--bot-key", "legacy_main",
840
878
  "--bot-id", mock.bots[0].id,
841
- "--bot-name", "MonitorSelftestBot",
879
+ "--bot-name", "ServerProtocolMonitorBot",
842
880
  "--role-profile", "monitor",
843
881
  "--client", "codex",
844
882
  "--permission-mode", "read_only",
@@ -186,9 +186,9 @@ export async function runSelftestRunnerScenarios(push, deps) {
186
186
  project_id: selftestProjectID,
187
187
  provider: "telegram",
188
188
  role: "monitor",
189
- bot_name: "RyoAI_bot",
189
+ bot_name: "ServerProtocolMonitorBot",
190
190
  }),
191
- { name: "RyoAI_bot", role: "review" },
191
+ { name: "ServerProtocolMonitorBot", role: "review" },
192
192
  normalizeBotRunnerConfigContents(
193
193
  {
194
194
  version: 2,
@@ -211,7 +211,7 @@ export async function runSelftestRunnerScenarios(push, deps) {
211
211
  },
212
212
  },
213
213
  bot_bindings: {
214
- RyoAI_bot: {
214
+ ServerProtocolMonitorBot: {
215
215
  role_profile: "approval",
216
216
  },
217
217
  },
@@ -237,9 +237,9 @@ export async function runSelftestRunnerScenarios(push, deps) {
237
237
  project_id: selftestProjectID,
238
238
  provider: "telegram",
239
239
  role: "monitor",
240
- bot_name: "RyoAI_bot",
240
+ bot_name: "ServerProtocolMonitorBot",
241
241
  }),
242
- { name: "RyoAI_bot", role: "monitor" },
242
+ { name: "ServerProtocolMonitorBot", role: "monitor" },
243
243
  normalizeBotRunnerConfigContents(
244
244
  {
245
245
  version: 2,
@@ -257,7 +257,7 @@ export async function runSelftestRunnerScenarios(push, deps) {
257
257
  },
258
258
  },
259
259
  bot_bindings: {
260
- RyoAI_bot: {
260
+ ServerProtocolMonitorBot: {
261
261
  role_profile: "monitor",
262
262
  client: "claude",
263
263
  reasoning_effort: "high",
@@ -435,7 +435,7 @@ export async function runSelftestRunnerScenarios(push, deps) {
435
435
  provider: "telegram",
436
436
  role: "monitor",
437
437
  role_profile: "monitor",
438
- bot_name: "RyoAI_bot",
438
+ bot_name: "ServerProtocolMonitorBot",
439
439
  destination_label: "AI incubating CHAT ROOM",
440
440
  archive_work_item_id: "304ce77a-7032-421c-aeda-bc54daf088dd",
441
441
  },
@@ -451,7 +451,7 @@ export async function runSelftestRunnerScenarios(push, deps) {
451
451
  provider: "telegram",
452
452
  role: "monitor",
453
453
  "role-profile": "monitor",
454
- "bot-name": "RyoAI_bot",
454
+ "bot-name": "ServerProtocolMonitorBot",
455
455
  "dry-run-delivery": true,
456
456
  },
457
457
  "once",
@@ -463,7 +463,7 @@ export async function runSelftestRunnerScenarios(push, deps) {
463
463
  && resolvedRunnerRoute.name === "telegram-monitor"
464
464
  && resolvedRunnerRoute.destinationLabel === "AI incubating CHAT ROOM"
465
465
  && resolvedRunnerRoute.dryRunDelivery === true
466
- && runnerRouteKey(resolvedRunnerRoute) === "telegram-monitor::11111111-1111-1111-1111-111111111111::telegram::monitor::RyoAI_bot::AI incubating CHAT ROOM",
466
+ && runnerRouteKey(resolvedRunnerRoute) === "telegram-monitor::11111111-1111-1111-1111-111111111111::telegram::monitor::ServerProtocolMonitorBot::AI incubating CHAT ROOM",
467
467
  `name=${resolvedRunnerRoute.name || "(none)"} destination=${resolvedRunnerRoute.destinationLabel || "(none)"} key=${runnerRouteKey(resolvedRunnerRoute)}`,
468
468
  );
469
469
  } catch (err) {
@@ -515,7 +515,7 @@ export async function runSelftestRunnerScenarios(push, deps) {
515
515
  provider: "telegram",
516
516
  role: "monitor",
517
517
  role_profile: "monitor",
518
- bot_name: "RyoAI_bot",
518
+ bot_name: "ServerProtocolMonitorBot",
519
519
  destination_label: "AI incubating CHAT ROOM",
520
520
  },
521
521
  ],
@@ -527,7 +527,7 @@ export async function runSelftestRunnerScenarios(push, deps) {
527
527
  `${JSON.stringify({
528
528
  version: 1,
529
529
  routes: {
530
- "-::11111111-1111-1111-1111-111111111111::telegram::monitor::RyoAI_bot::-": {
530
+ "-::11111111-1111-1111-1111-111111111111::telegram::monitor::ServerProtocolMonitorBot::-": {
531
531
  last_processed_comment_id: "comment-a",
532
532
  last_processed_created_at: "2026-03-13T06:00:00.000Z",
533
533
  last_source_message_id: 41,
@@ -543,7 +543,7 @@ export async function runSelftestRunnerScenarios(push, deps) {
543
543
  process.env.HOME = migrationHome;
544
544
  process.env.USERPROFILE = migrationHome;
545
545
  const migratedRunnerState = loadBotRunnerState();
546
- const canonicalRunnerStateKey = "telegram-monitor::11111111-1111-1111-1111-111111111111::telegram::monitor::RyoAI_bot::AI incubating CHAT ROOM";
546
+ const canonicalRunnerStateKey = "telegram-monitor::11111111-1111-1111-1111-111111111111::telegram::monitor::ServerProtocolMonitorBot::AI incubating CHAT ROOM";
547
547
  const persistedRunnerState = tryJsonParse(
548
548
  fs.readFileSync(path.join(migrationMetheusDir, "bot-runner-state.json"), "utf8"),
549
549
  );
@@ -552,7 +552,7 @@ export async function runSelftestRunnerScenarios(push, deps) {
552
552
  migratedRunnerState.migrated === true
553
553
  && migratedRunnerState.migratedKeys.length === 1
554
554
  && Boolean(migratedRunnerState.routes[canonicalRunnerStateKey])
555
- && !migratedRunnerState.routes["-::11111111-1111-1111-1111-111111111111::telegram::monitor::RyoAI_bot::-"]
555
+ && !migratedRunnerState.routes["-::11111111-1111-1111-1111-111111111111::telegram::monitor::ServerProtocolMonitorBot::-"]
556
556
  && Boolean(safeObject(persistedRunnerState.routes)[canonicalRunnerStateKey]),
557
557
  `migrated=${String(migratedRunnerState.migrated)} key=${canonicalRunnerStateKey}`,
558
558
  );
@@ -619,7 +619,7 @@ export async function runSelftestRunnerScenarios(push, deps) {
619
619
  },
620
620
  },
621
621
  mentionOnlyRoute,
622
- { name: "RyoAI_bot", role: "monitor" },
622
+ { name: "ServerProtocolMonitorBot", role: "monitor" },
623
623
  );
624
624
  push(
625
625
  "telegram_trigger_mentions_only_skips_unmentioned_group_message",
@@ -634,13 +634,13 @@ export async function runSelftestRunnerScenarios(push, deps) {
634
634
  kind: "telegram_message",
635
635
  chatID: "-100123",
636
636
  chatType: "supergroup",
637
- body: "hello @RyoAI_bot",
637
+ body: "hello @ServerProtocolMonitorBot",
638
638
  mentionUsernames: ["ryoai_bot"],
639
639
  replyToSenderIsBot: false,
640
640
  },
641
641
  },
642
642
  mentionOnlyRoute,
643
- { name: "RyoAI_bot", role: "monitor" },
643
+ { name: "ServerProtocolMonitorBot", role: "monitor" },
644
644
  );
645
645
  push(
646
646
  "telegram_trigger_mentions_only_accepts_bot_mention",
@@ -650,7 +650,7 @@ export async function runSelftestRunnerScenarios(push, deps) {
650
650
 
651
651
  const botReplyArchiveComment = formatBotReplyArchiveComment({
652
652
  provider: "telegram",
653
- bot: { id: "bot-1", name: "RyoAI_bot", role: "monitor" },
653
+ bot: { id: "bot-1", name: "ServerProtocolMonitorBot", role: "monitor" },
654
654
  destination: { chatID: "-100123", label: "Main Room" },
655
655
  replyText: "hello",
656
656
  messageID: 4321,
@@ -234,7 +234,7 @@ export async function runSelftestTelegramE2E(push, deps) {
234
234
  const e2eTelegramToken = "123456:test-token";
235
235
  const e2eBot = {
236
236
  id: "55555555-5555-4555-8555-555555555555",
237
- name: "RyoAI_bot",
237
+ name: "ServerProtocolMonitorBot",
238
238
  role: "monitor",
239
239
  };
240
240
  const e2eDestination = {
@@ -356,7 +356,7 @@ export async function runSelftestTelegramE2E(push, deps) {
356
356
  && e2eResult.execution_mode === "role_profile"
357
357
  && e2eResult.role_profile === "monitor"
358
358
  && telegramE2EServer.state.sentMessages.length === 1
359
- && String(telegramE2EServer.state.sentMessages[0]?.text || "").trim() === "Acknowledged: hello @RyoAI_bot"
359
+ && String(telegramE2EServer.state.sentMessages[0]?.text || "").trim() === "Acknowledged: hello @ServerProtocolMonitorBot"
360
360
  && intFromRawAllowZero(telegramE2EServer.state.sentMessages[0]?.reply_to_message_id, 0) === 41
361
361
  && telegramE2EServer.state.chatActions.length >= 1
362
362
  && Boolean(mirroredReply)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metheus-governance-mcp-cli",
3
- "version": "0.2.105",
3
+ "version": "0.2.107",
4
4
  "description": "Metheus Governance MCP CLI (setup + stdio proxy)",
5
5
  "type": "module",
6
6
  "files": [