metheus-governance-mcp-cli 0.2.112 → 0.2.114
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 +19 -1
- package/cli.mjs +770 -5
- package/lib/bot-commands.mjs +19 -37
- package/lib/selftest-bot-commands.mjs +4 -12
- package/lib/selftest-runner-scenarios.mjs +49 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -305,7 +305,7 @@ For direct Telegram adds, the CLI derives the local file name from the matched s
|
|
|
305
305
|
|
|
306
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
|
-
For runner commands,
|
|
308
|
+
For runner commands, routes are the executable unit. Use `--route-name` as the primary selector. `--bot-name` and `--bot-id` are convenience route aliases only when they identify one enabled route uniquely. Use `runner list` first if you are not sure which route belongs to which server bot, and use `runner show` to inspect the resolved route, server identity, workspace mapping, and execution profile together.
|
|
309
309
|
|
|
310
310
|
Current support status:
|
|
311
311
|
|
|
@@ -388,10 +388,20 @@ Commands:
|
|
|
388
388
|
|
|
389
389
|
```bash
|
|
390
390
|
metheus-governance-mcp-cli runner list
|
|
391
|
+
metheus-governance-mcp-cli runner route add
|
|
392
|
+
metheus-governance-mcp-cli runner route edit --route-name telegram-monitor
|
|
393
|
+
metheus-governance-mcp-cli runner route remove --route-name telegram-monitor
|
|
394
|
+
metheus-governance-mcp-cli runner show --route-name telegram-monitor
|
|
391
395
|
metheus-governance-mcp-cli runner once --route-name telegram-monitor
|
|
392
396
|
metheus-governance-mcp-cli runner start --route-name telegram-monitor
|
|
393
397
|
```
|
|
394
398
|
|
|
399
|
+
Route management:
|
|
400
|
+
- `runner route add` creates one executable route by selecting the project, provider, role, server bot, and project chat destination in order.
|
|
401
|
+
- `runner route edit` updates one stored route. Use it when the destination, role, or server bot for an existing route changes.
|
|
402
|
+
- `runner route remove` deletes one stored route from `~/.metheus/bot-runner.json`.
|
|
403
|
+
- `runner list` and `runner route list` are equivalent. They show which routes are executable and how `--bot-name` would resolve.
|
|
404
|
+
|
|
395
405
|
Recommended operational path:
|
|
396
406
|
|
|
397
407
|
```bash
|
|
@@ -399,9 +409,17 @@ metheus-governance-mcp-cli runner once --route-name telegram-monitor --dry-run-d
|
|
|
399
409
|
metheus-governance-mcp-cli runner start --route-name telegram-monitor
|
|
400
410
|
```
|
|
401
411
|
|
|
412
|
+
What `runner list` means:
|
|
413
|
+
- `server_bot_name` may come from `route.server_bot_name` or be resolved from the Telegram bot env file by `server_bot_id`
|
|
414
|
+
- `server_bot_name_source=route_config` means the route file already stores the name directly
|
|
415
|
+
- `server_bot_name_source=telegram_env_lookup` means the CLI resolved the name from the local Telegram bot file
|
|
416
|
+
- `route_alias_by_server_bot_name` is a convenience selector, not a separate execution target
|
|
417
|
+
- `run_once_by_server_bot_name` in `runner show` or docs means "find the one enabled route that matches this server bot name, then run that route"
|
|
418
|
+
|
|
402
419
|
Debug/selection overrides:
|
|
403
420
|
|
|
404
421
|
```bash
|
|
422
|
+
metheus-governance-mcp-cli runner show --bot-name <server_bot_name>
|
|
405
423
|
metheus-governance-mcp-cli runner once --project-id <project_uuid> --provider telegram --role monitor
|
|
406
424
|
metheus-governance-mcp-cli runner start --project-id <project_uuid> --provider telegram --role monitor --poll-interval-ms 5000
|
|
407
425
|
metheus-governance-mcp-cli runner once --project-id <project_uuid> --provider telegram --role monitor --role-profile review
|
package/cli.mjs
CHANGED
|
@@ -91,7 +91,15 @@ import {
|
|
|
91
91
|
writeSelftestReport,
|
|
92
92
|
} from "./lib/selftest-support.mjs";
|
|
93
93
|
import { runSelftestBotCommands } from "./lib/selftest-bot-commands.mjs";
|
|
94
|
-
import {
|
|
94
|
+
import {
|
|
95
|
+
createPrompter,
|
|
96
|
+
promptChoice,
|
|
97
|
+
promptConfirmChoice,
|
|
98
|
+
promptLine,
|
|
99
|
+
promptRequiredLine,
|
|
100
|
+
shouldRenderPromptChrome,
|
|
101
|
+
runBotCommand,
|
|
102
|
+
} from "./lib/bot-commands.mjs";
|
|
95
103
|
import { handleLocalBotMessageToolCall as handleLocalBotMessageToolCallImpl } from "./lib/local-tool-shims.mjs";
|
|
96
104
|
import { handleLocalProjectToolDispatch as handleLocalProjectToolDispatchImpl } from "./lib/local-project-dispatch.mjs";
|
|
97
105
|
import {
|
|
@@ -255,8 +263,13 @@ function printUsage() {
|
|
|
255
263
|
` ${cmd} selftest [--json <true|false>]`,
|
|
256
264
|
` ${cmd} local-bot-bridge [--client <gpt|claude|gemini|sample>] [--cwd <path>] [--model <name>] [--permission-mode <read_only|workspace_write|danger_full_access>] [--reasoning-effort <low|medium|high>]`,
|
|
257
265
|
` ${cmd} runner list [--json <true|false>]`,
|
|
258
|
-
` ${cmd} runner
|
|
259
|
-
` ${cmd} runner
|
|
266
|
+
` ${cmd} runner route list [--json <true|false>]`,
|
|
267
|
+
` ${cmd} runner route add [--project-id <uuid>] [--provider <telegram|slack|kakaotalk>] [--role <monitor|review|worker|approval>] [--bot-name <server_name> | --bot-id <uuid>] [--destination-label <label> | --destination-id <uuid>] [--poll-interval-ms <n>] [--enabled <true|false>]`,
|
|
268
|
+
` ${cmd} runner route edit [--route-name <name> | --bot-name <server_name> | --bot-id <uuid>]`,
|
|
269
|
+
` ${cmd} runner route remove [--route-name <name> | --bot-name <server_name> | --bot-id <uuid>]`,
|
|
270
|
+
` ${cmd} runner show [--route-name <name> | --bot-name <server_name> | --bot-id <uuid>] [--json <true|false>]`,
|
|
271
|
+
` ${cmd} runner once [--route-name <name> | --bot-name <server_name when one enabled route matches> | --bot-id <uuid when one enabled route matches>] [--project-id <uuid>] [--provider <telegram|slack|kakaotalk>] [--role <monitor|review|worker|approval>] [--role-profile <name>] [--mentions-only <true|false>] [--reply-to-bot-messages <true|false>] [--direct-messages <true|false>] [--ignore-edited-messages <true|false>] [--dry-run-delivery <true|false>] [--context-comments <n>] [--archive-replies <true|false>]`,
|
|
272
|
+
` ${cmd} runner start [--route-name <name> | --bot-name <server_name when one enabled route matches> | --bot-id <uuid when one enabled route matches>] [--project-id <uuid>] [--provider <telegram|slack|kakaotalk>] [--role <monitor|review|worker|approval>] [--role-profile <name>] [--mentions-only <true|false>] [--reply-to-bot-messages <true|false>] [--direct-messages <true|false>] [--ignore-edited-messages <true|false>] [--dry-run-delivery <true|false>] [--poll-interval-ms <n>] [--context-comments <n>] [--archive-replies <true|false>]`,
|
|
260
273
|
` ${cmd} ctxpack pull [--project-id <uuid>] [--base-url <url>] [--workspace-dir <path|auto>] [--paths <csv>] [--timeout-seconds <n>]`,
|
|
261
274
|
` ${cmd} auth status`,
|
|
262
275
|
` ${cmd} auth login [--base-url <url>] [--flow <auto|device|callback|manual>] [--keycloak-url <url>] [--realm <name>] [--client-id <id>] [--open-browser <true|false>] [--callback-port <n>] [--timeout-seconds <n>] [--manual <true|false>]`,
|
|
@@ -2632,6 +2645,11 @@ function buildRunnerRouteListRows() {
|
|
|
2632
2645
|
);
|
|
2633
2646
|
})
|
|
2634
2647
|
: null;
|
|
2648
|
+
const botNameSource = route.botName
|
|
2649
|
+
? "route_config"
|
|
2650
|
+
: matchedTelegramEntry?.serverBotName
|
|
2651
|
+
? "telegram_env_lookup"
|
|
2652
|
+
: "unresolved";
|
|
2635
2653
|
const resolvedBotName = firstNonEmptyString([
|
|
2636
2654
|
route.botName,
|
|
2637
2655
|
matchedTelegramEntry?.serverBotName,
|
|
@@ -2644,6 +2662,7 @@ function buildRunnerRouteListRows() {
|
|
|
2644
2662
|
provider: route.provider || "-",
|
|
2645
2663
|
projectID: route.projectID || "-",
|
|
2646
2664
|
botName: resolvedBotName,
|
|
2665
|
+
botNameSource,
|
|
2647
2666
|
botID: route.botID || "-",
|
|
2648
2667
|
role: route.role || "-",
|
|
2649
2668
|
roleProfile: route.roleProfile || "-",
|
|
@@ -2654,6 +2673,595 @@ function buildRunnerRouteListRows() {
|
|
|
2654
2673
|
});
|
|
2655
2674
|
}
|
|
2656
2675
|
|
|
2676
|
+
function slugifyRunnerRouteSegment(rawValue) {
|
|
2677
|
+
return String(rawValue || "")
|
|
2678
|
+
.trim()
|
|
2679
|
+
.toLowerCase()
|
|
2680
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
2681
|
+
.replace(/^-+|-+$/g, "");
|
|
2682
|
+
}
|
|
2683
|
+
|
|
2684
|
+
function buildRunnerRouteNameSuggestion({ provider = "", role = "", botName = "" }, existingNames = []) {
|
|
2685
|
+
const segments = [
|
|
2686
|
+
slugifyRunnerRouteSegment(provider),
|
|
2687
|
+
slugifyRunnerRouteSegment(role),
|
|
2688
|
+
slugifyRunnerRouteSegment(botName),
|
|
2689
|
+
].filter(Boolean);
|
|
2690
|
+
const base = segments.join("-") || "runner-route";
|
|
2691
|
+
const existing = new Set(ensureArray(existingNames).map((name) => String(name || "").trim().toLowerCase()).filter(Boolean));
|
|
2692
|
+
if (!existing.has(base)) {
|
|
2693
|
+
return base;
|
|
2694
|
+
}
|
|
2695
|
+
let suffix = 2;
|
|
2696
|
+
while (existing.has(`${base}-${suffix}`)) {
|
|
2697
|
+
suffix += 1;
|
|
2698
|
+
}
|
|
2699
|
+
return `${base}-${suffix}`;
|
|
2700
|
+
}
|
|
2701
|
+
|
|
2702
|
+
function upsertRunnerRouteConfig(config, route) {
|
|
2703
|
+
const normalizedConfig = safeObject(config);
|
|
2704
|
+
const normalizedRoute = normalizeRunnerRoute(route);
|
|
2705
|
+
const routeName = String(normalizedRoute.name || "").trim();
|
|
2706
|
+
if (!routeName) {
|
|
2707
|
+
throw new Error("route name is required");
|
|
2708
|
+
}
|
|
2709
|
+
const routes = ensureArray(normalizedConfig.routes).map((item) => normalizeRunnerRoute(item));
|
|
2710
|
+
const index = routes.findIndex((item) => String(item.name || "").trim().toLowerCase() === routeName.toLowerCase());
|
|
2711
|
+
if (index >= 0) {
|
|
2712
|
+
routes[index] = normalizedRoute;
|
|
2713
|
+
} else {
|
|
2714
|
+
routes.push(normalizedRoute);
|
|
2715
|
+
}
|
|
2716
|
+
return {
|
|
2717
|
+
...normalizedConfig,
|
|
2718
|
+
routes,
|
|
2719
|
+
};
|
|
2720
|
+
}
|
|
2721
|
+
|
|
2722
|
+
function removeRunnerRouteFromConfig(config, routeName) {
|
|
2723
|
+
const normalizedConfig = safeObject(config);
|
|
2724
|
+
const target = String(routeName || "").trim().toLowerCase();
|
|
2725
|
+
return {
|
|
2726
|
+
...normalizedConfig,
|
|
2727
|
+
routes: ensureArray(normalizedConfig.routes)
|
|
2728
|
+
.map((route) => normalizeRunnerRoute(route))
|
|
2729
|
+
.filter((route) => String(route.name || "").trim().toLowerCase() !== target),
|
|
2730
|
+
};
|
|
2731
|
+
}
|
|
2732
|
+
|
|
2733
|
+
function formatRunnerRouteChoiceLabel(route, telegramEntries = []) {
|
|
2734
|
+
const normalizedRoute = normalizeRunnerRoute(route);
|
|
2735
|
+
const resolvedName = resolveConfiguredRunnerRouteServerBotName(normalizedRoute, telegramEntries);
|
|
2736
|
+
const routeName = normalizedRoute.name || runnerRouteKey(normalizedRoute);
|
|
2737
|
+
const role = normalizedRoute.role || "-";
|
|
2738
|
+
const botName = resolvedName || normalizedRoute.botName || normalizedRoute.botID || "-";
|
|
2739
|
+
const destination = normalizedRoute.destinationLabel || normalizedRoute.destinationID || "-";
|
|
2740
|
+
return `${routeName}${normalizedRoute.enabled ? "" : " [disabled]"} - ${normalizedRoute.provider || "-"} | ${role} | ${botName} | ${destination}`;
|
|
2741
|
+
}
|
|
2742
|
+
|
|
2743
|
+
async function selectRunnerManagementRoute(ui, flags, { title = "Select runner route" } = {}) {
|
|
2744
|
+
const config = loadBotRunnerConfig({ persistIfNeeded: true });
|
|
2745
|
+
const routes = ensureArray(config.routes).map((route) => normalizeRunnerRoute(route));
|
|
2746
|
+
if (!routes.length) {
|
|
2747
|
+
throw new Error(`No runner route is configured in ${config.filePath}.`);
|
|
2748
|
+
}
|
|
2749
|
+
const selectionFilters = {
|
|
2750
|
+
"route-name": flags["route-name"],
|
|
2751
|
+
"bot-name": flags["bot-name"],
|
|
2752
|
+
"bot-id": flags["bot-id"],
|
|
2753
|
+
};
|
|
2754
|
+
const filtered = routes.filter((route) => {
|
|
2755
|
+
if (selectionFilters["route-name"] && !matchesRunnerRouteText(route.name, selectionFilters["route-name"])) return false;
|
|
2756
|
+
const candidateBotName = resolveConfiguredRunnerRouteServerBotName(route, ensureArray(readTelegramEnvState().entries));
|
|
2757
|
+
if (selectionFilters["bot-name"] && !matchesRunnerRouteText(candidateBotName, selectionFilters["bot-name"])) return false;
|
|
2758
|
+
if (selectionFilters["bot-id"] && String(route.botID || "").trim() !== String(selectionFilters["bot-id"] || "").trim()) return false;
|
|
2759
|
+
return true;
|
|
2760
|
+
});
|
|
2761
|
+
if (filtered.length === 1) {
|
|
2762
|
+
return {
|
|
2763
|
+
route: filtered[0],
|
|
2764
|
+
config,
|
|
2765
|
+
};
|
|
2766
|
+
}
|
|
2767
|
+
if (filtered.length > 1) {
|
|
2768
|
+
const matches = filtered.map((route) => route.name || runnerRouteKey(route)).join(", ");
|
|
2769
|
+
throw new Error(`Multiple runner routes matched the provided selectors. Narrow with --route-name. Matches: ${matches}`);
|
|
2770
|
+
}
|
|
2771
|
+
const telegramEntries = ensureArray(readTelegramEnvState().entries);
|
|
2772
|
+
const selected = await promptChoice(
|
|
2773
|
+
ui,
|
|
2774
|
+
title,
|
|
2775
|
+
routes.map((route) => ({
|
|
2776
|
+
value: route.name || runnerRouteKey(route),
|
|
2777
|
+
label: route.name || runnerRouteKey(route),
|
|
2778
|
+
description: formatRunnerRouteChoiceLabel(route, telegramEntries).replace(/^[^-]+ - /, ""),
|
|
2779
|
+
})),
|
|
2780
|
+
{ defaultIndex: 0 },
|
|
2781
|
+
);
|
|
2782
|
+
const selectedName = String(selected?.value || "").trim().toLowerCase();
|
|
2783
|
+
const matchedRoute = routes.find((route) => String(route.name || runnerRouteKey(route)).trim().toLowerCase() === selectedName);
|
|
2784
|
+
if (!matchedRoute) {
|
|
2785
|
+
throw new Error("selected runner route was not found");
|
|
2786
|
+
}
|
|
2787
|
+
return {
|
|
2788
|
+
route: matchedRoute,
|
|
2789
|
+
config,
|
|
2790
|
+
};
|
|
2791
|
+
}
|
|
2792
|
+
|
|
2793
|
+
async function selectRunnerRouteProvider(ui, flags, currentProvider = "") {
|
|
2794
|
+
const flagged = normalizeBotProvider(flags.provider || "");
|
|
2795
|
+
if (flagged) {
|
|
2796
|
+
return flagged;
|
|
2797
|
+
}
|
|
2798
|
+
if (currentProvider) {
|
|
2799
|
+
return currentProvider;
|
|
2800
|
+
}
|
|
2801
|
+
const options = PROVIDER_ENV_ORDER.map((provider) => ({
|
|
2802
|
+
value: provider,
|
|
2803
|
+
label: providerEnvConfig(provider).label,
|
|
2804
|
+
description: summarizeProviderSupport(provider),
|
|
2805
|
+
}));
|
|
2806
|
+
const selected = await promptChoice(ui, "Select runner route provider", options, { defaultIndex: 0 });
|
|
2807
|
+
return normalizeBotProvider(selected?.value || "telegram") || "telegram";
|
|
2808
|
+
}
|
|
2809
|
+
|
|
2810
|
+
async function resolveRunnerRouteProjectID(ui, flags, config, currentProjectID = "") {
|
|
2811
|
+
const flagged = String(flags["project-id"] || "").trim();
|
|
2812
|
+
if (flagged) {
|
|
2813
|
+
if (!isUUID(flagged)) {
|
|
2814
|
+
throw new Error("project_id must be a valid UUID");
|
|
2815
|
+
}
|
|
2816
|
+
return flagged;
|
|
2817
|
+
}
|
|
2818
|
+
if (currentProjectID && isUUID(currentProjectID)) {
|
|
2819
|
+
return currentProjectID;
|
|
2820
|
+
}
|
|
2821
|
+
const projectMappings = safeObject(config.projectMappings);
|
|
2822
|
+
const options = Object.entries(projectMappings)
|
|
2823
|
+
.filter(([projectID]) => isUUID(projectID))
|
|
2824
|
+
.map(([projectID, rawMapping]) => ({
|
|
2825
|
+
value: projectID,
|
|
2826
|
+
label: projectID,
|
|
2827
|
+
description: String(safeObject(rawMapping).workspaceDir || "").trim() || "-",
|
|
2828
|
+
}));
|
|
2829
|
+
if (options.length === 1) {
|
|
2830
|
+
return options[0].value;
|
|
2831
|
+
}
|
|
2832
|
+
if (options.length > 1) {
|
|
2833
|
+
const selected = await promptChoice(ui, "Select runner route project", options, { defaultIndex: 0 });
|
|
2834
|
+
return String(selected?.value || "").trim();
|
|
2835
|
+
}
|
|
2836
|
+
while (true) {
|
|
2837
|
+
const projectID = String(await promptRequiredLine(ui, "Project UUID", "") || "").trim();
|
|
2838
|
+
if (isUUID(projectID)) return projectID;
|
|
2839
|
+
process.stdout.write("Project UUID must be valid.\n");
|
|
2840
|
+
}
|
|
2841
|
+
}
|
|
2842
|
+
|
|
2843
|
+
async function selectRunnerRole(ui, flags, currentRole = "") {
|
|
2844
|
+
const flagged = normalizeBotRole(flags.role || "");
|
|
2845
|
+
if (flagged) {
|
|
2846
|
+
return flagged;
|
|
2847
|
+
}
|
|
2848
|
+
const roleOptions = ["monitor", "review", "worker", "approval"].map((role) => ({
|
|
2849
|
+
value: role,
|
|
2850
|
+
label: role,
|
|
2851
|
+
description: role === "monitor"
|
|
2852
|
+
? "inbound triage and monitoring"
|
|
2853
|
+
: role === "review"
|
|
2854
|
+
? "review and analysis"
|
|
2855
|
+
: role === "worker"
|
|
2856
|
+
? "workspace execution"
|
|
2857
|
+
: "final approval actions",
|
|
2858
|
+
}));
|
|
2859
|
+
const defaultIndex = Math.max(0, roleOptions.findIndex((item) => item.value === currentRole));
|
|
2860
|
+
const selected = await promptChoice(ui, "Select runner route role", roleOptions, { defaultIndex });
|
|
2861
|
+
return normalizeBotRole(selected?.value || currentRole) || "monitor";
|
|
2862
|
+
}
|
|
2863
|
+
|
|
2864
|
+
async function resolveRunnerCommandToken(baseURL, timeoutSeconds) {
|
|
2865
|
+
const resolved = await resolveAccessTokenForCommand(baseURL || DEFAULT_SITE_URL, timeoutSeconds);
|
|
2866
|
+
if (!resolved.token) {
|
|
2867
|
+
throw new Error("auth token missing; run auth login first");
|
|
2868
|
+
}
|
|
2869
|
+
return {
|
|
2870
|
+
siteBaseURL: normalizeSiteBaseURL(baseURL || DEFAULT_SITE_URL),
|
|
2871
|
+
token: resolved.token,
|
|
2872
|
+
};
|
|
2873
|
+
}
|
|
2874
|
+
|
|
2875
|
+
async function selectRunnerServerBotProfile(ui, { provider, role, flags, currentBotID = "", currentBotName = "" }) {
|
|
2876
|
+
const flaggedBotID = String(flags["bot-id"] || "").trim();
|
|
2877
|
+
const flaggedBotName = String(flags["bot-name"] || "").trim();
|
|
2878
|
+
const timeoutSeconds = intFromRaw(flags["timeout-seconds"], 15) || 15;
|
|
2879
|
+
const { siteBaseURL, token } = await resolveRunnerCommandToken(flags["base-url"], timeoutSeconds);
|
|
2880
|
+
const bots = await listUserBotsForRunner({
|
|
2881
|
+
siteBaseURL,
|
|
2882
|
+
token,
|
|
2883
|
+
timeoutSeconds,
|
|
2884
|
+
});
|
|
2885
|
+
const candidates = ensureArray(bots)
|
|
2886
|
+
.filter((bot) => bot.isActive && bot.provider === provider && bot.role === role);
|
|
2887
|
+
if (!candidates.length) {
|
|
2888
|
+
throw new Error(`No active ${providerEnvConfig(provider).label} server bot exists for role "${role}".`);
|
|
2889
|
+
}
|
|
2890
|
+
const resolveChoice = (bot) => ({
|
|
2891
|
+
name: String(bot.name || "").trim(),
|
|
2892
|
+
id: String(bot.id || "").trim(),
|
|
2893
|
+
});
|
|
2894
|
+
if (flaggedBotID) {
|
|
2895
|
+
const matched = candidates.find((bot) => bot.id === flaggedBotID);
|
|
2896
|
+
if (!matched) {
|
|
2897
|
+
throw new Error(`No active ${providerEnvConfig(provider).label} server bot matched bot-id ${flaggedBotID} for role "${role}".`);
|
|
2898
|
+
}
|
|
2899
|
+
return resolveChoice(matched);
|
|
2900
|
+
}
|
|
2901
|
+
if (flaggedBotName) {
|
|
2902
|
+
const matchedByName = candidates.filter((bot) => matchesRunnerRouteText(bot.name, flaggedBotName));
|
|
2903
|
+
if (matchedByName.length === 1) {
|
|
2904
|
+
return resolveChoice(matchedByName[0]);
|
|
2905
|
+
}
|
|
2906
|
+
if (matchedByName.length > 1) {
|
|
2907
|
+
throw new Error(`Multiple active ${providerEnvConfig(provider).label} server bots matched "${flaggedBotName}" for role "${role}". Use --bot-id instead.`);
|
|
2908
|
+
}
|
|
2909
|
+
throw new Error(`No active ${providerEnvConfig(provider).label} server bot matched "${flaggedBotName}" for role "${role}".`);
|
|
2910
|
+
}
|
|
2911
|
+
const currentIndex = Math.max(0, candidates.findIndex((bot) => (
|
|
2912
|
+
(currentBotID && bot.id === currentBotID)
|
|
2913
|
+
|| (currentBotName && matchesRunnerRouteText(bot.name, currentBotName))
|
|
2914
|
+
)));
|
|
2915
|
+
if (candidates.length === 1) {
|
|
2916
|
+
return resolveChoice(candidates[0]);
|
|
2917
|
+
}
|
|
2918
|
+
const selected = await promptChoice(
|
|
2919
|
+
ui,
|
|
2920
|
+
`Select server bot for role "${role}"`,
|
|
2921
|
+
candidates.map((bot) => ({
|
|
2922
|
+
value: bot.id,
|
|
2923
|
+
label: String(bot.name || "").trim() || bot.id,
|
|
2924
|
+
description: `server_bot_id:${bot.id}`,
|
|
2925
|
+
})),
|
|
2926
|
+
{ defaultIndex: currentIndex },
|
|
2927
|
+
);
|
|
2928
|
+
const matched = candidates.find((bot) => bot.id === String(selected?.value || "").trim());
|
|
2929
|
+
if (!matched) {
|
|
2930
|
+
throw new Error("selected server bot was not found");
|
|
2931
|
+
}
|
|
2932
|
+
return resolveChoice(matched);
|
|
2933
|
+
}
|
|
2934
|
+
|
|
2935
|
+
async function selectRunnerDestination(ui, { projectID, provider, flags, currentDestinationID = "", currentDestinationLabel = "" }) {
|
|
2936
|
+
const timeoutSeconds = intFromRaw(flags["timeout-seconds"], 15) || 15;
|
|
2937
|
+
const { siteBaseURL, token } = await resolveRunnerCommandToken(flags["base-url"], timeoutSeconds);
|
|
2938
|
+
const destinations = await listProjectChatDestinations({
|
|
2939
|
+
siteBaseURL,
|
|
2940
|
+
projectID,
|
|
2941
|
+
token,
|
|
2942
|
+
timeoutSeconds,
|
|
2943
|
+
});
|
|
2944
|
+
const candidates = ensureArray(destinations)
|
|
2945
|
+
.map((item) => normalizeChatDestination(item))
|
|
2946
|
+
.filter((item) => item.isActive && item.provider === provider);
|
|
2947
|
+
const flaggedID = String(flags["destination-id"] || "").trim();
|
|
2948
|
+
const flaggedLabel = String(flags["destination-label"] || "").trim();
|
|
2949
|
+
if (flaggedID) {
|
|
2950
|
+
const matched = candidates.find((item) => item.id === flaggedID);
|
|
2951
|
+
if (!matched) {
|
|
2952
|
+
throw new Error(`No active ${providerEnvConfig(provider).label} destination matched destination-id ${flaggedID}.`);
|
|
2953
|
+
}
|
|
2954
|
+
return { id: matched.id, label: matched.label || matched.id };
|
|
2955
|
+
}
|
|
2956
|
+
if (flaggedLabel) {
|
|
2957
|
+
const matchedByLabel = candidates.filter((item) => matchesRunnerRouteText(item.label, flaggedLabel));
|
|
2958
|
+
if (matchedByLabel.length === 1) {
|
|
2959
|
+
return { id: matchedByLabel[0].id, label: matchedByLabel[0].label || matchedByLabel[0].id };
|
|
2960
|
+
}
|
|
2961
|
+
if (matchedByLabel.length > 1) {
|
|
2962
|
+
throw new Error(`Multiple active ${providerEnvConfig(provider).label} destinations matched "${flaggedLabel}". Use --destination-id instead.`);
|
|
2963
|
+
}
|
|
2964
|
+
throw new Error(`No active ${providerEnvConfig(provider).label} destination matched "${flaggedLabel}".`);
|
|
2965
|
+
}
|
|
2966
|
+
const currentIndex = Math.max(0, candidates.findIndex((item) => (
|
|
2967
|
+
(currentDestinationID && item.id === currentDestinationID)
|
|
2968
|
+
|| (currentDestinationLabel && matchesRunnerRouteText(item.label, currentDestinationLabel))
|
|
2969
|
+
)));
|
|
2970
|
+
if (candidates.length === 1) {
|
|
2971
|
+
return { id: candidates[0].id, label: candidates[0].label || candidates[0].id };
|
|
2972
|
+
}
|
|
2973
|
+
if (!candidates.length) {
|
|
2974
|
+
throw new Error(`No active ${providerEnvConfig(provider).label} destination exists for project ${projectID}.`);
|
|
2975
|
+
}
|
|
2976
|
+
const selected = await promptChoice(
|
|
2977
|
+
ui,
|
|
2978
|
+
"Select project chat destination",
|
|
2979
|
+
candidates.map((item) => ({
|
|
2980
|
+
value: item.id,
|
|
2981
|
+
label: item.label || item.id,
|
|
2982
|
+
description: `chat_id:${item.chatID}`,
|
|
2983
|
+
})),
|
|
2984
|
+
{ defaultIndex: currentIndex },
|
|
2985
|
+
);
|
|
2986
|
+
const matched = candidates.find((item) => item.id === String(selected?.value || "").trim());
|
|
2987
|
+
if (!matched) {
|
|
2988
|
+
throw new Error("selected destination was not found");
|
|
2989
|
+
}
|
|
2990
|
+
return { id: matched.id, label: matched.label || matched.id };
|
|
2991
|
+
}
|
|
2992
|
+
|
|
2993
|
+
function buildRunnerRoutePayload({
|
|
2994
|
+
currentRoute = null,
|
|
2995
|
+
name = "",
|
|
2996
|
+
enabled = true,
|
|
2997
|
+
projectID = "",
|
|
2998
|
+
provider = "",
|
|
2999
|
+
role = "",
|
|
3000
|
+
roleProfile = "",
|
|
3001
|
+
serverBotName = "",
|
|
3002
|
+
serverBotID = "",
|
|
3003
|
+
destinationID = "",
|
|
3004
|
+
destinationLabel = "",
|
|
3005
|
+
pollIntervalMs = 5000,
|
|
3006
|
+
}) {
|
|
3007
|
+
const triggerPolicy = currentRoute
|
|
3008
|
+
? safeObject(currentRoute.triggerPolicy)
|
|
3009
|
+
: defaultRunnerTriggerPolicyForRole(roleProfile || role);
|
|
3010
|
+
const archivePolicy = currentRoute
|
|
3011
|
+
? safeObject(currentRoute.archivePolicy)
|
|
3012
|
+
: defaultRunnerArchivePolicyForRole(roleProfile || role);
|
|
3013
|
+
return normalizeRunnerRoute({
|
|
3014
|
+
...(currentRoute ? serializeRunnerRoute(currentRoute) : {}),
|
|
3015
|
+
name,
|
|
3016
|
+
enabled,
|
|
3017
|
+
project_id: projectID,
|
|
3018
|
+
provider,
|
|
3019
|
+
role,
|
|
3020
|
+
role_profile: roleProfile,
|
|
3021
|
+
server_bot_name: serverBotName,
|
|
3022
|
+
server_bot_id: serverBotID,
|
|
3023
|
+
destination_id: destinationID,
|
|
3024
|
+
destination_label: destinationLabel,
|
|
3025
|
+
trigger_policy: triggerPolicy,
|
|
3026
|
+
archive_policy: archivePolicy,
|
|
3027
|
+
poll_interval_ms: pollIntervalMs,
|
|
3028
|
+
});
|
|
3029
|
+
}
|
|
3030
|
+
|
|
3031
|
+
async function runRunnerRouteAdd(flags) {
|
|
3032
|
+
const ui = createPrompter();
|
|
3033
|
+
try {
|
|
3034
|
+
if (shouldRenderPromptChrome(flags)) {
|
|
3035
|
+
ui.setFlow("RUNNER ROUTE ADD", "Create one executable runner route from server bot and destination");
|
|
3036
|
+
}
|
|
3037
|
+
const config = loadBotRunnerConfig({ persistIfNeeded: true });
|
|
3038
|
+
const provider = await selectRunnerRouteProvider(ui, flags, "");
|
|
3039
|
+
const projectID = await resolveRunnerRouteProjectID(ui, flags, config);
|
|
3040
|
+
const role = await selectRunnerRole(ui, flags, "");
|
|
3041
|
+
const botSelection = await selectRunnerServerBotProfile(ui, { provider, role, flags });
|
|
3042
|
+
const destination = await selectRunnerDestination(ui, {
|
|
3043
|
+
projectID,
|
|
3044
|
+
provider,
|
|
3045
|
+
flags,
|
|
3046
|
+
});
|
|
3047
|
+
const suggestedName = buildRunnerRouteNameSuggestion({
|
|
3048
|
+
provider,
|
|
3049
|
+
role,
|
|
3050
|
+
botName: botSelection.name,
|
|
3051
|
+
}, ensureArray(config.routes).map((route) => normalizeRunnerRoute(route).name));
|
|
3052
|
+
const routeName = String(flags.name || "").trim() || await promptRequiredLine(ui, "Runner route name", suggestedName);
|
|
3053
|
+
const pollIntervalMs = intFromRaw(flags["poll-interval-ms"], 0) || intFromRaw(await promptLine(ui, "Poll interval (ms)", "5000"), 5000);
|
|
3054
|
+
const enabled = !Object.prototype.hasOwnProperty.call(flags, "enabled")
|
|
3055
|
+
? true
|
|
3056
|
+
: boolFromRaw(flags.enabled, true);
|
|
3057
|
+
if (!Object.prototype.hasOwnProperty.call(flags, "enabled")) {
|
|
3058
|
+
const enabledChoice = await promptChoice(
|
|
3059
|
+
ui,
|
|
3060
|
+
"Route enabled state",
|
|
3061
|
+
[
|
|
3062
|
+
{ value: "enabled", label: "Enabled", description: "route will run immediately" },
|
|
3063
|
+
{ value: "disabled", label: "Disabled", description: "save the route but keep it inactive" },
|
|
3064
|
+
],
|
|
3065
|
+
{ defaultIndex: enabled ? 0 : 1 },
|
|
3066
|
+
);
|
|
3067
|
+
flags.enabled = enabledChoice?.value === "disabled" ? "false" : "true";
|
|
3068
|
+
}
|
|
3069
|
+
const nextRoute = buildRunnerRoutePayload({
|
|
3070
|
+
name: routeName,
|
|
3071
|
+
enabled: boolFromRaw(flags.enabled, true),
|
|
3072
|
+
projectID,
|
|
3073
|
+
provider,
|
|
3074
|
+
role,
|
|
3075
|
+
roleProfile: role,
|
|
3076
|
+
serverBotName: botSelection.name,
|
|
3077
|
+
serverBotID: botSelection.id,
|
|
3078
|
+
destinationID: destination.id,
|
|
3079
|
+
destinationLabel: destination.label,
|
|
3080
|
+
pollIntervalMs,
|
|
3081
|
+
});
|
|
3082
|
+
const saved = upsertRunnerRouteConfig(config, nextRoute);
|
|
3083
|
+
const filePath = saveBotRunnerConfig(saved, config.filePath);
|
|
3084
|
+
process.stdout.write(`Saved runner route "${nextRoute.name}" to ${filePath}\n`);
|
|
3085
|
+
} finally {
|
|
3086
|
+
ui.close();
|
|
3087
|
+
}
|
|
3088
|
+
}
|
|
3089
|
+
|
|
3090
|
+
async function runRunnerRouteEdit(flags) {
|
|
3091
|
+
const ui = createPrompter();
|
|
3092
|
+
try {
|
|
3093
|
+
if (shouldRenderPromptChrome(flags)) {
|
|
3094
|
+
ui.setFlow("RUNNER ROUTE EDIT", "Update one executable runner route");
|
|
3095
|
+
}
|
|
3096
|
+
const selection = await selectRunnerManagementRoute(ui, flags, { title: "Select runner route to edit" });
|
|
3097
|
+
const currentRoute = normalizeRunnerRoute(selection.route);
|
|
3098
|
+
const config = selection.config;
|
|
3099
|
+
|
|
3100
|
+
const projectChange = await promptChoice(
|
|
3101
|
+
ui,
|
|
3102
|
+
"Project selection",
|
|
3103
|
+
[
|
|
3104
|
+
{ value: "keep", label: "Keep current project", description: currentRoute.projectID || "-" },
|
|
3105
|
+
{ value: "change", label: "Change project", description: "select another mapped project or enter a UUID" },
|
|
3106
|
+
],
|
|
3107
|
+
{ defaultIndex: 0 },
|
|
3108
|
+
);
|
|
3109
|
+
const projectID = projectChange?.value === "change"
|
|
3110
|
+
? await resolveRunnerRouteProjectID(ui, {}, config)
|
|
3111
|
+
: currentRoute.projectID;
|
|
3112
|
+
|
|
3113
|
+
const nameAction = await promptChoice(
|
|
3114
|
+
ui,
|
|
3115
|
+
"Runner route name",
|
|
3116
|
+
[
|
|
3117
|
+
{ value: "keep", label: "Keep current value", description: currentRoute.name || runnerRouteKey(currentRoute) },
|
|
3118
|
+
{ value: "change", label: "Change value", description: "rename this route" },
|
|
3119
|
+
],
|
|
3120
|
+
{ defaultIndex: 0 },
|
|
3121
|
+
);
|
|
3122
|
+
const routeName = nameAction?.value === "change"
|
|
3123
|
+
? await promptRequiredLine(ui, "Runner route name", currentRoute.name || "")
|
|
3124
|
+
: currentRoute.name;
|
|
3125
|
+
|
|
3126
|
+
const roleAction = await promptChoice(
|
|
3127
|
+
ui,
|
|
3128
|
+
"Runner role",
|
|
3129
|
+
[
|
|
3130
|
+
{ value: "keep", label: "Keep current role", description: currentRoute.role || "-" },
|
|
3131
|
+
{ value: "change", label: "Change role", description: "select a different execution role" },
|
|
3132
|
+
],
|
|
3133
|
+
{ defaultIndex: 0 },
|
|
3134
|
+
);
|
|
3135
|
+
const role = roleAction?.value === "change"
|
|
3136
|
+
? await selectRunnerRole(ui, {}, currentRoute.role)
|
|
3137
|
+
: currentRoute.role;
|
|
3138
|
+
|
|
3139
|
+
const botAction = await promptChoice(
|
|
3140
|
+
ui,
|
|
3141
|
+
"Server bot selection",
|
|
3142
|
+
[
|
|
3143
|
+
{ value: "keep", label: "Keep current server bot", description: currentRoute.botName || currentRoute.botID || "-" },
|
|
3144
|
+
{ value: "change", label: "Change server bot", description: "pick the server bot that should own this route" },
|
|
3145
|
+
],
|
|
3146
|
+
{ defaultIndex: role !== currentRoute.role ? 1 : 0 },
|
|
3147
|
+
);
|
|
3148
|
+
const botSelection = botAction?.value === "change" || role !== currentRoute.role
|
|
3149
|
+
? await selectRunnerServerBotProfile(ui, {
|
|
3150
|
+
provider: currentRoute.provider,
|
|
3151
|
+
role,
|
|
3152
|
+
flags: {},
|
|
3153
|
+
currentBotID: currentRoute.botID,
|
|
3154
|
+
currentBotName: currentRoute.botName,
|
|
3155
|
+
})
|
|
3156
|
+
: { name: currentRoute.botName, id: currentRoute.botID };
|
|
3157
|
+
|
|
3158
|
+
const destinationAction = await promptChoice(
|
|
3159
|
+
ui,
|
|
3160
|
+
"Project chat destination",
|
|
3161
|
+
[
|
|
3162
|
+
{ value: "keep", label: "Keep current destination", description: currentRoute.destinationLabel || currentRoute.destinationID || "-" },
|
|
3163
|
+
{ value: "change", label: "Change destination", description: "select another active project destination" },
|
|
3164
|
+
],
|
|
3165
|
+
{ defaultIndex: projectID !== currentRoute.projectID ? 1 : 0 },
|
|
3166
|
+
);
|
|
3167
|
+
const destination = destinationAction?.value === "change" || projectID !== currentRoute.projectID
|
|
3168
|
+
? await selectRunnerDestination(ui, {
|
|
3169
|
+
projectID,
|
|
3170
|
+
provider: currentRoute.provider,
|
|
3171
|
+
flags: {},
|
|
3172
|
+
currentDestinationID: currentRoute.destinationID,
|
|
3173
|
+
currentDestinationLabel: currentRoute.destinationLabel,
|
|
3174
|
+
})
|
|
3175
|
+
: { id: currentRoute.destinationID, label: currentRoute.destinationLabel };
|
|
3176
|
+
|
|
3177
|
+
const pollAction = await promptChoice(
|
|
3178
|
+
ui,
|
|
3179
|
+
"Poll interval (ms)",
|
|
3180
|
+
[
|
|
3181
|
+
{ value: "keep", label: "Keep current value", description: String(currentRoute.pollIntervalMs || 5000) },
|
|
3182
|
+
{ value: "change", label: "Change value", description: "set a different poll interval" },
|
|
3183
|
+
],
|
|
3184
|
+
{ defaultIndex: 0 },
|
|
3185
|
+
);
|
|
3186
|
+
const pollIntervalMs = pollAction?.value === "change"
|
|
3187
|
+
? intFromRaw(await promptRequiredLine(ui, "Poll interval (ms)", String(currentRoute.pollIntervalMs || 5000)), currentRoute.pollIntervalMs || 5000)
|
|
3188
|
+
: currentRoute.pollIntervalMs;
|
|
3189
|
+
|
|
3190
|
+
const enabledChoice = await promptChoice(
|
|
3191
|
+
ui,
|
|
3192
|
+
"Route enabled state",
|
|
3193
|
+
[
|
|
3194
|
+
{ value: "keep", label: "Keep current state", description: currentRoute.enabled ? "enabled" : "disabled" },
|
|
3195
|
+
{ value: "enabled", label: "Enable route", description: "runner can execute this route" },
|
|
3196
|
+
{ value: "disabled", label: "Disable route", description: "keep it configured but inactive" },
|
|
3197
|
+
],
|
|
3198
|
+
{ defaultIndex: 0 },
|
|
3199
|
+
);
|
|
3200
|
+
const enabled = enabledChoice?.value === "enabled"
|
|
3201
|
+
? true
|
|
3202
|
+
: enabledChoice?.value === "disabled"
|
|
3203
|
+
? false
|
|
3204
|
+
: currentRoute.enabled;
|
|
3205
|
+
|
|
3206
|
+
const confirmed = await promptConfirmChoice(ui, "Save runner route changes now?", {
|
|
3207
|
+
confirmDescription: "write the updated route to bot-runner.json",
|
|
3208
|
+
cancelDescription: "leave the route unchanged",
|
|
3209
|
+
});
|
|
3210
|
+
if (!confirmed) {
|
|
3211
|
+
process.stdout.write("Cancelled.\n");
|
|
3212
|
+
return;
|
|
3213
|
+
}
|
|
3214
|
+
const nextRoute = buildRunnerRoutePayload({
|
|
3215
|
+
currentRoute,
|
|
3216
|
+
name: routeName,
|
|
3217
|
+
enabled,
|
|
3218
|
+
projectID,
|
|
3219
|
+
provider: currentRoute.provider,
|
|
3220
|
+
role,
|
|
3221
|
+
roleProfile: role,
|
|
3222
|
+
serverBotName: botSelection.name,
|
|
3223
|
+
serverBotID: botSelection.id,
|
|
3224
|
+
destinationID: destination.id,
|
|
3225
|
+
destinationLabel: destination.label,
|
|
3226
|
+
pollIntervalMs,
|
|
3227
|
+
});
|
|
3228
|
+
const nextConfig = {
|
|
3229
|
+
...config,
|
|
3230
|
+
routes: ensureArray(config.routes)
|
|
3231
|
+
.map((route) => normalizeRunnerRoute(route))
|
|
3232
|
+
.filter((route) => String(route.name || "").trim().toLowerCase() !== String(currentRoute.name || "").trim().toLowerCase()),
|
|
3233
|
+
};
|
|
3234
|
+
const filePath = saveBotRunnerConfig(upsertRunnerRouteConfig(nextConfig, nextRoute), config.filePath);
|
|
3235
|
+
process.stdout.write(`Updated runner route "${nextRoute.name}" in ${filePath}\n`);
|
|
3236
|
+
} finally {
|
|
3237
|
+
ui.close();
|
|
3238
|
+
}
|
|
3239
|
+
}
|
|
3240
|
+
|
|
3241
|
+
async function runRunnerRouteRemove(flags) {
|
|
3242
|
+
const ui = createPrompter();
|
|
3243
|
+
try {
|
|
3244
|
+
if (shouldRenderPromptChrome(flags)) {
|
|
3245
|
+
ui.setFlow("RUNNER ROUTE REMOVE", "Delete one executable runner route");
|
|
3246
|
+
}
|
|
3247
|
+
const selection = await selectRunnerManagementRoute(ui, flags, { title: "Select runner route to remove" });
|
|
3248
|
+
const currentRoute = normalizeRunnerRoute(selection.route);
|
|
3249
|
+
const confirmed = await promptConfirmChoice(ui, `Remove runner route "${currentRoute.name || runnerRouteKey(currentRoute)}"?`, {
|
|
3250
|
+
confirmDescription: "delete the route from bot-runner.json",
|
|
3251
|
+
cancelDescription: "keep the route unchanged",
|
|
3252
|
+
});
|
|
3253
|
+
if (!confirmed) {
|
|
3254
|
+
process.stdout.write("Cancelled.\n");
|
|
3255
|
+
return;
|
|
3256
|
+
}
|
|
3257
|
+
const nextConfig = removeRunnerRouteFromConfig(selection.config, currentRoute.name);
|
|
3258
|
+
const filePath = saveBotRunnerConfig(nextConfig, selection.config.filePath);
|
|
3259
|
+
process.stdout.write(`Removed runner route "${currentRoute.name}" from ${filePath}\n`);
|
|
3260
|
+
} finally {
|
|
3261
|
+
ui.close();
|
|
3262
|
+
}
|
|
3263
|
+
}
|
|
3264
|
+
|
|
2657
3265
|
async function runRunnerList(flags) {
|
|
2658
3266
|
const rows = buildRunnerRouteListRows();
|
|
2659
3267
|
if (boolFromRaw(flags.json, false)) {
|
|
@@ -2661,6 +3269,7 @@ async function runRunnerList(flags) {
|
|
|
2661
3269
|
return;
|
|
2662
3270
|
}
|
|
2663
3271
|
process.stdout.write("Runner routes\n");
|
|
3272
|
+
process.stdout.write(" note: routes are the executable unit. --bot-name and --bot-id are convenience selectors that resolve one enabled route when the match is unique.\n");
|
|
2664
3273
|
if (!rows.length) {
|
|
2665
3274
|
process.stdout.write(" none configured\n");
|
|
2666
3275
|
return;
|
|
@@ -2673,18 +3282,141 @@ async function runRunnerList(flags) {
|
|
|
2673
3282
|
` provider: ${row.provider}`,
|
|
2674
3283
|
` project_id: ${row.projectID}`,
|
|
2675
3284
|
` server_bot_name: ${row.botName}`,
|
|
3285
|
+
` server_bot_name_source: ${row.botNameSource}`,
|
|
2676
3286
|
` server_bot_id: ${row.botID}`,
|
|
2677
3287
|
` role: ${row.role}`,
|
|
2678
3288
|
` role_profile: ${row.roleProfile}`,
|
|
2679
3289
|
` destination_label: ${row.destinationLabel}`,
|
|
2680
3290
|
` poll_interval_ms: ${row.pollIntervalMs}`,
|
|
2681
3291
|
` run_once: ${CLI_NAME} runner once --route-name ${row.name}`,
|
|
2682
|
-
row.botName
|
|
3292
|
+
row.botName && row.botNameSource !== "unresolved"
|
|
3293
|
+
? ` route_alias_by_server_bot_name: ${CLI_NAME} runner once --bot-name "${row.botName}"`
|
|
3294
|
+
: "",
|
|
2683
3295
|
].join("\n") + "\n",
|
|
2684
3296
|
);
|
|
2685
3297
|
});
|
|
2686
3298
|
}
|
|
2687
3299
|
|
|
3300
|
+
function resolveRunnerShowSelection(flags) {
|
|
3301
|
+
const routes = resolveRunnerRoutes(flags, "once");
|
|
3302
|
+
if (!routes.length) {
|
|
3303
|
+
throw new Error("no runner route matched the provided filters");
|
|
3304
|
+
}
|
|
3305
|
+
if (routes.length > 1) {
|
|
3306
|
+
const names = routes.map((route) => normalizeRunnerRoute(route).name || runnerRouteKey(route)).join(", ");
|
|
3307
|
+
throw new Error(`Multiple enabled runner routes matched. Narrow with --route-name, --bot-name, or --bot-id. Matches: ${names}`);
|
|
3308
|
+
}
|
|
3309
|
+
return normalizeRunnerRoute(routes[0]);
|
|
3310
|
+
}
|
|
3311
|
+
|
|
3312
|
+
function findRunnerTelegramEntryForRoute(route, telegramEntries) {
|
|
3313
|
+
const normalizedRoute = normalizeRunnerRoute(route);
|
|
3314
|
+
if (normalizedRoute.provider !== "telegram") return null;
|
|
3315
|
+
return ensureArray(telegramEntries).find((entry) => {
|
|
3316
|
+
const current = safeObject(entry);
|
|
3317
|
+
return (
|
|
3318
|
+
(normalizedRoute.botID && current.serverBotID && current.serverBotID === normalizedRoute.botID)
|
|
3319
|
+
|| (normalizedRoute.botName && current.serverBotName && current.serverBotName === normalizedRoute.botName)
|
|
3320
|
+
);
|
|
3321
|
+
}) || null;
|
|
3322
|
+
}
|
|
3323
|
+
|
|
3324
|
+
function buildRunnerShowPayload(route, flags = {}) {
|
|
3325
|
+
const normalizedRoute = normalizeRunnerRoute(route);
|
|
3326
|
+
const runnerConfig = loadBotRunnerConfig({ persistIfNeeded: true });
|
|
3327
|
+
const diagnostics = collectRunnerRouteDiagnostics(normalizedRoute, runnerConfig);
|
|
3328
|
+
const telegramState = readTelegramEnvState();
|
|
3329
|
+
const telegramEntries = ensureArray(telegramState.entries);
|
|
3330
|
+
const matchedTelegramEntry = findRunnerTelegramEntryForRoute(normalizedRoute, telegramEntries);
|
|
3331
|
+
const botNameSource = normalizedRoute.botName
|
|
3332
|
+
? "route_config"
|
|
3333
|
+
: matchedTelegramEntry?.serverBotName
|
|
3334
|
+
? "telegram_env_lookup"
|
|
3335
|
+
: "unresolved";
|
|
3336
|
+
const resolvedServerBotName = firstNonEmptyString([
|
|
3337
|
+
normalizedRoute.botName,
|
|
3338
|
+
matchedTelegramEntry?.serverBotName,
|
|
3339
|
+
"-",
|
|
3340
|
+
]);
|
|
3341
|
+
const envConfig = normalizedRoute.provider
|
|
3342
|
+
? loadProviderEnvConfig(normalizedRoute.provider, {
|
|
3343
|
+
botID: normalizedRoute.botID,
|
|
3344
|
+
botName: resolvedServerBotName !== "-" ? resolvedServerBotName : "",
|
|
3345
|
+
route: normalizedRoute,
|
|
3346
|
+
})
|
|
3347
|
+
: null;
|
|
3348
|
+
return {
|
|
3349
|
+
ok: diagnostics.errors.length === 0,
|
|
3350
|
+
route_name: normalizedRoute.name || runnerRouteKey(normalizedRoute),
|
|
3351
|
+
route_key: runnerRouteKey(normalizedRoute),
|
|
3352
|
+
route_config_file: runnerConfig.filePath,
|
|
3353
|
+
route_config: serializeRunnerRoute(normalizedRoute),
|
|
3354
|
+
resolved_server_identity: {
|
|
3355
|
+
server_bot_name: resolvedServerBotName,
|
|
3356
|
+
server_bot_name_source: botNameSource,
|
|
3357
|
+
server_bot_id: normalizedRoute.botID || "-",
|
|
3358
|
+
telegram_entry_file: String(envConfig?.entryFilePath || "").trim() || "-",
|
|
3359
|
+
},
|
|
3360
|
+
workspace_mapping: {
|
|
3361
|
+
workspace_dir: diagnostics.workspaceDir || "-",
|
|
3362
|
+
workspace_source: diagnostics.workspaceSource || "-",
|
|
3363
|
+
},
|
|
3364
|
+
execution_profile: {
|
|
3365
|
+
route_role: normalizedRoute.role || "-",
|
|
3366
|
+
role_profile_name: diagnostics.roleProfileName || "-",
|
|
3367
|
+
client: String(diagnostics.roleProfile?.client || "").trim() || "-",
|
|
3368
|
+
model: String(diagnostics.roleProfile?.model || "").trim() || "-",
|
|
3369
|
+
permission_mode: String(diagnostics.roleProfile?.permissionMode || "").trim() || "-",
|
|
3370
|
+
reasoning_effort: String(diagnostics.roleProfile?.reasoningEffort || "").trim() || "-",
|
|
3371
|
+
},
|
|
3372
|
+
route_selection_note: "Routes are the executable unit. --bot-name and --bot-id are convenience selectors that resolve one enabled route when the match is unique.",
|
|
3373
|
+
warnings: diagnostics.warnings,
|
|
3374
|
+
errors: diagnostics.errors,
|
|
3375
|
+
};
|
|
3376
|
+
}
|
|
3377
|
+
|
|
3378
|
+
async function runRunnerShow(flags) {
|
|
3379
|
+
const route = resolveRunnerShowSelection(flags);
|
|
3380
|
+
const payload = buildRunnerShowPayload(route, flags);
|
|
3381
|
+
if (boolFromRaw(flags.json, false)) {
|
|
3382
|
+
process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
3383
|
+
return;
|
|
3384
|
+
}
|
|
3385
|
+
process.stdout.write("Runner route details\n");
|
|
3386
|
+
process.stdout.write(" note: routes are the executable unit. --bot-name and --bot-id are convenience selectors that resolve one enabled route when the match is unique.\n");
|
|
3387
|
+
process.stdout.write(
|
|
3388
|
+
[
|
|
3389
|
+
` route_name: ${payload.route_name}`,
|
|
3390
|
+
` route_key: ${payload.route_key}`,
|
|
3391
|
+
` route_config_file: ${payload.route_config_file}`,
|
|
3392
|
+
" route_config:",
|
|
3393
|
+
` provider: ${payload.route_config.provider || "-"}`,
|
|
3394
|
+
` project_id: ${payload.route_config.project_id || "-"}`,
|
|
3395
|
+
` role: ${payload.route_config.role || "-"}`,
|
|
3396
|
+
` role_profile: ${payload.route_config.role_profile || "-"}`,
|
|
3397
|
+
` destination_label: ${payload.route_config.destination_label || "-"}`,
|
|
3398
|
+
` poll_interval_ms: ${payload.route_config.poll_interval_ms || 0}`,
|
|
3399
|
+
" resolved_server_identity:",
|
|
3400
|
+
` server_bot_name: ${payload.resolved_server_identity.server_bot_name}`,
|
|
3401
|
+
` server_bot_name_source: ${payload.resolved_server_identity.server_bot_name_source}`,
|
|
3402
|
+
` server_bot_id: ${payload.resolved_server_identity.server_bot_id}`,
|
|
3403
|
+
` telegram_entry_file: ${payload.resolved_server_identity.telegram_entry_file}`,
|
|
3404
|
+
" workspace_mapping:",
|
|
3405
|
+
` workspace_dir: ${payload.workspace_mapping.workspace_dir}`,
|
|
3406
|
+
` workspace_source: ${payload.workspace_mapping.workspace_source}`,
|
|
3407
|
+
" execution_profile:",
|
|
3408
|
+
` route_role: ${payload.execution_profile.route_role}`,
|
|
3409
|
+
` role_profile_name: ${payload.execution_profile.role_profile_name}`,
|
|
3410
|
+
` client: ${payload.execution_profile.client}`,
|
|
3411
|
+
` model: ${payload.execution_profile.model}`,
|
|
3412
|
+
` permission_mode: ${payload.execution_profile.permission_mode}`,
|
|
3413
|
+
` reasoning_effort: ${payload.execution_profile.reasoning_effort}`,
|
|
3414
|
+
payload.warnings.length ? ` warnings: ${payload.warnings.join("; ")}` : " warnings: -",
|
|
3415
|
+
payload.errors.length ? ` errors: ${payload.errors.join("; ")}` : " errors: -",
|
|
3416
|
+
].join("\n") + "\n",
|
|
3417
|
+
);
|
|
3418
|
+
}
|
|
3419
|
+
|
|
2688
3420
|
async function runRunnerStart(flags) {
|
|
2689
3421
|
const jsonMode = boolFromRaw(flags.json, false);
|
|
2690
3422
|
const routes = resolveRunnerRoutes(flags, "start");
|
|
@@ -2744,11 +3476,40 @@ async function runRunnerStart(flags) {
|
|
|
2744
3476
|
async function runRunner(argv) {
|
|
2745
3477
|
const [subcommandRaw = "", ...rest] = argv;
|
|
2746
3478
|
const subcommand = String(subcommandRaw || "").trim().toLowerCase();
|
|
3479
|
+
if (subcommand === "route" || subcommand === "routes") {
|
|
3480
|
+
const [routeSubcommandRaw = "", ...routeRest] = rest;
|
|
3481
|
+
const routeSubcommand = String(routeSubcommandRaw || "").trim().toLowerCase();
|
|
3482
|
+
const routeArgv = !routeSubcommand || routeSubcommand.startsWith("-")
|
|
3483
|
+
? [routeSubcommandRaw, ...routeRest].filter((value) => String(value || "").trim())
|
|
3484
|
+
: routeRest;
|
|
3485
|
+
const routeFlags = parseArgs(routeArgv);
|
|
3486
|
+
if (!routeSubcommand || routeSubcommand === "list" || routeSubcommand.startsWith("-")) {
|
|
3487
|
+
await runRunnerList(routeFlags);
|
|
3488
|
+
return;
|
|
3489
|
+
}
|
|
3490
|
+
if (routeSubcommand === "add") {
|
|
3491
|
+
await runRunnerRouteAdd(routeFlags);
|
|
3492
|
+
return;
|
|
3493
|
+
}
|
|
3494
|
+
if (routeSubcommand === "edit") {
|
|
3495
|
+
await runRunnerRouteEdit(routeFlags);
|
|
3496
|
+
return;
|
|
3497
|
+
}
|
|
3498
|
+
if (routeSubcommand === "remove" || routeSubcommand === "rm" || routeSubcommand === "delete") {
|
|
3499
|
+
await runRunnerRouteRemove(routeFlags);
|
|
3500
|
+
return;
|
|
3501
|
+
}
|
|
3502
|
+
throw new Error("runner route requires a subcommand: list | add | edit | remove");
|
|
3503
|
+
}
|
|
2747
3504
|
const flags = parseArgs(rest);
|
|
2748
3505
|
if (subcommand === "list") {
|
|
2749
3506
|
await runRunnerList(flags);
|
|
2750
3507
|
return;
|
|
2751
3508
|
}
|
|
3509
|
+
if (subcommand === "show") {
|
|
3510
|
+
await runRunnerShow(flags);
|
|
3511
|
+
return;
|
|
3512
|
+
}
|
|
2752
3513
|
if (subcommand === "once") {
|
|
2753
3514
|
await runRunnerOnce(flags);
|
|
2754
3515
|
return;
|
|
@@ -2757,7 +3518,7 @@ async function runRunner(argv) {
|
|
|
2757
3518
|
await runRunnerStart(flags);
|
|
2758
3519
|
return;
|
|
2759
3520
|
}
|
|
2760
|
-
throw new Error("runner requires a subcommand: list | once | start");
|
|
3521
|
+
throw new Error("runner requires a subcommand: list | show | once | start | route");
|
|
2761
3522
|
}
|
|
2762
3523
|
|
|
2763
3524
|
async function runLocalBotBridge(argv) {
|
|
@@ -5838,6 +6599,10 @@ TELEGRAM_BOT_REVIEW_TOKEN=review-token
|
|
|
5838
6599
|
defaultLocalBotBridgeCommand,
|
|
5839
6600
|
resolveRunnerExecutionPlan,
|
|
5840
6601
|
normalizeRunnerRoute,
|
|
6602
|
+
buildRunnerRouteNameSuggestion,
|
|
6603
|
+
buildRunnerRoutePayload,
|
|
6604
|
+
upsertRunnerRouteConfig,
|
|
6605
|
+
removeRunnerRouteFromConfig,
|
|
5841
6606
|
buildRunnerExecutionDeps,
|
|
5842
6607
|
defaultBotRunnerRoleProfiles,
|
|
5843
6608
|
resolveRunnerRoutes,
|
package/lib/bot-commands.mjs
CHANGED
|
@@ -201,7 +201,7 @@ function providerTokenKey(provider, deps) {
|
|
|
201
201
|
return safeObject(requireDependency(deps, "providerEnvConfig")(provider)).tokenKey || "";
|
|
202
202
|
}
|
|
203
203
|
|
|
204
|
-
function shouldRenderPromptChrome(flags) {
|
|
204
|
+
export function shouldRenderPromptChrome(flags) {
|
|
205
205
|
const parsedFlags = safeObject(flags);
|
|
206
206
|
if (boolFromRaw(parsedFlags["non-interactive"] ?? parsedFlags.yes, false)) return false;
|
|
207
207
|
if (boolFromRaw(parsedFlags.json, false)) return false;
|
|
@@ -240,7 +240,7 @@ function printBotUsage(deps) {
|
|
|
240
240
|
);
|
|
241
241
|
}
|
|
242
242
|
|
|
243
|
-
function createPrompter() {
|
|
243
|
+
export function createPrompter() {
|
|
244
244
|
let flowTitle = "";
|
|
245
245
|
let flowSubtitle = "";
|
|
246
246
|
let stepIndex = 0;
|
|
@@ -311,7 +311,7 @@ function createPrompter() {
|
|
|
311
311
|
};
|
|
312
312
|
}
|
|
313
313
|
|
|
314
|
-
async function promptLine(ui, promptText, defaultValue = "") {
|
|
314
|
+
export async function promptLine(ui, promptText, defaultValue = "") {
|
|
315
315
|
ui.beginPrompt(promptText, [
|
|
316
316
|
String(defaultValue || "").trim()
|
|
317
317
|
? `Press Enter to keep the current value: ${defaultValue}`
|
|
@@ -323,7 +323,7 @@ async function promptLine(ui, promptText, defaultValue = "") {
|
|
|
323
323
|
return text || String(defaultValue || "").trim();
|
|
324
324
|
}
|
|
325
325
|
|
|
326
|
-
async function promptRequiredLine(ui, promptText, defaultValue = "") {
|
|
326
|
+
export async function promptRequiredLine(ui, promptText, defaultValue = "") {
|
|
327
327
|
while (true) {
|
|
328
328
|
const answer = await promptLine(ui, promptText, defaultValue);
|
|
329
329
|
if (answer) return answer;
|
|
@@ -331,7 +331,7 @@ async function promptRequiredLine(ui, promptText, defaultValue = "") {
|
|
|
331
331
|
}
|
|
332
332
|
}
|
|
333
333
|
|
|
334
|
-
async function promptYesNo(ui, promptText, defaultValue = true) {
|
|
334
|
+
export async function promptYesNo(ui, promptText, defaultValue = true) {
|
|
335
335
|
ui.beginPrompt(promptText, ["Choose y or n, then press Enter."]);
|
|
336
336
|
const hint = defaultValue ? "Y/n" : "y/N";
|
|
337
337
|
while (true) {
|
|
@@ -343,7 +343,7 @@ async function promptYesNo(ui, promptText, defaultValue = true) {
|
|
|
343
343
|
}
|
|
344
344
|
}
|
|
345
345
|
|
|
346
|
-
async function promptConfirmChoice(
|
|
346
|
+
export async function promptConfirmChoice(
|
|
347
347
|
ui,
|
|
348
348
|
title,
|
|
349
349
|
{
|
|
@@ -367,7 +367,7 @@ function formatChoiceLabel(option) {
|
|
|
367
367
|
return `${String(option.label || option.value || "").trim()}${option.description ? ` - ${option.description}` : ""}`;
|
|
368
368
|
}
|
|
369
369
|
|
|
370
|
-
async function promptChoice(ui, title, options, { defaultIndex = 0, allowCancel = false } = {}) {
|
|
370
|
+
export async function promptChoice(ui, title, options, { defaultIndex = 0, allowCancel = false } = {}) {
|
|
371
371
|
const list = ensureArray(options).filter((item) => item && item.value !== undefined);
|
|
372
372
|
if (!list.length) {
|
|
373
373
|
throw new Error(`no options available for ${title}`);
|
|
@@ -399,7 +399,7 @@ async function promptChoice(ui, title, options, { defaultIndex = 0, allowCancel
|
|
|
399
399
|
}
|
|
400
400
|
}
|
|
401
401
|
|
|
402
|
-
async function promptKeepChangeClear(ui, title, { allowClear = true, defaultValue = "keep" } = {}) {
|
|
402
|
+
export async function promptKeepChangeClear(ui, title, { allowClear = true, defaultValue = "keep" } = {}) {
|
|
403
403
|
const options = [
|
|
404
404
|
{ value: "keep", label: "Keep current value" },
|
|
405
405
|
{ value: "change", label: "Change value" },
|
|
@@ -2054,36 +2054,18 @@ async function maybePromptGroupedServerRoleProfiles(ui, serverBot, deps) {
|
|
|
2054
2054
|
}
|
|
2055
2055
|
return true;
|
|
2056
2056
|
}
|
|
2057
|
-
const
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
{
|
|
2069
|
-
value: "__done__",
|
|
2070
|
-
label: "Done",
|
|
2071
|
-
description: "finish grouped role editing",
|
|
2072
|
-
},
|
|
2073
|
-
],
|
|
2074
|
-
{ defaultIndex: 0 },
|
|
2075
|
-
);
|
|
2076
|
-
if (!roleChoice || roleChoice.value === "__done__") {
|
|
2077
|
-
break;
|
|
2078
|
-
}
|
|
2057
|
+
const roleChoice = await promptChoice(
|
|
2058
|
+
ui,
|
|
2059
|
+
"Select role to edit",
|
|
2060
|
+
roles.map((role) => ({
|
|
2061
|
+
value: role,
|
|
2062
|
+
label: role,
|
|
2063
|
+
description: formatRoleProfileSummary(currentRoleProfileState(role, deps)),
|
|
2064
|
+
})),
|
|
2065
|
+
{ defaultIndex: 0 },
|
|
2066
|
+
);
|
|
2067
|
+
if (roleChoice?.value) {
|
|
2079
2068
|
await promptRoleExecutionProfile(ui, roleChoice.value, deps);
|
|
2080
|
-
remaining.delete(roleChoice.value);
|
|
2081
|
-
if (!remaining.size) {
|
|
2082
|
-
break;
|
|
2083
|
-
}
|
|
2084
|
-
if (!await promptYesNo(ui, "Edit another role?", false)) {
|
|
2085
|
-
break;
|
|
2086
|
-
}
|
|
2087
2069
|
}
|
|
2088
2070
|
return true;
|
|
2089
2071
|
}
|
|
@@ -479,14 +479,6 @@ export async function runSelftestBotCommands(push, deps) {
|
|
|
479
479
|
"2", // worker AI model: Sonnet 4.6r
|
|
480
480
|
"4", // worker permission: danger_full_access
|
|
481
481
|
"4", // worker reasoning: high
|
|
482
|
-
"y", // edit another role
|
|
483
|
-
"3", // select role to edit: approval
|
|
484
|
-
"2", // approval: edit settings
|
|
485
|
-
"4", // approval AI client: gemini
|
|
486
|
-
"2", // approval AI model: gemini-3.1-pro
|
|
487
|
-
"4", // approval permission: danger_full_access
|
|
488
|
-
"4", // approval reasoning: high
|
|
489
|
-
"n", // stop editing roles
|
|
490
482
|
"1", // keep current default setting
|
|
491
483
|
"y", // save
|
|
492
484
|
]),
|
|
@@ -501,13 +493,13 @@ export async function runSelftestBotCommands(push, deps) {
|
|
|
501
493
|
const groupedRunnerConfig = readJSON(fs.readFileSync(groupedRunnerConfigPath, "utf8"));
|
|
502
494
|
const groupedRoleProfiles = safeObject(groupedRunnerConfig.role_profiles || groupedRunnerConfig.roleProfiles);
|
|
503
495
|
push(
|
|
504
|
-
|
|
496
|
+
"bot_edit_grouped_server_roles_updates_role_profiles",
|
|
505
497
|
String(safeObject(safeObject(groupedRoleProfiles).worker).client || "") === "claude"
|
|
506
498
|
&& String(safeObject(safeObject(groupedRoleProfiles).worker).model || "") === "Sonnet 4.6r"
|
|
507
499
|
&& String(safeObject(safeObject(groupedRoleProfiles).worker).permission_mode || "") === "danger_full_access"
|
|
508
500
|
&& String(safeObject(safeObject(groupedRoleProfiles).worker).reasoning_effort || "") === "high"
|
|
509
|
-
&& String(safeObject(safeObject(groupedRoleProfiles).approval).client || "") === "
|
|
510
|
-
&& String(safeObject(safeObject(groupedRoleProfiles).approval).model || "") === "
|
|
501
|
+
&& String(safeObject(safeObject(groupedRoleProfiles).approval).client || "") === "claude"
|
|
502
|
+
&& String(safeObject(safeObject(groupedRoleProfiles).approval).model || "") === "Sonnet 4.6r",
|
|
511
503
|
`worker=${JSON.stringify(safeObject(safeObject(groupedRoleProfiles).worker))} approval=${JSON.stringify(safeObject(safeObject(groupedRoleProfiles).approval))}`,
|
|
512
504
|
);
|
|
513
505
|
|
|
@@ -527,7 +519,7 @@ export async function runSelftestBotCommands(push, deps) {
|
|
|
527
519
|
"bot_show_reports_grouped_server_roles",
|
|
528
520
|
safeObject(groupedShowPayload.serverBinding).mode === "group"
|
|
529
521
|
&& safeObject(safeObject(groupedShowPayload.serverBinding).effectiveRoleProfiles).worker?.client === "claude"
|
|
530
|
-
&& safeObject(safeObject(groupedShowPayload.serverBinding).effectiveRoleProfiles).approval?.client === "
|
|
522
|
+
&& safeObject(safeObject(groupedShowPayload.serverBinding).effectiveRoleProfiles).approval?.client === "claude"
|
|
531
523
|
&& String(safeObject(safeObject(groupedShowPayload.serverBinding).effectiveRoleProfiles).monitor?.model || "") === "gpt-5.4",
|
|
532
524
|
`mode=${String(safeObject(groupedShowPayload.serverBinding).mode || "")} worker=${String(safeObject(safeObject(groupedShowPayload.serverBinding).effectiveRoleProfiles).worker?.client || "")} approval=${String(safeObject(safeObject(groupedShowPayload.serverBinding).effectiveRoleProfiles).approval?.client || "")} monitor_model=${String(safeObject(safeObject(groupedShowPayload.serverBinding).effectiveRoleProfiles).monitor?.model || "")}`,
|
|
533
525
|
);
|
|
@@ -28,6 +28,10 @@ export async function runSelftestRunnerScenarios(push, deps) {
|
|
|
28
28
|
const defaultLocalBotBridgeCommand = requireDependency(deps, "defaultLocalBotBridgeCommand");
|
|
29
29
|
const resolveRunnerExecutionPlan = requireDependency(deps, "resolveRunnerExecutionPlan");
|
|
30
30
|
const normalizeRunnerRoute = requireDependency(deps, "normalizeRunnerRoute");
|
|
31
|
+
const buildRunnerRouteNameSuggestion = requireDependency(deps, "buildRunnerRouteNameSuggestion");
|
|
32
|
+
const buildRunnerRoutePayload = requireDependency(deps, "buildRunnerRoutePayload");
|
|
33
|
+
const upsertRunnerRouteConfig = requireDependency(deps, "upsertRunnerRouteConfig");
|
|
34
|
+
const removeRunnerRouteFromConfig = requireDependency(deps, "removeRunnerRouteFromConfig");
|
|
31
35
|
const buildRunnerExecutionDeps = requireDependency(deps, "buildRunnerExecutionDeps");
|
|
32
36
|
const defaultBotRunnerRoleProfiles = requireDependency(deps, "defaultBotRunnerRoleProfiles");
|
|
33
37
|
const resolveRunnerRoutes = requireDependency(deps, "resolveRunnerRoutes");
|
|
@@ -139,6 +143,51 @@ export async function runSelftestRunnerScenarios(push, deps) {
|
|
|
139
143
|
`mapping=${String(migratedRunnerConfig.projectMappings?.[selftestProjectID]?.workspaceDir || "(none)")}`,
|
|
140
144
|
);
|
|
141
145
|
|
|
146
|
+
const suggestedRouteName = buildRunnerRouteNameSuggestion(
|
|
147
|
+
{ provider: "telegram", role: "monitor", botName: "Server Protocol Monitor Bot" },
|
|
148
|
+
["telegram-monitor-server-protocol-monitor-bot"],
|
|
149
|
+
);
|
|
150
|
+
push(
|
|
151
|
+
"runner_route_name_suggestion_is_slugged_and_unique",
|
|
152
|
+
suggestedRouteName === "telegram-monitor-server-protocol-monitor-bot-2",
|
|
153
|
+
`suggested=${suggestedRouteName}`,
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
const routePayload = buildRunnerRoutePayload({
|
|
157
|
+
name: "telegram-monitor-server-protocol-monitor-bot",
|
|
158
|
+
enabled: true,
|
|
159
|
+
projectID: selftestProjectID,
|
|
160
|
+
provider: "telegram",
|
|
161
|
+
role: "monitor",
|
|
162
|
+
roleProfile: "monitor",
|
|
163
|
+
serverBotName: "Server Protocol Monitor Bot",
|
|
164
|
+
serverBotID: "11111111-2222-3333-4444-555555555555",
|
|
165
|
+
destinationID: "dest-1",
|
|
166
|
+
destinationLabel: "Main Room",
|
|
167
|
+
pollIntervalMs: 5000,
|
|
168
|
+
});
|
|
169
|
+
const routeConfigWithPayload = upsertRunnerRouteConfig(
|
|
170
|
+
normalizeBotRunnerConfigContents({ version: 2, routes: [] }, "selftest-runner-route-add.json"),
|
|
171
|
+
routePayload,
|
|
172
|
+
);
|
|
173
|
+
const savedRoute = (Array.isArray(routeConfigWithPayload.routes) ? routeConfigWithPayload.routes : [])
|
|
174
|
+
.map((route) => normalizeRunnerRoute(route))[0];
|
|
175
|
+
push(
|
|
176
|
+
"runner_route_payload_persists_server_identity_fields",
|
|
177
|
+
String(savedRoute.name || "") === "telegram-monitor-server-protocol-monitor-bot"
|
|
178
|
+
&& String(savedRoute.botName || "") === "Server Protocol Monitor Bot"
|
|
179
|
+
&& String(savedRoute.botID || "") === "11111111-2222-3333-4444-555555555555"
|
|
180
|
+
&& String(savedRoute.destinationID || "") === "dest-1",
|
|
181
|
+
`name=${String(savedRoute.name || "")} bot_name=${String(savedRoute.botName || "")} bot_id=${String(savedRoute.botID || "")} destination_id=${String(savedRoute.destinationID || "")}`,
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
const routeConfigWithoutPayload = removeRunnerRouteFromConfig(routeConfigWithPayload, "telegram-monitor-server-protocol-monitor-bot");
|
|
185
|
+
push(
|
|
186
|
+
"runner_route_remove_drops_named_route",
|
|
187
|
+
(Array.isArray(routeConfigWithoutPayload.routes) ? routeConfigWithoutPayload.routes.length : 0) === 0,
|
|
188
|
+
`remaining=${Array.isArray(routeConfigWithoutPayload.routes) ? routeConfigWithoutPayload.routes.length : 0}`,
|
|
189
|
+
);
|
|
190
|
+
|
|
142
191
|
const roleProfileOverrideConfig = normalizeBotRunnerConfigContents(
|
|
143
192
|
{
|
|
144
193
|
version: 2,
|