bosun 0.36.0 → 0.36.2
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/.env.example +98 -16
- package/README.md +27 -0
- package/agent-event-bus.mjs +5 -5
- package/agent-pool.mjs +129 -12
- package/agent-prompts.mjs +7 -1
- package/agent-sdk.mjs +13 -2
- package/agent-supervisor.mjs +2 -2
- package/agent-work-report.mjs +1 -1
- package/anomaly-detector.mjs +6 -6
- package/autofix.mjs +15 -15
- package/bosun-skills.mjs +4 -4
- package/bosun.schema.json +160 -4
- package/claude-shell.mjs +11 -11
- package/cli.mjs +21 -21
- package/codex-config.mjs +19 -19
- package/codex-shell.mjs +180 -29
- package/config-doctor.mjs +27 -2
- package/config.mjs +60 -7
- package/copilot-shell.mjs +4 -4
- package/error-detector.mjs +1 -1
- package/fleet-coordinator.mjs +2 -2
- package/gemini-shell.mjs +692 -0
- package/github-oauth-portal.mjs +1 -1
- package/github-reconciler.mjs +2 -2
- package/kanban-adapter.mjs +741 -168
- package/merge-strategy.mjs +25 -25
- package/monitor.mjs +123 -105
- package/opencode-shell.mjs +22 -22
- package/package.json +7 -1
- package/postinstall.mjs +22 -22
- package/pr-cleanup-daemon.mjs +6 -6
- package/prepublish-check.mjs +4 -4
- package/presence.mjs +2 -2
- package/primary-agent.mjs +85 -7
- package/publish.mjs +1 -1
- package/review-agent.mjs +1 -1
- package/session-tracker.mjs +11 -0
- package/setup-web-server.mjs +429 -21
- package/setup.mjs +367 -12
- package/shared-knowledge.mjs +1 -1
- package/startup-service.mjs +9 -9
- package/stream-resilience.mjs +58 -4
- package/sync-engine.mjs +2 -2
- package/task-assessment.mjs +9 -9
- package/task-cli.mjs +1 -1
- package/task-complexity.mjs +71 -2
- package/task-context.mjs +1 -2
- package/task-executor.mjs +104 -41
- package/telegram-bot.mjs +825 -494
- package/telegram-sentinel.mjs +28 -28
- package/ui/app.js +256 -23
- package/ui/app.monolith.js +1 -1
- package/ui/components/agent-selector.js +4 -3
- package/ui/components/chat-view.js +101 -28
- package/ui/components/diff-viewer.js +3 -3
- package/ui/components/kanban-board.js +3 -3
- package/ui/components/session-list.js +255 -35
- package/ui/components/workspace-switcher.js +3 -3
- package/ui/demo.html +209 -194
- package/ui/index.html +3 -3
- package/ui/modules/icon-utils.js +206 -142
- package/ui/modules/icons.js +2 -27
- package/ui/modules/settings-schema.js +29 -5
- package/ui/modules/streaming.js +30 -2
- package/ui/modules/vision-stream.js +275 -0
- package/ui/modules/voice-client.js +102 -9
- package/ui/modules/voice-fallback.js +62 -6
- package/ui/modules/voice-overlay.js +594 -59
- package/ui/modules/voice.js +31 -38
- package/ui/setup.html +284 -34
- package/ui/styles/components.css +47 -0
- package/ui/styles/sessions.css +75 -0
- package/ui/tabs/agents.js +73 -43
- package/ui/tabs/chat.js +37 -40
- package/ui/tabs/control.js +2 -2
- package/ui/tabs/dashboard.js +1 -1
- package/ui/tabs/infra.js +10 -10
- package/ui/tabs/library.js +8 -8
- package/ui/tabs/logs.js +10 -10
- package/ui/tabs/settings.js +20 -20
- package/ui/tabs/tasks.js +76 -47
- package/ui-server.mjs +1761 -124
- package/update-check.mjs +13 -13
- package/ve-kanban.mjs +1 -1
- package/whatsapp-channel.mjs +5 -5
- package/workflow-engine.mjs +20 -1
- package/workflow-nodes.mjs +904 -4
- package/workflow-templates/agents.mjs +321 -7
- package/workflow-templates/ci-cd.mjs +6 -6
- package/workflow-templates/github.mjs +156 -84
- package/workflow-templates/planning.mjs +8 -8
- package/workflow-templates/reliability.mjs +8 -8
- package/workflow-templates/security.mjs +3 -3
- package/workflow-templates.mjs +15 -9
- package/workspace-manager.mjs +85 -1
- package/workspace-monitor.mjs +2 -2
- package/workspace-registry.mjs +2 -2
- package/worktree-manager.mjs +1 -1
package/telegram-bot.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* telegram-bot.mjs — Two-way Telegram
|
|
2
|
+
* telegram-bot.mjs — Two-way Telegram :workflow: primary agent for bosun.
|
|
3
3
|
*
|
|
4
4
|
* Polls Telegram Bot API for incoming messages, routes slash commands to
|
|
5
5
|
* built-in handlers, and forwards free-text to the persistent primary agent.
|
|
@@ -900,17 +900,17 @@ const uiInputRequests = new Map();
|
|
|
900
900
|
* users connecting from outside the local network.
|
|
901
901
|
*/
|
|
902
902
|
function getBrowserUiUrl() {
|
|
903
|
-
const base = telegramUiUrl;
|
|
904
|
-
if (!base) return null;
|
|
905
903
|
const token = getSessionToken();
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
const tUrl = getTunnelUrl();
|
|
910
|
-
if (tUrl) {
|
|
911
|
-
return appendTokenToUrl(tUrl, token) || tUrl;
|
|
904
|
+
const tunnelUrl = getTunnelUrl();
|
|
905
|
+
if (tunnelUrl) {
|
|
906
|
+
return appendTokenToUrl(tunnelUrl, token) || tunnelUrl;
|
|
912
907
|
}
|
|
913
908
|
|
|
909
|
+
const base = telegramUiUrl || getTelegramUiUrl?.() || null;
|
|
910
|
+
if (!base) return null;
|
|
911
|
+
|
|
912
|
+
// 1. Tunnel URL already checked above.
|
|
913
|
+
|
|
914
914
|
// 2. Fall back to configured/explicit URL
|
|
915
915
|
const explicit =
|
|
916
916
|
process.env.TELEGRAM_WEBAPP_URL || process.env.TELEGRAM_UI_BASE_URL || "";
|
|
@@ -959,9 +959,9 @@ function isTelegramInlineButtonUrlAllowed(inputUrl) {
|
|
|
959
959
|
}
|
|
960
960
|
|
|
961
961
|
function getBrowserUiUrlOptions({ forTelegramButtons = true } = {}) {
|
|
962
|
-
const
|
|
963
|
-
|
|
964
|
-
|
|
962
|
+
const tunnelUrl = getTunnelUrl();
|
|
963
|
+
const base = String(telegramUiUrl || getTelegramUiUrl?.() || tunnelUrl || "").trim();
|
|
964
|
+
if (!base && !tunnelUrl) return [];
|
|
965
965
|
const token = getSessionToken();
|
|
966
966
|
const options = [];
|
|
967
967
|
const seen = new Set();
|
|
@@ -985,32 +985,88 @@ function getBrowserUiUrlOptions({ forTelegramButtons = true } = {}) {
|
|
|
985
985
|
parsed = null;
|
|
986
986
|
}
|
|
987
987
|
|
|
988
|
+
if (tunnelUrl) {
|
|
989
|
+
let label = ":globe: Cloudflare";
|
|
990
|
+
try {
|
|
991
|
+
const host = String(new URL(tunnelUrl).hostname || "").toLowerCase();
|
|
992
|
+
label = host.endsWith(".trycloudflare.com")
|
|
993
|
+
? ":globe: Cloudflare (Quick)"
|
|
994
|
+
: ":globe: Cloudflare (Permanent)";
|
|
995
|
+
} catch {
|
|
996
|
+
// keep default label
|
|
997
|
+
}
|
|
998
|
+
add(label, tunnelUrl);
|
|
999
|
+
}
|
|
1000
|
+
|
|
988
1001
|
if (parsed) {
|
|
989
1002
|
const localhostUrl = `${parsed.protocol}//localhost${parsed.port ? `:${parsed.port}` : ""}`;
|
|
990
|
-
add("
|
|
1003
|
+
add(":monitor: Localhost", localhostUrl);
|
|
991
1004
|
}
|
|
992
1005
|
|
|
993
1006
|
if (parsed) {
|
|
994
1007
|
const lanIp = getLocalLanIp?.();
|
|
995
1008
|
if (lanIp && parsed.port) {
|
|
996
1009
|
const lanUrl = `${parsed.protocol}//${lanIp}:${parsed.port}`;
|
|
997
|
-
add("
|
|
1010
|
+
add(":chart: LAN", lanUrl);
|
|
998
1011
|
}
|
|
999
1012
|
}
|
|
1000
1013
|
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
add("☁️ Cloudflare", tunnelUrl);
|
|
1014
|
+
if (options.length === 0 && base) {
|
|
1015
|
+
add(":globe: Browser URL", base);
|
|
1004
1016
|
}
|
|
1017
|
+
return options;
|
|
1018
|
+
}
|
|
1005
1019
|
|
|
1006
|
-
|
|
1007
|
-
|
|
1020
|
+
function normalizeMeetingCallType(raw) {
|
|
1021
|
+
const value = String(raw || "").trim().toLowerCase();
|
|
1022
|
+
if (!value) return "voice";
|
|
1023
|
+
if (value === "video" || value === "videocall") return "video";
|
|
1024
|
+
if (/\bvideo\b/.test(value)) return "video";
|
|
1025
|
+
return "voice";
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
function appendQueryParams(inputUrl, params = {}) {
|
|
1029
|
+
const raw = String(inputUrl || "").trim();
|
|
1030
|
+
if (!raw) return null;
|
|
1031
|
+
try {
|
|
1032
|
+
const url = new URL(raw);
|
|
1033
|
+
for (const [key, value] of Object.entries(params || {})) {
|
|
1034
|
+
const next = String(value ?? "").trim();
|
|
1035
|
+
if (!next) continue;
|
|
1036
|
+
url.searchParams.set(key, next);
|
|
1037
|
+
}
|
|
1038
|
+
return url.toString();
|
|
1039
|
+
} catch {
|
|
1040
|
+
return raw;
|
|
1008
1041
|
}
|
|
1009
|
-
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
function buildMeetingLaunchUrl(baseUrl, callType = "voice", extra = {}) {
|
|
1045
|
+
const normalizedCall = normalizeMeetingCallType(callType);
|
|
1046
|
+
return appendQueryParams(baseUrl, {
|
|
1047
|
+
launch: "meeting",
|
|
1048
|
+
call: normalizedCall,
|
|
1049
|
+
autostart: "1",
|
|
1050
|
+
source: "telegram",
|
|
1051
|
+
...extra,
|
|
1052
|
+
});
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
function getMeetingWebAppUrl(callType = "voice", extra = {}) {
|
|
1056
|
+
const base = telegramWebAppUrl || getTelegramWebAppUrl(telegramUiUrl);
|
|
1057
|
+
if (!base) return null;
|
|
1058
|
+
return buildMeetingLaunchUrl(base, callType, extra);
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
function getMeetingBrowserUrlOptions(callType = "voice", extra = {}) {
|
|
1062
|
+
return getBrowserUiUrlOptions().map((option) => ({
|
|
1063
|
+
label: option.label,
|
|
1064
|
+
url: buildMeetingLaunchUrl(option.url, callType, extra),
|
|
1065
|
+
}));
|
|
1010
1066
|
}
|
|
1011
1067
|
|
|
1012
1068
|
function syncUiUrlsFromServer() {
|
|
1013
|
-
const currentUiUrl = getTelegramUiUrl?.() || null;
|
|
1069
|
+
const currentUiUrl = getTunnelUrl() || getTelegramUiUrl?.() || null;
|
|
1014
1070
|
telegramUiUrl = currentUiUrl;
|
|
1015
1071
|
telegramWebAppUrl = getTelegramWebAppUrl(currentUiUrl);
|
|
1016
1072
|
return {
|
|
@@ -1382,9 +1438,10 @@ async function sendReply(chatId, text, options = {}) {
|
|
|
1382
1438
|
async function sendDirect(chatId, text, options = {}) {
|
|
1383
1439
|
if (!telegramToken) return null;
|
|
1384
1440
|
const skipSticky = options.skipSticky;
|
|
1441
|
+
const normalizedText = formatTelegramIconTokens(text, { button: false });
|
|
1385
1442
|
|
|
1386
1443
|
// Split long messages
|
|
1387
|
-
const chunks = splitMessage(
|
|
1444
|
+
const chunks = splitMessage(normalizedText, MAX_MESSAGE_LEN);
|
|
1388
1445
|
let lastMessageId = null;
|
|
1389
1446
|
for (const chunk of chunks) {
|
|
1390
1447
|
const payload = {
|
|
@@ -1454,12 +1511,13 @@ async function sendDirect(chatId, text, options = {}) {
|
|
|
1454
1511
|
*/
|
|
1455
1512
|
async function editDirect(chatId, messageId, text, options = {}) {
|
|
1456
1513
|
if (!telegramToken || !messageId) return messageId;
|
|
1514
|
+
const normalizedText = formatTelegramIconTokens(text, { button: false });
|
|
1457
1515
|
|
|
1458
1516
|
// Telegram editMessageText has 4096 char limit — truncate if needed
|
|
1459
1517
|
const truncated =
|
|
1460
|
-
|
|
1461
|
-
?
|
|
1462
|
-
:
|
|
1518
|
+
normalizedText.length > MAX_MESSAGE_LEN
|
|
1519
|
+
? normalizedText.slice(0, MAX_MESSAGE_LEN - 20) + "\n\n…(truncated)"
|
|
1520
|
+
: normalizedText;
|
|
1463
1521
|
|
|
1464
1522
|
const payload = {
|
|
1465
1523
|
chat_id: chatId,
|
|
@@ -1784,31 +1842,31 @@ function summarizeAction(event) {
|
|
|
1784
1842
|
const desc = summarizeCommand(item.command);
|
|
1785
1843
|
const target = extractTarget(item.command);
|
|
1786
1844
|
return {
|
|
1787
|
-
icon: "
|
|
1845
|
+
icon: ":zap:",
|
|
1788
1846
|
text: target ? `${desc} → ${target}` : desc,
|
|
1789
1847
|
phase: "running",
|
|
1790
1848
|
};
|
|
1791
1849
|
}
|
|
1792
1850
|
case "mcp_tool_call":
|
|
1793
1851
|
return {
|
|
1794
|
-
icon: "
|
|
1852
|
+
icon: ":plug:",
|
|
1795
1853
|
text: `MCP: ${item.server}/${item.tool}`,
|
|
1796
1854
|
phase: "running",
|
|
1797
1855
|
};
|
|
1798
1856
|
case "reasoning":
|
|
1799
1857
|
return item.text
|
|
1800
|
-
? { icon: "
|
|
1858
|
+
? { icon: ":u1f4ad:", text: item.text.slice(0, 200), phase: "thinking" }
|
|
1801
1859
|
: null;
|
|
1802
1860
|
case "web_search":
|
|
1803
1861
|
return {
|
|
1804
|
-
icon: "
|
|
1862
|
+
icon: ":search:",
|
|
1805
1863
|
text: `Searching: ${item.query?.slice(0, 80)}`,
|
|
1806
1864
|
phase: "searching",
|
|
1807
1865
|
};
|
|
1808
1866
|
case "todo_list":
|
|
1809
1867
|
return item.items?.length
|
|
1810
1868
|
? {
|
|
1811
|
-
icon: "
|
|
1869
|
+
icon: ":clipboard:",
|
|
1812
1870
|
text: `Planning ${item.items.length} steps`,
|
|
1813
1871
|
phase: "planning",
|
|
1814
1872
|
}
|
|
@@ -1827,7 +1885,7 @@ function summarizeAction(event) {
|
|
|
1827
1885
|
const target = extractTarget(item.command);
|
|
1828
1886
|
const label = target ? `${desc} → ${target}` : desc;
|
|
1829
1887
|
return {
|
|
1830
|
-
icon: ok ? "
|
|
1888
|
+
icon: ok ? ":check:" : ":close:",
|
|
1831
1889
|
text: label + (ok ? "" : ` (exit ${item.exit_code})`),
|
|
1832
1890
|
phase: "done",
|
|
1833
1891
|
};
|
|
@@ -1837,7 +1895,7 @@ function summarizeAction(event) {
|
|
|
1837
1895
|
const fileDescs = item.changes.map((c) => {
|
|
1838
1896
|
const name = shortPath(c.path);
|
|
1839
1897
|
const kind =
|
|
1840
|
-
c.kind === "add" ? "
|
|
1898
|
+
c.kind === "add" ? ":plus:" : c.kind === "delete" ? ":trash:" : ":edit:";
|
|
1841
1899
|
// Show line counts if available
|
|
1842
1900
|
const adds = c.additions ?? c.lines_added ?? 0;
|
|
1843
1901
|
const dels = c.deletions ?? c.lines_deleted ?? 0;
|
|
@@ -1845,7 +1903,7 @@ function summarizeAction(event) {
|
|
|
1845
1903
|
return `${kind} ${name}${stats}`;
|
|
1846
1904
|
});
|
|
1847
1905
|
return {
|
|
1848
|
-
icon: "
|
|
1906
|
+
icon: ":folder:",
|
|
1849
1907
|
text: fileDescs.join(", "),
|
|
1850
1908
|
phase: "done",
|
|
1851
1909
|
detail: "file_change",
|
|
@@ -1862,7 +1920,7 @@ function summarizeAction(event) {
|
|
|
1862
1920
|
case "mcp_tool_call": {
|
|
1863
1921
|
const ok = item.status === "completed";
|
|
1864
1922
|
return {
|
|
1865
|
-
icon: ok ? "
|
|
1923
|
+
icon: ok ? ":check:" : ":close:",
|
|
1866
1924
|
text: `MCP ${item.server}/${item.tool}: ${ok ? "done" : "failed"}`,
|
|
1867
1925
|
phase: "done",
|
|
1868
1926
|
};
|
|
@@ -1878,14 +1936,14 @@ function summarizeAction(event) {
|
|
|
1878
1936
|
case "assistant.reasoning_delta": {
|
|
1879
1937
|
const text = event.data?.content || event.data?.deltaContent || "";
|
|
1880
1938
|
return text
|
|
1881
|
-
? { icon: "
|
|
1939
|
+
? { icon: ":u1f4ad:", text: text.slice(0, 200), phase: "thinking" }
|
|
1882
1940
|
: null;
|
|
1883
1941
|
}
|
|
1884
1942
|
|
|
1885
1943
|
case "tool.execution_start": {
|
|
1886
1944
|
const { toolName, input } = getCopilotToolInfo(event);
|
|
1887
1945
|
return {
|
|
1888
|
-
icon: "
|
|
1946
|
+
icon: ":u1f6e0:",
|
|
1889
1947
|
text: summarizeCopilotTool(toolName, input),
|
|
1890
1948
|
phase: "running",
|
|
1891
1949
|
};
|
|
@@ -1899,7 +1957,7 @@ function summarizeAction(event) {
|
|
|
1899
1957
|
String(status).toLowerCase(),
|
|
1900
1958
|
);
|
|
1901
1959
|
return {
|
|
1902
|
-
icon: ok ? "
|
|
1960
|
+
icon: ok ? ":check:" : ":close:",
|
|
1903
1961
|
text: summarizeCopilotTool(toolName, input) + (ok ? "" : " (failed)"),
|
|
1904
1962
|
phase: "done",
|
|
1905
1963
|
};
|
|
@@ -1907,7 +1965,7 @@ function summarizeAction(event) {
|
|
|
1907
1965
|
|
|
1908
1966
|
case "session.error":
|
|
1909
1967
|
return {
|
|
1910
|
-
icon: "
|
|
1968
|
+
icon: ":close:",
|
|
1911
1969
|
text: `Failed: ${event.data?.message || "unknown"}`,
|
|
1912
1970
|
phase: "error",
|
|
1913
1971
|
};
|
|
@@ -1915,12 +1973,12 @@ function summarizeAction(event) {
|
|
|
1915
1973
|
case "item.updated": {
|
|
1916
1974
|
const item = event.item;
|
|
1917
1975
|
if (item.type === "reasoning" && item.text) {
|
|
1918
|
-
return { icon: "
|
|
1976
|
+
return { icon: ":u1f4ad:", text: item.text.slice(0, 200), phase: "thinking" };
|
|
1919
1977
|
}
|
|
1920
1978
|
if (item.type === "todo_list" && item.items) {
|
|
1921
1979
|
const done = item.items.filter((t) => t.completed).length;
|
|
1922
1980
|
return {
|
|
1923
|
-
icon: "
|
|
1981
|
+
icon: ":clipboard:",
|
|
1924
1982
|
text: `Progress: ${done}/${item.items.length} steps`,
|
|
1925
1983
|
phase: "planning",
|
|
1926
1984
|
};
|
|
@@ -1930,7 +1988,7 @@ function summarizeAction(event) {
|
|
|
1930
1988
|
|
|
1931
1989
|
case "turn.failed":
|
|
1932
1990
|
return {
|
|
1933
|
-
icon: "
|
|
1991
|
+
icon: ":close:",
|
|
1934
1992
|
text: `Failed: ${event.error?.message || "unknown"}`,
|
|
1935
1993
|
phase: "error",
|
|
1936
1994
|
};
|
|
@@ -2073,6 +2131,158 @@ function extractGoPackages(command) {
|
|
|
2073
2131
|
return unique.slice(0, 3).join(" ") + (unique.length > 3 ? " …" : "");
|
|
2074
2132
|
}
|
|
2075
2133
|
|
|
2134
|
+
const TELEGRAM_ICON_TOKEN_LABELS = Object.freeze({
|
|
2135
|
+
check: "OK",
|
|
2136
|
+
close: "Close",
|
|
2137
|
+
alert: "Alert",
|
|
2138
|
+
pause: "Pause",
|
|
2139
|
+
play: "Run",
|
|
2140
|
+
stop: "Stop",
|
|
2141
|
+
refresh: "Refresh",
|
|
2142
|
+
chart: "Status",
|
|
2143
|
+
clipboard: "Tasks",
|
|
2144
|
+
bot: "Agents",
|
|
2145
|
+
git: "Workspaces",
|
|
2146
|
+
settings: "Executor",
|
|
2147
|
+
server: "Routing",
|
|
2148
|
+
folder: "Logs",
|
|
2149
|
+
file: "Commands",
|
|
2150
|
+
phone: "Control Center",
|
|
2151
|
+
globe: "Browser",
|
|
2152
|
+
heart: "Health",
|
|
2153
|
+
cpu: "Session",
|
|
2154
|
+
chat: "Ask",
|
|
2155
|
+
hash: "Parallel",
|
|
2156
|
+
repeat: "Retry",
|
|
2157
|
+
beaker: "Executors",
|
|
2158
|
+
compass: "Tasks",
|
|
2159
|
+
target: "Coordinator",
|
|
2160
|
+
workflow: "Flow",
|
|
2161
|
+
arrowRight: "Back",
|
|
2162
|
+
plus: "New",
|
|
2163
|
+
menu: "Menu",
|
|
2164
|
+
lock: "Lock",
|
|
2165
|
+
unlock: "Unlock",
|
|
2166
|
+
search: "Search",
|
|
2167
|
+
link: "Link",
|
|
2168
|
+
upload: "Upload",
|
|
2169
|
+
download: "Download",
|
|
2170
|
+
box: "SDK",
|
|
2171
|
+
bell: "Alerts",
|
|
2172
|
+
lightbulb: "Tips",
|
|
2173
|
+
rocket: "Start",
|
|
2174
|
+
home: "Home",
|
|
2175
|
+
pin: "Pin",
|
|
2176
|
+
star: "Star",
|
|
2177
|
+
help: "Help",
|
|
2178
|
+
cloud: "Cloud",
|
|
2179
|
+
});
|
|
2180
|
+
|
|
2181
|
+
const TELEGRAM_ICON_TOKEN_EMOJI = Object.freeze({
|
|
2182
|
+
check: "✅",
|
|
2183
|
+
close: "❌",
|
|
2184
|
+
alert: "⚠️",
|
|
2185
|
+
pause: "⏸️",
|
|
2186
|
+
play: "▶️",
|
|
2187
|
+
stop: "⏹️",
|
|
2188
|
+
refresh: "🔄",
|
|
2189
|
+
chart: "📊",
|
|
2190
|
+
clipboard: "📋",
|
|
2191
|
+
bot: "🤖",
|
|
2192
|
+
git: "🌿",
|
|
2193
|
+
settings: "⚙️",
|
|
2194
|
+
server: "🖧",
|
|
2195
|
+
folder: "📁",
|
|
2196
|
+
file: "📄",
|
|
2197
|
+
phone: "📱",
|
|
2198
|
+
globe: "🌐",
|
|
2199
|
+
heart: "❤️",
|
|
2200
|
+
cpu: "🧠",
|
|
2201
|
+
chat: "💬",
|
|
2202
|
+
hash: "#️⃣",
|
|
2203
|
+
repeat: "🔁",
|
|
2204
|
+
beaker: "🧪",
|
|
2205
|
+
compass: "🧭",
|
|
2206
|
+
target: "🎯",
|
|
2207
|
+
workflow: "🧩",
|
|
2208
|
+
arrowright: "➡️",
|
|
2209
|
+
plus: "➕",
|
|
2210
|
+
menu: "☰",
|
|
2211
|
+
lock: "🔒",
|
|
2212
|
+
unlock: "🔓",
|
|
2213
|
+
search: "🔍",
|
|
2214
|
+
link: "🔗",
|
|
2215
|
+
upload: "📤",
|
|
2216
|
+
download: "📥",
|
|
2217
|
+
box: "📦",
|
|
2218
|
+
bell: "🔔",
|
|
2219
|
+
lightbulb: "💡",
|
|
2220
|
+
rocket: "🚀",
|
|
2221
|
+
home: "🏠",
|
|
2222
|
+
pin: "📌",
|
|
2223
|
+
star: "⭐",
|
|
2224
|
+
help: "❓",
|
|
2225
|
+
cloud: "☁️",
|
|
2226
|
+
monitor: "🖥️",
|
|
2227
|
+
eye: "👁️",
|
|
2228
|
+
edit: "✏️",
|
|
2229
|
+
trash: "🗑️",
|
|
2230
|
+
dot: "•",
|
|
2231
|
+
});
|
|
2232
|
+
|
|
2233
|
+
function decodeUnicodeIconToken(name) {
|
|
2234
|
+
const raw = String(name || "").trim();
|
|
2235
|
+
const match = raw.match(/^u([0-9a-f]{4,6})$/i);
|
|
2236
|
+
if (!match) return "";
|
|
2237
|
+
const codePoint = Number.parseInt(match[1], 16);
|
|
2238
|
+
if (!Number.isFinite(codePoint)) return "";
|
|
2239
|
+
try {
|
|
2240
|
+
return String.fromCodePoint(codePoint);
|
|
2241
|
+
} catch {
|
|
2242
|
+
return "";
|
|
2243
|
+
}
|
|
2244
|
+
}
|
|
2245
|
+
|
|
2246
|
+
function resolveTelegramIconTokenGlyph(name) {
|
|
2247
|
+
const raw = String(name || "").trim();
|
|
2248
|
+
if (!raw) return "";
|
|
2249
|
+
const lowered = raw.toLowerCase();
|
|
2250
|
+
const squashed = lowered.replace(/[_-]+/g, "");
|
|
2251
|
+
const glyph = TELEGRAM_ICON_TOKEN_EMOJI[lowered]
|
|
2252
|
+
|| TELEGRAM_ICON_TOKEN_EMOJI[squashed]
|
|
2253
|
+
|| decodeUnicodeIconToken(lowered)
|
|
2254
|
+
|| decodeUnicodeIconToken(squashed);
|
|
2255
|
+
return glyph || "";
|
|
2256
|
+
}
|
|
2257
|
+
|
|
2258
|
+
function humanizeIconTokenName(name) {
|
|
2259
|
+
const spaced = String(name || "")
|
|
2260
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
|
|
2261
|
+
.replace(/[_-]+/g, " ")
|
|
2262
|
+
.trim();
|
|
2263
|
+
if (!spaced) return "";
|
|
2264
|
+
return spaced
|
|
2265
|
+
.split(/\s+/)
|
|
2266
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
2267
|
+
.join(" ");
|
|
2268
|
+
}
|
|
2269
|
+
|
|
2270
|
+
function formatTelegramIconTokens(value, { button = false } = {}) {
|
|
2271
|
+
if (value == null) return value;
|
|
2272
|
+
const str = String(value);
|
|
2273
|
+
return str.replace(/:([a-zA-Z][a-zA-Z0-9_-]*):/g, (_match, rawName) => {
|
|
2274
|
+
const tokenName = String(rawName || "");
|
|
2275
|
+
const glyph = resolveTelegramIconTokenGlyph(tokenName);
|
|
2276
|
+
if (glyph) return glyph;
|
|
2277
|
+
const key = tokenName.toLowerCase();
|
|
2278
|
+
const label = TELEGRAM_ICON_TOKEN_LABELS[tokenName]
|
|
2279
|
+
|| TELEGRAM_ICON_TOKEN_LABELS[key]
|
|
2280
|
+
|| humanizeIconTokenName(tokenName);
|
|
2281
|
+
if (!label) return "";
|
|
2282
|
+
return button ? label : `[${label}]`;
|
|
2283
|
+
});
|
|
2284
|
+
}
|
|
2285
|
+
|
|
2076
2286
|
/**
|
|
2077
2287
|
* Strip web_app buttons whose URL is not HTTPS — Telegram rejects non-HTTPS
|
|
2078
2288
|
* web_app URLs with a 400 error. Returns the sanitized reply_markup object.
|
|
@@ -2081,7 +2291,19 @@ function sanitizeWebAppButtons(markup) {
|
|
|
2081
2291
|
if (!markup || !markup.inline_keyboard) return markup;
|
|
2082
2292
|
const filtered = markup.inline_keyboard
|
|
2083
2293
|
.map((row) =>
|
|
2084
|
-
row
|
|
2294
|
+
row
|
|
2295
|
+
.map((btn) => {
|
|
2296
|
+
if (!btn || typeof btn !== "object") return btn;
|
|
2297
|
+
if (typeof btn.text === "string") {
|
|
2298
|
+
return {
|
|
2299
|
+
...btn,
|
|
2300
|
+
text: formatTelegramIconTokens(btn.text, { button: true }),
|
|
2301
|
+
};
|
|
2302
|
+
}
|
|
2303
|
+
return btn;
|
|
2304
|
+
})
|
|
2305
|
+
.filter((btn) => {
|
|
2306
|
+
if (!btn || typeof btn !== "object") return false;
|
|
2085
2307
|
if (btn.web_app && btn.web_app.url) {
|
|
2086
2308
|
try {
|
|
2087
2309
|
const u = new URL(btn.web_app.url);
|
|
@@ -2090,8 +2312,8 @@ function sanitizeWebAppButtons(markup) {
|
|
|
2090
2312
|
return false;
|
|
2091
2313
|
}
|
|
2092
2314
|
}
|
|
2093
|
-
|
|
2094
|
-
|
|
2315
|
+
return true;
|
|
2316
|
+
}),
|
|
2095
2317
|
)
|
|
2096
2318
|
.filter((row) => row.length > 0);
|
|
2097
2319
|
return { ...markup, inline_keyboard: filtered };
|
|
@@ -2360,7 +2582,7 @@ async function handleCallbackQuery(query) {
|
|
|
2360
2582
|
if (data === "cb:confirm_restart") {
|
|
2361
2583
|
await sendReply(
|
|
2362
2584
|
chatId,
|
|
2363
|
-
"
|
|
2585
|
+
":alert: Restart will stop the orchestrator process and let the monitor respawn it.\nProceed?",
|
|
2364
2586
|
{ reply_markup: buildConfirmKeyboard("cb:do_restart", "Confirm Restart") },
|
|
2365
2587
|
);
|
|
2366
2588
|
return;
|
|
@@ -2403,7 +2625,7 @@ async function handleCallbackQuery(query) {
|
|
|
2403
2625
|
if (query.message?.message_id) {
|
|
2404
2626
|
await deleteDirect(chatId, query.message.message_id);
|
|
2405
2627
|
}
|
|
2406
|
-
await sendReply(chatId, "
|
|
2628
|
+
await sendReply(chatId, ":clock: That action expired. Please try again.");
|
|
2407
2629
|
return;
|
|
2408
2630
|
}
|
|
2409
2631
|
uiTokenRegistry.delete(token);
|
|
@@ -2417,12 +2639,12 @@ async function handleCallbackQuery(query) {
|
|
|
2417
2639
|
if (data === "fw:open") {
|
|
2418
2640
|
const fwState = getFirewallState();
|
|
2419
2641
|
if (!fwState || !fwState.blocked) {
|
|
2420
|
-
await sendReply(chatId, "
|
|
2642
|
+
await sendReply(chatId, ":check: Port is already open or no firewall detected.");
|
|
2421
2643
|
return;
|
|
2422
2644
|
}
|
|
2423
2645
|
await sendReply(
|
|
2424
2646
|
chatId,
|
|
2425
|
-
|
|
2647
|
+
`:settings: Attempting to open port via \`${fwState.firewall}\`...\n` +
|
|
2426
2648
|
`Please enter your admin password on the server if prompted.`,
|
|
2427
2649
|
{ parseMode: "Markdown" },
|
|
2428
2650
|
);
|
|
@@ -2430,11 +2652,11 @@ async function handleCallbackQuery(query) {
|
|
|
2430
2652
|
Number(new URL(getTelegramUiUrl() || "http://localhost:5511").port || 5511),
|
|
2431
2653
|
);
|
|
2432
2654
|
if (result.success) {
|
|
2433
|
-
await sendReply(chatId,
|
|
2655
|
+
await sendReply(chatId, `:check: ${result.message}\nThe Control Center should now be reachable.`);
|
|
2434
2656
|
} else {
|
|
2435
2657
|
await sendReply(
|
|
2436
2658
|
chatId,
|
|
2437
|
-
|
|
2659
|
+
`:alert: Auto-fix failed.\n\n${result.message}`,
|
|
2438
2660
|
{ parseMode: "Markdown" },
|
|
2439
2661
|
);
|
|
2440
2662
|
}
|
|
@@ -2456,7 +2678,7 @@ async function transcribeAndProcessVoice(voiceFile, chatId) {
|
|
|
2456
2678
|
|| "";
|
|
2457
2679
|
|
|
2458
2680
|
if (!apiKey) {
|
|
2459
|
-
await sendReply(chatId, "
|
|
2681
|
+
await sendReply(chatId, ":alert: Voice messages require an OpenAI API key for transcription. Set OPENAI_API_KEY in your configuration.");
|
|
2460
2682
|
return;
|
|
2461
2683
|
}
|
|
2462
2684
|
|
|
@@ -2505,13 +2727,13 @@ async function transcribeAndProcessVoice(voiceFile, chatId) {
|
|
|
2505
2727
|
|
|
2506
2728
|
const { text: transcribedText } = await whisperRes.json();
|
|
2507
2729
|
if (!transcribedText?.trim()) {
|
|
2508
|
-
await sendReply(chatId, "
|
|
2730
|
+
await sendReply(chatId, ":mic: Could not transcribe voice message (empty result).");
|
|
2509
2731
|
return;
|
|
2510
2732
|
}
|
|
2511
2733
|
|
|
2512
2734
|
// 4. Show transcription and process as free text
|
|
2513
2735
|
console.log(`[telegram-bot] voice transcription: "${transcribedText.slice(0, 80)}"`);
|
|
2514
|
-
await sendReply(chatId,
|
|
2736
|
+
await sendReply(chatId, `:mic: _${transcribedText}_`, { parseMode: "Markdown" });
|
|
2515
2737
|
|
|
2516
2738
|
// Route through the same free-text handler
|
|
2517
2739
|
if (transcribedText.startsWith("/")) {
|
|
@@ -2576,7 +2798,7 @@ async function handleUpdate(update) {
|
|
|
2576
2798
|
await transcribeAndProcessVoice(voiceFile, chatId);
|
|
2577
2799
|
} catch (err) {
|
|
2578
2800
|
console.error(`[telegram-bot] voice processing error:`, err.message);
|
|
2579
|
-
await sendReply(chatId,
|
|
2801
|
+
await sendReply(chatId, `:alert: Could not process voice message: ${err.message}`);
|
|
2580
2802
|
}
|
|
2581
2803
|
return;
|
|
2582
2804
|
}
|
|
@@ -2592,7 +2814,7 @@ async function handleUpdate(update) {
|
|
|
2592
2814
|
const cmdText = text.split(/\s+/)[0].toLowerCase().replace(/@\w+/, "");
|
|
2593
2815
|
if (cmdText === "/cancel") {
|
|
2594
2816
|
clearPendingUiInput(chatId);
|
|
2595
|
-
await sendReply(chatId, "
|
|
2817
|
+
await sendReply(chatId, ":check: Input cancelled.");
|
|
2596
2818
|
return;
|
|
2597
2819
|
}
|
|
2598
2820
|
if (!text.startsWith("/")) {
|
|
@@ -2637,7 +2859,7 @@ async function cmdPauseTasks(chatId) {
|
|
|
2637
2859
|
if (!executor) {
|
|
2638
2860
|
return sendDirect(
|
|
2639
2861
|
chatId,
|
|
2640
|
-
"
|
|
2862
|
+
":alert: Internal executor not enabled — nothing to pause.",
|
|
2641
2863
|
);
|
|
2642
2864
|
}
|
|
2643
2865
|
if (executor.isPaused()) {
|
|
@@ -2645,12 +2867,12 @@ async function cmdPauseTasks(chatId) {
|
|
|
2645
2867
|
const dur = info.pauseDuration;
|
|
2646
2868
|
return sendDirect(
|
|
2647
2869
|
chatId,
|
|
2648
|
-
|
|
2870
|
+
`:pause: Already paused (${dur >= 60 ? Math.round(dur / 60) + "m" : dur + "s"} ago).\nUse /resumetasks to resume.`,
|
|
2649
2871
|
);
|
|
2650
2872
|
}
|
|
2651
2873
|
executor.pause();
|
|
2652
2874
|
const status = executor.getStatus();
|
|
2653
|
-
const lines = [
|
|
2875
|
+
const lines = [`:pause: *Task executor paused*`];
|
|
2654
2876
|
if (status.activeSlots > 0) {
|
|
2655
2877
|
lines.push(
|
|
2656
2878
|
`\n${status.activeSlots} running task(s) will continue to completion.`,
|
|
@@ -2662,8 +2884,8 @@ async function cmdPauseTasks(chatId) {
|
|
|
2662
2884
|
const keyboard = {
|
|
2663
2885
|
inline_keyboard: [
|
|
2664
2886
|
[
|
|
2665
|
-
{ text: "
|
|
2666
|
-
{ text: "
|
|
2887
|
+
{ text: ":play: Resume Tasks", callback_data: "cb:confirm_resume" },
|
|
2888
|
+
{ text: ":chart: Status", callback_data: "/status" },
|
|
2667
2889
|
],
|
|
2668
2890
|
],
|
|
2669
2891
|
};
|
|
@@ -2681,11 +2903,11 @@ async function cmdResumeTasks(chatId) {
|
|
|
2681
2903
|
if (!executor) {
|
|
2682
2904
|
return sendDirect(
|
|
2683
2905
|
chatId,
|
|
2684
|
-
"
|
|
2906
|
+
":alert: Internal executor not enabled — nothing to resume.",
|
|
2685
2907
|
);
|
|
2686
2908
|
}
|
|
2687
2909
|
if (!executor.isPaused()) {
|
|
2688
|
-
return sendDirect(chatId, "
|
|
2910
|
+
return sendDirect(chatId, ":play: Executor is already running — not paused.");
|
|
2689
2911
|
}
|
|
2690
2912
|
const info = executor.getPauseInfo();
|
|
2691
2913
|
const dur = info.pauseDuration;
|
|
@@ -2694,14 +2916,14 @@ async function cmdResumeTasks(chatId) {
|
|
|
2694
2916
|
const keyboard = {
|
|
2695
2917
|
inline_keyboard: [
|
|
2696
2918
|
[
|
|
2697
|
-
{ text: "
|
|
2698
|
-
{ text: "
|
|
2919
|
+
{ text: ":pause: Pause Tasks", callback_data: "cb:confirm_pause" },
|
|
2920
|
+
{ text: ":clipboard: Tasks", callback_data: "/tasks" },
|
|
2699
2921
|
],
|
|
2700
2922
|
],
|
|
2701
2923
|
};
|
|
2702
2924
|
return sendDirect(
|
|
2703
2925
|
chatId,
|
|
2704
|
-
|
|
2926
|
+
`:play: *Task executor resumed* (was paused for ${durStr}).\nWill pick up tasks on next poll cycle.`,
|
|
2705
2927
|
{ parse_mode: "Markdown", reply_markup: keyboard },
|
|
2706
2928
|
);
|
|
2707
2929
|
}
|
|
@@ -2721,7 +2943,7 @@ async function cmdRepos(chatId, _text) {
|
|
|
2721
2943
|
return sendDirect(
|
|
2722
2944
|
chatId,
|
|
2723
2945
|
[
|
|
2724
|
-
"
|
|
2946
|
+
":folder: *Repositories*",
|
|
2725
2947
|
"",
|
|
2726
2948
|
`Active: \`${config.repoSlug || config.repoRoot || "current directory"}\``,
|
|
2727
2949
|
"",
|
|
@@ -2748,7 +2970,7 @@ async function cmdRepos(chatId, _text) {
|
|
|
2748
2970
|
);
|
|
2749
2971
|
}
|
|
2750
2972
|
|
|
2751
|
-
const lines = ["
|
|
2973
|
+
const lines = [":folder: *Repositories*", ""];
|
|
2752
2974
|
if (activeWorkspace) {
|
|
2753
2975
|
lines.push(`Workspace: \`${activeWorkspace}\``, "");
|
|
2754
2976
|
}
|
|
@@ -2758,7 +2980,7 @@ async function cmdRepos(chatId, _text) {
|
|
|
2758
2980
|
repo.name === selected?.name ||
|
|
2759
2981
|
repo.slug === selected?.slug ||
|
|
2760
2982
|
repo.primary;
|
|
2761
|
-
const icon = isCurrent ? "
|
|
2983
|
+
const icon = isCurrent ? ":dot:" : ":dot:";
|
|
2762
2984
|
const primary = repo.primary ? " _(primary)_" : "";
|
|
2763
2985
|
lines.push(
|
|
2764
2986
|
`${icon} \`${repo.name}\` — ${repo.slug || repo.path || "?"}${primary}`,
|
|
@@ -2770,7 +2992,7 @@ async function cmdRepos(chatId, _text) {
|
|
|
2770
2992
|
|
|
2771
2993
|
return sendDirect(chatId, lines.join("\n"), { parse_mode: "Markdown" });
|
|
2772
2994
|
} catch (err) {
|
|
2773
|
-
return sendDirect(chatId,
|
|
2995
|
+
return sendDirect(chatId, `:close: Failed to read repo config: ${err.message}`);
|
|
2774
2996
|
}
|
|
2775
2997
|
}
|
|
2776
2998
|
|
|
@@ -2802,7 +3024,7 @@ async function cmdWorkspace(chatId, text) {
|
|
|
2802
3024
|
await sendReply(
|
|
2803
3025
|
chatId,
|
|
2804
3026
|
[
|
|
2805
|
-
"
|
|
3027
|
+
":search: Workspace Scan Complete",
|
|
2806
3028
|
`Scanned: ${merged.scanned}`,
|
|
2807
3029
|
`Added: ${merged.added}`,
|
|
2808
3030
|
`Updated: ${merged.updated}`,
|
|
@@ -2823,7 +3045,7 @@ async function cmdWorkspace(chatId, text) {
|
|
|
2823
3045
|
const active = getActiveLocalWorkspace(configDir);
|
|
2824
3046
|
await sendReply(
|
|
2825
3047
|
chatId,
|
|
2826
|
-
|
|
3048
|
+
`:check: Active workspace: ${active?.name || targetId} (\`${active?.id || targetId}\`)`,
|
|
2827
3049
|
);
|
|
2828
3050
|
return;
|
|
2829
3051
|
}
|
|
@@ -2837,7 +3059,7 @@ async function cmdWorkspace(chatId, text) {
|
|
|
2837
3059
|
const ws = createManagedWs(configDir, { name });
|
|
2838
3060
|
await sendReply(
|
|
2839
3061
|
chatId,
|
|
2840
|
-
|
|
3062
|
+
`:check: Created workspace: *${ws.name}* (\`${ws.id}\`)\nPath: \`${ws.path}\``,
|
|
2841
3063
|
{ parseMode: "Markdown" },
|
|
2842
3064
|
);
|
|
2843
3065
|
return;
|
|
@@ -2851,14 +3073,14 @@ async function cmdWorkspace(chatId, text) {
|
|
|
2851
3073
|
await sendReply(chatId, "Usage: /workspace add-repo <wsId> <gitUrl> [branch]");
|
|
2852
3074
|
return;
|
|
2853
3075
|
}
|
|
2854
|
-
await sendReply(chatId,
|
|
3076
|
+
await sendReply(chatId, `:clock: Cloning ${gitUrl} into workspace ${wsId}...`);
|
|
2855
3077
|
const repo = addRepoToManagedWs(configDir, wsId, {
|
|
2856
3078
|
url: gitUrl,
|
|
2857
3079
|
branch,
|
|
2858
3080
|
});
|
|
2859
3081
|
await sendReply(
|
|
2860
3082
|
chatId,
|
|
2861
|
-
|
|
3083
|
+
`:check: Added repo *${repo.name}* to workspace \`${wsId}\`\n${repo.cloned ? "Cloned from remote" : "Already existed on disk"}`,
|
|
2862
3084
|
{ parseMode: "Markdown" },
|
|
2863
3085
|
);
|
|
2864
3086
|
return;
|
|
@@ -2875,8 +3097,8 @@ async function cmdWorkspace(chatId, text) {
|
|
|
2875
3097
|
await sendReply(
|
|
2876
3098
|
chatId,
|
|
2877
3099
|
removed
|
|
2878
|
-
?
|
|
2879
|
-
:
|
|
3100
|
+
? `:check: Removed repo \`${repoName}\` from workspace \`${wsId}\``
|
|
3101
|
+
: `:alert: Repo \`${repoName}\` not found in workspace \`${wsId}\``,
|
|
2880
3102
|
);
|
|
2881
3103
|
return;
|
|
2882
3104
|
}
|
|
@@ -2889,18 +3111,18 @@ async function cmdWorkspace(chatId, text) {
|
|
|
2889
3111
|
await sendReply(chatId, "Usage: /workspace pull <wsId> (no active workspace)");
|
|
2890
3112
|
return;
|
|
2891
3113
|
}
|
|
2892
|
-
await sendReply(chatId,
|
|
3114
|
+
await sendReply(chatId, `:clock: Pulling repos in workspace ${active.name}...`);
|
|
2893
3115
|
const results = pullManagedWsRepos(configDir, active.id);
|
|
2894
3116
|
const lines = results.map(
|
|
2895
|
-
(r) => `${r.success ? "
|
|
3117
|
+
(r) => `${r.success ? ":check:" : ":close:"} ${r.name}${r.error ? ` — ${r.error}` : ""}`,
|
|
2896
3118
|
);
|
|
2897
3119
|
await sendReply(chatId, ["Pull results:", ...lines].join("\n"));
|
|
2898
3120
|
return;
|
|
2899
3121
|
}
|
|
2900
|
-
await sendReply(chatId,
|
|
3122
|
+
await sendReply(chatId, `:clock: Pulling repos in workspace ${wsId}...`);
|
|
2901
3123
|
const results = pullManagedWsRepos(configDir, wsId);
|
|
2902
3124
|
const lines = results.map(
|
|
2903
|
-
(r) => `${r.success ? "
|
|
3125
|
+
(r) => `${r.success ? ":check:" : ":close:"} ${r.name}${r.error ? ` — ${r.error}` : ""}`,
|
|
2904
3126
|
);
|
|
2905
3127
|
await sendReply(chatId, ["Pull results:", ...lines].join("\n"));
|
|
2906
3128
|
return;
|
|
@@ -2916,8 +3138,8 @@ async function cmdWorkspace(chatId, text) {
|
|
|
2916
3138
|
await sendReply(
|
|
2917
3139
|
chatId,
|
|
2918
3140
|
deleted
|
|
2919
|
-
?
|
|
2920
|
-
:
|
|
3141
|
+
? `:check: Removed workspace \`${wsId}\` from config (files preserved on disk)`
|
|
3142
|
+
: `:alert: Workspace \`${wsId}\` not found`,
|
|
2921
3143
|
);
|
|
2922
3144
|
return;
|
|
2923
3145
|
}
|
|
@@ -2929,7 +3151,7 @@ async function cmdWorkspace(chatId, text) {
|
|
|
2929
3151
|
await sendReply(
|
|
2930
3152
|
chatId,
|
|
2931
3153
|
[
|
|
2932
|
-
"
|
|
3154
|
+
":git: No local workspaces configured.",
|
|
2933
3155
|
`Expected directory: ${resolve(configDir, "workspaces")}`,
|
|
2934
3156
|
"",
|
|
2935
3157
|
"Quick start:",
|
|
@@ -2941,14 +3163,14 @@ async function cmdWorkspace(chatId, text) {
|
|
|
2941
3163
|
return;
|
|
2942
3164
|
}
|
|
2943
3165
|
|
|
2944
|
-
const lines = ["
|
|
3166
|
+
const lines = [":git: *Local Workspaces*", ""];
|
|
2945
3167
|
for (const workspace of workspaces) {
|
|
2946
|
-
const marker = workspace.id === active?.id ? "
|
|
3168
|
+
const marker = workspace.id === active?.id ? ":dot:" : ":dot:";
|
|
2947
3169
|
const repoCount = Array.isArray(workspace.repos) ? workspace.repos.length : 0;
|
|
2948
3170
|
lines.push(`${marker} *${workspace.name}* (\`${workspace.id}\`) — ${repoCount} repo(s)`);
|
|
2949
3171
|
for (const repo of (workspace.repos || []).slice(0, 4)) {
|
|
2950
|
-
const primary = repo.primary ? "
|
|
2951
|
-
const exists = repo.exists === false ? "
|
|
3172
|
+
const primary = repo.primary ? ":star: " : "";
|
|
3173
|
+
const exists = repo.exists === false ? ":close:" : ":check:";
|
|
2952
3174
|
lines.push(` ${exists} ${primary}${repo.name}${repo.slug ? ` (${repo.slug})` : ""}`);
|
|
2953
3175
|
}
|
|
2954
3176
|
if ((workspace.repos || []).length > 4) {
|
|
@@ -2964,7 +3186,7 @@ async function cmdWorkspace(chatId, text) {
|
|
|
2964
3186
|
lines.push("• /workspace scan");
|
|
2965
3187
|
await sendReply(chatId, lines.join("\n"), { parseMode: "Markdown" });
|
|
2966
3188
|
} catch (err) {
|
|
2967
|
-
await sendReply(chatId,
|
|
3189
|
+
await sendReply(chatId, `:close: Workspace command failed: ${err.message}`);
|
|
2968
3190
|
}
|
|
2969
3191
|
}
|
|
2970
3192
|
|
|
@@ -2974,13 +3196,13 @@ async function cmdWorkspace(chatId, text) {
|
|
|
2974
3196
|
async function cmdMaxParallel(chatId, text) {
|
|
2975
3197
|
const executor = _getInternalExecutor?.();
|
|
2976
3198
|
if (!executor) {
|
|
2977
|
-
return sendDirect(chatId, "
|
|
3199
|
+
return sendDirect(chatId, ":alert: Internal executor not enabled.");
|
|
2978
3200
|
}
|
|
2979
3201
|
const arg = (text || "").replace("/maxparallel", "").trim();
|
|
2980
3202
|
if (arg) {
|
|
2981
3203
|
const n = parseInt(arg, 10);
|
|
2982
3204
|
if (isNaN(n) || n < 0 || n > 20) {
|
|
2983
|
-
return sendDirect(chatId, "
|
|
3205
|
+
return sendDirect(chatId, ":alert: Provide a number between 0 and 20.");
|
|
2984
3206
|
}
|
|
2985
3207
|
const old = executor.maxParallel;
|
|
2986
3208
|
executor.maxParallel = n;
|
|
@@ -2988,18 +3210,18 @@ async function cmdMaxParallel(chatId, text) {
|
|
|
2988
3210
|
executor.pause();
|
|
2989
3211
|
return sendDirect(
|
|
2990
3212
|
chatId,
|
|
2991
|
-
|
|
3213
|
+
`:pause: Max parallel set to 0 — executor paused. Use /maxparallel <n> to resume.`,
|
|
2992
3214
|
);
|
|
2993
3215
|
}
|
|
2994
3216
|
if (executor.isPaused() && n > 0) {
|
|
2995
3217
|
executor.resume();
|
|
2996
3218
|
}
|
|
2997
|
-
return sendDirect(chatId,
|
|
3219
|
+
return sendDirect(chatId, `:check: Max parallel: ${old} → ${n}`);
|
|
2998
3220
|
}
|
|
2999
3221
|
const status = executor.getStatus();
|
|
3000
3222
|
return sendDirect(
|
|
3001
3223
|
chatId,
|
|
3002
|
-
|
|
3224
|
+
`:chart: Max parallel: ${status.maxParallel} (active: ${status.activeSlots})`,
|
|
3003
3225
|
);
|
|
3004
3226
|
}
|
|
3005
3227
|
|
|
@@ -3013,14 +3235,14 @@ async function cmdWhatsApp(chatId) {
|
|
|
3013
3235
|
if (!isWhatsAppEnabled()) {
|
|
3014
3236
|
return sendDirect(
|
|
3015
3237
|
chatId,
|
|
3016
|
-
"
|
|
3238
|
+
":dot: WhatsApp channel is not enabled.\n\nSet WHATSAPP_ENABLED=1 in your .env to enable.",
|
|
3017
3239
|
);
|
|
3018
3240
|
}
|
|
3019
3241
|
const status = getWhatsAppStatus();
|
|
3020
3242
|
const lines = [
|
|
3021
|
-
"
|
|
3243
|
+
":phone: <b>WhatsApp Channel Status</b>",
|
|
3022
3244
|
"",
|
|
3023
|
-
`Status: ${status.connected ? "
|
|
3245
|
+
`Status: ${status.connected ? ":dot: Connected" : ":dot: Disconnected"}`,
|
|
3024
3246
|
`Chat ID: <code>${status.chatId || "not set"}</code>`,
|
|
3025
3247
|
`Pending messages: ${status.pendingMessages || 0}`,
|
|
3026
3248
|
];
|
|
@@ -3030,7 +3252,7 @@ async function cmdWhatsApp(chatId) {
|
|
|
3030
3252
|
}
|
|
3031
3253
|
return sendDirect(chatId, lines.join("\n"), { parse_mode: "HTML" });
|
|
3032
3254
|
} catch (err) {
|
|
3033
|
-
return sendDirect(chatId,
|
|
3255
|
+
return sendDirect(chatId, `:close: WhatsApp status error: ${err.message}`);
|
|
3034
3256
|
}
|
|
3035
3257
|
}
|
|
3036
3258
|
|
|
@@ -3044,12 +3266,12 @@ async function cmdContainer(chatId) {
|
|
|
3044
3266
|
if (!isContainerEnabled()) {
|
|
3045
3267
|
return sendDirect(
|
|
3046
3268
|
chatId,
|
|
3047
|
-
"
|
|
3269
|
+
":dot: Container isolation is not enabled.\n\nSet CONTAINER_ENABLED=1 in your .env to enable.",
|
|
3048
3270
|
);
|
|
3049
3271
|
}
|
|
3050
3272
|
const status = getContainerStatus();
|
|
3051
3273
|
const lines = [
|
|
3052
|
-
"
|
|
3274
|
+
":box: <b>Container Runtime Status</b>",
|
|
3053
3275
|
"",
|
|
3054
3276
|
`Runtime: ${status.runtime || "detecting..."}`,
|
|
3055
3277
|
`Active containers: ${status.activeContainers || 0}`,
|
|
@@ -3060,7 +3282,7 @@ async function cmdContainer(chatId) {
|
|
|
3060
3282
|
}
|
|
3061
3283
|
return sendDirect(chatId, lines.join("\n"), { parse_mode: "HTML" });
|
|
3062
3284
|
} catch (err) {
|
|
3063
|
-
return sendDirect(chatId,
|
|
3285
|
+
return sendDirect(chatId, `:close: Container status error: ${err.message}`);
|
|
3064
3286
|
}
|
|
3065
3287
|
}
|
|
3066
3288
|
|
|
@@ -3071,6 +3293,11 @@ const COMMANDS = {
|
|
|
3071
3293
|
"/app": { handler: cmdApp, desc: "Open the Control Center Mini App" },
|
|
3072
3294
|
"/miniapp": { handler: cmdApp, desc: "Open the Control Center Mini App" },
|
|
3073
3295
|
"/webapp": { handler: cmdApp, desc: "Open the Control Center Mini App" },
|
|
3296
|
+
"/call": { handler: cmdCall, desc: "Open one-click voice meeting room" },
|
|
3297
|
+
"/videocall": {
|
|
3298
|
+
handler: cmdVideoCall,
|
|
3299
|
+
desc: "Open one-click video meeting room",
|
|
3300
|
+
},
|
|
3074
3301
|
"/cancel": { handler: cmdCancel, desc: "Cancel a pending input prompt" },
|
|
3075
3302
|
"/ask": { handler: cmdAsk, desc: "Send prompt to agent: /ask <prompt>" },
|
|
3076
3303
|
"/status": { handler: cmdStatus, desc: "Detailed orchestrator status" },
|
|
@@ -3456,6 +3683,8 @@ async function refreshMenuButton() {
|
|
|
3456
3683
|
|
|
3457
3684
|
const FAST_COMMANDS = new Set([
|
|
3458
3685
|
"/menu",
|
|
3686
|
+
"/call",
|
|
3687
|
+
"/videocall",
|
|
3459
3688
|
"/background",
|
|
3460
3689
|
"/status",
|
|
3461
3690
|
"/weekly",
|
|
@@ -3480,11 +3709,11 @@ const FAST_COMMANDS = new Set([
|
|
|
3480
3709
|
|
|
3481
3710
|
function getTelegramWebAppUrl(url) {
|
|
3482
3711
|
// Telegram Mini App must be HTTPS and publicly reachable.
|
|
3483
|
-
// Priority:
|
|
3712
|
+
// Priority: tunnel URL (permanent hostname) -> explicit env URL -> provided URL.
|
|
3713
|
+
const tUrl = getTunnelUrl();
|
|
3484
3714
|
const explicit =
|
|
3485
3715
|
process.env.TELEGRAM_WEBAPP_URL || process.env.TELEGRAM_UI_BASE_URL || "";
|
|
3486
|
-
const
|
|
3487
|
-
const candidates = [explicit, tUrl, url];
|
|
3716
|
+
const candidates = [tUrl, explicit, url];
|
|
3488
3717
|
|
|
3489
3718
|
for (const candidate of candidates) {
|
|
3490
3719
|
const normalized = String(candidate || "")
|
|
@@ -3534,7 +3763,7 @@ async function handleCommand(text, chatId) {
|
|
|
3534
3763
|
try {
|
|
3535
3764
|
await entry.handler(chatId, cmdArgs);
|
|
3536
3765
|
} catch (err) {
|
|
3537
|
-
await sendReply(chatId,
|
|
3766
|
+
await sendReply(chatId, `:close: Command error: ${err.message}`);
|
|
3538
3767
|
}
|
|
3539
3768
|
} else {
|
|
3540
3769
|
await sendReply(
|
|
@@ -3805,10 +4034,11 @@ function uiTokenAction(token) {
|
|
|
3805
4034
|
}
|
|
3806
4035
|
|
|
3807
4036
|
function uiButton(text, action) {
|
|
4037
|
+
const normalizedText = formatTelegramIconTokens(text, { button: true });
|
|
3808
4038
|
if (typeof action === "string" && (action.startsWith("cb:") || action.startsWith("ui:"))) {
|
|
3809
|
-
return { text, callback_data: action };
|
|
4039
|
+
return { text: normalizedText, callback_data: action };
|
|
3810
4040
|
}
|
|
3811
|
-
return { text, callback_data: uiCallback(action) };
|
|
4041
|
+
return { text: normalizedText, callback_data: uiCallback(action) };
|
|
3812
4042
|
}
|
|
3813
4043
|
|
|
3814
4044
|
function buildKeyboard(rows) {
|
|
@@ -3819,8 +4049,8 @@ function buildConfirmKeyboard(confirmId, confirmLabel = "Confirm") {
|
|
|
3819
4049
|
return {
|
|
3820
4050
|
inline_keyboard: [
|
|
3821
4051
|
[
|
|
3822
|
-
{ text:
|
|
3823
|
-
{ text: "
|
|
4052
|
+
{ text: `:check: ${confirmLabel}`, callback_data: confirmId },
|
|
4053
|
+
{ text: ":close: Cancel", callback_data: "cb:dismiss" },
|
|
3824
4054
|
],
|
|
3825
4055
|
],
|
|
3826
4056
|
};
|
|
@@ -3829,12 +4059,12 @@ function buildConfirmKeyboard(confirmId, confirmLabel = "Confirm") {
|
|
|
3829
4059
|
function buildActionConfirmKeyboard(confirmId, confirmLabel, backAction) {
|
|
3830
4060
|
const rows = [
|
|
3831
4061
|
[
|
|
3832
|
-
{ text:
|
|
3833
|
-
{ text: "
|
|
4062
|
+
{ text: `:check: ${confirmLabel || "Confirm"}`, callback_data: confirmId },
|
|
4063
|
+
{ text: ":close: Cancel", callback_data: "cb:dismiss" },
|
|
3834
4064
|
],
|
|
3835
4065
|
];
|
|
3836
4066
|
if (backAction) {
|
|
3837
|
-
rows.push([uiButton("
|
|
4067
|
+
rows.push([uiButton(":arrowRight: Back", backAction)]);
|
|
3838
4068
|
}
|
|
3839
4069
|
return { inline_keyboard: rows };
|
|
3840
4070
|
}
|
|
@@ -3854,13 +4084,17 @@ function appendRefreshRow(keyboard, screenId, params = {}) {
|
|
|
3854
4084
|
)
|
|
3855
4085
|
: uiGoAction(screenId, params.page);
|
|
3856
4086
|
const rows = keyboard.inline_keyboard || [];
|
|
4087
|
+
const refreshLabel = formatTelegramIconTokens(":refresh: Refresh", { button: true });
|
|
3857
4088
|
const hasRefresh = rows.some((row) =>
|
|
3858
|
-
row.some((btn) =>
|
|
4089
|
+
row.some((btn) =>
|
|
4090
|
+
btn?.text === ":refresh: Refresh"
|
|
4091
|
+
|| btn?.text === refreshLabel
|
|
4092
|
+
|| btn?.callback_data === action),
|
|
3859
4093
|
);
|
|
3860
4094
|
if (hasRefresh) return keyboard;
|
|
3861
4095
|
return {
|
|
3862
4096
|
...keyboard,
|
|
3863
|
-
inline_keyboard: [...rows, [uiButton("
|
|
4097
|
+
inline_keyboard: [...rows, [uiButton(":refresh: Refresh", action)]],
|
|
3864
4098
|
};
|
|
3865
4099
|
}
|
|
3866
4100
|
|
|
@@ -3943,7 +4177,7 @@ async function promptUiInput(chatId, key, extra = {}) {
|
|
|
3943
4177
|
...extra,
|
|
3944
4178
|
});
|
|
3945
4179
|
const keyboard = buildKeyboard([
|
|
3946
|
-
[{ text: "
|
|
4180
|
+
[{ text: ":close: Cancel", callback_data: uiCallback("cancel") }],
|
|
3947
4181
|
]);
|
|
3948
4182
|
await sendReply(chatId, `${prompt}\n\nSend /cancel to abort.`, {
|
|
3949
4183
|
reply_markup: keyboard,
|
|
@@ -3982,7 +4216,7 @@ async function promptActionConfirm(chatId, token, payload, options = {}) {
|
|
|
3982
4216
|
async function handleUiInput(chatId, request, text) {
|
|
3983
4217
|
const trimmed = String(text || "").trim();
|
|
3984
4218
|
if (!trimmed) {
|
|
3985
|
-
await sendReply(chatId, "
|
|
4219
|
+
await sendReply(chatId, ":alert: Input was empty. Prompt cancelled.");
|
|
3986
4220
|
return;
|
|
3987
4221
|
}
|
|
3988
4222
|
if (request.key === "starttask") {
|
|
@@ -4001,12 +4235,12 @@ async function handleUiInput(chatId, request, text) {
|
|
|
4001
4235
|
}
|
|
4002
4236
|
const buildCommand = request.buildCommand;
|
|
4003
4237
|
if (typeof buildCommand !== "function") {
|
|
4004
|
-
await sendReply(chatId, "
|
|
4238
|
+
await sendReply(chatId, ":alert: Unable to process that input.");
|
|
4005
4239
|
return;
|
|
4006
4240
|
}
|
|
4007
4241
|
const command = buildCommand(trimmed, request);
|
|
4008
4242
|
if (!command) {
|
|
4009
|
-
await sendReply(chatId, "
|
|
4243
|
+
await sendReply(chatId, ":alert: Could not build a command from that input.");
|
|
4010
4244
|
return;
|
|
4011
4245
|
}
|
|
4012
4246
|
if (request.confirm) {
|
|
@@ -4088,7 +4322,7 @@ function buildStartTaskCommand(taskId, sdk, model, executor) {
|
|
|
4088
4322
|
async function promptStartTaskConfirm(chatId, details = {}) {
|
|
4089
4323
|
const taskId = String(details.taskId || "").trim();
|
|
4090
4324
|
if (!taskId) {
|
|
4091
|
-
await sendReply(chatId, "
|
|
4325
|
+
await sendReply(chatId, ":alert: Task ID missing for manual start.");
|
|
4092
4326
|
return;
|
|
4093
4327
|
}
|
|
4094
4328
|
const executor = normalizeStartTaskExecutor(details.executor);
|
|
@@ -4101,7 +4335,7 @@ async function promptStartTaskConfirm(chatId, details = {}) {
|
|
|
4101
4335
|
const sdkLabel = isVk ? "n/a" : sdk || "auto";
|
|
4102
4336
|
const modelLabel = isVk ? "n/a" : model || "default";
|
|
4103
4337
|
const lines = [
|
|
4104
|
-
"
|
|
4338
|
+
":rocket: *Confirm Manual Start*",
|
|
4105
4339
|
"",
|
|
4106
4340
|
"This will enqueue the task immediately.",
|
|
4107
4341
|
"",
|
|
@@ -4115,8 +4349,8 @@ async function promptStartTaskConfirm(chatId, details = {}) {
|
|
|
4115
4349
|
}
|
|
4116
4350
|
const keyboard = buildKeyboard([
|
|
4117
4351
|
[
|
|
4118
|
-
uiButton("
|
|
4119
|
-
uiButton("
|
|
4352
|
+
uiButton(":check: Start", uiTokenAction(token)),
|
|
4353
|
+
uiButton(":close: Cancel", "cancel"),
|
|
4120
4354
|
],
|
|
4121
4355
|
]);
|
|
4122
4356
|
await sendReply(chatId, lines.join("\n"), {
|
|
@@ -4128,7 +4362,7 @@ async function promptStartTaskConfirm(chatId, details = {}) {
|
|
|
4128
4362
|
async function showStartTaskExecutorPicker(chatId, taskId) {
|
|
4129
4363
|
const safeId = String(taskId || "").trim();
|
|
4130
4364
|
if (!safeId) {
|
|
4131
|
-
await sendReply(chatId, "
|
|
4365
|
+
await sendReply(chatId, ":alert: Task ID missing.");
|
|
4132
4366
|
return;
|
|
4133
4367
|
}
|
|
4134
4368
|
const mode = resolveStartTaskExecutorMode();
|
|
@@ -4136,23 +4370,23 @@ async function showStartTaskExecutorPicker(chatId, taskId) {
|
|
|
4136
4370
|
const buttons = [];
|
|
4137
4371
|
if (availability.internal || availability.vk) {
|
|
4138
4372
|
buttons.push({
|
|
4139
|
-
label: "
|
|
4373
|
+
label: ":star: Auto (recommended)",
|
|
4140
4374
|
executor: "auto",
|
|
4141
4375
|
});
|
|
4142
4376
|
}
|
|
4143
4377
|
if (availability.internal) {
|
|
4144
4378
|
buttons.push({
|
|
4145
|
-
label: availability.vk ? "
|
|
4379
|
+
label: availability.vk ? ":cpu: Internal" : ":cpu: Internal (only)",
|
|
4146
4380
|
executor: "internal",
|
|
4147
4381
|
});
|
|
4148
4382
|
}
|
|
4149
4383
|
if (availability.vk) {
|
|
4150
|
-
buttons.push({ label: "
|
|
4384
|
+
buttons.push({ label: ":globe: VK", executor: "vk" });
|
|
4151
4385
|
}
|
|
4152
4386
|
if (!buttons.length) {
|
|
4153
4387
|
await sendReply(
|
|
4154
4388
|
chatId,
|
|
4155
|
-
"
|
|
4389
|
+
":alert: No executors available. Check EXECUTOR_MODE configuration.",
|
|
4156
4390
|
);
|
|
4157
4391
|
return;
|
|
4158
4392
|
}
|
|
@@ -4172,15 +4406,15 @@ async function showStartTaskExecutorPicker(chatId, taskId) {
|
|
|
4172
4406
|
),
|
|
4173
4407
|
2,
|
|
4174
4408
|
),
|
|
4175
|
-
[uiButton("
|
|
4409
|
+
[uiButton(":close: Cancel", "cancel")],
|
|
4176
4410
|
];
|
|
4177
4411
|
const lines = [
|
|
4178
4412
|
"Step 1/3 • Choose executor",
|
|
4179
4413
|
"",
|
|
4180
4414
|
`Task: \`${safeId}\``,
|
|
4181
4415
|
`Mode: \`${mode}\``,
|
|
4182
|
-
availability.internal ? "
|
|
4183
|
-
availability.vk ? "
|
|
4416
|
+
availability.internal ? ":cpu: Internal executor available" : ":cpu: Internal executor unavailable",
|
|
4417
|
+
availability.vk ? ":globe: VK executor available" : ":globe: VK executor unavailable",
|
|
4184
4418
|
"",
|
|
4185
4419
|
"Auto picks internal if available, otherwise VK.",
|
|
4186
4420
|
];
|
|
@@ -4193,7 +4427,7 @@ async function showStartTaskExecutorPicker(chatId, taskId) {
|
|
|
4193
4427
|
async function showStartTaskSdkPicker(chatId, taskId, executor) {
|
|
4194
4428
|
const safeId = String(taskId || "").trim();
|
|
4195
4429
|
if (!safeId) {
|
|
4196
|
-
await sendReply(chatId, "
|
|
4430
|
+
await sendReply(chatId, ":alert: Task ID missing.");
|
|
4197
4431
|
return;
|
|
4198
4432
|
}
|
|
4199
4433
|
const safeExecutor = normalizeStartTaskExecutor(executor) || "internal";
|
|
@@ -4225,13 +4459,13 @@ async function showStartTaskSdkPicker(chatId, taskId, executor) {
|
|
|
4225
4459
|
"Auto uses the current pool SDK.",
|
|
4226
4460
|
];
|
|
4227
4461
|
const rows = [
|
|
4228
|
-
[uiButton("
|
|
4462
|
+
[uiButton(":bot: Auto", uiTokenAction(tokenAuto))],
|
|
4229
4463
|
...chunkButtons(
|
|
4230
4464
|
tokens.map((entry) => uiButton(entry.sdk, uiTokenAction(entry.token))),
|
|
4231
4465
|
2,
|
|
4232
4466
|
),
|
|
4233
|
-
[uiButton("
|
|
4234
|
-
[uiButton("
|
|
4467
|
+
[uiButton(":arrowRight: Back", uiTokenAction(tokenBack))],
|
|
4468
|
+
[uiButton(":close: Cancel", "cancel")],
|
|
4235
4469
|
];
|
|
4236
4470
|
const keyboard = buildKeyboard(rows);
|
|
4237
4471
|
await sendReply(chatId, lines.join("\n"), {
|
|
@@ -4245,7 +4479,7 @@ async function showStartTaskModelPicker(chatId, taskId, sdk, executor) {
|
|
|
4245
4479
|
const safeSdk = String(sdk || "").trim();
|
|
4246
4480
|
const safeExecutor = normalizeStartTaskExecutor(executor) || "internal";
|
|
4247
4481
|
if (!safeId || !safeSdk) {
|
|
4248
|
-
await sendReply(chatId, "
|
|
4482
|
+
await sendReply(chatId, ":alert: Missing task ID or SDK.");
|
|
4249
4483
|
return;
|
|
4250
4484
|
}
|
|
4251
4485
|
const modelOptions = getDefaultModelPriority()
|
|
@@ -4301,7 +4535,7 @@ async function showStartTaskModelPicker(chatId, taskId, sdk, executor) {
|
|
|
4301
4535
|
);
|
|
4302
4536
|
}
|
|
4303
4537
|
rows.push([uiButton("Custom Model", uiTokenAction(tokenCustom))]);
|
|
4304
|
-
rows.push([uiButton("
|
|
4538
|
+
rows.push([uiButton(":arrowRight: Back", uiTokenAction(tokenBack))]);
|
|
4305
4539
|
const keyboard = buildKeyboard(rows);
|
|
4306
4540
|
await sendReply(chatId, lines.join("\n"), {
|
|
4307
4541
|
parseMode: "Markdown",
|
|
@@ -4312,14 +4546,14 @@ async function showStartTaskModelPicker(chatId, taskId, sdk, executor) {
|
|
|
4312
4546
|
function uiNavRow(parent) {
|
|
4313
4547
|
if (!parent) {
|
|
4314
4548
|
return [
|
|
4315
|
-
uiButton("
|
|
4316
|
-
uiButton("
|
|
4549
|
+
uiButton(":home: Home", uiGoAction("home")),
|
|
4550
|
+
uiButton(":close: Close", "cb:close_menu"),
|
|
4317
4551
|
];
|
|
4318
4552
|
}
|
|
4319
4553
|
return [
|
|
4320
|
-
uiButton("
|
|
4321
|
-
uiButton("
|
|
4322
|
-
uiButton("
|
|
4554
|
+
uiButton(":arrowRight: Back", uiGoAction(parent)),
|
|
4555
|
+
uiButton(":home: Home", uiGoAction("home")),
|
|
4556
|
+
uiButton(":close: Close", "cb:close_menu"),
|
|
4323
4557
|
];
|
|
4324
4558
|
}
|
|
4325
4559
|
|
|
@@ -4384,13 +4618,13 @@ function formatDurationMs(ms) {
|
|
|
4384
4618
|
|
|
4385
4619
|
async function buildHomeStatusLine() {
|
|
4386
4620
|
const data = await readStatusSnapshot();
|
|
4387
|
-
if (!data) return "Status:
|
|
4621
|
+
if (!data) return "Status: :close: unavailable";
|
|
4388
4622
|
const counts = data.counts || {};
|
|
4389
4623
|
const backlog = data.backlog_remaining ?? "?";
|
|
4390
4624
|
const running = counts.running ?? 0;
|
|
4391
4625
|
const review = counts.review ?? 0;
|
|
4392
4626
|
const error = counts.error ?? 0;
|
|
4393
|
-
return
|
|
4627
|
+
return `:play: Running ${running} • :eye: Review ${review} • :alert: Error ${error} • :download: Backlog ${backlog}`;
|
|
4394
4628
|
}
|
|
4395
4629
|
|
|
4396
4630
|
async function listWorktreeNames() {
|
|
@@ -4508,10 +4742,10 @@ Object.assign(UI_SCREENS, {
|
|
|
4508
4742
|
let executorLine = "";
|
|
4509
4743
|
if (executor) {
|
|
4510
4744
|
const status = executor.getStatus();
|
|
4511
|
-
const paused = executor.isPaused?.() ? "
|
|
4512
|
-
executorLine =
|
|
4745
|
+
const paused = executor.isPaused?.() ? ":pause: paused" : ":play: running";
|
|
4746
|
+
executorLine = `:settings: Executor: ${paused} • :sliders: Slots ${status.activeSlots}/${status.maxParallel}`;
|
|
4513
4747
|
} else {
|
|
4514
|
-
executorLine =
|
|
4748
|
+
executorLine = `:settings: Executor: ${_getExecutorMode?.() || "internal"}`;
|
|
4515
4749
|
}
|
|
4516
4750
|
return [
|
|
4517
4751
|
"Pick a section below to manage Bosun.",
|
|
@@ -4522,49 +4756,66 @@ Object.assign(UI_SCREENS, {
|
|
|
4522
4756
|
},
|
|
4523
4757
|
keyboard: () => {
|
|
4524
4758
|
syncUiUrlsFromServer();
|
|
4759
|
+
const voiceMeetingWebAppUrl = getMeetingWebAppUrl("voice");
|
|
4760
|
+
const videoMeetingWebAppUrl = getMeetingWebAppUrl("video");
|
|
4761
|
+
const meetingRow = [
|
|
4762
|
+
voiceMeetingWebAppUrl
|
|
4763
|
+
? {
|
|
4764
|
+
text: "Voice Meeting",
|
|
4765
|
+
web_app: { url: voiceMeetingWebAppUrl },
|
|
4766
|
+
}
|
|
4767
|
+
: uiButton("Voice Meeting", uiCmdAction("/call")),
|
|
4768
|
+
videoMeetingWebAppUrl
|
|
4769
|
+
? {
|
|
4770
|
+
text: "Video Meeting",
|
|
4771
|
+
web_app: { url: videoMeetingWebAppUrl },
|
|
4772
|
+
}
|
|
4773
|
+
: uiButton("Video Meeting", uiCmdAction("/videocall")),
|
|
4774
|
+
];
|
|
4525
4775
|
const rows = [
|
|
4526
4776
|
// Core Operations
|
|
4527
4777
|
[
|
|
4528
|
-
uiButton("
|
|
4529
|
-
uiButton("
|
|
4530
|
-
uiButton("
|
|
4778
|
+
uiButton(":chart: Dashboard", uiGoAction("overview")),
|
|
4779
|
+
uiButton(":compass: Tasks", uiGoAction("tasks")),
|
|
4780
|
+
uiButton(":bot: Agents", uiGoAction("agents")),
|
|
4531
4781
|
],
|
|
4532
4782
|
// System & Config
|
|
4533
4783
|
[
|
|
4534
|
-
uiButton("
|
|
4535
|
-
uiButton("
|
|
4536
|
-
uiButton("
|
|
4784
|
+
uiButton(":git: Workspaces", uiGoAction("workspaces")),
|
|
4785
|
+
uiButton(":settings: Executor", uiGoAction("executor")),
|
|
4786
|
+
uiButton(":server: Routing", uiGoAction("routing")),
|
|
4537
4787
|
],
|
|
4538
4788
|
// Monitoring & Tools
|
|
4539
4789
|
[
|
|
4540
|
-
uiButton("
|
|
4541
|
-
uiButton("
|
|
4542
|
-
uiButton("
|
|
4790
|
+
uiButton(":folder: Logs & Git", uiGoAction("logs")),
|
|
4791
|
+
uiButton(":chart: Telemetry", uiGoAction("telemetry")),
|
|
4792
|
+
uiButton(":cpu: Session", uiGoAction("session")),
|
|
4543
4793
|
],
|
|
4544
4794
|
// Quick Actions
|
|
4545
4795
|
[
|
|
4546
|
-
uiButton("
|
|
4547
|
-
uiButton("
|
|
4796
|
+
uiButton(":chat: Ask Agent", uiInputAction("ask")),
|
|
4797
|
+
uiButton(":file: All Commands", uiCmdAction("/helpfull")),
|
|
4548
4798
|
],
|
|
4549
4799
|
];
|
|
4800
|
+
rows.unshift(meetingRow);
|
|
4550
4801
|
if (telegramWebAppUrl) {
|
|
4551
4802
|
rows.unshift([
|
|
4552
4803
|
{
|
|
4553
|
-
text: "
|
|
4804
|
+
text: ":phone: Open Control Center",
|
|
4554
4805
|
web_app: { url: telegramWebAppUrl },
|
|
4555
4806
|
},
|
|
4556
|
-
uiButton("
|
|
4807
|
+
uiButton(":close:", "cb:close_menu"),
|
|
4557
4808
|
]);
|
|
4558
4809
|
if (getBrowserUiUrlOptions().length > 0) {
|
|
4559
|
-
rows.unshift([uiButton("
|
|
4810
|
+
rows.unshift([uiButton(":globe: Open in Browser", uiGoAction("browser_urls"))]);
|
|
4560
4811
|
}
|
|
4561
4812
|
} else if (telegramUiUrl) {
|
|
4562
4813
|
rows.unshift([
|
|
4563
|
-
uiButton("
|
|
4564
|
-
uiButton("
|
|
4814
|
+
uiButton(":globe: Open in Browser", uiGoAction("browser_urls")),
|
|
4815
|
+
uiButton(":close:", "cb:close_menu"),
|
|
4565
4816
|
]);
|
|
4566
4817
|
} else {
|
|
4567
|
-
rows.unshift([uiButton("
|
|
4818
|
+
rows.unshift([uiButton(":close: Close Menu", "cb:close_menu")]);
|
|
4568
4819
|
}
|
|
4569
4820
|
return buildKeyboard(rows);
|
|
4570
4821
|
},
|
|
@@ -4598,23 +4849,23 @@ Object.assign(UI_SCREENS, {
|
|
|
4598
4849
|
buildKeyboard([
|
|
4599
4850
|
// Core Status
|
|
4600
4851
|
[
|
|
4601
|
-
uiButton("
|
|
4602
|
-
uiButton("
|
|
4603
|
-
uiButton("
|
|
4852
|
+
uiButton(":chart: Status", uiCmdAction("/status")),
|
|
4853
|
+
uiButton(":clipboard: Tasks", uiCmdAction("/tasks")),
|
|
4854
|
+
uiButton(":bot: Agents", uiCmdAction("/agents")),
|
|
4604
4855
|
],
|
|
4605
4856
|
// System Health
|
|
4606
4857
|
[
|
|
4607
|
-
uiButton("
|
|
4608
|
-
uiButton("
|
|
4609
|
-
uiButton("
|
|
4858
|
+
uiButton(":heart: Health", uiCmdAction("/health")),
|
|
4859
|
+
uiButton(":alert: Anomalies", uiCmdAction("/anomalies")),
|
|
4860
|
+
uiButton(":eye: Presence", uiCmdAction("/presence")),
|
|
4610
4861
|
],
|
|
4611
4862
|
// Deep Dives
|
|
4612
4863
|
[
|
|
4613
|
-
uiButton("
|
|
4614
|
-
uiButton("
|
|
4864
|
+
uiButton(":target: Coordinator", uiCmdAction("/coordinator")),
|
|
4865
|
+
uiButton(":edit: Logs", uiCmdAction("/logs 50")),
|
|
4615
4866
|
],
|
|
4616
4867
|
[
|
|
4617
|
-
uiButton("
|
|
4868
|
+
uiButton(":chart: Telemetry", uiGoAction("telemetry")),
|
|
4618
4869
|
],
|
|
4619
4870
|
uiNavRow("home"),
|
|
4620
4871
|
]),
|
|
@@ -4627,12 +4878,12 @@ Object.assign(UI_SCREENS, {
|
|
|
4627
4878
|
keyboard: () =>
|
|
4628
4879
|
buildKeyboard([
|
|
4629
4880
|
[
|
|
4630
|
-
uiButton("
|
|
4631
|
-
uiButton("
|
|
4881
|
+
uiButton(":chart: Summary", uiCmdAction("/telemetry")),
|
|
4882
|
+
uiButton(":alert: Errors", uiCmdAction("/telemetry errors")),
|
|
4632
4883
|
],
|
|
4633
4884
|
[
|
|
4634
|
-
uiButton("
|
|
4635
|
-
uiButton("
|
|
4885
|
+
uiButton(":beaker: Executors", uiCmdAction("/telemetry executors")),
|
|
4886
|
+
uiButton(":alert: Alerts", uiCmdAction("/telemetry alerts")),
|
|
4636
4887
|
],
|
|
4637
4888
|
uiNavRow("home"),
|
|
4638
4889
|
]),
|
|
@@ -4646,21 +4897,21 @@ Object.assign(UI_SCREENS, {
|
|
|
4646
4897
|
buildKeyboard([
|
|
4647
4898
|
// Execution Controls
|
|
4648
4899
|
[
|
|
4649
|
-
{ text: "
|
|
4650
|
-
{ text: "
|
|
4651
|
-
{ text: "
|
|
4900
|
+
{ text: ":pause: Pause", callback_data: "cb:confirm_pause" },
|
|
4901
|
+
{ text: ":play: Resume", callback_data: "cb:confirm_resume" },
|
|
4902
|
+
{ text: ":refresh: Restart", callback_data: "cb:confirm_restart" },
|
|
4652
4903
|
],
|
|
4653
4904
|
// Viewing & Starting
|
|
4654
4905
|
[
|
|
4655
|
-
uiButton("
|
|
4656
|
-
uiButton("
|
|
4657
|
-
uiButton("
|
|
4906
|
+
uiButton(":clipboard: Active Tasks", uiCmdAction("/tasks")),
|
|
4907
|
+
uiButton(":folder: Task Lists", uiGoAction("task_lists")),
|
|
4908
|
+
uiButton(":play: Start Task", uiInputAction("starttask")),
|
|
4658
4909
|
],
|
|
4659
4910
|
// Management
|
|
4660
4911
|
[
|
|
4661
|
-
uiButton("
|
|
4662
|
-
uiButton("
|
|
4663
|
-
uiButton("
|
|
4912
|
+
uiButton(":grid: Planner", uiGoAction("plan")),
|
|
4913
|
+
uiButton(":repeat: Retry", uiGoAction("retry")),
|
|
4914
|
+
uiButton(":trash: Cleanup", uiCmdAction("/cleanup")),
|
|
4664
4915
|
],
|
|
4665
4916
|
uiNavRow("home"),
|
|
4666
4917
|
]),
|
|
@@ -4674,7 +4925,7 @@ Object.assign(UI_SCREENS, {
|
|
|
4674
4925
|
const rows = [
|
|
4675
4926
|
[
|
|
4676
4927
|
uiButton(
|
|
4677
|
-
"
|
|
4928
|
+
":download: Backlog",
|
|
4678
4929
|
uiTokenAction(
|
|
4679
4930
|
issueUiToken({
|
|
4680
4931
|
type: "go",
|
|
@@ -4684,7 +4935,7 @@ Object.assign(UI_SCREENS, {
|
|
|
4684
4935
|
),
|
|
4685
4936
|
),
|
|
4686
4937
|
uiButton(
|
|
4687
|
-
"
|
|
4938
|
+
":edit: Draft",
|
|
4688
4939
|
uiTokenAction(
|
|
4689
4940
|
issueUiToken({
|
|
4690
4941
|
type: "go",
|
|
@@ -4696,7 +4947,7 @@ Object.assign(UI_SCREENS, {
|
|
|
4696
4947
|
],
|
|
4697
4948
|
[
|
|
4698
4949
|
uiButton(
|
|
4699
|
-
"
|
|
4950
|
+
":check: Todo",
|
|
4700
4951
|
uiTokenAction(
|
|
4701
4952
|
issueUiToken({
|
|
4702
4953
|
type: "go",
|
|
@@ -4706,7 +4957,7 @@ Object.assign(UI_SCREENS, {
|
|
|
4706
4957
|
),
|
|
4707
4958
|
),
|
|
4708
4959
|
uiButton(
|
|
4709
|
-
"
|
|
4960
|
+
":alert: Active",
|
|
4710
4961
|
uiTokenAction(
|
|
4711
4962
|
issueUiToken({
|
|
4712
4963
|
type: "go",
|
|
@@ -4718,7 +4969,7 @@ Object.assign(UI_SCREENS, {
|
|
|
4718
4969
|
],
|
|
4719
4970
|
[
|
|
4720
4971
|
uiButton(
|
|
4721
|
-
"
|
|
4972
|
+
":search: Review",
|
|
4722
4973
|
uiTokenAction(
|
|
4723
4974
|
issueUiToken({
|
|
4724
4975
|
type: "go",
|
|
@@ -4728,7 +4979,7 @@ Object.assign(UI_SCREENS, {
|
|
|
4728
4979
|
),
|
|
4729
4980
|
),
|
|
4730
4981
|
uiButton(
|
|
4731
|
-
"
|
|
4982
|
+
":ban: Blocked",
|
|
4732
4983
|
uiTokenAction(
|
|
4733
4984
|
issueUiToken({
|
|
4734
4985
|
type: "go",
|
|
@@ -4740,7 +4991,7 @@ Object.assign(UI_SCREENS, {
|
|
|
4740
4991
|
],
|
|
4741
4992
|
[
|
|
4742
4993
|
uiButton(
|
|
4743
|
-
"
|
|
4994
|
+
":flag: Done",
|
|
4744
4995
|
uiTokenAction(
|
|
4745
4996
|
issueUiToken({
|
|
4746
4997
|
type: "go",
|
|
@@ -4813,7 +5064,7 @@ Object.assign(UI_SCREENS, {
|
|
|
4813
5064
|
type: "starttask_executor",
|
|
4814
5065
|
taskId: task.id,
|
|
4815
5066
|
});
|
|
4816
|
-
const label =
|
|
5067
|
+
const label = `:play: ${shortenLabel(task.id || task.title || "Task", 28)}`;
|
|
4817
5068
|
return [uiButton(label, uiTokenAction(token))];
|
|
4818
5069
|
}),
|
|
4819
5070
|
);
|
|
@@ -4823,7 +5074,7 @@ Object.assign(UI_SCREENS, {
|
|
|
4823
5074
|
if (safePage > 0) {
|
|
4824
5075
|
pager.push(
|
|
4825
5076
|
uiButton(
|
|
4826
|
-
"
|
|
5077
|
+
":arrowRight: Prev",
|
|
4827
5078
|
uiTokenAction(
|
|
4828
5079
|
issueUiToken({
|
|
4829
5080
|
type: "go",
|
|
@@ -4837,7 +5088,7 @@ Object.assign(UI_SCREENS, {
|
|
|
4837
5088
|
if (safePage < totalPages - 1) {
|
|
4838
5089
|
pager.push(
|
|
4839
5090
|
uiButton(
|
|
4840
|
-
"Next
|
|
5091
|
+
"Next :arrowRight:",
|
|
4841
5092
|
uiTokenAction(
|
|
4842
5093
|
issueUiToken({
|
|
4843
5094
|
type: "go",
|
|
@@ -4852,8 +5103,8 @@ Object.assign(UI_SCREENS, {
|
|
|
4852
5103
|
}
|
|
4853
5104
|
rows.push(
|
|
4854
5105
|
[
|
|
4855
|
-
uiButton("
|
|
4856
|
-
uiButton("
|
|
5106
|
+
uiButton(":repeat: Change Status", uiGoAction("task_lists")),
|
|
5107
|
+
uiButton(":play: Start Task", uiInputAction("starttask")),
|
|
4857
5108
|
],
|
|
4858
5109
|
);
|
|
4859
5110
|
rows.push(uiNavRow("task_lists"));
|
|
@@ -4901,15 +5152,15 @@ Object.assign(UI_SCREENS, {
|
|
|
4901
5152
|
buildKeyboard([
|
|
4902
5153
|
// Status
|
|
4903
5154
|
[
|
|
4904
|
-
uiButton("
|
|
4905
|
-
uiButton("
|
|
4906
|
-
uiButton("
|
|
5155
|
+
uiButton(":chart: Status", uiCmdAction("/executor")),
|
|
5156
|
+
uiButton(":sliders: Slots", uiCmdAction("/executor slots")),
|
|
5157
|
+
uiButton(":settings: Mode", uiCmdAction("/executor mode")),
|
|
4907
5158
|
],
|
|
4908
5159
|
// Controls
|
|
4909
5160
|
[
|
|
4910
|
-
uiButton("
|
|
4911
|
-
uiButton("
|
|
4912
|
-
uiButton("
|
|
5161
|
+
uiButton(":pause: Pause", uiCmdAction("/pausetasks")),
|
|
5162
|
+
uiButton(":play: Resume", uiCmdAction("/resumetasks")),
|
|
5163
|
+
uiButton(":hash: Max Parallel", uiGoAction("maxparallel")),
|
|
4913
5164
|
],
|
|
4914
5165
|
uiNavRow("home"),
|
|
4915
5166
|
]),
|
|
@@ -4947,20 +5198,20 @@ Object.assign(UI_SCREENS, {
|
|
|
4947
5198
|
buildKeyboard([
|
|
4948
5199
|
// Monitoring
|
|
4949
5200
|
[
|
|
4950
|
-
uiButton("
|
|
4951
|
-
uiButton("
|
|
4952
|
-
uiButton("
|
|
5201
|
+
uiButton(":bot: Active Agents", uiCmdAction("/agents")),
|
|
5202
|
+
uiButton(":folder: Agent Logs", uiGoAction("agent_logs")),
|
|
5203
|
+
uiButton(":link: Threads", uiGoAction("threads")),
|
|
4953
5204
|
],
|
|
4954
5205
|
// Control
|
|
4955
5206
|
[
|
|
4956
|
-
uiButton("
|
|
4957
|
-
uiButton("
|
|
4958
|
-
uiButton("
|
|
5207
|
+
uiButton(":compass: Steer", uiInputAction("steer")),
|
|
5208
|
+
uiButton(":close: Stop", uiCmdAction("/stop")),
|
|
5209
|
+
uiButton(":server: Background", uiGoAction("background")),
|
|
4959
5210
|
],
|
|
4960
5211
|
// Context
|
|
4961
5212
|
[
|
|
4962
|
-
uiButton("
|
|
4963
|
-
uiButton("
|
|
5213
|
+
uiButton(":cpu: History", uiCmdAction("/history")),
|
|
5214
|
+
uiButton(":chart: Status", uiCmdAction("/status")),
|
|
4964
5215
|
],
|
|
4965
5216
|
uiNavRow("home"),
|
|
4966
5217
|
]),
|
|
@@ -4976,8 +5227,8 @@ Object.assign(UI_SCREENS, {
|
|
|
4976
5227
|
uiButton("New Background Task", uiInputAction("background")),
|
|
4977
5228
|
],
|
|
4978
5229
|
[
|
|
4979
|
-
uiButton("
|
|
4980
|
-
uiButton("
|
|
5230
|
+
uiButton(":compass: Steer", uiInputAction("steer")),
|
|
5231
|
+
uiButton(":close: Stop", uiCmdAction("/stop")),
|
|
4981
5232
|
],
|
|
4982
5233
|
uiNavRow("agents"),
|
|
4983
5234
|
]),
|
|
@@ -5031,12 +5282,12 @@ Object.assign(UI_SCREENS, {
|
|
|
5031
5282
|
const pager = [];
|
|
5032
5283
|
if (safePage > 0) {
|
|
5033
5284
|
pager.push(
|
|
5034
|
-
uiButton("
|
|
5285
|
+
uiButton(":arrowRight: Prev", uiGoAction("agent_logs", safePage - 1)),
|
|
5035
5286
|
);
|
|
5036
5287
|
}
|
|
5037
5288
|
if (safePage < totalPages - 1) {
|
|
5038
5289
|
pager.push(
|
|
5039
|
-
uiButton("Next
|
|
5290
|
+
uiButton("Next :arrowRight:", uiGoAction("agent_logs", safePage + 1)),
|
|
5040
5291
|
);
|
|
5041
5292
|
}
|
|
5042
5293
|
if (pager.length) rows.push(pager);
|
|
@@ -5094,12 +5345,12 @@ Object.assign(UI_SCREENS, {
|
|
|
5094
5345
|
const pager = [];
|
|
5095
5346
|
if (safePage > 0) {
|
|
5096
5347
|
pager.push(
|
|
5097
|
-
uiButton("
|
|
5348
|
+
uiButton(":arrowRight: Prev", uiGoAction("threads_kill", safePage - 1)),
|
|
5098
5349
|
);
|
|
5099
5350
|
}
|
|
5100
5351
|
if (safePage < totalPages - 1) {
|
|
5101
5352
|
pager.push(
|
|
5102
|
-
uiButton("Next
|
|
5353
|
+
uiButton("Next :arrowRight:", uiGoAction("threads_kill", safePage + 1)),
|
|
5103
5354
|
);
|
|
5104
5355
|
}
|
|
5105
5356
|
if (pager.length) rows.push(pager);
|
|
@@ -5120,20 +5371,20 @@ Object.assign(UI_SCREENS, {
|
|
|
5120
5371
|
buildKeyboard([
|
|
5121
5372
|
// Core Routing
|
|
5122
5373
|
[
|
|
5123
|
-
uiButton("
|
|
5124
|
-
uiButton("
|
|
5125
|
-
uiButton("
|
|
5374
|
+
uiButton(":bot: Model", uiGoAction("model")),
|
|
5375
|
+
uiButton(":box: SDK", uiGoAction("sdk")),
|
|
5376
|
+
uiButton(":globe: Region", uiGoAction("region")),
|
|
5126
5377
|
],
|
|
5127
5378
|
// Task Routing
|
|
5128
5379
|
[
|
|
5129
|
-
uiButton("
|
|
5130
|
-
uiButton("
|
|
5131
|
-
uiButton("
|
|
5380
|
+
uiButton(":target: Route Task", uiGoAction("route_task")),
|
|
5381
|
+
uiButton(":clipboard: Kanban", uiGoAction("kanban")),
|
|
5382
|
+
uiButton(":repeat: Auto Backlog", uiGoAction("autobacklog")),
|
|
5132
5383
|
],
|
|
5133
5384
|
// Config
|
|
5134
5385
|
[
|
|
5135
|
-
uiButton("
|
|
5136
|
-
uiButton("
|
|
5386
|
+
uiButton(":ruler: Requirements", uiGoAction("requirements")),
|
|
5387
|
+
uiButton(":heart: Health", uiCmdAction("/health")),
|
|
5137
5388
|
],
|
|
5138
5389
|
uiNavRow("home"),
|
|
5139
5390
|
]),
|
|
@@ -5307,12 +5558,12 @@ Object.assign(UI_SCREENS, {
|
|
|
5307
5558
|
const pager = [];
|
|
5308
5559
|
if (safePage > 0) {
|
|
5309
5560
|
pager.push(
|
|
5310
|
-
uiButton("
|
|
5561
|
+
uiButton(":arrowRight: Prev", uiGoAction("route_workspace", safePage - 1)),
|
|
5311
5562
|
);
|
|
5312
5563
|
}
|
|
5313
5564
|
if (safePage < totalPages - 1) {
|
|
5314
5565
|
pager.push(
|
|
5315
|
-
uiButton("Next
|
|
5566
|
+
uiButton("Next :arrowRight:", uiGoAction("route_workspace", safePage + 1)),
|
|
5316
5567
|
);
|
|
5317
5568
|
}
|
|
5318
5569
|
if (pager.length) rows.push(pager);
|
|
@@ -5362,12 +5613,12 @@ Object.assign(UI_SCREENS, {
|
|
|
5362
5613
|
const pager = [];
|
|
5363
5614
|
if (safePage > 0) {
|
|
5364
5615
|
pager.push(
|
|
5365
|
-
uiButton("
|
|
5616
|
+
uiButton(":arrowRight: Prev", uiGoAction("route_role", safePage - 1)),
|
|
5366
5617
|
);
|
|
5367
5618
|
}
|
|
5368
5619
|
if (safePage < totalPages - 1) {
|
|
5369
5620
|
pager.push(
|
|
5370
|
-
uiButton("Next
|
|
5621
|
+
uiButton("Next :arrowRight:", uiGoAction("route_role", safePage + 1)),
|
|
5371
5622
|
);
|
|
5372
5623
|
}
|
|
5373
5624
|
if (pager.length) rows.push(pager);
|
|
@@ -5388,27 +5639,27 @@ Object.assign(UI_SCREENS, {
|
|
|
5388
5639
|
buildKeyboard([
|
|
5389
5640
|
// Workspaces
|
|
5390
5641
|
[
|
|
5391
|
-
uiButton("
|
|
5392
|
-
uiButton("
|
|
5393
|
-
uiButton("
|
|
5642
|
+
uiButton(":folder: My Workspaces", uiGoAction("managed_workspaces")),
|
|
5643
|
+
uiButton(":plus: New Workspace", uiInputAction("workspace_create")),
|
|
5644
|
+
uiButton(":target: Switch Active", uiGoAction("workspace_switch")),
|
|
5394
5645
|
],
|
|
5395
5646
|
// Worktrees
|
|
5396
5647
|
[
|
|
5397
|
-
uiButton("
|
|
5398
|
-
uiButton("
|
|
5399
|
-
uiButton("
|
|
5648
|
+
uiButton(":git: Worktrees", uiCmdAction("/worktrees")),
|
|
5649
|
+
uiButton(":chart: Stats", uiCmdAction("/worktrees stats")),
|
|
5650
|
+
uiButton(":trash: Prune", uiCmdAction("/worktrees prune")),
|
|
5400
5651
|
],
|
|
5401
5652
|
// Repos & Shared
|
|
5402
5653
|
[
|
|
5403
|
-
uiButton("
|
|
5404
|
-
uiButton("
|
|
5405
|
-
uiButton("
|
|
5654
|
+
uiButton(":folder: Repos", uiCmdAction("/repos")),
|
|
5655
|
+
uiButton(":unlock: Release WT", uiGoAction("worktrees_release")),
|
|
5656
|
+
uiButton(":refresh: Scan Disk", uiCmdAction("/workspace scan")),
|
|
5406
5657
|
],
|
|
5407
5658
|
// Shared
|
|
5408
5659
|
[
|
|
5409
|
-
uiButton("
|
|
5410
|
-
uiButton("
|
|
5411
|
-
uiButton("
|
|
5660
|
+
uiButton(":clipboard: Shared", uiCmdAction("/shared_workspaces")),
|
|
5661
|
+
uiButton(":check: Claim", uiGoAction("shared_claim")),
|
|
5662
|
+
uiButton(":close: Release", uiGoAction("shared_release")),
|
|
5412
5663
|
],
|
|
5413
5664
|
uiNavRow("home"),
|
|
5414
5665
|
]),
|
|
@@ -5424,7 +5675,7 @@ Object.assign(UI_SCREENS, {
|
|
|
5424
5675
|
const active = getActiveLocalWorkspace(configDir);
|
|
5425
5676
|
if (!workspaces.length) {
|
|
5426
5677
|
return buildKeyboard([
|
|
5427
|
-
[uiButton("
|
|
5678
|
+
[uiButton(":refresh: Scan", uiCmdAction("/workspace scan"))],
|
|
5428
5679
|
[uiButton("Type Workspace ID", uiInputAction("workspace_switch"))],
|
|
5429
5680
|
uiNavRow("workspaces"),
|
|
5430
5681
|
]);
|
|
@@ -5457,7 +5708,7 @@ Object.assign(UI_SCREENS, {
|
|
|
5457
5708
|
],
|
|
5458
5709
|
backAction: uiGoAction("workspace_switch", safePage),
|
|
5459
5710
|
});
|
|
5460
|
-
const prefix = isActive ? "
|
|
5711
|
+
const prefix = isActive ? ":dot:" : ":dot:";
|
|
5461
5712
|
return uiButton(
|
|
5462
5713
|
`${prefix} ${shortenLabel(workspace.name || workspace.id)}`,
|
|
5463
5714
|
uiTokenAction(token),
|
|
@@ -5470,12 +5721,12 @@ Object.assign(UI_SCREENS, {
|
|
|
5470
5721
|
const pager = [];
|
|
5471
5722
|
if (safePage > 0) {
|
|
5472
5723
|
pager.push(
|
|
5473
|
-
uiButton("
|
|
5724
|
+
uiButton(":arrowRight: Prev", uiGoAction("workspace_switch", safePage - 1)),
|
|
5474
5725
|
);
|
|
5475
5726
|
}
|
|
5476
5727
|
if (safePage < totalPages - 1) {
|
|
5477
5728
|
pager.push(
|
|
5478
|
-
uiButton("Next
|
|
5729
|
+
uiButton("Next :arrowRight:", uiGoAction("workspace_switch", safePage + 1)),
|
|
5479
5730
|
);
|
|
5480
5731
|
}
|
|
5481
5732
|
if (pager.length) rows.push(pager);
|
|
@@ -5484,7 +5735,7 @@ Object.assign(UI_SCREENS, {
|
|
|
5484
5735
|
rows.push([
|
|
5485
5736
|
uiButton("Type Workspace ID", uiInputAction("workspace_switch")),
|
|
5486
5737
|
]);
|
|
5487
|
-
rows.push([uiButton("
|
|
5738
|
+
rows.push([uiButton(":refresh: Scan", uiCmdAction("/workspace scan"))]);
|
|
5488
5739
|
rows.push(uiNavRow("workspaces"));
|
|
5489
5740
|
return buildKeyboard(rows);
|
|
5490
5741
|
},
|
|
@@ -5536,12 +5787,12 @@ Object.assign(UI_SCREENS, {
|
|
|
5536
5787
|
const pager = [];
|
|
5537
5788
|
if (safePage > 0) {
|
|
5538
5789
|
pager.push(
|
|
5539
|
-
uiButton("
|
|
5790
|
+
uiButton(":arrowRight: Prev", uiGoAction("worktrees_release", safePage - 1)),
|
|
5540
5791
|
);
|
|
5541
5792
|
}
|
|
5542
5793
|
if (safePage < totalPages - 1) {
|
|
5543
5794
|
pager.push(
|
|
5544
|
-
uiButton("Next
|
|
5795
|
+
uiButton("Next :arrowRight:", uiGoAction("worktrees_release", safePage + 1)),
|
|
5545
5796
|
);
|
|
5546
5797
|
}
|
|
5547
5798
|
if (pager.length) rows.push(pager);
|
|
@@ -5589,7 +5840,7 @@ Object.assign(UI_SCREENS, {
|
|
|
5589
5840
|
detailLines,
|
|
5590
5841
|
backAction: uiGoAction("shared_claim", safePage),
|
|
5591
5842
|
});
|
|
5592
|
-
const emoji = ws.availability === "available" ? "
|
|
5843
|
+
const emoji = ws.availability === "available" ? ":check:" : ":lock:";
|
|
5593
5844
|
return uiButton(
|
|
5594
5845
|
`${emoji} ${shortenLabel(ws.id)}`,
|
|
5595
5846
|
uiTokenAction(token),
|
|
@@ -5601,12 +5852,12 @@ Object.assign(UI_SCREENS, {
|
|
|
5601
5852
|
const pager = [];
|
|
5602
5853
|
if (safePage > 0) {
|
|
5603
5854
|
pager.push(
|
|
5604
|
-
uiButton("
|
|
5855
|
+
uiButton(":arrowRight: Prev", uiGoAction("shared_claim", safePage - 1)),
|
|
5605
5856
|
);
|
|
5606
5857
|
}
|
|
5607
5858
|
if (safePage < totalPages - 1) {
|
|
5608
5859
|
pager.push(
|
|
5609
|
-
uiButton("Next
|
|
5860
|
+
uiButton("Next :arrowRight:", uiGoAction("shared_claim", safePage + 1)),
|
|
5610
5861
|
);
|
|
5611
5862
|
}
|
|
5612
5863
|
if (pager.length) rows.push(pager);
|
|
@@ -5650,7 +5901,7 @@ Object.assign(UI_SCREENS, {
|
|
|
5650
5901
|
detailLines,
|
|
5651
5902
|
backAction: uiGoAction("shared_release", safePage),
|
|
5652
5903
|
});
|
|
5653
|
-
const emoji = ws.availability === "leased" ? "
|
|
5904
|
+
const emoji = ws.availability === "leased" ? ":unlock:" : ":help:";
|
|
5654
5905
|
return uiButton(
|
|
5655
5906
|
`${emoji} ${shortenLabel(ws.id)}`,
|
|
5656
5907
|
uiTokenAction(token),
|
|
@@ -5662,12 +5913,12 @@ Object.assign(UI_SCREENS, {
|
|
|
5662
5913
|
const pager = [];
|
|
5663
5914
|
if (safePage > 0) {
|
|
5664
5915
|
pager.push(
|
|
5665
|
-
uiButton("
|
|
5916
|
+
uiButton(":arrowRight: Prev", uiGoAction("shared_release", safePage - 1)),
|
|
5666
5917
|
);
|
|
5667
5918
|
}
|
|
5668
5919
|
if (safePage < totalPages - 1) {
|
|
5669
5920
|
pager.push(
|
|
5670
|
-
uiButton("Next
|
|
5921
|
+
uiButton("Next :arrowRight:", uiGoAction("shared_release", safePage + 1)),
|
|
5671
5922
|
);
|
|
5672
5923
|
}
|
|
5673
5924
|
if (pager.length) rows.push(pager);
|
|
@@ -5688,8 +5939,8 @@ Object.assign(UI_SCREENS, {
|
|
|
5688
5939
|
const active = getActiveLocalWorkspace(configDir);
|
|
5689
5940
|
if (!workspaces.length) {
|
|
5690
5941
|
return buildKeyboard([
|
|
5691
|
-
[uiButton("
|
|
5692
|
-
[uiButton("
|
|
5942
|
+
[uiButton(":plus: Create Workspace", uiInputAction("workspace_create"))],
|
|
5943
|
+
[uiButton(":refresh: Scan Disk", uiCmdAction("/workspace scan"))],
|
|
5693
5944
|
uiNavRow("workspaces"),
|
|
5694
5945
|
]);
|
|
5695
5946
|
}
|
|
@@ -5707,7 +5958,7 @@ Object.assign(UI_SCREENS, {
|
|
|
5707
5958
|
const repoNames = Array.isArray(ws.repos)
|
|
5708
5959
|
? ws.repos.map((r) => (typeof r === "string" ? r : r.name || r.url || "?")).join(", ")
|
|
5709
5960
|
: "none";
|
|
5710
|
-
const prefix = isActive ? "
|
|
5961
|
+
const prefix = isActive ? ":dot:" : ":dot:";
|
|
5711
5962
|
const switchToken = issueUiToken({
|
|
5712
5963
|
type: "confirm_cmd",
|
|
5713
5964
|
command: `/workspace switch ${ws.id}`,
|
|
@@ -5717,7 +5968,7 @@ Object.assign(UI_SCREENS, {
|
|
|
5717
5968
|
`Name: \`${ws.name || ws.id}\``,
|
|
5718
5969
|
`ID: \`${ws.id}\``,
|
|
5719
5970
|
`Repos (${repoCount}): ${repoNames || "none"}`,
|
|
5720
|
-
`Status: ${isActive ? "
|
|
5971
|
+
`Status: ${isActive ? ":check: Active" : "Inactive"}`,
|
|
5721
5972
|
],
|
|
5722
5973
|
backAction: uiGoAction("managed_workspaces", safePage),
|
|
5723
5974
|
});
|
|
@@ -5726,27 +5977,27 @@ Object.assign(UI_SCREENS, {
|
|
|
5726
5977
|
`${prefix} ${shortenLabel(ws.name || ws.id, 20)} (${repoCount})`,
|
|
5727
5978
|
uiTokenAction(switchToken),
|
|
5728
5979
|
),
|
|
5729
|
-
uiButton("
|
|
5730
|
-
uiButton("
|
|
5980
|
+
uiButton(":download: Pull", uiCmdAction(`/workspace pull ${ws.id}`)),
|
|
5981
|
+
uiButton(":trash:", uiCmdAction(`/workspace delete ${ws.id}`)),
|
|
5731
5982
|
]);
|
|
5732
5983
|
}
|
|
5733
5984
|
if (totalPages > 1) {
|
|
5734
5985
|
const pager = [];
|
|
5735
5986
|
if (safePage > 0) {
|
|
5736
5987
|
pager.push(
|
|
5737
|
-
uiButton("
|
|
5988
|
+
uiButton(":arrowRight: Prev", uiGoAction("managed_workspaces", safePage - 1)),
|
|
5738
5989
|
);
|
|
5739
5990
|
}
|
|
5740
5991
|
if (safePage < totalPages - 1) {
|
|
5741
5992
|
pager.push(
|
|
5742
|
-
uiButton("Next
|
|
5993
|
+
uiButton("Next :arrowRight:", uiGoAction("managed_workspaces", safePage + 1)),
|
|
5743
5994
|
);
|
|
5744
5995
|
}
|
|
5745
5996
|
if (pager.length) rows.push(pager);
|
|
5746
5997
|
}
|
|
5747
5998
|
rows.push([
|
|
5748
|
-
uiButton("
|
|
5749
|
-
uiButton("
|
|
5999
|
+
uiButton(":plus: Create", uiInputAction("workspace_create")),
|
|
6000
|
+
uiButton(":refresh: Scan", uiCmdAction("/workspace scan")),
|
|
5750
6001
|
]);
|
|
5751
6002
|
rows.push(uiNavRow("workspaces"));
|
|
5752
6003
|
return buildKeyboard(rows);
|
|
@@ -5763,18 +6014,18 @@ Object.assign(UI_SCREENS, {
|
|
|
5763
6014
|
buildKeyboard([
|
|
5764
6015
|
// Logs
|
|
5765
6016
|
[
|
|
5766
|
-
uiButton("
|
|
5767
|
-
uiButton("
|
|
6017
|
+
uiButton(":edit: System Logs", uiGoAction("logs_tail")),
|
|
6018
|
+
uiButton(":folder: Agent Logs", uiGoAction("agent_logs")),
|
|
5768
6019
|
],
|
|
5769
6020
|
// Git
|
|
5770
6021
|
[
|
|
5771
|
-
uiButton("
|
|
5772
|
-
uiButton("
|
|
5773
|
-
uiButton("
|
|
6022
|
+
uiButton(":git: Branches", uiCmdAction("/branches")),
|
|
6023
|
+
uiButton(":lightbulb: Diff", uiCmdAction("/diff")),
|
|
6024
|
+
uiButton(":search: Git", uiGoAction("git")),
|
|
5774
6025
|
],
|
|
5775
6026
|
// Utils
|
|
5776
6027
|
[
|
|
5777
|
-
uiButton("
|
|
6028
|
+
uiButton(":monitor: Shell", uiGoAction("shell")),
|
|
5778
6029
|
],
|
|
5779
6030
|
uiNavRow("home"),
|
|
5780
6031
|
]),
|
|
@@ -5843,8 +6094,8 @@ Object.assign(UI_SCREENS, {
|
|
|
5843
6094
|
keyboard: () =>
|
|
5844
6095
|
buildKeyboard([
|
|
5845
6096
|
[
|
|
5846
|
-
uiButton("
|
|
5847
|
-
uiButton("
|
|
6097
|
+
uiButton(":phone: WhatsApp", uiCmdAction("/whatsapp")),
|
|
6098
|
+
uiButton(":box: Container", uiCmdAction("/container")),
|
|
5848
6099
|
],
|
|
5849
6100
|
uiNavRow("home"),
|
|
5850
6101
|
]),
|
|
@@ -5857,18 +6108,18 @@ Object.assign(UI_SCREENS, {
|
|
|
5857
6108
|
buildKeyboard([
|
|
5858
6109
|
// Interaction
|
|
5859
6110
|
[
|
|
5860
|
-
uiButton("
|
|
5861
|
-
uiButton("
|
|
6111
|
+
uiButton(":chat: Ask", uiInputAction("ask")),
|
|
6112
|
+
uiButton(":compass: Steer", uiInputAction("steer")),
|
|
5862
6113
|
],
|
|
5863
6114
|
// Context
|
|
5864
6115
|
[
|
|
5865
|
-
uiButton("
|
|
5866
|
-
uiButton("
|
|
6116
|
+
uiButton(":cpu: History", uiCmdAction("/history")),
|
|
6117
|
+
uiButton(":trash: Clear", "confirm_clear"),
|
|
5867
6118
|
],
|
|
5868
6119
|
// Control
|
|
5869
6120
|
[
|
|
5870
|
-
uiButton("
|
|
5871
|
-
uiButton("
|
|
6121
|
+
uiButton(":server: Background", uiGoAction("background")),
|
|
6122
|
+
uiButton(":close: Stop", uiCmdAction("/stop")),
|
|
5872
6123
|
],
|
|
5873
6124
|
uiNavRow("home"),
|
|
5874
6125
|
]),
|
|
@@ -5965,7 +6216,7 @@ async function handleUiAction({ chatId, messageId, data }) {
|
|
|
5965
6216
|
if (messageId) {
|
|
5966
6217
|
await deleteDirect(chatId, messageId);
|
|
5967
6218
|
}
|
|
5968
|
-
await sendReply(chatId, "
|
|
6219
|
+
await sendReply(chatId, ":check: Input cancelled.");
|
|
5969
6220
|
return;
|
|
5970
6221
|
}
|
|
5971
6222
|
if (type === "confirm_clear") {
|
|
@@ -6040,7 +6291,7 @@ async function handleUiAction({ chatId, messageId, data }) {
|
|
|
6040
6291
|
}
|
|
6041
6292
|
await sendReply(
|
|
6042
6293
|
chatId,
|
|
6043
|
-
"
|
|
6294
|
+
":clock: That option expired. Please reopen the menu.",
|
|
6044
6295
|
);
|
|
6045
6296
|
return;
|
|
6046
6297
|
}
|
|
@@ -6105,7 +6356,7 @@ async function handleUiAction({ chatId, messageId, data }) {
|
|
|
6105
6356
|
if (!executor) {
|
|
6106
6357
|
await sendReply(
|
|
6107
6358
|
chatId,
|
|
6108
|
-
|
|
6359
|
+
`:alert: Executor "${payload.executor || "auto"}" not available (mode: ${mode}).`,
|
|
6109
6360
|
);
|
|
6110
6361
|
await showStartTaskExecutorPicker(chatId, payload.taskId);
|
|
6111
6362
|
return;
|
|
@@ -6154,7 +6405,7 @@ async function handleWebAppData(raw, chatId) {
|
|
|
6154
6405
|
}
|
|
6155
6406
|
|
|
6156
6407
|
if (!payload || typeof payload !== "object") {
|
|
6157
|
-
await sendReply(chatId, "
|
|
6408
|
+
await sendReply(chatId, ":alert: Web app sent an invalid payload.");
|
|
6158
6409
|
return;
|
|
6159
6410
|
}
|
|
6160
6411
|
|
|
@@ -6175,7 +6426,7 @@ async function handleWebAppData(raw, chatId) {
|
|
|
6175
6426
|
return;
|
|
6176
6427
|
}
|
|
6177
6428
|
|
|
6178
|
-
await sendReply(chatId, "
|
|
6429
|
+
await sendReply(chatId, ":alert: Web app request not recognized.");
|
|
6179
6430
|
}
|
|
6180
6431
|
|
|
6181
6432
|
// ── Built-in Command Handlers ────────────────────────────────────────────────
|
|
@@ -6418,25 +6669,41 @@ async function cmdApp(chatId) {
|
|
|
6418
6669
|
if (!uiUrl) {
|
|
6419
6670
|
await sendReply(
|
|
6420
6671
|
chatId,
|
|
6421
|
-
"
|
|
6672
|
+
":alert: Mini App not configured. Set TELEGRAM_UI_PORT and TELEGRAM_MINIAPP_ENABLED=true in your environment.",
|
|
6422
6673
|
);
|
|
6423
6674
|
return;
|
|
6424
6675
|
}
|
|
6425
6676
|
const browserOptions = getBrowserUiUrlOptions();
|
|
6677
|
+
const voiceMeetingWebAppUrl = getMeetingWebAppUrl("voice", {
|
|
6678
|
+
chat_id: String(chatId || "").trim(),
|
|
6679
|
+
});
|
|
6680
|
+
const videoMeetingWebAppUrl = getMeetingWebAppUrl("video", {
|
|
6681
|
+
chat_id: String(chatId || "").trim(),
|
|
6682
|
+
});
|
|
6426
6683
|
const rows = [];
|
|
6427
6684
|
if (webAppUrl) {
|
|
6428
|
-
rows.unshift([{ text: "
|
|
6685
|
+
rows.unshift([{ text: ":phone: Open Control Center", web_app: { url: webAppUrl } }]);
|
|
6686
|
+
}
|
|
6687
|
+
if (voiceMeetingWebAppUrl || videoMeetingWebAppUrl) {
|
|
6688
|
+
rows.push([
|
|
6689
|
+
voiceMeetingWebAppUrl
|
|
6690
|
+
? { text: "Voice Meeting", web_app: { url: voiceMeetingWebAppUrl } }
|
|
6691
|
+
: { text: "Voice Meeting", callback_data: "/call" },
|
|
6692
|
+
videoMeetingWebAppUrl
|
|
6693
|
+
? { text: "Video Meeting", web_app: { url: videoMeetingWebAppUrl } }
|
|
6694
|
+
: { text: "Video Meeting", callback_data: "/videocall" },
|
|
6695
|
+
]);
|
|
6429
6696
|
}
|
|
6430
6697
|
if (browserOptions.length > 0) {
|
|
6431
6698
|
rows.push(...browserOptions.map((option) => [{ text: option.label, url: option.url }]));
|
|
6432
6699
|
} else {
|
|
6433
|
-
rows.push([{ text: "
|
|
6700
|
+
rows.push([{ text: ":globe: Open in Browser", url: getBrowserUiUrl() || uiUrl }]);
|
|
6434
6701
|
}
|
|
6435
6702
|
const keyboard = { inline_keyboard: rows };
|
|
6436
6703
|
|
|
6437
6704
|
await sendDirect(
|
|
6438
6705
|
chatId,
|
|
6439
|
-
"
|
|
6706
|
+
":rocket: *Bosun Control Center*\n\nOpen the Mini App or access via browser:",
|
|
6440
6707
|
{
|
|
6441
6708
|
parseMode: "Markdown",
|
|
6442
6709
|
reply_markup: keyboard,
|
|
@@ -6444,6 +6711,70 @@ async function cmdApp(chatId) {
|
|
|
6444
6711
|
);
|
|
6445
6712
|
}
|
|
6446
6713
|
|
|
6714
|
+
async function cmdCall(chatId, args = "") {
|
|
6715
|
+
const callType = normalizeMeetingCallType(args);
|
|
6716
|
+
const isVideo = callType === "video";
|
|
6717
|
+
const label = isVideo ? "video" : "voice";
|
|
6718
|
+
const title = isVideo ? "*Video Meeting*" : "*Voice Meeting*";
|
|
6719
|
+
syncUiUrlsFromServer();
|
|
6720
|
+
|
|
6721
|
+
const webAppMeetingUrl = getMeetingWebAppUrl(callType, {
|
|
6722
|
+
chat_id: String(chatId || "").trim(),
|
|
6723
|
+
});
|
|
6724
|
+
const browserOptions = getMeetingBrowserUrlOptions(callType, {
|
|
6725
|
+
chat_id: String(chatId || "").trim(),
|
|
6726
|
+
});
|
|
6727
|
+
|
|
6728
|
+
const rows = [];
|
|
6729
|
+
if (webAppMeetingUrl) {
|
|
6730
|
+
rows.push([
|
|
6731
|
+
{
|
|
6732
|
+
text: isVideo ? "Start Video Meeting" : "Start Voice Meeting",
|
|
6733
|
+
web_app: { url: webAppMeetingUrl },
|
|
6734
|
+
},
|
|
6735
|
+
]);
|
|
6736
|
+
}
|
|
6737
|
+
if (browserOptions.length > 0) {
|
|
6738
|
+
rows.push(
|
|
6739
|
+
...browserOptions.map((option) => [
|
|
6740
|
+
{
|
|
6741
|
+
text: `${option.label} (${label})`,
|
|
6742
|
+
url: option.url,
|
|
6743
|
+
},
|
|
6744
|
+
]),
|
|
6745
|
+
);
|
|
6746
|
+
}
|
|
6747
|
+
|
|
6748
|
+
if (rows.length === 0) {
|
|
6749
|
+
await sendReply(
|
|
6750
|
+
chatId,
|
|
6751
|
+
":alert: Meeting UI is not available yet. Set TELEGRAM_WEBAPP_URL (HTTPS) or enable the UI tunnel first.",
|
|
6752
|
+
);
|
|
6753
|
+
return;
|
|
6754
|
+
}
|
|
6755
|
+
|
|
6756
|
+
await sendDirect(
|
|
6757
|
+
chatId,
|
|
6758
|
+
[
|
|
6759
|
+
title,
|
|
6760
|
+
"",
|
|
6761
|
+
"One tap opens the Bosun meeting room with your default agent + voice settings.",
|
|
6762
|
+
isVideo
|
|
6763
|
+
? "Video mode auto-starts camera. You can switch to screen share any time."
|
|
6764
|
+
: "Voice mode starts instantly. You can enable camera/screen share in-call.",
|
|
6765
|
+
].join("\n"),
|
|
6766
|
+
{
|
|
6767
|
+
parseMode: "Markdown",
|
|
6768
|
+
reply_markup: { inline_keyboard: rows },
|
|
6769
|
+
},
|
|
6770
|
+
);
|
|
6771
|
+
}
|
|
6772
|
+
|
|
6773
|
+
async function cmdVideoCall(chatId, args = "") {
|
|
6774
|
+
const normalized = String(args || "").trim();
|
|
6775
|
+
await cmdCall(chatId, normalized ? `video ${normalized}` : "video");
|
|
6776
|
+
}
|
|
6777
|
+
|
|
6447
6778
|
async function cmdMenu(chatId) {
|
|
6448
6779
|
syncUiUrlsFromServer();
|
|
6449
6780
|
if (telegramApiReachable !== false) {
|
|
@@ -6460,7 +6791,7 @@ async function cmdCancel(chatId) {
|
|
|
6460
6791
|
return;
|
|
6461
6792
|
}
|
|
6462
6793
|
clearPendingUiInput(chatId);
|
|
6463
|
-
await sendReply(chatId, "
|
|
6794
|
+
await sendReply(chatId, ":check: Input cancelled.");
|
|
6464
6795
|
}
|
|
6465
6796
|
|
|
6466
6797
|
async function cmdHelp(chatId) {
|
|
@@ -6468,7 +6799,7 @@ async function cmdHelp(chatId) {
|
|
|
6468
6799
|
}
|
|
6469
6800
|
|
|
6470
6801
|
async function cmdHelpFull(chatId) {
|
|
6471
|
-
const lines = ["
|
|
6802
|
+
const lines = [":bot: Bosun Primary Agent — All Commands:\n"];
|
|
6472
6803
|
for (const [cmd, { desc }] of Object.entries(COMMANDS)) {
|
|
6473
6804
|
lines.push(`${cmd} — ${desc}`);
|
|
6474
6805
|
}
|
|
@@ -6564,7 +6895,7 @@ async function cmdTelemetry(chatId, args = "") {
|
|
|
6564
6895
|
.sort((a, b) => b[1] - a[1])
|
|
6565
6896
|
.slice(0, 8);
|
|
6566
6897
|
const lines = [
|
|
6567
|
-
"
|
|
6898
|
+
":alert: Telemetry — Top Errors",
|
|
6568
6899
|
`Window: last ${days}d`,
|
|
6569
6900
|
"",
|
|
6570
6901
|
];
|
|
@@ -6579,7 +6910,7 @@ async function cmdTelemetry(chatId, args = "") {
|
|
|
6579
6910
|
const summary = summarizeTelemetry(metrics, days);
|
|
6580
6911
|
if (!summary) return sendReply(chatId, "No telemetry metrics found.");
|
|
6581
6912
|
const lines = [
|
|
6582
|
-
"
|
|
6913
|
+
":beaker: Telemetry — Executor Mix",
|
|
6583
6914
|
`Window: last ${days}d`,
|
|
6584
6915
|
"",
|
|
6585
6916
|
];
|
|
@@ -6597,7 +6928,7 @@ async function cmdTelemetry(chatId, args = "") {
|
|
|
6597
6928
|
return sendReply(chatId, "No analyzer alerts found.");
|
|
6598
6929
|
}
|
|
6599
6930
|
const lines = [
|
|
6600
|
-
"
|
|
6931
|
+
":alert: Telemetry — Recent Alerts",
|
|
6601
6932
|
`Window: last ${days}d`,
|
|
6602
6933
|
"",
|
|
6603
6934
|
...alerts.slice(-10).map((a) => {
|
|
@@ -6616,7 +6947,7 @@ async function cmdTelemetry(chatId, args = "") {
|
|
|
6616
6947
|
return sendReply(chatId, "No telemetry metrics found.");
|
|
6617
6948
|
}
|
|
6618
6949
|
const lines = [
|
|
6619
|
-
"
|
|
6950
|
+
":chart: Telemetry Summary",
|
|
6620
6951
|
`Window: last ${days}d`,
|
|
6621
6952
|
"",
|
|
6622
6953
|
`Sessions: ${summary.total}`,
|
|
@@ -6663,7 +6994,7 @@ async function cmdAsk(chatId, args) {
|
|
|
6663
6994
|
}
|
|
6664
6995
|
|
|
6665
6996
|
async function cmdStatus(chatId) {
|
|
6666
|
-
await sendReply(chatId, "
|
|
6997
|
+
await sendReply(chatId, ":clock: Reading orchestrator status...");
|
|
6667
6998
|
|
|
6668
6999
|
let statusText = "Status unavailable";
|
|
6669
7000
|
|
|
@@ -6703,7 +7034,7 @@ async function cmdStatus(chatId) {
|
|
|
6703
7034
|
: [];
|
|
6704
7035
|
|
|
6705
7036
|
const lines = [
|
|
6706
|
-
"
|
|
7037
|
+
":chart: Bosun Orchestrator Status",
|
|
6707
7038
|
"",
|
|
6708
7039
|
`Running: ${counts.running ?? 0}`,
|
|
6709
7040
|
`Review: ${counts.review ?? 0}`,
|
|
@@ -6719,14 +7050,14 @@ async function cmdStatus(chatId) {
|
|
|
6719
7050
|
if (errors.length > 0) {
|
|
6720
7051
|
lines.push(
|
|
6721
7052
|
"",
|
|
6722
|
-
"
|
|
7053
|
+
":alert: Error tasks:",
|
|
6723
7054
|
...errors.slice(0, 5).map((t) => ` - ${t}`),
|
|
6724
7055
|
);
|
|
6725
7056
|
}
|
|
6726
7057
|
if (manualReviews.length > 0) {
|
|
6727
7058
|
lines.push(
|
|
6728
7059
|
"",
|
|
6729
|
-
"
|
|
7060
|
+
":eye: Manual review:",
|
|
6730
7061
|
...manualReviews.slice(0, 5).map((t) => ` - ${t}`),
|
|
6731
7062
|
);
|
|
6732
7063
|
}
|
|
@@ -6825,22 +7156,22 @@ async function cmdTasks(chatId) {
|
|
|
6825
7156
|
: dur >= 60
|
|
6826
7157
|
? Math.round(dur / 60) + "m"
|
|
6827
7158
|
: dur + "s";
|
|
6828
|
-
lines.push(
|
|
7159
|
+
lines.push(`:pause: PAUSED (for ${durStr}) — /resumetasks to resume`);
|
|
6829
7160
|
lines.push("");
|
|
6830
7161
|
}
|
|
6831
7162
|
|
|
6832
7163
|
if (executorStatus.slots.length > 0) {
|
|
6833
7164
|
lines.push(
|
|
6834
|
-
|
|
7165
|
+
`:clipboard: Active Agents (${executorStatus.activeSlots}/${executorStatus.maxParallel} slots)\n`,
|
|
6835
7166
|
);
|
|
6836
7167
|
|
|
6837
7168
|
for (const slot of executorStatus.slots) {
|
|
6838
7169
|
const emoji =
|
|
6839
7170
|
slot.status === "running"
|
|
6840
|
-
? "
|
|
7171
|
+
? ":dot:"
|
|
6841
7172
|
: slot.status === "error"
|
|
6842
|
-
? "
|
|
6843
|
-
: "
|
|
7173
|
+
? ":close:"
|
|
7174
|
+
: ":dot:";
|
|
6844
7175
|
const runStr = formatRuntimeSeconds(slot.runningFor);
|
|
6845
7176
|
const agentId =
|
|
6846
7177
|
Number.isFinite(slot.agentInstanceId) && slot.agentInstanceId > 0
|
|
@@ -6853,7 +7184,7 @@ async function cmdTasks(chatId) {
|
|
|
6853
7184
|
lines.push(`${emoji} Agent ${agentId} • ${shortBranch}`);
|
|
6854
7185
|
lines.push(` ${slot.taskTitle}`);
|
|
6855
7186
|
lines.push(
|
|
6856
|
-
` SDK: ${slot.sdk} |
|
|
7187
|
+
` SDK: ${slot.sdk} | :clock: ${runStr} | Attempt #${slot.attempt} | Task ${slot.taskId.substring(0, 8)}`,
|
|
6857
7188
|
);
|
|
6858
7189
|
|
|
6859
7190
|
// Git diff stats
|
|
@@ -6868,7 +7199,7 @@ async function cmdTasks(chatId) {
|
|
|
6868
7199
|
const delMatch = diffStat.match(/(\d+) deletion/);
|
|
6869
7200
|
const filesMatch = diffStat.match(/(\d+) file/);
|
|
6870
7201
|
lines.push(
|
|
6871
|
-
`
|
|
7202
|
+
` :chart: ${filesMatch?.[1] || 0} files | +${insMatch?.[1] || 0} -${delMatch?.[1] || 0}`,
|
|
6872
7203
|
);
|
|
6873
7204
|
}
|
|
6874
7205
|
} catch {
|
|
@@ -6892,7 +7223,7 @@ async function cmdTasks(chatId) {
|
|
|
6892
7223
|
reviewQueued,
|
|
6893
7224
|
);
|
|
6894
7225
|
if (reviewCount > 0) {
|
|
6895
|
-
lines.push(
|
|
7226
|
+
lines.push(`:eye: In review: ${reviewCount} task(s)`);
|
|
6896
7227
|
if (reviewStatus) {
|
|
6897
7228
|
lines.push(
|
|
6898
7229
|
` Review agent queue: active=${reviewStatus.activeReviews || 0}, queued=${reviewStatus.queuedReviews || 0}, completed=${reviewStatus.completedReviews || 0}`,
|
|
@@ -6914,7 +7245,7 @@ async function cmdTasks(chatId) {
|
|
|
6914
7245
|
} else {
|
|
6915
7246
|
// No active slots — show status summary
|
|
6916
7247
|
lines.push(
|
|
6917
|
-
|
|
7248
|
+
`:clipboard: No active agents (0/${executorStatus.maxParallel} slots)`,
|
|
6918
7249
|
);
|
|
6919
7250
|
const reviewAgent = _getReviewAgent?.();
|
|
6920
7251
|
const reviewStatus =
|
|
@@ -6928,7 +7259,7 @@ async function cmdTasks(chatId) {
|
|
|
6928
7259
|
Number(reviewStatus?.queuedReviews || 0),
|
|
6929
7260
|
);
|
|
6930
7261
|
if (reviewCount > 0) {
|
|
6931
|
-
lines.push(
|
|
7262
|
+
lines.push(`:eye: In review: ${reviewCount} task(s)`);
|
|
6932
7263
|
if (reviewTaskIds.length > 0) {
|
|
6933
7264
|
for (const taskId of reviewTaskIds.slice(0, 5)) {
|
|
6934
7265
|
lines.push(` - ${taskId}`);
|
|
@@ -6937,7 +7268,7 @@ async function cmdTasks(chatId) {
|
|
|
6937
7268
|
}
|
|
6938
7269
|
if (executorStatus.blockedTasks?.length > 0) {
|
|
6939
7270
|
lines.push(
|
|
6940
|
-
`\n
|
|
7271
|
+
`\n:ban: ${executorStatus.blockedTasks.length} task(s) blocked (exceeded retry limit)`,
|
|
6941
7272
|
);
|
|
6942
7273
|
}
|
|
6943
7274
|
lines.push("");
|
|
@@ -6962,21 +7293,21 @@ async function cmdTasks(chatId) {
|
|
|
6962
7293
|
return;
|
|
6963
7294
|
}
|
|
6964
7295
|
|
|
6965
|
-
const lines = ["
|
|
7296
|
+
const lines = [":clipboard: Active Task Attempts\n"];
|
|
6966
7297
|
|
|
6967
7298
|
for (const [id, attempt] of Object.entries(attempts)) {
|
|
6968
7299
|
if (!attempt) continue;
|
|
6969
7300
|
const status = attempt.status || "unknown";
|
|
6970
7301
|
const emoji =
|
|
6971
7302
|
status === "running"
|
|
6972
|
-
? "
|
|
7303
|
+
? ":dot:"
|
|
6973
7304
|
: status === "review"
|
|
6974
|
-
? "
|
|
7305
|
+
? ":eye:"
|
|
6975
7306
|
: status === "error"
|
|
6976
|
-
? "
|
|
7307
|
+
? ":close:"
|
|
6977
7308
|
: status === "completed"
|
|
6978
|
-
? "
|
|
6979
|
-
: "
|
|
7309
|
+
? ":check:"
|
|
7310
|
+
: ":pause:";
|
|
6980
7311
|
const branch = attempt.branch || "";
|
|
6981
7312
|
const pr = attempt.pr_number ? ` PR#${attempt.pr_number}` : "";
|
|
6982
7313
|
const title = attempt.task_title || attempt.task_id || id;
|
|
@@ -6999,7 +7330,7 @@ async function cmdTasks(chatId) {
|
|
|
6999
7330
|
const hrs = Math.floor(mins / 60);
|
|
7000
7331
|
const remMin = mins % 60;
|
|
7001
7332
|
const durStr = hrs > 0 ? `${hrs}h ${remMin}m` : `${mins}m`;
|
|
7002
|
-
lines.push(`
|
|
7333
|
+
lines.push(` :clock: Active: ${durStr}`);
|
|
7003
7334
|
}
|
|
7004
7335
|
|
|
7005
7336
|
if (branch) {
|
|
@@ -7013,7 +7344,7 @@ async function cmdTasks(chatId) {
|
|
|
7013
7344
|
const delMatch = diffStat.match(/(\d+) deletion/);
|
|
7014
7345
|
const filesMatch = diffStat.match(/(\d+) file/);
|
|
7015
7346
|
lines.push(
|
|
7016
|
-
`
|
|
7347
|
+
` :chart: ${filesMatch?.[1] || 0} files | +${insMatch?.[1] || 0} -${delMatch?.[1] || 0}`,
|
|
7017
7348
|
);
|
|
7018
7349
|
}
|
|
7019
7350
|
} catch {
|
|
@@ -7092,7 +7423,7 @@ async function cmdStartTask(chatId, args) {
|
|
|
7092
7423
|
if (executorArg && !normalizedExecutor) {
|
|
7093
7424
|
await sendReply(
|
|
7094
7425
|
chatId,
|
|
7095
|
-
|
|
7426
|
+
`:alert: Unknown executor "${executorArg}". Use internal or vk.`,
|
|
7096
7427
|
);
|
|
7097
7428
|
return;
|
|
7098
7429
|
}
|
|
@@ -7110,7 +7441,7 @@ async function cmdStartTask(chatId, args) {
|
|
|
7110
7441
|
.join(" | ");
|
|
7111
7442
|
await sendReply(
|
|
7112
7443
|
chatId,
|
|
7113
|
-
|
|
7444
|
+
`:alert: Executor "${normalizedExecutor || "auto"}" not available (mode: ${executorMode}). Available: ${options || "none"}`,
|
|
7114
7445
|
);
|
|
7115
7446
|
return;
|
|
7116
7447
|
}
|
|
@@ -7125,7 +7456,7 @@ async function cmdStartTask(chatId, args) {
|
|
|
7125
7456
|
if (typeof adapter.submitTaskAttempt !== "function") {
|
|
7126
7457
|
await sendReply(
|
|
7127
7458
|
chatId,
|
|
7128
|
-
|
|
7459
|
+
`:alert: VK executor not available for current backend (${getKanbanBackendName()}).`,
|
|
7129
7460
|
);
|
|
7130
7461
|
return;
|
|
7131
7462
|
}
|
|
@@ -7142,7 +7473,7 @@ async function cmdStartTask(chatId, args) {
|
|
|
7142
7473
|
);
|
|
7143
7474
|
}
|
|
7144
7475
|
const detailLines = [
|
|
7145
|
-
|
|
7476
|
+
`:check: VK executor submitted for ${task.title || task.id}.`,
|
|
7146
7477
|
attempt?.id ? `Attempt: ${attempt.id}` : null,
|
|
7147
7478
|
attempt?.branch ? `Branch: ${attempt.branch}` : null,
|
|
7148
7479
|
].filter(Boolean);
|
|
@@ -7157,7 +7488,7 @@ async function cmdStartTask(chatId, args) {
|
|
|
7157
7488
|
if (!executor) {
|
|
7158
7489
|
await sendReply(
|
|
7159
7490
|
chatId,
|
|
7160
|
-
"
|
|
7491
|
+
":alert: Manual start requires internal executor. Set EXECUTOR_MODE=internal or hybrid and restart the monitor.",
|
|
7161
7492
|
);
|
|
7162
7493
|
return;
|
|
7163
7494
|
}
|
|
@@ -7167,19 +7498,19 @@ async function cmdStartTask(chatId, args) {
|
|
|
7167
7498
|
}));
|
|
7168
7499
|
await sendReply(
|
|
7169
7500
|
chatId,
|
|
7170
|
-
|
|
7501
|
+
`:check: Manual start queued for ${task.title || task.id}.` +
|
|
7171
7502
|
`\nExecutor: ${selectedExecutor}` +
|
|
7172
7503
|
(sdk ? `\nSDK: ${sdk}` : "") +
|
|
7173
7504
|
(model ? `\nModel: ${model}` : ""),
|
|
7174
7505
|
);
|
|
7175
7506
|
} catch (err) {
|
|
7176
|
-
await sendReply(chatId,
|
|
7507
|
+
await sendReply(chatId, `:close: Manual start failed: ${err.message}`);
|
|
7177
7508
|
}
|
|
7178
7509
|
}
|
|
7179
7510
|
|
|
7180
7511
|
async function cmdAgents(chatId) {
|
|
7181
7512
|
try {
|
|
7182
|
-
const lines = ["
|
|
7513
|
+
const lines = [":bot: Agent Fleet", ""];
|
|
7183
7514
|
let statusSnapshot = null;
|
|
7184
7515
|
if (_readStatusData) {
|
|
7185
7516
|
try {
|
|
@@ -7307,7 +7638,7 @@ async function cmdAgents(chatId) {
|
|
|
7307
7638
|
} catch (err) {
|
|
7308
7639
|
await sendReply(
|
|
7309
7640
|
chatId,
|
|
7310
|
-
|
|
7641
|
+
`:close: Failed to read agent fleet status: ${err.message}`,
|
|
7311
7642
|
);
|
|
7312
7643
|
}
|
|
7313
7644
|
}
|
|
@@ -7351,7 +7682,7 @@ async function cmdAgentLogs(chatId, args) {
|
|
|
7351
7682
|
|
|
7352
7683
|
const wtName = matches[0]; // Best match
|
|
7353
7684
|
const wtPath = resolve(worktreeDir, wtName);
|
|
7354
|
-
const lines = [
|
|
7685
|
+
const lines = [`:folder: Agent: ${wtName}\n`];
|
|
7355
7686
|
|
|
7356
7687
|
// Git log (last 5 commits)
|
|
7357
7688
|
try {
|
|
@@ -7361,13 +7692,13 @@ async function cmdAgentLogs(chatId, args) {
|
|
|
7361
7692
|
timeout: 10000,
|
|
7362
7693
|
}).trim();
|
|
7363
7694
|
if (gitLog) {
|
|
7364
|
-
lines.push("
|
|
7695
|
+
lines.push(":edit: Recent commits:");
|
|
7365
7696
|
lines.push(gitLog);
|
|
7366
7697
|
} else {
|
|
7367
|
-
lines.push("
|
|
7698
|
+
lines.push(":edit: No commits yet");
|
|
7368
7699
|
}
|
|
7369
7700
|
} catch {
|
|
7370
|
-
lines.push("
|
|
7701
|
+
lines.push(":edit: Git log unavailable");
|
|
7371
7702
|
}
|
|
7372
7703
|
|
|
7373
7704
|
lines.push("");
|
|
@@ -7381,15 +7712,15 @@ async function cmdAgentLogs(chatId, args) {
|
|
|
7381
7712
|
}).trim();
|
|
7382
7713
|
if (gitStatus) {
|
|
7383
7714
|
const statusLines = gitStatus.split("\n");
|
|
7384
|
-
lines.push(
|
|
7715
|
+
lines.push(`:file: Working tree: ${statusLines.length} changed files`);
|
|
7385
7716
|
lines.push(statusLines.slice(0, 15).join("\n"));
|
|
7386
7717
|
if (statusLines.length > 15)
|
|
7387
7718
|
lines.push(`... +${statusLines.length - 15} more`);
|
|
7388
7719
|
} else {
|
|
7389
|
-
lines.push("
|
|
7720
|
+
lines.push(":file: Working tree: clean");
|
|
7390
7721
|
}
|
|
7391
7722
|
} catch {
|
|
7392
|
-
lines.push("
|
|
7723
|
+
lines.push(":file: Git status unavailable");
|
|
7393
7724
|
}
|
|
7394
7725
|
|
|
7395
7726
|
lines.push("");
|
|
@@ -7408,7 +7739,7 @@ async function cmdAgentLogs(chatId, args) {
|
|
|
7408
7739
|
}).trim();
|
|
7409
7740
|
if (diffStat) {
|
|
7410
7741
|
const statLines = diffStat.split("\n");
|
|
7411
|
-
lines.push("
|
|
7742
|
+
lines.push(":chart: Diff vs main:");
|
|
7412
7743
|
// Show only summary line (last line)
|
|
7413
7744
|
lines.push(statLines[statLines.length - 1] || "(none)");
|
|
7414
7745
|
}
|
|
@@ -7434,10 +7765,10 @@ async function cmdAgentLogs(chatId, args) {
|
|
|
7434
7765
|
? `${Math.floor(runMin / 60)}h${runMin % 60}m`
|
|
7435
7766
|
: `${runMin}m`;
|
|
7436
7767
|
lines.push(
|
|
7437
|
-
|
|
7768
|
+
`:bot: Active agent: ${slot.sdk} | Running: ${runStr} | Attempt #${slot.attempt}`,
|
|
7438
7769
|
);
|
|
7439
7770
|
} else {
|
|
7440
|
-
lines.push("
|
|
7771
|
+
lines.push(":bot: No active agent on this branch");
|
|
7441
7772
|
}
|
|
7442
7773
|
}
|
|
7443
7774
|
|
|
@@ -7468,7 +7799,7 @@ async function cmdLogs(chatId, _args) {
|
|
|
7468
7799
|
|
|
7469
7800
|
await sendReply(
|
|
7470
7801
|
chatId,
|
|
7471
|
-
|
|
7802
|
+
`:file: Last ${numLines} lines of ${logFile}:\n\n${tail || "(empty)"}`,
|
|
7472
7803
|
);
|
|
7473
7804
|
} catch (err) {
|
|
7474
7805
|
await sendReply(chatId, `Error reading logs: ${err.message}`);
|
|
@@ -7485,7 +7816,7 @@ async function cmdBranches(chatId, _args) {
|
|
|
7485
7816
|
const lines = result.split("\n").filter(Boolean).slice(0, 20);
|
|
7486
7817
|
await sendReply(
|
|
7487
7818
|
chatId,
|
|
7488
|
-
|
|
7819
|
+
`:git: Recent branches (top 20):\n\n${lines.join("\n")}`,
|
|
7489
7820
|
);
|
|
7490
7821
|
} catch (err) {
|
|
7491
7822
|
await sendReply(chatId, `Error listing branches: ${err.message}`);
|
|
@@ -7505,7 +7836,7 @@ async function cmdDiff(chatId, _args) {
|
|
|
7505
7836
|
}
|
|
7506
7837
|
await sendReply(
|
|
7507
7838
|
chatId,
|
|
7508
|
-
|
|
7839
|
+
`:edit: Working tree changes:\n\n${diffStat.slice(0, 3500)}`,
|
|
7509
7840
|
);
|
|
7510
7841
|
} catch (err) {
|
|
7511
7842
|
await sendReply(chatId, `Error reading diff: ${err.message}`);
|
|
@@ -7526,7 +7857,7 @@ async function cmdDisableUnsafeAccess(chatId, _args, editMessageId) {
|
|
|
7526
7857
|
if (ok) {
|
|
7527
7858
|
await sendReply(
|
|
7528
7859
|
chatId,
|
|
7529
|
-
"
|
|
7860
|
+
":check: *Unsafe access disabled.*\n\n"
|
|
7530
7861
|
+ "`TELEGRAM_UI_ALLOW_UNSAFE=false` has been written to your .env file.\n\n"
|
|
7531
7862
|
+ "Send /restart to restart Bosun — Cloudflare tunnel will start automatically on the next boot.",
|
|
7532
7863
|
{ parse_mode: "Markdown" },
|
|
@@ -7534,7 +7865,7 @@ async function cmdDisableUnsafeAccess(chatId, _args, editMessageId) {
|
|
|
7534
7865
|
} else {
|
|
7535
7866
|
await sendReply(
|
|
7536
7867
|
chatId,
|
|
7537
|
-
"
|
|
7868
|
+
":close: Could not write to .env automatically.\n\n"
|
|
7538
7869
|
+ "Please edit your .env file manually:\n"
|
|
7539
7870
|
+ "`TELEGRAM_UI_ALLOW_UNSAFE=false`\n\n"
|
|
7540
7871
|
+ "Then send /restart.",
|
|
@@ -7548,12 +7879,12 @@ async function cmdRestart(chatId, args) {
|
|
|
7548
7879
|
if (!confirmFlag) {
|
|
7549
7880
|
await sendReply(
|
|
7550
7881
|
chatId,
|
|
7551
|
-
"
|
|
7882
|
+
":alert: Restart will stop the orchestrator process and let the monitor respawn it.\nProceed?",
|
|
7552
7883
|
{ reply_markup: buildConfirmKeyboard("cb:do_restart", "Confirm Restart") },
|
|
7553
7884
|
);
|
|
7554
7885
|
return;
|
|
7555
7886
|
}
|
|
7556
|
-
await sendReply(chatId, "
|
|
7887
|
+
await sendReply(chatId, ":refresh: Restarting orchestrator process...");
|
|
7557
7888
|
try {
|
|
7558
7889
|
if (_getCurrentChild) {
|
|
7559
7890
|
const child = _getCurrentChild();
|
|
@@ -7568,10 +7899,10 @@ async function cmdRestart(chatId, args) {
|
|
|
7568
7899
|
// The monitor's handleExit will auto-restart the process
|
|
7569
7900
|
await sendReply(
|
|
7570
7901
|
chatId,
|
|
7571
|
-
"
|
|
7902
|
+
":check: Restart signal sent. Monitor will auto-restart the orchestrator.",
|
|
7572
7903
|
);
|
|
7573
7904
|
} catch (err) {
|
|
7574
|
-
await sendReply(chatId,
|
|
7905
|
+
await sendReply(chatId, `:close: Restart failed: ${err.message}`);
|
|
7575
7906
|
}
|
|
7576
7907
|
}
|
|
7577
7908
|
|
|
@@ -7579,29 +7910,29 @@ async function cmdRetry(chatId, args) {
|
|
|
7579
7910
|
if (!_attemptFreshSessionRetry) {
|
|
7580
7911
|
await sendReply(
|
|
7581
7912
|
chatId,
|
|
7582
|
-
"
|
|
7913
|
+
":close: Fresh session retry not available (not injected from monitor).",
|
|
7583
7914
|
);
|
|
7584
7915
|
return;
|
|
7585
7916
|
}
|
|
7586
7917
|
|
|
7587
7918
|
const reason = args?.trim() || "manual_retry_via_telegram";
|
|
7588
|
-
await sendReply(chatId,
|
|
7919
|
+
await sendReply(chatId, `:refresh: Attempting fresh session retry (${reason})...`);
|
|
7589
7920
|
|
|
7590
7921
|
try {
|
|
7591
7922
|
const started = await _attemptFreshSessionRetry(reason);
|
|
7592
7923
|
if (started) {
|
|
7593
7924
|
await sendReply(
|
|
7594
7925
|
chatId,
|
|
7595
|
-
"
|
|
7926
|
+
":check: Fresh session started. New agent will pick up the task.",
|
|
7596
7927
|
);
|
|
7597
7928
|
} else {
|
|
7598
7929
|
await sendReply(
|
|
7599
7930
|
chatId,
|
|
7600
|
-
"
|
|
7931
|
+
":alert: Fresh session retry failed. Check logs for details (rate limit, no active attempt, or VK endpoint unavailable).",
|
|
7601
7932
|
);
|
|
7602
7933
|
}
|
|
7603
7934
|
} catch (err) {
|
|
7604
|
-
await sendReply(chatId,
|
|
7935
|
+
await sendReply(chatId, `:close: Retry error: ${err.message || err}`);
|
|
7605
7936
|
}
|
|
7606
7937
|
}
|
|
7607
7938
|
|
|
@@ -7609,7 +7940,7 @@ async function cmdPlan(chatId, args) {
|
|
|
7609
7940
|
if (!_triggerTaskPlanner) {
|
|
7610
7941
|
await sendReply(
|
|
7611
7942
|
chatId,
|
|
7612
|
-
"
|
|
7943
|
+
":close: Task planner not available (not injected from monitor).",
|
|
7613
7944
|
);
|
|
7614
7945
|
return;
|
|
7615
7946
|
}
|
|
@@ -7633,7 +7964,7 @@ async function cmdPlan(chatId, args) {
|
|
|
7633
7964
|
}
|
|
7634
7965
|
|
|
7635
7966
|
const promptSuffix = userPrompt ? ` — "${userPrompt.slice(0, 60)}${userPrompt.length > 60 ? "…" : ""}"` : "";
|
|
7636
|
-
await sendReply(chatId,
|
|
7967
|
+
await sendReply(chatId, `:clipboard: Triggering task planner (${taskCount} tasks${promptSuffix})...`);
|
|
7637
7968
|
|
|
7638
7969
|
try {
|
|
7639
7970
|
const result = await _triggerTaskPlanner(
|
|
@@ -7651,19 +7982,19 @@ async function cmdPlan(chatId, args) {
|
|
|
7651
7982
|
if (result.reason === "planner_disabled") {
|
|
7652
7983
|
await sendReply(
|
|
7653
7984
|
chatId,
|
|
7654
|
-
"
|
|
7985
|
+
":alert: Task planner disabled. Set TASK_PLANNER_MODE=kanban or codex-sdk.",
|
|
7655
7986
|
);
|
|
7656
7987
|
return;
|
|
7657
7988
|
}
|
|
7658
7989
|
if (result.reason === "planner_busy") {
|
|
7659
7990
|
await sendReply(
|
|
7660
7991
|
chatId,
|
|
7661
|
-
"
|
|
7992
|
+
":alert: Task planner already running. Try again in a moment.",
|
|
7662
7993
|
);
|
|
7663
7994
|
return;
|
|
7664
7995
|
}
|
|
7665
7996
|
const lines = [
|
|
7666
|
-
"
|
|
7997
|
+
":alert: Task planner skipped — a planning task already exists.",
|
|
7667
7998
|
];
|
|
7668
7999
|
if (result.taskTitle) {
|
|
7669
8000
|
lines.push(`Title: ${result.taskTitle}`);
|
|
@@ -7679,7 +8010,7 @@ async function cmdPlan(chatId, args) {
|
|
|
7679
8010
|
}
|
|
7680
8011
|
if (result?.status === "created") {
|
|
7681
8012
|
const lines = [
|
|
7682
|
-
"
|
|
8013
|
+
":check: Task planner task created.",
|
|
7683
8014
|
result.taskTitle ? `Title: ${result.taskTitle}` : null,
|
|
7684
8015
|
result.taskId ? `Task ID: ${result.taskId}` : null,
|
|
7685
8016
|
result.taskUrl || null,
|
|
@@ -7698,16 +8029,16 @@ async function cmdPlan(chatId, args) {
|
|
|
7698
8029
|
: "";
|
|
7699
8030
|
await sendReply(
|
|
7700
8031
|
chatId,
|
|
7701
|
-
|
|
8032
|
+
`:check: Task planner completed.\n${createdInfo}Output: ${result.outputPath}${artifactInfo}`,
|
|
7702
8033
|
);
|
|
7703
8034
|
return;
|
|
7704
8035
|
}
|
|
7705
8036
|
await sendReply(
|
|
7706
8037
|
chatId,
|
|
7707
|
-
|
|
8038
|
+
`:check: Task planner triggered for ${taskCount} tasks. Check backlog shortly.`,
|
|
7708
8039
|
);
|
|
7709
8040
|
} catch (err) {
|
|
7710
|
-
await sendReply(chatId,
|
|
8041
|
+
await sendReply(chatId, `:close: Task planner error: ${err.message || err}`);
|
|
7711
8042
|
}
|
|
7712
8043
|
}
|
|
7713
8044
|
|
|
@@ -7716,33 +8047,33 @@ async function cmdCleanupMerged(chatId, args) {
|
|
|
7716
8047
|
if (!_reconcileTaskStatuses) {
|
|
7717
8048
|
await sendReply(
|
|
7718
8049
|
chatId,
|
|
7719
|
-
"
|
|
8050
|
+
":close: Cleanup not available (not injected from monitor).",
|
|
7720
8051
|
);
|
|
7721
8052
|
return;
|
|
7722
8053
|
}
|
|
7723
8054
|
if (!confirmFlag) {
|
|
7724
8055
|
await sendReply(
|
|
7725
8056
|
chatId,
|
|
7726
|
-
"
|
|
8057
|
+
":alert: Cleanup will reconcile VK task statuses with PR/branch state.\nProceed?",
|
|
7727
8058
|
{ reply_markup: buildConfirmKeyboard("cb:confirm_cleanup", "Confirm Cleanup") },
|
|
7728
8059
|
);
|
|
7729
8060
|
return;
|
|
7730
8061
|
}
|
|
7731
8062
|
await sendReply(
|
|
7732
8063
|
chatId,
|
|
7733
|
-
"
|
|
8064
|
+
":trash: Reconciling VK task statuses with PR/branch state…",
|
|
7734
8065
|
);
|
|
7735
8066
|
try {
|
|
7736
8067
|
const result = await _reconcileTaskStatuses("manual-telegram");
|
|
7737
8068
|
const lines = [
|
|
7738
|
-
"
|
|
8069
|
+
":check: Cleanup complete.",
|
|
7739
8070
|
`Checked: ${result?.checked ?? 0}`,
|
|
7740
8071
|
`Moved to done: ${result?.movedDone ?? 0}`,
|
|
7741
8072
|
`Moved to inreview: ${result?.movedReview ?? 0}`,
|
|
7742
8073
|
];
|
|
7743
8074
|
await sendReply(chatId, lines.join("\n"));
|
|
7744
8075
|
} catch (err) {
|
|
7745
|
-
await sendReply(chatId,
|
|
8076
|
+
await sendReply(chatId, `:close: Cleanup error: ${err.message || err}`);
|
|
7746
8077
|
}
|
|
7747
8078
|
}
|
|
7748
8079
|
|
|
@@ -7751,7 +8082,7 @@ async function cmdHistory(chatId) {
|
|
|
7751
8082
|
const sessionLabel = info.sessionId || info.threadId || "(none)";
|
|
7752
8083
|
const agentLabel = info.adapter || info.provider || getPrimaryAgentName();
|
|
7753
8084
|
const lines = [
|
|
7754
|
-
|
|
8085
|
+
`:cpu: Primary Agent (${agentLabel})`,
|
|
7755
8086
|
"",
|
|
7756
8087
|
`Session: ${sessionLabel}`,
|
|
7757
8088
|
`Turns: ${info.turnCount}`,
|
|
@@ -7773,7 +8104,7 @@ async function cmdClear(chatId, args) {
|
|
|
7773
8104
|
const sessionLabel = info.sessionId || info.threadId || "(none)";
|
|
7774
8105
|
const agentLabel = info.adapter || info.provider || getPrimaryAgentName();
|
|
7775
8106
|
const lines = [
|
|
7776
|
-
"
|
|
8107
|
+
":alert: This will clear the primary agent session and reset context.",
|
|
7777
8108
|
"",
|
|
7778
8109
|
`Agent: ${agentLabel}`,
|
|
7779
8110
|
`Session: ${sessionLabel}`,
|
|
@@ -7790,7 +8121,7 @@ async function cmdClear(chatId, args) {
|
|
|
7790
8121
|
await resetPrimaryAgent();
|
|
7791
8122
|
await sendReply(
|
|
7792
8123
|
chatId,
|
|
7793
|
-
"
|
|
8124
|
+
":trash: Agent session reset. Next message starts a fresh conversation.",
|
|
7794
8125
|
);
|
|
7795
8126
|
}
|
|
7796
8127
|
|
|
@@ -7849,7 +8180,7 @@ async function cmdGit(chatId, gitArgs) {
|
|
|
7849
8180
|
} catch (err) {
|
|
7850
8181
|
await sendReply(
|
|
7851
8182
|
chatId,
|
|
7852
|
-
`$ git ${args}\n\n
|
|
8183
|
+
`$ git ${args}\n\n:close: ${err.message?.slice(0, 1500) || err}`,
|
|
7853
8184
|
);
|
|
7854
8185
|
}
|
|
7855
8186
|
}
|
|
@@ -7874,7 +8205,7 @@ async function cmdShell(chatId, shellArgs) {
|
|
|
7874
8205
|
|
|
7875
8206
|
const blockedShell = [/rm\s+-rf\s+\/(\s|$)/i];
|
|
7876
8207
|
if (matchesAnyPattern(args, blockedShell)) {
|
|
7877
|
-
await sendReply(chatId,
|
|
8208
|
+
await sendReply(chatId, `:ban: Blocked: '${args}' is too destructive.`);
|
|
7878
8209
|
return;
|
|
7879
8210
|
}
|
|
7880
8211
|
|
|
@@ -7922,7 +8253,7 @@ async function cmdShell(chatId, shellArgs) {
|
|
|
7922
8253
|
const stdout = err.stdout ? err.stdout.toString().slice(0, 1000) : "";
|
|
7923
8254
|
await sendReply(
|
|
7924
8255
|
chatId,
|
|
7925
|
-
`$ ${args}\n\n
|
|
8256
|
+
`$ ${args}\n\n:close: ${stderr || stdout || err.message}`,
|
|
7926
8257
|
);
|
|
7927
8258
|
}
|
|
7928
8259
|
}
|
|
@@ -8078,11 +8409,11 @@ async function cmdRegion(chatId, regionArg) {
|
|
|
8078
8409
|
);
|
|
8079
8410
|
const status = JSON.parse(result);
|
|
8080
8411
|
const lines = [
|
|
8081
|
-
"
|
|
8412
|
+
":globe: Codex Region Status",
|
|
8082
8413
|
"",
|
|
8083
8414
|
`Active: ${status.active_region?.toUpperCase() || "unknown"}`,
|
|
8084
8415
|
`Override: ${status.override || "auto"}`,
|
|
8085
|
-
`Sweden available: ${status.sweden_available ? "
|
|
8416
|
+
`Sweden available: ${status.sweden_available ? ":check:" : ":close:"}`,
|
|
8086
8417
|
`Cooldown: ${status.cooldown_min}min`,
|
|
8087
8418
|
];
|
|
8088
8419
|
if (status.switched_ago_min !== null) {
|
|
@@ -8117,13 +8448,13 @@ async function cmdRegion(chatId, regionArg) {
|
|
|
8117
8448
|
: `. '${resolveVeKanbanPs1Path()}'; Set-RegionOverride -Region '${target}' | ConvertTo-Json`;
|
|
8118
8449
|
const result = runPwsh(psCmd);
|
|
8119
8450
|
const info = JSON.parse(result);
|
|
8120
|
-
const icon = info.changed ? "
|
|
8451
|
+
const icon = info.changed ? ":check:" : ":help:";
|
|
8121
8452
|
await sendReply(
|
|
8122
8453
|
chatId,
|
|
8123
8454
|
`${icon} Region: ${info.region?.toUpperCase()}\nReason: ${info.reason}`,
|
|
8124
8455
|
);
|
|
8125
8456
|
} catch (err) {
|
|
8126
|
-
await sendReply(chatId,
|
|
8457
|
+
await sendReply(chatId, `:close: Region switch failed: ${err.message}`);
|
|
8127
8458
|
}
|
|
8128
8459
|
}
|
|
8129
8460
|
|
|
@@ -8140,23 +8471,23 @@ async function cmdHealth(chatId) {
|
|
|
8140
8471
|
const arr = buildExecutorHealthEntries(executorConfig, metrics);
|
|
8141
8472
|
|
|
8142
8473
|
const iconMap = {
|
|
8143
|
-
healthy: "
|
|
8144
|
-
degraded: "
|
|
8145
|
-
cooldown: "
|
|
8146
|
-
disabled: "
|
|
8474
|
+
healthy: ":check:",
|
|
8475
|
+
degraded: ":alert:",
|
|
8476
|
+
cooldown: ":pause:",
|
|
8477
|
+
disabled: ":close:",
|
|
8147
8478
|
};
|
|
8148
|
-
const lines = ["
|
|
8479
|
+
const lines = [":heart: Executor Health Dashboard\n"];
|
|
8149
8480
|
|
|
8150
8481
|
if (!arr.length) {
|
|
8151
8482
|
lines.push("No executor data available.");
|
|
8152
8483
|
}
|
|
8153
8484
|
|
|
8154
8485
|
for (const e of arr) {
|
|
8155
|
-
const icon = iconMap[e.status] || "
|
|
8486
|
+
const icon = iconMap[e.status] || ":help:";
|
|
8156
8487
|
lines.push(
|
|
8157
8488
|
`${icon} ${e.label} (${e.tier}/${e.region})\n` +
|
|
8158
8489
|
` Status: ${e.status} | Active: ${e.stats.active}\n` +
|
|
8159
|
-
` ✓${e.stats.successes} ✗${e.stats.failures}
|
|
8490
|
+
` ✓${e.stats.successes} ✗${e.stats.failures} :clock:${e.stats.timeouts} :ban:${e.stats.rate_limits}`,
|
|
8160
8491
|
);
|
|
8161
8492
|
}
|
|
8162
8493
|
|
|
@@ -8171,11 +8502,11 @@ async function cmdHealth(chatId) {
|
|
|
8171
8502
|
const region = JSON.parse(regionResult);
|
|
8172
8503
|
lines.push(
|
|
8173
8504
|
"",
|
|
8174
|
-
|
|
8505
|
+
`:globe: Region: ${region.active_region?.toUpperCase()} ${region.override ? `(override: ${region.override})` : "(auto)"}`,
|
|
8175
8506
|
`Sweden backup: ${region.sweden_available ? "available" : "not configured"}`,
|
|
8176
8507
|
);
|
|
8177
8508
|
} catch {
|
|
8178
|
-
lines.push("", "
|
|
8509
|
+
lines.push("", ":globe: Region: unavailable");
|
|
8179
8510
|
}
|
|
8180
8511
|
|
|
8181
8512
|
await sendReply(chatId, lines.join("\n"));
|
|
@@ -8228,7 +8559,7 @@ async function cmdModel(chatId, modelArg) {
|
|
|
8228
8559
|
}
|
|
8229
8560
|
}
|
|
8230
8561
|
const lines = [
|
|
8231
|
-
"
|
|
8562
|
+
":bot: Model Routing",
|
|
8232
8563
|
"",
|
|
8233
8564
|
`Override: ${overrideText}`,
|
|
8234
8565
|
"",
|
|
@@ -8259,10 +8590,10 @@ async function cmdModel(chatId, modelArg) {
|
|
|
8259
8590
|
}
|
|
8260
8591
|
await sendReply(
|
|
8261
8592
|
chatId,
|
|
8262
|
-
"
|
|
8593
|
+
":check: Model override cleared. Smart routing active.",
|
|
8263
8594
|
);
|
|
8264
8595
|
} catch (err) {
|
|
8265
|
-
await sendReply(chatId,
|
|
8596
|
+
await sendReply(chatId, `:close: Error: ${err.message}`);
|
|
8266
8597
|
}
|
|
8267
8598
|
return;
|
|
8268
8599
|
}
|
|
@@ -8296,10 +8627,10 @@ async function cmdModel(chatId, modelArg) {
|
|
|
8296
8627
|
);
|
|
8297
8628
|
await sendReply(
|
|
8298
8629
|
chatId,
|
|
8299
|
-
|
|
8630
|
+
`:check: Model override set: ${target}\nApplies to next 3 tasks (or 1 hour)`,
|
|
8300
8631
|
);
|
|
8301
8632
|
} catch (err) {
|
|
8302
|
-
await sendReply(chatId,
|
|
8633
|
+
await sendReply(chatId, `:close: Error: ${err.message}`);
|
|
8303
8634
|
}
|
|
8304
8635
|
}
|
|
8305
8636
|
|
|
@@ -8311,7 +8642,7 @@ async function cmdKanban(chatId, backendArg) {
|
|
|
8311
8642
|
process.env.KANBAN_SYNC_POLICY || "internal-primary",
|
|
8312
8643
|
).toLowerCase();
|
|
8313
8644
|
const lines = [
|
|
8314
|
-
"
|
|
8645
|
+
":clipboard: Kanban Backend Status",
|
|
8315
8646
|
"",
|
|
8316
8647
|
`Active: ${current}`,
|
|
8317
8648
|
`Sync Policy: ${syncPolicy}`,
|
|
@@ -8342,17 +8673,17 @@ async function cmdKanban(chatId, backendArg) {
|
|
|
8342
8673
|
setKanbanBackend(target);
|
|
8343
8674
|
await sendReply(
|
|
8344
8675
|
chatId,
|
|
8345
|
-
|
|
8676
|
+
`:check: Kanban backend switched to: ${target}\nActive: ${getKanbanBackendName()}`,
|
|
8346
8677
|
);
|
|
8347
8678
|
} catch (err) {
|
|
8348
|
-
await sendReply(chatId,
|
|
8679
|
+
await sendReply(chatId, `:close: Error switching backend: ${err.message}`);
|
|
8349
8680
|
}
|
|
8350
8681
|
}
|
|
8351
8682
|
|
|
8352
8683
|
async function cmdAutoBacklog(chatId, args) {
|
|
8353
8684
|
const executor = _getInternalExecutor?.();
|
|
8354
8685
|
if (!executor) {
|
|
8355
|
-
await sendReply(chatId, "
|
|
8686
|
+
await sendReply(chatId, ":alert: Internal executor is not available.");
|
|
8356
8687
|
return;
|
|
8357
8688
|
}
|
|
8358
8689
|
|
|
@@ -8366,7 +8697,7 @@ async function cmdAutoBacklog(chatId, args) {
|
|
|
8366
8697
|
await sendReply(
|
|
8367
8698
|
chatId,
|
|
8368
8699
|
[
|
|
8369
|
-
"
|
|
8700
|
+
":repeat: Experimental Auto-Backlog",
|
|
8370
8701
|
"",
|
|
8371
8702
|
`Enabled: ${cfg.enabled ? "yes" : "no"}`,
|
|
8372
8703
|
`Min new tasks: ${cfg.minNewTasks ?? 1}`,
|
|
@@ -8390,7 +8721,7 @@ async function cmdAutoBacklog(chatId, args) {
|
|
|
8390
8721
|
});
|
|
8391
8722
|
await sendReply(
|
|
8392
8723
|
chatId,
|
|
8393
|
-
|
|
8724
|
+
`:check: Auto-backlog ${op === "on" ? "enabled" : "disabled"}. Min=${cfg?.minNewTasks ?? 1}, Max=${cfg?.maxNewTasks ?? 2}`,
|
|
8394
8725
|
);
|
|
8395
8726
|
return;
|
|
8396
8727
|
}
|
|
@@ -8398,14 +8729,14 @@ async function cmdAutoBacklog(chatId, args) {
|
|
|
8398
8729
|
if ((op === "min" || op === "max") && parts[1]) {
|
|
8399
8730
|
const value = Number(parts[1]);
|
|
8400
8731
|
if (!Number.isFinite(value)) {
|
|
8401
|
-
await sendReply(chatId,
|
|
8732
|
+
await sendReply(chatId, `:close: Invalid ${op} value: ${parts[1]}`);
|
|
8402
8733
|
return;
|
|
8403
8734
|
}
|
|
8404
8735
|
const patch = op === "min" ? { minNewTasks: value } : { maxNewTasks: value };
|
|
8405
8736
|
const cfg = executor.setBacklogReplenishmentConfig?.(patch);
|
|
8406
8737
|
await sendReply(
|
|
8407
8738
|
chatId,
|
|
8408
|
-
|
|
8739
|
+
`:check: Auto-backlog updated. Enabled=${cfg?.enabled ? "yes" : "no"}, Min=${cfg?.minNewTasks ?? 1}, Max=${cfg?.maxNewTasks ?? 2}`,
|
|
8409
8740
|
);
|
|
8410
8741
|
return;
|
|
8411
8742
|
}
|
|
@@ -8416,7 +8747,7 @@ async function cmdAutoBacklog(chatId, args) {
|
|
|
8416
8747
|
async function cmdRequirements(chatId, args) {
|
|
8417
8748
|
const executor = _getInternalExecutor?.();
|
|
8418
8749
|
if (!executor) {
|
|
8419
|
-
await sendReply(chatId, "
|
|
8750
|
+
await sendReply(chatId, ":alert: Internal executor is not available.");
|
|
8420
8751
|
return;
|
|
8421
8752
|
}
|
|
8422
8753
|
const profiles = [
|
|
@@ -8435,7 +8766,7 @@ async function cmdRequirements(chatId, args) {
|
|
|
8435
8766
|
await sendReply(
|
|
8436
8767
|
chatId,
|
|
8437
8768
|
[
|
|
8438
|
-
"
|
|
8769
|
+
":ruler: Project Requirements",
|
|
8439
8770
|
"",
|
|
8440
8771
|
`Profile: ${req.profile || "feature"}`,
|
|
8441
8772
|
`Notes: ${req.notes || "(none)"}`,
|
|
@@ -8451,7 +8782,7 @@ async function cmdRequirements(chatId, args) {
|
|
|
8451
8782
|
if (!profiles.includes(profile)) {
|
|
8452
8783
|
await sendReply(
|
|
8453
8784
|
chatId,
|
|
8454
|
-
|
|
8785
|
+
`:close: Unknown requirements profile: ${input}\nValid: ${profiles.join(", ")}`,
|
|
8455
8786
|
);
|
|
8456
8787
|
return;
|
|
8457
8788
|
}
|
|
@@ -8459,7 +8790,7 @@ async function cmdRequirements(chatId, args) {
|
|
|
8459
8790
|
const req = executor.setProjectRequirements?.({ profile });
|
|
8460
8791
|
await sendReply(
|
|
8461
8792
|
chatId,
|
|
8462
|
-
|
|
8793
|
+
`:check: Project requirements profile set to ${req?.profile || profile}`,
|
|
8463
8794
|
);
|
|
8464
8795
|
}
|
|
8465
8796
|
|
|
@@ -8472,7 +8803,7 @@ async function cmdThreads(chatId, subArg) {
|
|
|
8472
8803
|
if (!confirmed) {
|
|
8473
8804
|
await sendReply(
|
|
8474
8805
|
chatId,
|
|
8475
|
-
"
|
|
8806
|
+
":alert: This will clear all thread records. Proceed?",
|
|
8476
8807
|
{
|
|
8477
8808
|
reply_markup: buildConfirmKeyboard(
|
|
8478
8809
|
"cb:confirm_threads_clear",
|
|
@@ -8483,7 +8814,7 @@ async function cmdThreads(chatId, subArg) {
|
|
|
8483
8814
|
return;
|
|
8484
8815
|
}
|
|
8485
8816
|
clearThreadRegistry();
|
|
8486
|
-
await sendReply(chatId, "
|
|
8817
|
+
await sendReply(chatId, ":check: Thread registry cleared.");
|
|
8487
8818
|
return;
|
|
8488
8819
|
}
|
|
8489
8820
|
}
|
|
@@ -8515,7 +8846,7 @@ async function cmdThreads(chatId, subArg) {
|
|
|
8515
8846
|
return;
|
|
8516
8847
|
}
|
|
8517
8848
|
invalidateThread(taskKey);
|
|
8518
|
-
await sendReply(chatId,
|
|
8849
|
+
await sendReply(chatId, `:check: Thread for "${taskKey}" invalidated.`);
|
|
8519
8850
|
return;
|
|
8520
8851
|
}
|
|
8521
8852
|
|
|
@@ -8523,12 +8854,12 @@ async function cmdThreads(chatId, subArg) {
|
|
|
8523
8854
|
if (threads.length === 0) {
|
|
8524
8855
|
await sendReply(
|
|
8525
8856
|
chatId,
|
|
8526
|
-
"
|
|
8857
|
+
":link: No active agent threads.\n\nThreads are created when tasks run via the agent pool with thread persistence.",
|
|
8527
8858
|
);
|
|
8528
8859
|
return;
|
|
8529
8860
|
}
|
|
8530
8861
|
|
|
8531
|
-
const lines = [
|
|
8862
|
+
const lines = [`:link: Active Agent Threads (${threads.length})`, ""];
|
|
8532
8863
|
|
|
8533
8864
|
for (const t of threads) {
|
|
8534
8865
|
const ageMin = Math.round(t.age / 60_000);
|
|
@@ -8585,12 +8916,12 @@ async function cmdWorktrees(chatId, args) {
|
|
|
8585
8916
|
// Prune stale worktrees
|
|
8586
8917
|
try {
|
|
8587
8918
|
const result = await pruneStaleWorktrees(repoRoot);
|
|
8588
|
-
const lines = [
|
|
8919
|
+
const lines = [`:trash: Worktree prune complete:`];
|
|
8589
8920
|
lines.push(` Pruned: ${result.pruned}`);
|
|
8590
8921
|
lines.push(` Registry evicted: ${result.evicted}`);
|
|
8591
8922
|
await sendReply(chatId, lines.join("\n"));
|
|
8592
8923
|
} catch (err) {
|
|
8593
|
-
await sendReply(chatId,
|
|
8924
|
+
await sendReply(chatId, `:close: Prune failed: ${err.message}`);
|
|
8594
8925
|
}
|
|
8595
8926
|
return;
|
|
8596
8927
|
}
|
|
@@ -8626,16 +8957,16 @@ async function cmdWorktrees(chatId, args) {
|
|
|
8626
8957
|
if (result.success) {
|
|
8627
8958
|
await sendReply(
|
|
8628
8959
|
chatId,
|
|
8629
|
-
|
|
8960
|
+
`:check: Released worktree for "${taskKey}": ${result.path}`,
|
|
8630
8961
|
);
|
|
8631
8962
|
} else {
|
|
8632
8963
|
await sendReply(
|
|
8633
8964
|
chatId,
|
|
8634
|
-
|
|
8965
|
+
`:alert: No worktree found for task key "${taskKey}"`,
|
|
8635
8966
|
);
|
|
8636
8967
|
}
|
|
8637
8968
|
} catch (err) {
|
|
8638
|
-
await sendReply(chatId,
|
|
8969
|
+
await sendReply(chatId, `:close: Release failed: ${err.message}`);
|
|
8639
8970
|
}
|
|
8640
8971
|
return;
|
|
8641
8972
|
}
|
|
@@ -8643,7 +8974,7 @@ async function cmdWorktrees(chatId, args) {
|
|
|
8643
8974
|
if (sub === "stats") {
|
|
8644
8975
|
try {
|
|
8645
8976
|
const stats = getWorktreeStats();
|
|
8646
|
-
const lines = [
|
|
8977
|
+
const lines = [`:chart: Worktree Stats:`];
|
|
8647
8978
|
lines.push(` Total tracked: ${stats.total}`);
|
|
8648
8979
|
lines.push(` Active: ${stats.active}`);
|
|
8649
8980
|
lines.push(` Stale: ${stats.stale}`);
|
|
@@ -8655,7 +8986,7 @@ async function cmdWorktrees(chatId, args) {
|
|
|
8655
8986
|
}
|
|
8656
8987
|
await sendReply(chatId, lines.join("\n"));
|
|
8657
8988
|
} catch (err) {
|
|
8658
|
-
await sendReply(chatId,
|
|
8989
|
+
await sendReply(chatId, `:close: Stats failed: ${err.message}`);
|
|
8659
8990
|
}
|
|
8660
8991
|
return;
|
|
8661
8992
|
}
|
|
@@ -8664,11 +8995,11 @@ async function cmdWorktrees(chatId, args) {
|
|
|
8664
8995
|
try {
|
|
8665
8996
|
const worktrees = listManagedWorktrees(repoRoot);
|
|
8666
8997
|
if (!worktrees || worktrees.length === 0) {
|
|
8667
|
-
await sendReply(chatId, "
|
|
8998
|
+
await sendReply(chatId, ":git: No active worktrees tracked.");
|
|
8668
8999
|
return;
|
|
8669
9000
|
}
|
|
8670
9001
|
|
|
8671
|
-
const lines = [
|
|
9002
|
+
const lines = [`:git: Active Worktrees (${worktrees.length}):\n`];
|
|
8672
9003
|
for (const wt of worktrees) {
|
|
8673
9004
|
const ageMin = Math.round((wt.age || 0) / 60000);
|
|
8674
9005
|
const ageStr =
|
|
@@ -8687,7 +9018,7 @@ async function cmdWorktrees(chatId, args) {
|
|
|
8687
9018
|
);
|
|
8688
9019
|
await sendReply(chatId, lines.join("\n"));
|
|
8689
9020
|
} catch (err) {
|
|
8690
|
-
await sendReply(chatId,
|
|
9021
|
+
await sendReply(chatId, `:close: Worktree list failed: ${err.message}`);
|
|
8691
9022
|
}
|
|
8692
9023
|
}
|
|
8693
9024
|
|
|
@@ -8712,7 +9043,7 @@ async function cmdExecutor(chatId, args) {
|
|
|
8712
9043
|
if (!executor) {
|
|
8713
9044
|
await sendReply(
|
|
8714
9045
|
chatId,
|
|
8715
|
-
|
|
9046
|
+
`:settings: Internal executor not active (mode: ${mode})`,
|
|
8716
9047
|
);
|
|
8717
9048
|
return;
|
|
8718
9049
|
}
|
|
@@ -8720,12 +9051,12 @@ async function cmdExecutor(chatId, args) {
|
|
|
8720
9051
|
if (status.slots.length === 0) {
|
|
8721
9052
|
await sendReply(
|
|
8722
9053
|
chatId,
|
|
8723
|
-
|
|
9054
|
+
`:settings: No active task slots (${status.activeSlots}/${status.maxParallel} used)`,
|
|
8724
9055
|
);
|
|
8725
9056
|
return;
|
|
8726
9057
|
}
|
|
8727
9058
|
const lines = [
|
|
8728
|
-
|
|
9059
|
+
`:settings: Active Task Slots (${status.activeSlots}/${status.maxParallel}):\n`,
|
|
8729
9060
|
];
|
|
8730
9061
|
for (const slot of status.slots) {
|
|
8731
9062
|
const runStr = formatRuntimeSeconds(slot.runningFor);
|
|
@@ -8751,26 +9082,26 @@ async function cmdExecutor(chatId, args) {
|
|
|
8751
9082
|
if (target && ["vk", "internal", "hybrid"].includes(target)) {
|
|
8752
9083
|
await sendReply(
|
|
8753
9084
|
chatId,
|
|
8754
|
-
|
|
8755
|
-
|
|
9085
|
+
`:settings: Current mode: ${mode}\n` +
|
|
9086
|
+
`:help: Mode can be changed via EXECUTOR_MODE env var or config.\n` +
|
|
8756
9087
|
`Restart the monitor after changing to apply.`,
|
|
8757
9088
|
);
|
|
8758
9089
|
} else {
|
|
8759
9090
|
await sendReply(
|
|
8760
9091
|
chatId,
|
|
8761
|
-
|
|
9092
|
+
`:settings: Current executor mode: ${mode}\n\nValid modes: vk, internal, hybrid`,
|
|
8762
9093
|
);
|
|
8763
9094
|
}
|
|
8764
9095
|
return;
|
|
8765
9096
|
}
|
|
8766
9097
|
|
|
8767
9098
|
// Default: show status
|
|
8768
|
-
const lines = [
|
|
9099
|
+
const lines = [`:settings: Executor Status\n`];
|
|
8769
9100
|
lines.push(`Mode: ${mode}`);
|
|
8770
9101
|
|
|
8771
9102
|
if (executor) {
|
|
8772
9103
|
const status = executor.getStatus();
|
|
8773
|
-
lines.push(`Running: ${status.running ? "
|
|
9104
|
+
lines.push(`Running: ${status.running ? ":check: Yes" : ":close: No"}`);
|
|
8774
9105
|
lines.push(`SDK: ${status.sdk}`);
|
|
8775
9106
|
lines.push(`Active Slots: ${status.activeSlots}/${status.maxParallel}`);
|
|
8776
9107
|
lines.push(`Poll Interval: ${status.pollIntervalMs / 1000}s`);
|
|
@@ -8784,7 +9115,7 @@ async function cmdExecutor(chatId, args) {
|
|
|
8784
9115
|
lines.push(`Internal executor: not active`);
|
|
8785
9116
|
if (mode === "vk") {
|
|
8786
9117
|
lines.push(
|
|
8787
|
-
`\n
|
|
9118
|
+
`\n:help: Using VK executor only. Set EXECUTOR_MODE=internal or hybrid to enable.`,
|
|
8788
9119
|
);
|
|
8789
9120
|
}
|
|
8790
9121
|
}
|
|
@@ -8800,7 +9131,7 @@ async function cmdSdk(chatId, sdkArg) {
|
|
|
8800
9131
|
const primaryAgent = getPrimaryAgentName();
|
|
8801
9132
|
const available = getAvailableSdks();
|
|
8802
9133
|
const lines = [
|
|
8803
|
-
"
|
|
9134
|
+
":plug: Agent SDK Status",
|
|
8804
9135
|
"",
|
|
8805
9136
|
`Pool SDK: ${poolSdk}`,
|
|
8806
9137
|
`Primary Agent: ${primaryAgent}`,
|
|
@@ -8822,7 +9153,7 @@ async function cmdSdk(chatId, sdkArg) {
|
|
|
8822
9153
|
resetPoolSdkCache();
|
|
8823
9154
|
await sendReply(
|
|
8824
9155
|
chatId,
|
|
8825
|
-
"
|
|
9156
|
+
":check: Agent pool SDK reset to config default.\nCurrent: " +
|
|
8826
9157
|
getPoolSdkName(),
|
|
8827
9158
|
);
|
|
8828
9159
|
return;
|
|
@@ -8849,10 +9180,10 @@ async function cmdSdk(chatId, sdkArg) {
|
|
|
8849
9180
|
|
|
8850
9181
|
await sendReply(
|
|
8851
9182
|
chatId,
|
|
8852
|
-
|
|
9183
|
+
`:check: SDK switched to: ${target}\nPool SDK: ${getPoolSdkName()}\n${primaryStatus}`,
|
|
8853
9184
|
);
|
|
8854
9185
|
} catch (err) {
|
|
8855
|
-
await sendReply(chatId,
|
|
9186
|
+
await sendReply(chatId, `:close: Error switching SDK: ${err.message}`);
|
|
8856
9187
|
}
|
|
8857
9188
|
}
|
|
8858
9189
|
|
|
@@ -8895,12 +9226,12 @@ async function cmdSharedWorkspaceClaim(chatId, rawArgs) {
|
|
|
8895
9226
|
actor,
|
|
8896
9227
|
});
|
|
8897
9228
|
if (result.error) {
|
|
8898
|
-
await sendReply(chatId,
|
|
9229
|
+
await sendReply(chatId, `:close: ${result.error}`);
|
|
8899
9230
|
return;
|
|
8900
9231
|
}
|
|
8901
9232
|
await sendReply(
|
|
8902
9233
|
chatId,
|
|
8903
|
-
|
|
9234
|
+
`:check: Claimed ${result.workspace.id} for ${result.lease.owner} (expires ${result.lease.lease_expires_at})`,
|
|
8904
9235
|
);
|
|
8905
9236
|
}
|
|
8906
9237
|
|
|
@@ -8946,10 +9277,10 @@ async function cmdSharedWorkspaceRelease(chatId, rawArgs) {
|
|
|
8946
9277
|
actor,
|
|
8947
9278
|
});
|
|
8948
9279
|
if (result.error) {
|
|
8949
|
-
await sendReply(chatId,
|
|
9280
|
+
await sendReply(chatId, `:close: ${result.error}`);
|
|
8950
9281
|
return;
|
|
8951
9282
|
}
|
|
8952
|
-
await sendReply(chatId,
|
|
9283
|
+
await sendReply(chatId, `:check: Released ${result.workspace.id}`);
|
|
8953
9284
|
}
|
|
8954
9285
|
|
|
8955
9286
|
// ── /agent — route to workspace registry ────────────────────────────────────
|
|
@@ -9462,7 +9793,7 @@ async function cmdAgent(chatId, rawArgs) {
|
|
|
9462
9793
|
} catch (err) {
|
|
9463
9794
|
await sendReply(
|
|
9464
9795
|
chatId,
|
|
9465
|
-
|
|
9796
|
+
`:close: /agent failed: ${err.message || err}\n${infoLines.join("\\n")}`,
|
|
9466
9797
|
);
|
|
9467
9798
|
}
|
|
9468
9799
|
}
|
|
@@ -9474,7 +9805,7 @@ async function cmdBackground(chatId, args) {
|
|
|
9474
9805
|
if (task) {
|
|
9475
9806
|
await sendReply(
|
|
9476
9807
|
chatId,
|
|
9477
|
-
|
|
9808
|
+
`:server: Background task queued: "${task.slice(0, 80)}${task.length > 80 ? "…" : ""}"`,
|
|
9478
9809
|
);
|
|
9479
9810
|
safeDetach("background-free-text", () => handleFreeText(task, chatId, { background: true, isolated: true }));
|
|
9480
9811
|
return;
|
|
@@ -9507,7 +9838,7 @@ async function cmdBackground(chatId, args) {
|
|
|
9507
9838
|
|
|
9508
9839
|
await sendReply(
|
|
9509
9840
|
chatId,
|
|
9510
|
-
"
|
|
9841
|
+
":server: Background mode enabled for the active agent. I will post a final summary when it completes. Use /stop to cancel or /steer to adjust context.",
|
|
9511
9842
|
);
|
|
9512
9843
|
}
|
|
9513
9844
|
|
|
@@ -9518,7 +9849,7 @@ async function cmdStop(chatId, args) {
|
|
|
9518
9849
|
if (!confirmFlag) {
|
|
9519
9850
|
await sendReply(
|
|
9520
9851
|
chatId,
|
|
9521
|
-
"
|
|
9852
|
+
":alert: Stop will halt the active agent session. Proceed?",
|
|
9522
9853
|
{ reply_markup: buildConfirmKeyboard("cb:confirm_stop", "Confirm Stop") },
|
|
9523
9854
|
);
|
|
9524
9855
|
return;
|
|
@@ -9538,14 +9869,14 @@ async function cmdStop(chatId, args) {
|
|
|
9538
9869
|
}
|
|
9539
9870
|
if (session.actionLog) {
|
|
9540
9871
|
session.actionLog.push({
|
|
9541
|
-
icon: "
|
|
9872
|
+
icon: ":close:",
|
|
9542
9873
|
text: "Stop requested by user (will halt after current step)",
|
|
9543
9874
|
});
|
|
9544
9875
|
if (session.scheduleEdit) {
|
|
9545
9876
|
session.scheduleEdit();
|
|
9546
9877
|
}
|
|
9547
9878
|
}
|
|
9548
|
-
await sendReply(chatId, "
|
|
9879
|
+
await sendReply(chatId, ":close: Stop signal sent. Agent will halt and wait.");
|
|
9549
9880
|
}
|
|
9550
9881
|
|
|
9551
9882
|
// ── /steer — Steering update for running agent ───────────────────────────────
|
|
@@ -9568,14 +9899,14 @@ async function cmdSteer(chatId, steerArgs) {
|
|
|
9568
9899
|
if (result.ok) {
|
|
9569
9900
|
if (session.actionLog) {
|
|
9570
9901
|
session.actionLog.push({
|
|
9571
|
-
icon: "
|
|
9902
|
+
icon: ":compass:",
|
|
9572
9903
|
text: `Steering update delivered (${result.mode})`,
|
|
9573
9904
|
});
|
|
9574
9905
|
if (session.scheduleEdit) {
|
|
9575
9906
|
session.scheduleEdit();
|
|
9576
9907
|
}
|
|
9577
9908
|
}
|
|
9578
|
-
await sendReply(chatId,
|
|
9909
|
+
await sendReply(chatId, `:compass: Steering sent (${result.mode}).`);
|
|
9579
9910
|
return;
|
|
9580
9911
|
}
|
|
9581
9912
|
|
|
@@ -9587,7 +9918,7 @@ async function cmdSteer(chatId, steerArgs) {
|
|
|
9587
9918
|
if (session.actionLog) {
|
|
9588
9919
|
const steerStatus = result.reason || "failed";
|
|
9589
9920
|
session.actionLog.push({
|
|
9590
|
-
icon: "
|
|
9921
|
+
icon: ":compass:",
|
|
9591
9922
|
text: `Steering queued (#${qLen}; steer failed: ${steerStatus})`,
|
|
9592
9923
|
kind: "followup_queued",
|
|
9593
9924
|
steerStatus,
|
|
@@ -9596,7 +9927,7 @@ async function cmdSteer(chatId, steerArgs) {
|
|
|
9596
9927
|
session.scheduleEdit();
|
|
9597
9928
|
}
|
|
9598
9929
|
}
|
|
9599
|
-
await sendReply(chatId,
|
|
9930
|
+
await sendReply(chatId, `:compass: Steering queued (#${qLen}).`);
|
|
9600
9931
|
}
|
|
9601
9932
|
|
|
9602
9933
|
// ── Free-text → Primary Agent Dispatch ───────────────────────────────────────
|
|
@@ -9628,8 +9959,8 @@ function buildStreamMessage({
|
|
|
9628
9959
|
searchesDone,
|
|
9629
9960
|
statusIcon,
|
|
9630
9961
|
}) {
|
|
9631
|
-
const header =
|
|
9632
|
-
const counter =
|
|
9962
|
+
const header = `:settings: Agent: ${taskPreview}`;
|
|
9963
|
+
const counter = `:chart: Actions: ${totalActions} | ${phase}`;
|
|
9633
9964
|
const separator = "────────────────────────────";
|
|
9634
9965
|
|
|
9635
9966
|
// Show last N actions (keep message compact)
|
|
@@ -9648,37 +9979,37 @@ function buildStreamMessage({
|
|
|
9648
9979
|
}
|
|
9649
9980
|
|
|
9650
9981
|
if (currentThought) {
|
|
9651
|
-
lines.push("",
|
|
9982
|
+
lines.push("", `:u1f4ad: ${currentThought}`);
|
|
9652
9983
|
}
|
|
9653
9984
|
|
|
9654
9985
|
if (!finalResponse) {
|
|
9655
9986
|
if (filesWritten?.size) {
|
|
9656
|
-
lines.push("", "
|
|
9987
|
+
lines.push("", ":edit: Files modified so far:");
|
|
9657
9988
|
const recent = Array.from(filesWritten.entries()).slice(-6);
|
|
9658
9989
|
for (const [fpath, info] of recent) {
|
|
9659
9990
|
const name = shortPath(fpath);
|
|
9660
9991
|
if (info.adds || info.dels) {
|
|
9661
|
-
lines.push(`
|
|
9992
|
+
lines.push(` :edit: ${name} (+${info.adds} -${info.dels})`);
|
|
9662
9993
|
} else {
|
|
9663
|
-
lines.push(`
|
|
9994
|
+
lines.push(` :edit: ${name}`);
|
|
9664
9995
|
}
|
|
9665
9996
|
}
|
|
9666
9997
|
}
|
|
9667
9998
|
if (filesRead?.size) {
|
|
9668
|
-
lines.push("", "
|
|
9999
|
+
lines.push("", ":file: Files read so far:");
|
|
9669
10000
|
const recent = Array.from(filesRead.values()).slice(-6);
|
|
9670
10001
|
for (const fpath of recent) {
|
|
9671
|
-
lines.push(`
|
|
10002
|
+
lines.push(` :file: ${shortPath(fpath)}`);
|
|
9672
10003
|
}
|
|
9673
10004
|
}
|
|
9674
10005
|
if (searchesDone) {
|
|
9675
|
-
lines.push("",
|
|
10006
|
+
lines.push("", `:search: Searches: ${searchesDone}`);
|
|
9676
10007
|
}
|
|
9677
10008
|
}
|
|
9678
10009
|
|
|
9679
10010
|
if (finalResponse) {
|
|
9680
10011
|
// ── Final summary block ──────────────────────────────────────
|
|
9681
|
-
const icon = statusIcon || "
|
|
10012
|
+
const icon = statusIcon || ":check:";
|
|
9682
10013
|
lines.push("", separator);
|
|
9683
10014
|
lines.push(`${icon} ${phase}`);
|
|
9684
10015
|
lines.push("");
|
|
@@ -9689,20 +10020,20 @@ function buildStreamMessage({
|
|
|
9689
10020
|
if (filesWritten?.size) stats.push(`${filesWritten.size} files modified`);
|
|
9690
10021
|
if (searchesDone) stats.push(`${searchesDone} searches`);
|
|
9691
10022
|
if (stats.length) {
|
|
9692
|
-
lines.push(
|
|
10023
|
+
lines.push(`:chart: ${stats.join(" · ")}`);
|
|
9693
10024
|
}
|
|
9694
10025
|
|
|
9695
10026
|
// Files modified detail
|
|
9696
10027
|
if (filesWritten?.size) {
|
|
9697
10028
|
lines.push("");
|
|
9698
|
-
lines.push("
|
|
10029
|
+
lines.push(":folder: Files modified:");
|
|
9699
10030
|
for (const [fpath, info] of filesWritten) {
|
|
9700
10031
|
const name = shortPath(fpath);
|
|
9701
10032
|
if (info.adds || info.dels) {
|
|
9702
|
-
lines.push(`
|
|
10033
|
+
lines.push(` :edit: ${name} (+${info.adds} -${info.dels})`);
|
|
9703
10034
|
} else {
|
|
9704
10035
|
const kindIcon =
|
|
9705
|
-
info.kind === "add" ? "
|
|
10036
|
+
info.kind === "add" ? ":plus:" : info.kind === "delete" ? ":trash:" : ":edit:";
|
|
9706
10037
|
lines.push(` ${kindIcon} ${name}`);
|
|
9707
10038
|
}
|
|
9708
10039
|
}
|
|
@@ -9737,13 +10068,13 @@ async function handleFreeText(text, chatId, options = {}) {
|
|
|
9737
10068
|
// Acknowledge the follow-up in both the user's chat and update the agent message
|
|
9738
10069
|
await sendDirect(
|
|
9739
10070
|
chatId,
|
|
9740
|
-
|
|
10071
|
+
`:pin: Follow-up queued (#${qLen}). Agent will process it after current action. ${steerNote}`,
|
|
9741
10072
|
);
|
|
9742
10073
|
|
|
9743
10074
|
// Add follow-up indicator to the streaming message
|
|
9744
10075
|
if (chatSession.actionLog) {
|
|
9745
10076
|
chatSession.actionLog.push({
|
|
9746
|
-
icon: "
|
|
10077
|
+
icon: ":pin:",
|
|
9747
10078
|
text: `Follow-up: "${text.length > 60 ? text.slice(0, 60) + "…" : text}" (${steerNote})`,
|
|
9748
10079
|
kind: "followup_queued",
|
|
9749
10080
|
steerStatus,
|
|
@@ -9997,7 +10328,7 @@ async function handleFreeText(text, chatId, options = {}) {
|
|
|
9997
10328
|
if (followUps.length > 0 && !sessionState.aborted) {
|
|
9998
10329
|
for (const followUp of followUps) {
|
|
9999
10330
|
actionLog.push({
|
|
10000
|
-
icon: "
|
|
10331
|
+
icon: ":pin:",
|
|
10001
10332
|
text: `Processing follow-up: "${followUp.slice(0, 60)}"`,
|
|
10002
10333
|
});
|
|
10003
10334
|
phase = "processing follow-up…";
|
|
@@ -10015,12 +10346,12 @@ async function handleFreeText(text, chatId, options = {}) {
|
|
|
10015
10346
|
if (followUpResult.finalResponse) {
|
|
10016
10347
|
result.finalResponse =
|
|
10017
10348
|
(result.finalResponse || "") +
|
|
10018
|
-
`\n\n
|
|
10349
|
+
`\n\n:pin: Follow-up result:\n${followUpResult.finalResponse}`;
|
|
10019
10350
|
suppressSteerFailedLines(actionLog);
|
|
10020
10351
|
}
|
|
10021
10352
|
} catch (err) {
|
|
10022
10353
|
actionLog.push({
|
|
10023
|
-
icon: "
|
|
10354
|
+
icon: ":close:",
|
|
10024
10355
|
text: `Follow-up error: ${err.message}`,
|
|
10025
10356
|
});
|
|
10026
10357
|
}
|
|
@@ -10041,14 +10372,14 @@ async function handleFreeText(text, chatId, options = {}) {
|
|
|
10041
10372
|
const hasChanges = filesWritten.size > 0;
|
|
10042
10373
|
let statusIcon;
|
|
10043
10374
|
if (hadError) {
|
|
10044
|
-
statusIcon = "
|
|
10375
|
+
statusIcon = ":close:";
|
|
10045
10376
|
phase = "Failed — needs manual review";
|
|
10046
10377
|
} else if (hasChanges) {
|
|
10047
|
-
statusIcon = "
|
|
10378
|
+
statusIcon = ":check:";
|
|
10048
10379
|
phase = "Completed successfully";
|
|
10049
10380
|
} else {
|
|
10050
10381
|
// No files changed — might be informational or might need user input
|
|
10051
|
-
statusIcon = "
|
|
10382
|
+
statusIcon = ":help:";
|
|
10052
10383
|
phase = "Completed — no files changed";
|
|
10053
10384
|
}
|
|
10054
10385
|
|
|
@@ -10084,7 +10415,7 @@ async function handleFreeText(text, chatId, options = {}) {
|
|
|
10084
10415
|
filesRead,
|
|
10085
10416
|
filesWritten,
|
|
10086
10417
|
searchesDone: searchCount,
|
|
10087
|
-
statusIcon: "
|
|
10418
|
+
statusIcon: ":close:",
|
|
10088
10419
|
});
|
|
10089
10420
|
if (backgroundMode || sessionState.background) {
|
|
10090
10421
|
await sendReply(chatId, finalMsg);
|
|
@@ -10232,11 +10563,11 @@ let liveDigest = {
|
|
|
10232
10563
|
};
|
|
10233
10564
|
|
|
10234
10565
|
const PRIORITY_EMOJI = {
|
|
10235
|
-
1: "
|
|
10236
|
-
2: "
|
|
10237
|
-
3: "
|
|
10238
|
-
4: "
|
|
10239
|
-
5: "
|
|
10566
|
+
1: ":dot:",
|
|
10567
|
+
2: ":close:",
|
|
10568
|
+
3: ":alert:",
|
|
10569
|
+
4: ":help:",
|
|
10570
|
+
5: ":dot:",
|
|
10240
10571
|
};
|
|
10241
10572
|
|
|
10242
10573
|
/**
|
|
@@ -10254,14 +10585,14 @@ function buildLiveDigestText() {
|
|
|
10254
10585
|
}
|
|
10255
10586
|
|
|
10256
10587
|
const countParts = [];
|
|
10257
|
-
if (counts[1] > 0) countParts.push(
|
|
10258
|
-
if (counts[2] > 0) countParts.push(
|
|
10259
|
-
if (counts[3] > 0) countParts.push(
|
|
10260
|
-
if (counts[4] > 0) countParts.push(
|
|
10588
|
+
if (counts[1] > 0) countParts.push(`:dot: ${counts[1]}`);
|
|
10589
|
+
if (counts[2] > 0) countParts.push(`:close: ${counts[2]}`);
|
|
10590
|
+
if (counts[3] > 0) countParts.push(`:alert: ${counts[3]}`);
|
|
10591
|
+
if (counts[4] > 0) countParts.push(`:help: ${counts[4]}`);
|
|
10261
10592
|
|
|
10262
10593
|
const statusLine = d.sealed
|
|
10263
|
-
?
|
|
10264
|
-
:
|
|
10594
|
+
? `:chart: Digest (${startTime} → ${now}) — sealed`
|
|
10595
|
+
: `:chart: Live Digest (since ${startTime}) — updating...`;
|
|
10265
10596
|
const headerLine =
|
|
10266
10597
|
countParts.length > 0
|
|
10267
10598
|
? `${statusLine}\n${countParts.join(" • ")}`
|
|
@@ -10434,7 +10765,7 @@ async function addToLiveDigest(text, priority, category) {
|
|
|
10434
10765
|
const d = liveDigest;
|
|
10435
10766
|
const now = Date.now();
|
|
10436
10767
|
const timeStr = new Date(now).toISOString().slice(11, 19);
|
|
10437
|
-
const emoji = PRIORITY_EMOJI[priority] || "
|
|
10768
|
+
const emoji = PRIORITY_EMOJI[priority] || ":help:";
|
|
10438
10769
|
|
|
10439
10770
|
// Check if we need a new digest window
|
|
10440
10771
|
const windowMs = liveDigestWindowSec * 1000;
|
|
@@ -10554,7 +10885,7 @@ export async function initStatusBoard() {
|
|
|
10554
10885
|
`[telegram-bot] status board restored (msg ${saved.messageId})`,
|
|
10555
10886
|
);
|
|
10556
10887
|
// Edit the board straight away so it shows "restarted" state
|
|
10557
|
-
scheduleStatusBoardEdit("
|
|
10888
|
+
scheduleStatusBoardEdit(":refresh: Orchestrator restarting…", {});
|
|
10558
10889
|
return;
|
|
10559
10890
|
}
|
|
10560
10891
|
} catch {
|
|
@@ -10564,7 +10895,7 @@ export async function initStatusBoard() {
|
|
|
10564
10895
|
// Create the initial status board message
|
|
10565
10896
|
const msgId = await sendDirect(
|
|
10566
10897
|
telegramChatId,
|
|
10567
|
-
"
|
|
10898
|
+
":server: Orchestrator starting…",
|
|
10568
10899
|
{ silent: true },
|
|
10569
10900
|
);
|
|
10570
10901
|
if (!msgId) return;
|
|
@@ -10675,20 +11006,20 @@ async function flushNotificationQueue() {
|
|
|
10675
11006
|
|
|
10676
11007
|
// Build summary header
|
|
10677
11008
|
const timestamp = new Date().toISOString().slice(11, 19);
|
|
10678
|
-
let header =
|
|
11009
|
+
let header = `:chart: Update Summary (${timestamp})`;
|
|
10679
11010
|
if (totalMessages > 0) {
|
|
10680
11011
|
const parts = [];
|
|
10681
|
-
if (counts.critical > 0) parts.push(
|
|
10682
|
-
if (counts.errors > 0) parts.push(
|
|
10683
|
-
if (counts.warnings > 0) parts.push(
|
|
10684
|
-
if (counts.info > 0) parts.push(
|
|
11012
|
+
if (counts.critical > 0) parts.push(`:dot: ${counts.critical}`);
|
|
11013
|
+
if (counts.errors > 0) parts.push(`:close: ${counts.errors}`);
|
|
11014
|
+
if (counts.warnings > 0) parts.push(`:alert: ${counts.warnings}`);
|
|
11015
|
+
if (counts.info > 0) parts.push(`:help: ${counts.info}`);
|
|
10685
11016
|
header += `\n${parts.join(" • ")}`;
|
|
10686
11017
|
}
|
|
10687
11018
|
|
|
10688
11019
|
// Critical messages (show all)
|
|
10689
11020
|
if (counts.critical > 0) {
|
|
10690
11021
|
sections.push(
|
|
10691
|
-
|
|
11022
|
+
`:dot: Critical:\n${messageQueue.critical.map((m) => ` • ${m.text}`).join("\n")}`,
|
|
10692
11023
|
);
|
|
10693
11024
|
}
|
|
10694
11025
|
|
|
@@ -10700,7 +11031,7 @@ async function flushNotificationQueue() {
|
|
|
10700
11031
|
if (counts.errors > 5) {
|
|
10701
11032
|
errorTexts.push(` • ... and ${counts.errors - 5} more errors`);
|
|
10702
11033
|
}
|
|
10703
|
-
sections.push(
|
|
11034
|
+
sections.push(`:close: Errors:\n${errorTexts.join("\n")}`);
|
|
10704
11035
|
}
|
|
10705
11036
|
|
|
10706
11037
|
// Warnings (show up to 3, then summarize)
|
|
@@ -10711,7 +11042,7 @@ async function flushNotificationQueue() {
|
|
|
10711
11042
|
if (counts.warnings > 3) {
|
|
10712
11043
|
warnTexts.push(` • ... and ${counts.warnings - 3} more warnings`);
|
|
10713
11044
|
}
|
|
10714
|
-
sections.push(
|
|
11045
|
+
sections.push(`:alert: Warnings:\n${warnTexts.join("\n")}`);
|
|
10715
11046
|
}
|
|
10716
11047
|
|
|
10717
11048
|
// Info messages (aggregate by category)
|
|
@@ -10724,7 +11055,7 @@ async function flushNotificationQueue() {
|
|
|
10724
11055
|
const summary = Object.entries(categories)
|
|
10725
11056
|
.map(([cat, count]) => ` • ${cat}: ${count}`)
|
|
10726
11057
|
.join("\n");
|
|
10727
|
-
sections.push(
|
|
11058
|
+
sections.push(`:help: Info:\n${summary}`);
|
|
10728
11059
|
}
|
|
10729
11060
|
|
|
10730
11061
|
// Build final message
|
|
@@ -10857,7 +11188,7 @@ export async function startTelegramBot(options = {}) {
|
|
|
10857
11188
|
onProjectSyncAlert: async (alert) => {
|
|
10858
11189
|
if (!_sendTelegramMessage) return;
|
|
10859
11190
|
const text = String(alert?.message || "Project sync alert");
|
|
10860
|
-
await _sendTelegramMessage(
|
|
11191
|
+
await _sendTelegramMessage(`:alert: ${text}`);
|
|
10861
11192
|
},
|
|
10862
11193
|
},
|
|
10863
11194
|
});
|
|
@@ -10908,7 +11239,7 @@ export async function startTelegramBot(options = {}) {
|
|
|
10908
11239
|
const port = new URL(telegramUiUrl || "http://localhost:5511").port || "5511";
|
|
10909
11240
|
await sendDirect(
|
|
10910
11241
|
telegramChatId,
|
|
10911
|
-
|
|
11242
|
+
`:zap: *Firewall Alert*\n\n` +
|
|
10912
11243
|
`Port ${port}/tcp appears blocked by \`${fwState.firewall}\`.\n` +
|
|
10913
11244
|
`The Control Center may not be reachable from your phone or LAN browser.\n\n` +
|
|
10914
11245
|
`To fix, run on the server:\n\`\`\`\n${fwState.allowCmd}\n\`\`\``,
|
|
@@ -10916,7 +11247,7 @@ export async function startTelegramBot(options = {}) {
|
|
|
10916
11247
|
parseMode: "Markdown",
|
|
10917
11248
|
reply_markup: {
|
|
10918
11249
|
inline_keyboard: [[
|
|
10919
|
-
{ text: "
|
|
11250
|
+
{ text: ":unlock: Open Port (requires admin password on server)", callback_data: "fw:open" },
|
|
10920
11251
|
]],
|
|
10921
11252
|
},
|
|
10922
11253
|
},
|
|
@@ -10995,7 +11326,7 @@ export async function startTelegramBot(options = {}) {
|
|
|
10995
11326
|
} else {
|
|
10996
11327
|
await sendDirect(
|
|
10997
11328
|
telegramChatId,
|
|
10998
|
-
|
|
11329
|
+
`:bot: Bosun primary agent online (${getPrimaryAgentName()}).\n\nType /menu for the control center or send any message to chat with the agent.\n\nRefreshing control center menu below…`,
|
|
10999
11330
|
);
|
|
11000
11331
|
await refreshStickyMenu(telegramChatId, "home", {});
|
|
11001
11332
|
|
|
@@ -11004,11 +11335,11 @@ export async function startTelegramBot(options = {}) {
|
|
|
11004
11335
|
String(process.env.TELEGRAM_UI_ALLOW_UNSAFE || "").toLowerCase(),
|
|
11005
11336
|
);
|
|
11006
11337
|
if (_isUnsafe) {
|
|
11007
|
-
const _tunnelMode = (process.env.TELEGRAM_UI_TUNNEL || "
|
|
11338
|
+
const _tunnelMode = (process.env.TELEGRAM_UI_TUNNEL || "named").toLowerCase();
|
|
11008
11339
|
const _tunnelWanted = _tunnelMode !== "disabled" && _tunnelMode !== "off" && _tunnelMode !== "0";
|
|
11009
11340
|
const title = _tunnelWanted
|
|
11010
|
-
? "
|
|
11011
|
-
: "
|
|
11341
|
+
? ":ban: *Unsafe UI Access + Cloudflare Tunnel conflict detected*"
|
|
11342
|
+
: ":alert: *Unsafe UI Access enabled*";
|
|
11012
11343
|
const body = _tunnelWanted
|
|
11013
11344
|
? "*Unsafe UI access was enabled alongside Cloudflare tunnel — the tunnel has been disabled* until unsafe access is turned off.\n\n"
|
|
11014
11345
|
+ "With both active, anyone on the internet could control your agents, execute code, and read your secrets.\n\n"
|
|
@@ -11024,7 +11355,7 @@ export async function startTelegramBot(options = {}) {
|
|
|
11024
11355
|
reply_markup: {
|
|
11025
11356
|
inline_keyboard: [
|
|
11026
11357
|
[
|
|
11027
|
-
{ text: "
|
|
11358
|
+
{ text: ":lock: Disable Unsafe Access", callback_data: "cb:do_disable_unsafe" },
|
|
11028
11359
|
],
|
|
11029
11360
|
],
|
|
11030
11361
|
},
|