@nordbyte/nordrelay 0.3.1 → 0.4.1
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 +45 -2
- package/README.md +221 -35
- package/dist/access-control.js +3 -0
- package/dist/agent-activity.js +300 -0
- package/dist/agent-adapter.js +17 -30
- package/dist/agent-factory.js +27 -0
- package/dist/agent-feature-matrix.js +42 -0
- package/dist/agent-updates.js +294 -0
- package/dist/agent.js +123 -9
- package/dist/artifacts.js +1 -1
- package/dist/audit-log.js +1 -1
- package/dist/bot-ui.js +1 -1
- package/dist/bot.js +483 -354
- package/dist/channel-actions.js +372 -0
- package/dist/claude-code-auth.js +121 -0
- package/dist/claude-code-cli.js +19 -0
- package/dist/claude-code-launch.js +73 -0
- package/dist/claude-code-session.js +660 -0
- package/dist/claude-code-state.js +590 -0
- package/dist/codex-session.js +12 -1
- package/dist/config.js +113 -9
- package/dist/hermes-api.js +150 -0
- package/dist/hermes-auth.js +96 -0
- package/dist/hermes-cli.js +19 -0
- package/dist/hermes-launch.js +57 -0
- package/dist/hermes-session.js +477 -0
- package/dist/hermes-state.js +609 -0
- package/dist/index.js +51 -8
- package/dist/openclaw-auth.js +27 -0
- package/dist/openclaw-cli.js +19 -0
- package/dist/openclaw-gateway.js +285 -0
- package/dist/openclaw-launch.js +65 -0
- package/dist/openclaw-session.js +549 -0
- package/dist/openclaw-state.js +409 -0
- package/dist/operations.js +115 -9
- package/dist/pi-auth.js +59 -0
- package/dist/pi-launch.js +61 -0
- package/dist/pi-rpc.js +18 -0
- package/dist/pi-session.js +103 -15
- package/dist/pi-state.js +253 -0
- package/dist/relay-runtime.js +798 -72
- package/dist/session-format.js +98 -19
- package/dist/session-registry.js +40 -15
- package/dist/settings-service.js +35 -4
- package/dist/web-dashboard-assets.js +2 -0
- package/dist/web-dashboard-client.js +275 -0
- package/dist/web-dashboard-style.js +9 -0
- package/dist/web-dashboard-ui.js +18 -0
- package/dist/web-dashboard.js +296 -196
- package/package.json +8 -3
- package/plugins/nordrelay/.codex-plugin/plugin.json +7 -4
- package/plugins/nordrelay/commands/remote.md +2 -2
- package/plugins/nordrelay/scripts/nordrelay.mjs +187 -12
- package/plugins/nordrelay/skills/telegram-remote/SKILL.md +2 -2
- package/CHANGELOG.md +0 -26
package/dist/bot.js
CHANGED
|
@@ -8,23 +8,29 @@ import { hasTelegramPermission, permissionForCallbackData, permissionForCommand,
|
|
|
8
8
|
import { buildFileInstructions, outboxPath, stageFile, } from "./attachments.js";
|
|
9
9
|
import { collectArtifactReport, collectRecentWorkspaceArtifacts, createArtifactZipBundle, ensureOutDir, formatArtifactSummary, getArtifactTurnReport, isTelegramImagePreview, listRecentArtifactReports, persistWorkspaceArtifactReport, pruneConnectorTurnDirs, removeArtifactTurn, telegramArtifactFilename, totalArtifactSize, } from "./artifacts.js";
|
|
10
10
|
import { listAgentAdapterDescriptors } from "./agent-adapter.js";
|
|
11
|
+
import { AgentUpdateManager } from "./agent-updates.js";
|
|
11
12
|
import { AuditLogStore } from "./audit-log.js";
|
|
12
13
|
import { formatSessionLabel, renderHelpMessage, renderWelcomeFirstTime, renderWelcomeReturning, } from "./bot-ui.js";
|
|
13
14
|
import { BotPreferencesStore, formatQuietHours, isQuietNow, parseMirrorMode, parseNotifyMode, parseQuietHours, parseVoiceBackendPreference, } from "./bot-preferences.js";
|
|
15
|
+
import { logTailRequests, parseAgentUpdateId, parseLogsCommand, renderAgentUpdateJobAction, renderAgentUpdateJobsAction, renderAgentUpdateLogAction, renderAgentUpdatePickerAction, renderAgentsAction, renderArtifactReportsAction, renderChannelsAction, renderLogTailsAction, renderQueueListAction, renderQueuedPromptDetailAction, renderSelfUpdateStartedAction, } from "./channel-actions.js";
|
|
14
16
|
import { listChannelDescriptors } from "./channel-adapter.js";
|
|
15
|
-
import {
|
|
17
|
+
import { CODEX_AGENT_CAPABILITIES, agentLabel, agentReasoningLabel, agentReasoningOptions, } from "./agent.js";
|
|
18
|
+
import { getAgentActivityLog, getAgentDiagnostics, getExternalActivityForSession, getExternalSnapshotForSession, } from "./agent-activity.js";
|
|
16
19
|
import { enabledAgents } from "./agent-factory.js";
|
|
17
|
-
import { checkAuthStatus, clearAuthCache, startLogin, startLogout } from "./codex-auth.js";
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
+
import { checkAuthStatus, clearAuthCache, startLogin as startCodexLogin, startLogout as startCodexLogout } from "./codex-auth.js";
|
|
21
|
+
import { checkClaudeCodeAuthStatus, startClaudeCodeLogin, startClaudeCodeLogout } from "./claude-code-auth.js";
|
|
22
|
+
import { formatLaunchProfileBehavior } from "./codex-launch.js";
|
|
20
23
|
import { contextKeyFromCtx, isTelegramContextKey, isTopicContextKey, parseContextKey } from "./context-key.js";
|
|
21
24
|
import { friendlyErrorText } from "./error-messages.js";
|
|
22
25
|
import { escapeHTML, formatTelegramHTML } from "./format.js";
|
|
23
|
-
import { getConnectorHealth,
|
|
26
|
+
import { getConnectorHealth, getVersionChecks, readConnectorState, readFormattedLogTail, spawnConnectorRestart, spawnSelfUpdate, } from "./operations.js";
|
|
24
27
|
import { PromptStore, toPromptEnvelope } from "./prompt-store.js";
|
|
28
|
+
import { checkHermesAuthStatus, startHermesLogin, startHermesLogout } from "./hermes-auth.js";
|
|
29
|
+
import { checkOpenClawAuthStatus } from "./openclaw-auth.js";
|
|
30
|
+
import { checkPiAuthStatus } from "./pi-auth.js";
|
|
25
31
|
import { configureRedaction, redactText } from "./redaction.js";
|
|
26
32
|
import { canWriteWithLock, SessionLockStore } from "./session-locks.js";
|
|
27
|
-
import {
|
|
33
|
+
import { renderLaunchSummaryHTML, renderLaunchSummaryPlain, renderSessionInfoHTML, renderSessionInfoPlain, } from "./session-format.js";
|
|
28
34
|
import { SessionRegistry } from "./session-registry.js";
|
|
29
35
|
import { getAvailableBackends, transcribeAudio } from "./voice.js";
|
|
30
36
|
import { getTelegramRateLimitMetrics, telegramRateLimiter } from "./telegram-rate-limit.js";
|
|
@@ -63,6 +69,37 @@ function paginateKeyboard(items, page, prefix) {
|
|
|
63
69
|
}
|
|
64
70
|
return keyboard;
|
|
65
71
|
}
|
|
72
|
+
function actionKeyboard(rows) {
|
|
73
|
+
if (!rows || rows.length === 0) {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
const keyboard = new InlineKeyboard();
|
|
77
|
+
for (const row of rows) {
|
|
78
|
+
for (const button of row) {
|
|
79
|
+
keyboard.text(button.label, telegramActionData(button.action));
|
|
80
|
+
}
|
|
81
|
+
keyboard.row();
|
|
82
|
+
}
|
|
83
|
+
return keyboard;
|
|
84
|
+
}
|
|
85
|
+
function telegramActionData(action) {
|
|
86
|
+
if (action === "agent-update:jobs") {
|
|
87
|
+
return "upd_jobs";
|
|
88
|
+
}
|
|
89
|
+
const agentUpdateStart = action.match(/^agent-update:start:(.+)$/);
|
|
90
|
+
if (agentUpdateStart?.[1]) {
|
|
91
|
+
return `upd_agent:${agentUpdateStart[1]}`;
|
|
92
|
+
}
|
|
93
|
+
const agentUpdateLog = action.match(/^agent-update:log:(.+)$/);
|
|
94
|
+
if (agentUpdateLog?.[1]) {
|
|
95
|
+
return `upd_log:${agentUpdateLog[1]}`;
|
|
96
|
+
}
|
|
97
|
+
const agentUpdateCancel = action.match(/^agent-update:cancel:(.+)$/);
|
|
98
|
+
if (agentUpdateCancel?.[1]) {
|
|
99
|
+
return `upd_cancel:${agentUpdateCancel[1]}`;
|
|
100
|
+
}
|
|
101
|
+
return action;
|
|
102
|
+
}
|
|
66
103
|
export function createBot(config, registry) {
|
|
67
104
|
configureRedaction(config.telegramRedactPatterns);
|
|
68
105
|
telegramRateLimiter.configure({
|
|
@@ -90,6 +127,7 @@ export function createBot(config, registry) {
|
|
|
90
127
|
const preferencesStore = new BotPreferencesStore(config.workspace, config.stateBackend);
|
|
91
128
|
const auditLog = new AuditLogStore(config.workspace, config.stateBackend, config.auditMaxEvents);
|
|
92
129
|
const lockStore = new SessionLockStore(config.workspace, config.stateBackend);
|
|
130
|
+
const agentUpdates = new AgentUpdateManager();
|
|
93
131
|
const drainingQueues = new Set();
|
|
94
132
|
const externalQueueTimers = new Map();
|
|
95
133
|
const externalMirrors = new Map();
|
|
@@ -97,23 +135,23 @@ export function createBot(config, registry) {
|
|
|
97
135
|
const syncInterval = config.codexSyncIntervalMs > 0
|
|
98
136
|
? setInterval(() => {
|
|
99
137
|
try {
|
|
100
|
-
registry.
|
|
138
|
+
registry.syncAllFromAgentState({ reattach: true });
|
|
101
139
|
}
|
|
102
140
|
catch (error) {
|
|
103
|
-
console.error("Failed to sync sessions from
|
|
141
|
+
console.error("Failed to sync sessions from agent state:", error);
|
|
104
142
|
}
|
|
105
143
|
}, config.codexSyncIntervalMs)
|
|
106
144
|
: undefined;
|
|
107
145
|
syncInterval?.unref?.();
|
|
108
146
|
const externalMonitorInterval = setInterval(() => {
|
|
109
147
|
void monitorExternalContexts().catch((error) => {
|
|
110
|
-
console.error("Failed to monitor external
|
|
148
|
+
console.error("Failed to monitor external agent activity:", error);
|
|
111
149
|
});
|
|
112
150
|
}, config.codexExternalBusyCheckMs);
|
|
113
151
|
externalMonitorInterval.unref?.();
|
|
114
152
|
setTimeout(() => {
|
|
115
153
|
void monitorExternalContexts().catch((error) => {
|
|
116
|
-
console.error("Failed to run initial external
|
|
154
|
+
console.error("Failed to run initial external agent monitor:", error);
|
|
117
155
|
});
|
|
118
156
|
}, 0).unref?.();
|
|
119
157
|
registry.onRemove((key) => {
|
|
@@ -152,19 +190,7 @@ export function createBot(config, registry) {
|
|
|
152
190
|
}
|
|
153
191
|
return state;
|
|
154
192
|
};
|
|
155
|
-
const getExternalActivity = (session) =>
|
|
156
|
-
const info = session?.getInfo();
|
|
157
|
-
if (!info || !capabilitiesOf(info).externalActivity) {
|
|
158
|
-
return null;
|
|
159
|
-
}
|
|
160
|
-
const threadId = session?.getActiveThreadId();
|
|
161
|
-
if (!threadId) {
|
|
162
|
-
return null;
|
|
163
|
-
}
|
|
164
|
-
return getThreadActivity(threadId, {
|
|
165
|
-
staleAfterMs: config.codexExternalBusyStaleMs,
|
|
166
|
-
});
|
|
167
|
-
};
|
|
193
|
+
const getExternalActivity = (session) => getExternalActivityForSession(session, config);
|
|
168
194
|
const getBusyReason = (contextKey) => {
|
|
169
195
|
const state = contextBusy.get(contextKey);
|
|
170
196
|
const session = registry.get(contextKey);
|
|
@@ -191,6 +217,114 @@ export function createBot(config, registry) {
|
|
|
191
217
|
const updateSessionMetadata = (contextKey, session) => {
|
|
192
218
|
registry.updateMetadata(contextKey, session);
|
|
193
219
|
};
|
|
220
|
+
const checkAgentAuthStatus = async (info) => {
|
|
221
|
+
if (idOf(info) === "pi") {
|
|
222
|
+
return checkPiAuthStatus(info.model);
|
|
223
|
+
}
|
|
224
|
+
if (idOf(info) === "hermes") {
|
|
225
|
+
return checkHermesAuthStatus({
|
|
226
|
+
baseUrl: config.hermesApiBaseUrl,
|
|
227
|
+
apiKey: config.hermesApiKey,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
if (idOf(info) === "openclaw") {
|
|
231
|
+
return checkOpenClawAuthStatus({
|
|
232
|
+
gatewayUrl: config.openClawGatewayUrl,
|
|
233
|
+
token: config.openClawGatewayToken,
|
|
234
|
+
password: config.openClawGatewayPassword,
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
if (idOf(info) === "claude-code") {
|
|
238
|
+
return checkClaudeCodeAuthStatus(config.claudeCodeCliPath);
|
|
239
|
+
}
|
|
240
|
+
return checkAuthStatus(config.codexApiKey);
|
|
241
|
+
};
|
|
242
|
+
const agentIdForAuth = (info) => info ? idOf(info) : "codex";
|
|
243
|
+
const labelForAuth = (info) => info ? labelOf(info) : "Codex";
|
|
244
|
+
const checkLoginAuthStatus = async (info) => {
|
|
245
|
+
const agentId = agentIdForAuth(info);
|
|
246
|
+
if (agentId === "hermes") {
|
|
247
|
+
return checkHermesAuthStatus({
|
|
248
|
+
baseUrl: config.hermesApiBaseUrl,
|
|
249
|
+
apiKey: config.hermesApiKey,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
if (agentId === "claude-code") {
|
|
253
|
+
return checkClaudeCodeAuthStatus(config.claudeCodeCliPath);
|
|
254
|
+
}
|
|
255
|
+
return checkAuthStatus(config.codexApiKey);
|
|
256
|
+
};
|
|
257
|
+
const agentUpdateContext = () => ({
|
|
258
|
+
piCliPath: config.piCliPath,
|
|
259
|
+
hermesCliPath: config.hermesCliPath,
|
|
260
|
+
openClawCliPath: config.openClawCliPath,
|
|
261
|
+
claudeCodeCliPath: config.claudeCodeCliPath,
|
|
262
|
+
});
|
|
263
|
+
const startTelegramAgentUpdate = async (ctx, agentId) => {
|
|
264
|
+
try {
|
|
265
|
+
const job = agentUpdates.start(agentId, agentUpdateContext());
|
|
266
|
+
const contextKey = contextKeyFromCtx(ctx);
|
|
267
|
+
if (contextKey) {
|
|
268
|
+
audit({
|
|
269
|
+
action: "command",
|
|
270
|
+
status: "ok",
|
|
271
|
+
contextKey,
|
|
272
|
+
agentId,
|
|
273
|
+
description: `update ${agentId}`,
|
|
274
|
+
detail: job.summary,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
const rendered = renderAgentUpdateJobAction(job);
|
|
278
|
+
await safeReply(ctx, rendered.html, {
|
|
279
|
+
fallbackText: rendered.plain,
|
|
280
|
+
replyMarkup: actionKeyboard(rendered.buttons),
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
catch (error) {
|
|
284
|
+
const message = `Failed to start ${agentLabel(agentId)} update: ${friendlyErrorText(error)}`;
|
|
285
|
+
await safeReply(ctx, `<b>Update failed:</b> ${escapeHTML(message)}`, { fallbackText: message });
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
const startAgentLogin = (info) => {
|
|
289
|
+
const agentId = agentIdForAuth(info);
|
|
290
|
+
if (agentId === "hermes") {
|
|
291
|
+
return startHermesLogin(config.hermesCliPath);
|
|
292
|
+
}
|
|
293
|
+
if (agentId === "claude-code") {
|
|
294
|
+
return startClaudeCodeLogin(config.claudeCodeCliPath);
|
|
295
|
+
}
|
|
296
|
+
return startCodexLogin();
|
|
297
|
+
};
|
|
298
|
+
const startAgentLogout = (info) => {
|
|
299
|
+
const agentId = agentIdForAuth(info);
|
|
300
|
+
if (agentId === "hermes") {
|
|
301
|
+
return startHermesLogout(config.hermesCliPath);
|
|
302
|
+
}
|
|
303
|
+
if (agentId === "claude-code") {
|
|
304
|
+
return startClaudeCodeLogout(config.claudeCodeCliPath);
|
|
305
|
+
}
|
|
306
|
+
return startCodexLogout();
|
|
307
|
+
};
|
|
308
|
+
const hostLoginCommand = (info) => {
|
|
309
|
+
const agentId = agentIdForAuth(info);
|
|
310
|
+
if (agentId === "hermes") {
|
|
311
|
+
return `${config.hermesCliPath ?? "hermes"} login --no-browser`;
|
|
312
|
+
}
|
|
313
|
+
if (agentId === "claude-code") {
|
|
314
|
+
return `${config.claudeCodeCliPath ?? "claude"} auth login`;
|
|
315
|
+
}
|
|
316
|
+
return "codex login --device-auth";
|
|
317
|
+
};
|
|
318
|
+
const hostLogoutCommand = (info) => {
|
|
319
|
+
const agentId = agentIdForAuth(info);
|
|
320
|
+
if (agentId === "hermes") {
|
|
321
|
+
return `${config.hermesCliPath ?? "hermes"} logout`;
|
|
322
|
+
}
|
|
323
|
+
if (agentId === "claude-code") {
|
|
324
|
+
return `${config.claudeCodeCliPath ?? "claude"} auth logout`;
|
|
325
|
+
}
|
|
326
|
+
return "codex logout";
|
|
327
|
+
};
|
|
194
328
|
const isTopicContext = (contextKey) => isTopicContextKey(contextKey);
|
|
195
329
|
const getPreferences = (contextKey) => preferencesStore.get(contextKey);
|
|
196
330
|
const getEffectiveMirrorMode = (contextKey) => getPreferences(contextKey).mirrorMode ?? config.telegramMirrorMode;
|
|
@@ -255,22 +389,10 @@ export function createBot(config, registry) {
|
|
|
255
389
|
const createQueuedPromptCancelKeyboard = (contextKey, queueId, label = "Cancel queued message") => new InlineKeyboard().text(label, queueCancelCallbackData("cancel", contextKey, queueId));
|
|
256
390
|
const renderQueueList = (contextKey, queue) => {
|
|
257
391
|
const paused = promptStore.isPaused(contextKey);
|
|
392
|
+
const rendered = renderQueueListAction(queue, paused);
|
|
258
393
|
if (queue.length === 0) {
|
|
259
|
-
return
|
|
260
|
-
plain: paused ? "Queue is empty and paused." : "Queue is empty.",
|
|
261
|
-
html: escapeHTML(paused ? "Queue is empty and paused." : "Queue is empty."),
|
|
262
|
-
};
|
|
394
|
+
return rendered;
|
|
263
395
|
}
|
|
264
|
-
const lines = queue.map((item, index) => {
|
|
265
|
-
const age = formatRelativeTime(new Date(item.createdAt));
|
|
266
|
-
const attempts = item.attempts && item.attempts > 0 ? ` · attempts ${item.attempts}` : "";
|
|
267
|
-
const error = item.lastError ? ` · last error: ${trimLine(item.lastError, 80)}` : "";
|
|
268
|
-
const scheduled = item.notBefore && item.notBefore > Date.now()
|
|
269
|
-
? `scheduled ${formatLocalDateTime(new Date(item.notBefore))}`
|
|
270
|
-
: index === 0 ? "next" : `after ${index} queued item${index === 1 ? "" : "s"}`;
|
|
271
|
-
const eta = scheduled;
|
|
272
|
-
return `${index + 1}. ${item.id} · ${age} · ${eta}${attempts}${error} · ${item.description}`;
|
|
273
|
-
});
|
|
274
396
|
const keyboard = new InlineKeyboard();
|
|
275
397
|
queue.forEach((item, index) => {
|
|
276
398
|
keyboard
|
|
@@ -283,11 +405,7 @@ export function createBot(config, registry) {
|
|
|
283
405
|
.text("Down", queueCancelCallbackData("down", contextKey, item.id))
|
|
284
406
|
.row();
|
|
285
407
|
});
|
|
286
|
-
return {
|
|
287
|
-
plain: [paused ? "Queued prompts (paused):" : "Queued prompts:", ...lines].join("\n"),
|
|
288
|
-
html: [paused ? "<b>Queued prompts:</b> <code>paused</code>" : "<b>Queued prompts:</b>", ...lines.map(escapeHTML)].join("\n"),
|
|
289
|
-
keyboard,
|
|
290
|
-
};
|
|
408
|
+
return { ...rendered, keyboard };
|
|
291
409
|
};
|
|
292
410
|
const createSystemContext = (contextKey) => {
|
|
293
411
|
const parsed = parseContextKey(contextKey);
|
|
@@ -364,11 +482,9 @@ export function createBot(config, registry) {
|
|
|
364
482
|
return;
|
|
365
483
|
}
|
|
366
484
|
const previous = externalMirrors.get(contextKey);
|
|
367
|
-
const snapshot =
|
|
485
|
+
const snapshot = getExternalSnapshotForSession(session, config, {
|
|
368
486
|
afterLine: previous?.lastLine ?? Number.MAX_SAFE_INTEGER,
|
|
369
|
-
|
|
370
|
-
}) ?? getThreadRolloutSnapshot(threadId, {
|
|
371
|
-
staleAfterMs: config.codexExternalBusyStaleMs,
|
|
487
|
+
}) ?? getExternalSnapshotForSession(session, config, {
|
|
372
488
|
maxEvents: 0,
|
|
373
489
|
});
|
|
374
490
|
if (!snapshot) {
|
|
@@ -382,7 +498,7 @@ export function createBot(config, registry) {
|
|
|
382
498
|
}
|
|
383
499
|
const activity = snapshot.activity;
|
|
384
500
|
if (activity.active && queueLength > 0) {
|
|
385
|
-
await updateQueueStatusMessage(contextKey, `Waiting for
|
|
501
|
+
await updateQueueStatusMessage(contextKey, `Waiting for ${info.agentLabel} CLI task... ${queueLength} queued${paused ? " (paused)" : ""}.`);
|
|
386
502
|
return;
|
|
387
503
|
}
|
|
388
504
|
if (!activity.active && queueLength > 0 && !paused && !session.isProcessing()) {
|
|
@@ -394,10 +510,10 @@ export function createBot(config, registry) {
|
|
|
394
510
|
const parsed = parseContextKey(contextKey);
|
|
395
511
|
const previous = externalMirrors.get(contextKey);
|
|
396
512
|
let state = previous;
|
|
397
|
-
if (!state || state.threadId !== snapshot.threadId || state.rolloutPath !== snapshot.
|
|
513
|
+
if (!state || state.threadId !== snapshot.threadId || state.rolloutPath !== snapshot.sourcePath) {
|
|
398
514
|
state = {
|
|
399
515
|
threadId: snapshot.threadId,
|
|
400
|
-
rolloutPath: snapshot.
|
|
516
|
+
rolloutPath: snapshot.sourcePath,
|
|
401
517
|
lastLine: snapshot.lineCount,
|
|
402
518
|
turnId: snapshot.activity.turnId,
|
|
403
519
|
startedAt: snapshot.activity.startedAt,
|
|
@@ -458,7 +574,7 @@ export function createBot(config, registry) {
|
|
|
458
574
|
const terminalEvent = [...snapshot.events].reverse().find((event) => event.kind === "task" && event.status && event.status !== "started");
|
|
459
575
|
if (terminalEvent) {
|
|
460
576
|
if (mirrorMode !== "off") {
|
|
461
|
-
const doneText =
|
|
577
|
+
const doneText = `${snapshot.agentLabel} CLI task ${terminalEvent.status}.`;
|
|
462
578
|
if (state.statusMessageId) {
|
|
463
579
|
await safeEditMessage(bot, chatId, state.statusMessageId, escapeHTML(doneText), {
|
|
464
580
|
fallbackText: doneText,
|
|
@@ -473,8 +589,8 @@ export function createBot(config, registry) {
|
|
|
473
589
|
}
|
|
474
590
|
const finalAgent = snapshot.events.filter((event) => event.kind === "agent" && event.text).at(-1);
|
|
475
591
|
if (mirrorMode !== "off" && mirrorMode !== "status" && finalAgent?.text && finalAgent.lineNumber !== state.latestAgentLine) {
|
|
476
|
-
await sendTextMessage(bot.api, chatId,
|
|
477
|
-
fallbackText:
|
|
592
|
+
await sendTextMessage(bot.api, chatId, `<b>${escapeHTML(snapshot.agentLabel)} CLI final answer:</b>`, {
|
|
593
|
+
fallbackText: `${snapshot.agentLabel} CLI final answer:`,
|
|
478
594
|
messageThreadId: parsed.messageThreadId,
|
|
479
595
|
});
|
|
480
596
|
for (const chunk of splitMarkdownForTelegram(finalAgent.text)) {
|
|
@@ -544,7 +660,8 @@ export function createBot(config, registry) {
|
|
|
544
660
|
}
|
|
545
661
|
const busy = getBusyReason(contextKey);
|
|
546
662
|
if (busy.kind === "external") {
|
|
547
|
-
|
|
663
|
+
const label = busy.activity.agentLabel;
|
|
664
|
+
await updateQueueStatusMessage(contextKey, `Waiting for ${label} CLI task... ${promptStore.list(contextKey).length} queued${promptStore.isPaused(contextKey) ? " (paused)" : ""}.`);
|
|
548
665
|
scheduleExternalQueueDrain(ctx, contextKey, chatId, session);
|
|
549
666
|
return;
|
|
550
667
|
}
|
|
@@ -554,7 +671,7 @@ export function createBot(config, registry) {
|
|
|
554
671
|
await updateQueueStatusMessage(contextKey, `CLI task finished, running queued prompt 1/${promptStore.list(contextKey).length}.`);
|
|
555
672
|
await drainQueuedPrompts(ctx, contextKey, chatId, session);
|
|
556
673
|
})().catch((error) => {
|
|
557
|
-
console.error("Failed to drain queue after external
|
|
674
|
+
console.error("Failed to drain queue after external CLI activity:", error);
|
|
558
675
|
});
|
|
559
676
|
}, config.codexExternalBusyCheckMs);
|
|
560
677
|
timer.unref?.();
|
|
@@ -1109,21 +1226,21 @@ export function createBot(config, registry) {
|
|
|
1109
1226
|
try {
|
|
1110
1227
|
const sessionInfo = session.getInfo();
|
|
1111
1228
|
if (capabilitiesOf(sessionInfo).auth) {
|
|
1112
|
-
const authStatus = await
|
|
1229
|
+
const authStatus = await checkAgentAuthStatus(sessionInfo);
|
|
1113
1230
|
if (!authStatus.authenticated) {
|
|
1114
1231
|
await safeReply(ctx, [
|
|
1115
1232
|
`<b>⚠️ ${escapeHTML(labelOf(sessionInfo))} is not authenticated.</b>`,
|
|
1116
1233
|
"",
|
|
1117
1234
|
`<code>${escapeHTML(authStatus.detail)}</code>`,
|
|
1118
1235
|
"",
|
|
1119
|
-
|
|
1236
|
+
authHelpText(sessionInfo),
|
|
1120
1237
|
].join("\n"), {
|
|
1121
1238
|
fallbackText: [
|
|
1122
1239
|
`⚠️ ${labelOf(sessionInfo)} is not authenticated.`,
|
|
1123
1240
|
"",
|
|
1124
1241
|
authStatus.detail,
|
|
1125
1242
|
"",
|
|
1126
|
-
|
|
1243
|
+
authHelpText(sessionInfo),
|
|
1127
1244
|
].join("\n"),
|
|
1128
1245
|
});
|
|
1129
1246
|
return;
|
|
@@ -1135,6 +1252,24 @@ export function createBot(config, registry) {
|
|
|
1135
1252
|
});
|
|
1136
1253
|
return;
|
|
1137
1254
|
}
|
|
1255
|
+
if (idOf(sessionInfo) === "hermes" && !config.hermesEnabled) {
|
|
1256
|
+
await safeReply(ctx, "<b>⚠️ Hermes is disabled.</b>\nEnable it with <code>NORDRELAY_HERMES_ENABLED=true</code>.", {
|
|
1257
|
+
fallbackText: "⚠️ Hermes is disabled.\nEnable it with NORDRELAY_HERMES_ENABLED=true.",
|
|
1258
|
+
});
|
|
1259
|
+
return;
|
|
1260
|
+
}
|
|
1261
|
+
if (idOf(sessionInfo) === "openclaw" && !config.openClawEnabled) {
|
|
1262
|
+
await safeReply(ctx, "<b>⚠️ OpenClaw is disabled.</b>\nEnable it with <code>NORDRELAY_OPENCLAW_ENABLED=true</code>.", {
|
|
1263
|
+
fallbackText: "⚠️ OpenClaw is disabled.\nEnable it with NORDRELAY_OPENCLAW_ENABLED=true.",
|
|
1264
|
+
});
|
|
1265
|
+
return;
|
|
1266
|
+
}
|
|
1267
|
+
if (idOf(sessionInfo) === "claude-code" && !config.claudeCodeEnabled) {
|
|
1268
|
+
await safeReply(ctx, "<b>⚠️ Claude Code is disabled.</b>\nEnable it with <code>NORDRELAY_CLAUDE_CODE_ENABLED=true</code>.", {
|
|
1269
|
+
fallbackText: "⚠️ Claude Code is disabled.\nEnable it with NORDRELAY_CLAUDE_CODE_ENABLED=true.",
|
|
1270
|
+
});
|
|
1271
|
+
return;
|
|
1272
|
+
}
|
|
1138
1273
|
if (!(await ensureActiveThread(ctx, contextKey, session))) {
|
|
1139
1274
|
return;
|
|
1140
1275
|
}
|
|
@@ -1148,12 +1283,13 @@ export function createBot(config, registry) {
|
|
|
1148
1283
|
const finalExternalActivity = getExternalActivity(session);
|
|
1149
1284
|
if (finalExternalActivity?.active) {
|
|
1150
1285
|
const item = maybeRequeuePromptAtFront(contextKey, envelope);
|
|
1151
|
-
const
|
|
1286
|
+
const label = finalExternalActivity.agentLabel;
|
|
1287
|
+
const message = `Queued prompt ${item.id} at position 1. The ${label} session became active in ${label} CLI and is processing another task.`;
|
|
1152
1288
|
await safeReply(ctx, escapeHTML(message), {
|
|
1153
1289
|
fallbackText: message,
|
|
1154
1290
|
replyMarkup: createQueuedPromptCancelKeyboard(contextKey, item.id),
|
|
1155
1291
|
});
|
|
1156
|
-
await updateQueueStatusMessage(contextKey, `Waiting for
|
|
1292
|
+
await updateQueueStatusMessage(contextKey, `Waiting for ${label} CLI task... ${promptStore.list(contextKey).length} queued.`);
|
|
1157
1293
|
scheduleExternalQueueDrain(ctx, contextKey, chatId, session);
|
|
1158
1294
|
turnProgress.delete(contextKey);
|
|
1159
1295
|
return;
|
|
@@ -1164,9 +1300,14 @@ export function createBot(config, registry) {
|
|
|
1164
1300
|
status: "ok",
|
|
1165
1301
|
description: envelope.description,
|
|
1166
1302
|
});
|
|
1303
|
+
const artifactStartedAt = new Date();
|
|
1304
|
+
const artifactTurnId = envelope.artifactOutDir
|
|
1305
|
+
? path.basename(path.dirname(envelope.artifactOutDir))
|
|
1306
|
+
: randomUUID().slice(0, 12);
|
|
1167
1307
|
await session.prompt(envelope.input, callbacks);
|
|
1168
1308
|
updateSessionMetadata(contextKey, session);
|
|
1169
1309
|
await finalizeResponse();
|
|
1310
|
+
await deliverCliGeneratedArtifacts(contextKey, chatId, session, artifactStartedAt, artifactTurnId, messageThreadId);
|
|
1170
1311
|
if (envelope.artifactOutDir) {
|
|
1171
1312
|
if (config.telegramAutoSendArtifacts) {
|
|
1172
1313
|
await deliverArtifacts(ctx, chatId, envelope.artifactOutDir, session.getInfo().workspace, messageThreadId);
|
|
@@ -1321,7 +1462,7 @@ export function createBot(config, registry) {
|
|
|
1321
1462
|
const deliverArtifactReportZip = async (ctx, chatId, report, messageThreadId) => {
|
|
1322
1463
|
const bundle = await createArtifactZipBundle(report.artifacts, report.outDir, {
|
|
1323
1464
|
maxFileSize: config.maxFileSize,
|
|
1324
|
-
bundleName: `
|
|
1465
|
+
bundleName: `nordrelay-artifacts-${report.turnId}.zip`,
|
|
1325
1466
|
});
|
|
1326
1467
|
if (!bundle) {
|
|
1327
1468
|
await safeReply(ctx, escapeHTML("Could not create a ZIP bundle for this artifact turn."), {
|
|
@@ -1516,9 +1657,9 @@ export function createBot(config, registry) {
|
|
|
1516
1657
|
}
|
|
1517
1658
|
const { contextKey, session } = contextSession;
|
|
1518
1659
|
const info = session.getInfo();
|
|
1519
|
-
const authStatus = capabilitiesOf(info).auth ? await
|
|
1660
|
+
const authStatus = capabilitiesOf(info).auth ? await checkAgentAuthStatus(info) : null;
|
|
1520
1661
|
const authWarning = authStatus && !authStatus.authenticated
|
|
1521
|
-
?
|
|
1662
|
+
? [`${labelOf(info)} is not authenticated.`, authStatus.detail, authHelpText(info)].filter(Boolean).join(" ")
|
|
1522
1663
|
: undefined;
|
|
1523
1664
|
const isReturning = registry.hasMetadata(contextKey);
|
|
1524
1665
|
if (isReturning) {
|
|
@@ -1537,48 +1678,12 @@ export function createBot(config, registry) {
|
|
|
1537
1678
|
await safeReply(ctx, help.html, { fallbackText: help.plain });
|
|
1538
1679
|
});
|
|
1539
1680
|
bot.command("channels", async (ctx) => {
|
|
1540
|
-
const
|
|
1541
|
-
|
|
1542
|
-
const status = descriptor.status === "available" ? "available" : "planned";
|
|
1543
|
-
return `${descriptor.label}: ${status} · ${descriptor.capabilities.join(", ")}`;
|
|
1544
|
-
});
|
|
1545
|
-
const html = [
|
|
1546
|
-
"<b>Channel adapters:</b>",
|
|
1547
|
-
...descriptors.map((descriptor) => {
|
|
1548
|
-
const statusIcon = descriptor.status === "available" ? "✅" : "🟡";
|
|
1549
|
-
const notes = descriptor.notes ? `\n ${escapeHTML(descriptor.notes)}` : "";
|
|
1550
|
-
return `${statusIcon} <b>${escapeHTML(descriptor.label)}</b> <code>${escapeHTML(descriptor.status)}</code>\n <code>${escapeHTML(descriptor.capabilities.join(", "))}</code>${notes}`;
|
|
1551
|
-
}),
|
|
1552
|
-
].join("\n");
|
|
1553
|
-
await safeReply(ctx, html, { fallbackText: ["Channel adapters:", ...lines].join("\n") });
|
|
1681
|
+
const rendered = renderChannelsAction(listChannelDescriptors());
|
|
1682
|
+
await safeReply(ctx, rendered.html, { fallbackText: rendered.plain });
|
|
1554
1683
|
});
|
|
1555
1684
|
bot.command("agents", async (ctx) => {
|
|
1556
|
-
const
|
|
1557
|
-
|
|
1558
|
-
"Agent adapters:",
|
|
1559
|
-
...descriptors.map((descriptor) => {
|
|
1560
|
-
const enabled = descriptor.id === "codex"
|
|
1561
|
-
? config.codexEnabled
|
|
1562
|
-
: descriptor.id === "pi"
|
|
1563
|
-
? config.piEnabled
|
|
1564
|
-
: false;
|
|
1565
|
-
return `${descriptor.label}: ${descriptor.status}${descriptor.status === "available" ? ` · ${enabled ? "enabled" : "disabled"}` : ""}`;
|
|
1566
|
-
}),
|
|
1567
|
-
].join("\n");
|
|
1568
|
-
const html = [
|
|
1569
|
-
"<b>Agent adapters:</b>",
|
|
1570
|
-
...descriptors.map((descriptor) => {
|
|
1571
|
-
const enabled = descriptor.id === "codex"
|
|
1572
|
-
? config.codexEnabled
|
|
1573
|
-
: descriptor.id === "pi"
|
|
1574
|
-
? config.piEnabled
|
|
1575
|
-
: false;
|
|
1576
|
-
const status = descriptor.status === "available" ? `${enabled ? "enabled" : "disabled"}` : "planned";
|
|
1577
|
-
const notes = descriptor.notes ? `\n ${escapeHTML(descriptor.notes)}` : "";
|
|
1578
|
-
return `${descriptor.status === "available" ? "✅" : "🟡"} <b>${escapeHTML(descriptor.label)}</b> <code>${escapeHTML(status)}</code>${notes}`;
|
|
1579
|
-
}),
|
|
1580
|
-
].join("\n");
|
|
1581
|
-
await safeReply(ctx, html, { fallbackText: plain });
|
|
1685
|
+
const rendered = renderAgentsAction(listAgentAdapterDescriptors(), enabledAgents(config));
|
|
1686
|
+
await safeReply(ctx, rendered.html, { fallbackText: rendered.plain });
|
|
1582
1687
|
});
|
|
1583
1688
|
bot.command("agent", async (ctx) => {
|
|
1584
1689
|
const contextSession = await getContextSession(ctx, { deferThreadStart: true });
|
|
@@ -1586,11 +1691,6 @@ export function createBot(config, registry) {
|
|
|
1586
1691
|
return;
|
|
1587
1692
|
}
|
|
1588
1693
|
const { contextKey, session } = contextSession;
|
|
1589
|
-
if (!capabilitiesOf(session.getInfo()).modelSelection) {
|
|
1590
|
-
const text = `Model selection is not supported for ${labelOf(session.getInfo())}.`;
|
|
1591
|
-
await safeReply(ctx, escapeHTML(text), { fallbackText: text });
|
|
1592
|
-
return;
|
|
1593
|
-
}
|
|
1594
1694
|
if (isBusy(contextKey)) {
|
|
1595
1695
|
await safeReply(ctx, escapeHTML("Cannot switch agent while a prompt is running."), {
|
|
1596
1696
|
fallbackText: "Cannot switch agent while a prompt is running.",
|
|
@@ -1627,7 +1727,7 @@ export function createBot(config, registry) {
|
|
|
1627
1727
|
await safeReply(ctx, escapeHTML(text), { fallbackText: text });
|
|
1628
1728
|
return;
|
|
1629
1729
|
}
|
|
1630
|
-
const authStatus = await checkAuthStatus(config.codexApiKey);
|
|
1730
|
+
const authStatus = info ? await checkAgentAuthStatus(info) : await checkAuthStatus(config.codexApiKey);
|
|
1631
1731
|
const icon = authStatus.authenticated ? "✅" : "❌";
|
|
1632
1732
|
const html = [
|
|
1633
1733
|
`<b>${icon} Auth status:</b> ${authStatus.authenticated ? "authenticated" : "not authenticated"}`,
|
|
@@ -1652,8 +1752,8 @@ export function createBot(config, registry) {
|
|
|
1652
1752
|
await safeReply(ctx, escapeHTML(text), { fallbackText: text });
|
|
1653
1753
|
return;
|
|
1654
1754
|
}
|
|
1655
|
-
const authStatus = await
|
|
1656
|
-
if (authStatus.authenticated) {
|
|
1755
|
+
const authStatus = await checkLoginAuthStatus(info);
|
|
1756
|
+
if (agentIdForAuth(info) !== "hermes" && authStatus.authenticated) {
|
|
1657
1757
|
await safeReply(ctx, `<b>✅ Already authenticated</b> via <code>${escapeHTML(authStatus.method)}</code>.`, {
|
|
1658
1758
|
fallbackText: `✅ Already authenticated via ${authStatus.method}.`,
|
|
1659
1759
|
});
|
|
@@ -1663,17 +1763,17 @@ export function createBot(config, registry) {
|
|
|
1663
1763
|
await safeReply(ctx, [
|
|
1664
1764
|
"<b>Telegram-initiated login is disabled.</b>",
|
|
1665
1765
|
"",
|
|
1666
|
-
|
|
1766
|
+
`Run <code>${escapeHTML(hostLoginCommand(info))}</code> on the host.`,
|
|
1667
1767
|
].join("\n"), {
|
|
1668
1768
|
fallbackText: [
|
|
1669
1769
|
"Telegram-initiated login is disabled.",
|
|
1670
1770
|
"",
|
|
1671
|
-
|
|
1771
|
+
`Run '${hostLoginCommand(info)}' on the host.`,
|
|
1672
1772
|
].join("\n"),
|
|
1673
1773
|
});
|
|
1674
1774
|
return;
|
|
1675
1775
|
}
|
|
1676
|
-
const result = await
|
|
1776
|
+
const result = await startAgentLogin(info);
|
|
1677
1777
|
if (result.success) {
|
|
1678
1778
|
await safeReply(ctx, `<b>🔑 Login initiated.</b>\n\n<code>${escapeHTML(result.message)}</code>`, {
|
|
1679
1779
|
fallbackText: `🔑 Login initiated.\n\n${result.message}`,
|
|
@@ -1695,17 +1795,17 @@ export function createBot(config, registry) {
|
|
|
1695
1795
|
await safeReply(ctx, escapeHTML(text), { fallbackText: text });
|
|
1696
1796
|
return;
|
|
1697
1797
|
}
|
|
1698
|
-
const authStatus = await
|
|
1798
|
+
const authStatus = await checkLoginAuthStatus(info);
|
|
1699
1799
|
if (authStatus.method === "api-key") {
|
|
1700
1800
|
await safeReply(ctx, [
|
|
1701
|
-
|
|
1801
|
+
`<b>Cannot logout via Telegram when ${escapeHTML(labelForAuth(info))} uses API-key authentication.</b>`,
|
|
1702
1802
|
"",
|
|
1703
|
-
"Remove
|
|
1803
|
+
"Remove the API key from .env to use CLI-based auth instead.",
|
|
1704
1804
|
].join("\n"), {
|
|
1705
1805
|
fallbackText: [
|
|
1706
|
-
|
|
1806
|
+
`Cannot logout via Telegram when ${labelForAuth(info)} uses API-key authentication.`,
|
|
1707
1807
|
"",
|
|
1708
|
-
"Remove
|
|
1808
|
+
"Remove the API key from .env to use CLI-based auth instead.",
|
|
1709
1809
|
].join("\n"),
|
|
1710
1810
|
});
|
|
1711
1811
|
return;
|
|
@@ -1714,23 +1814,23 @@ export function createBot(config, registry) {
|
|
|
1714
1814
|
await safeReply(ctx, [
|
|
1715
1815
|
"<b>Telegram-initiated auth management is disabled.</b>",
|
|
1716
1816
|
"",
|
|
1717
|
-
|
|
1817
|
+
`Run <code>${escapeHTML(hostLogoutCommand(info))}</code> on the host.`,
|
|
1718
1818
|
].join("\n"), {
|
|
1719
1819
|
fallbackText: [
|
|
1720
1820
|
"Telegram-initiated auth management is disabled.",
|
|
1721
1821
|
"",
|
|
1722
|
-
|
|
1822
|
+
`Run '${hostLogoutCommand(info)}' on the host.`,
|
|
1723
1823
|
].join("\n"),
|
|
1724
1824
|
});
|
|
1725
1825
|
return;
|
|
1726
1826
|
}
|
|
1727
|
-
if (!authStatus.authenticated) {
|
|
1827
|
+
if (agentIdForAuth(info) !== "hermes" && !authStatus.authenticated) {
|
|
1728
1828
|
await safeReply(ctx, escapeHTML("Not currently authenticated."), {
|
|
1729
1829
|
fallbackText: "Not currently authenticated.",
|
|
1730
1830
|
});
|
|
1731
1831
|
return;
|
|
1732
1832
|
}
|
|
1733
|
-
const result = await
|
|
1833
|
+
const result = await startAgentLogout(info);
|
|
1734
1834
|
if (result.success) {
|
|
1735
1835
|
await safeReply(ctx, `<b>🔓 Logged out.</b>\n\n${escapeHTML(result.message)}`, {
|
|
1736
1836
|
fallbackText: `🔓 Logged out.\n\n${result.message}`,
|
|
@@ -1931,16 +2031,19 @@ export function createBot(config, registry) {
|
|
|
1931
2031
|
});
|
|
1932
2032
|
});
|
|
1933
2033
|
bot.command(["status", "health"], async (ctx) => {
|
|
1934
|
-
const health = await getConnectorHealth();
|
|
1935
|
-
const
|
|
2034
|
+
const health = await getConnectorHealth({ piCliPath: config.piCliPath, hermesCliPath: config.hermesCliPath, openClawCliPath: config.openClawCliPath, claudeCodeCliPath: config.claudeCodeCliPath });
|
|
2035
|
+
const contextSession = await getContextSession(ctx, { deferThreadStart: true });
|
|
2036
|
+
const authStatus = contextSession
|
|
2037
|
+
? await checkAgentAuthStatus(contextSession.session.getInfo())
|
|
2038
|
+
: await checkAuthStatus(config.codexApiKey);
|
|
1936
2039
|
const html = renderHealthHTML(health, authStatus.authenticated, getUserRole(ctx));
|
|
1937
2040
|
const plain = renderHealthPlain(health, authStatus.authenticated, getUserRole(ctx));
|
|
1938
2041
|
await safeReply(ctx, html, { fallbackText: plain });
|
|
1939
2042
|
});
|
|
1940
2043
|
bot.command("version", async (ctx) => {
|
|
1941
|
-
const health = await getConnectorHealth();
|
|
2044
|
+
const health = await getConnectorHealth({ piCliPath: config.piCliPath, hermesCliPath: config.hermesCliPath, openClawCliPath: config.openClawCliPath, claudeCodeCliPath: config.claudeCodeCliPath });
|
|
1942
2045
|
const state = await readConnectorState();
|
|
1943
|
-
const versions = await getVersionChecks({ piCliPath: config.piCliPath });
|
|
2046
|
+
const versions = await getVersionChecks({ piCliPath: config.piCliPath, hermesCliPath: config.hermesCliPath, openClawCliPath: config.openClawCliPath, claudeCodeCliPath: config.claudeCodeCliPath });
|
|
1944
2047
|
const plain = [
|
|
1945
2048
|
renderVersionCheckPlain(versions.nordrelay),
|
|
1946
2049
|
`Runtime status: ${state.status ?? "unknown"}`,
|
|
@@ -1948,6 +2051,12 @@ export function createBot(config, registry) {
|
|
|
1948
2051
|
renderVersionCheckPlain(versions.codex),
|
|
1949
2052
|
formatCliPathPlain("Pi CLI", health.piCliPath, health.piCli),
|
|
1950
2053
|
renderVersionCheckPlain(versions.pi),
|
|
2054
|
+
formatCliPathPlain("Hermes CLI", health.hermesCliPath, health.hermesCli),
|
|
2055
|
+
renderVersionCheckPlain(versions.hermes),
|
|
2056
|
+
formatCliPathPlain("OpenClaw CLI", health.openClawCliPath, health.openClawCli),
|
|
2057
|
+
renderVersionCheckPlain(versions.openclaw),
|
|
2058
|
+
formatCliPathPlain("Claude Code CLI", health.claudeCodeCliPath, health.claudeCodeCli),
|
|
2059
|
+
renderVersionCheckPlain(versions.claudeCode),
|
|
1951
2060
|
].join("\n");
|
|
1952
2061
|
const html = [
|
|
1953
2062
|
renderVersionCheckHTML(versions.nordrelay),
|
|
@@ -1956,6 +2065,12 @@ export function createBot(config, registry) {
|
|
|
1956
2065
|
renderVersionCheckHTML(versions.codex),
|
|
1957
2066
|
formatCliPathHTML("Pi CLI", health.piCliPath, health.piCli),
|
|
1958
2067
|
renderVersionCheckHTML(versions.pi),
|
|
2068
|
+
formatCliPathHTML("Hermes CLI", health.hermesCliPath, health.hermesCli),
|
|
2069
|
+
renderVersionCheckHTML(versions.hermes),
|
|
2070
|
+
formatCliPathHTML("OpenClaw CLI", health.openClawCliPath, health.openClawCli),
|
|
2071
|
+
renderVersionCheckHTML(versions.openclaw),
|
|
2072
|
+
formatCliPathHTML("Claude Code CLI", health.claudeCodeCliPath, health.claudeCodeCli),
|
|
2073
|
+
renderVersionCheckHTML(versions.claudeCode),
|
|
1959
2074
|
].join("\n");
|
|
1960
2075
|
await safeReply(ctx, html, { fallbackText: plain });
|
|
1961
2076
|
});
|
|
@@ -1993,10 +2108,10 @@ export function createBot(config, registry) {
|
|
|
1993
2108
|
return;
|
|
1994
2109
|
}
|
|
1995
2110
|
const options = parseActivityOptions((ctx.message?.text ?? "").replace(/^\/activity(?:@\w+)?\s*/i, "").trim());
|
|
1996
|
-
const events = filterActivityEvents(
|
|
2111
|
+
const events = filterActivityEvents(getAgentActivityLog(contextSession.session, config, options.exportFile ? 200 : options.limit), options);
|
|
1997
2112
|
const rendered = renderActivityTimeline(threadId, events, options);
|
|
1998
2113
|
if (options.exportFile && ctx.chat) {
|
|
1999
|
-
const exportPath = path.join(tmpdir(), `
|
|
2114
|
+
const exportPath = path.join(tmpdir(), `nordrelay-activity-${threadId}-${randomUUID().slice(0, 8)}.txt`);
|
|
2000
2115
|
await writeFile(exportPath, rendered.plain, "utf8");
|
|
2001
2116
|
try {
|
|
2002
2117
|
await telegramRateLimiter.run(chatBucket(ctx.chat.id), "sendDocument", () => ctx.api.sendDocument(ctx.chat.id, new InputFile(exportPath, path.basename(exportPath)), {
|
|
@@ -2066,15 +2181,17 @@ export function createBot(config, registry) {
|
|
|
2066
2181
|
await safeReply(ctx, rendered.html, { fallbackText: rendered.plain });
|
|
2067
2182
|
});
|
|
2068
2183
|
bot.command("diagnostics", async (ctx) => {
|
|
2069
|
-
const health = await getConnectorHealth();
|
|
2070
|
-
const authStatus = await checkAuthStatus(config.codexApiKey);
|
|
2184
|
+
const health = await getConnectorHealth({ piCliPath: config.piCliPath, hermesCliPath: config.hermesCliPath, openClawCliPath: config.openClawCliPath, claudeCodeCliPath: config.claudeCodeCliPath });
|
|
2071
2185
|
const contextKey = contextKeyFromCtx(ctx);
|
|
2072
2186
|
const queueLength = contextKey ? promptStore.list(contextKey).length : 0;
|
|
2073
2187
|
const progress = contextKey ? turnProgress.get(contextKey) : undefined;
|
|
2074
2188
|
const contextSession = contextKey ? await getContextSession(ctx, { deferThreadStart: true }) : null;
|
|
2075
|
-
const
|
|
2076
|
-
?
|
|
2077
|
-
:
|
|
2189
|
+
const authStatus = contextSession
|
|
2190
|
+
? await checkAgentAuthStatus(contextSession.session.getInfo())
|
|
2191
|
+
: await checkAuthStatus(config.codexApiKey);
|
|
2192
|
+
const agentDiagnostics = contextSession
|
|
2193
|
+
? renderAgentDiagnostics(getAgentDiagnostics(contextSession.session, config))
|
|
2194
|
+
: { plain: "Agent state: no context", html: "<b>Agent state:</b> <code>no context</code>" };
|
|
2078
2195
|
const runtime = {
|
|
2079
2196
|
rateLimit: getTelegramRateLimitMetrics(),
|
|
2080
2197
|
externalMirrors: externalMirrors.size,
|
|
@@ -2087,8 +2204,8 @@ export function createBot(config, registry) {
|
|
|
2087
2204
|
voiceLanguage: contextKey ? getEffectiveVoiceLanguage(contextKey) ?? "auto" : config.voiceDefaultLanguage ?? "auto",
|
|
2088
2205
|
voiceTranscribeOnly: contextKey ? isVoiceTranscribeOnly(contextKey) : config.voiceTranscribeOnly,
|
|
2089
2206
|
};
|
|
2090
|
-
const plain = `${renderDiagnosticsPlain(config, registry, health, authStatus.authenticated, getUserRole(ctx), queueLength, progress, runtime)}\n${
|
|
2091
|
-
const html = `${renderDiagnosticsHTML(config, registry, health, authStatus.authenticated, getUserRole(ctx), queueLength, progress, runtime)}\n${
|
|
2207
|
+
const plain = `${renderDiagnosticsPlain(config, registry, health, authStatus.authenticated, getUserRole(ctx), queueLength, progress, runtime)}\n${agentDiagnostics.plain}`;
|
|
2208
|
+
const html = `${renderDiagnosticsHTML(config, registry, health, authStatus.authenticated, getUserRole(ctx), queueLength, progress, runtime)}\n${agentDiagnostics.html}`;
|
|
2092
2209
|
await safeReply(ctx, html, { fallbackText: plain });
|
|
2093
2210
|
});
|
|
2094
2211
|
bot.command("sync", async (ctx) => {
|
|
@@ -2103,7 +2220,7 @@ export function createBot(config, registry) {
|
|
|
2103
2220
|
await safeReply(ctx, html, { fallbackText: plain });
|
|
2104
2221
|
return;
|
|
2105
2222
|
}
|
|
2106
|
-
const result = contextSession.session.
|
|
2223
|
+
const result = contextSession.session.syncFromAgentState({ reattach: true });
|
|
2107
2224
|
if (result.changed) {
|
|
2108
2225
|
updateSessionMetadata(contextSession.contextKey, contextSession.session);
|
|
2109
2226
|
}
|
|
@@ -2128,20 +2245,12 @@ export function createBot(config, registry) {
|
|
|
2128
2245
|
const rawText = ctx.message?.text ?? "";
|
|
2129
2246
|
const argument = rawText.replace(/^\/logs(?:@\w+)?\s*/i, "").trim();
|
|
2130
2247
|
const logRequest = parseLogsCommand(argument);
|
|
2131
|
-
const logs = logRequest.target
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
{
|
|
2138
|
-
title: logRequest.target === "update" ? "Update" : "Connector",
|
|
2139
|
-
tail: await readFormattedLogTail(logRequest.lines, logRequest.target === "update" ? getUpdateLogPath() : undefined),
|
|
2140
|
-
},
|
|
2141
|
-
];
|
|
2142
|
-
const plain = logs.map(({ title, tail }) => renderLogTailPlain(title, tail)).join("\n\n");
|
|
2143
|
-
const html = logs.map(({ title, tail }) => renderLogTailHTML(title, tail)).join("\n\n");
|
|
2144
|
-
await safeReply(ctx, html, { fallbackText: plain });
|
|
2248
|
+
const logs = await Promise.all(logTailRequests(logRequest.target).map(async (request) => ({
|
|
2249
|
+
title: request.title,
|
|
2250
|
+
tail: await readFormattedLogTail(logRequest.lines, request.path),
|
|
2251
|
+
})));
|
|
2252
|
+
const rendered = renderLogTailsAction(logs);
|
|
2253
|
+
await safeReply(ctx, rendered.html, { fallbackText: rendered.plain });
|
|
2145
2254
|
});
|
|
2146
2255
|
bot.command("restart", async (ctx) => {
|
|
2147
2256
|
await safeReply(ctx, escapeHTML("Restarting connector..."), {
|
|
@@ -2152,24 +2261,92 @@ export function createBot(config, registry) {
|
|
|
2152
2261
|
}, 300);
|
|
2153
2262
|
});
|
|
2154
2263
|
bot.command("update", async (ctx) => {
|
|
2264
|
+
const rawText = ctx.message?.text ?? "";
|
|
2265
|
+
const argument = rawText.replace(/^\/update(?:@\w+)?\s*/i, "").trim();
|
|
2266
|
+
const tokens = argument.split(/\s+/).filter(Boolean);
|
|
2267
|
+
const subcommand = tokens[0]?.toLowerCase();
|
|
2268
|
+
if (subcommand === "agents" || subcommand === "agent") {
|
|
2269
|
+
const rendered = renderAgentUpdatePickerAction(listAgentAdapterDescriptors());
|
|
2270
|
+
await safeReply(ctx, rendered.html, { fallbackText: rendered.plain, replyMarkup: actionKeyboard(rendered.buttons) });
|
|
2271
|
+
return;
|
|
2272
|
+
}
|
|
2273
|
+
if (subcommand === "jobs" || subcommand === "status") {
|
|
2274
|
+
const rendered = renderAgentUpdateJobsAction(agentUpdates.list());
|
|
2275
|
+
await safeReply(ctx, rendered.html, { fallbackText: rendered.plain });
|
|
2276
|
+
return;
|
|
2277
|
+
}
|
|
2278
|
+
if (subcommand === "log" && tokens[1]) {
|
|
2279
|
+
const rendered = renderAgentUpdateLogAction(agentUpdates.readLog(tokens[1]));
|
|
2280
|
+
await safeReply(ctx, rendered.html, { fallbackText: rendered.plain });
|
|
2281
|
+
return;
|
|
2282
|
+
}
|
|
2283
|
+
if (subcommand === "cancel" && tokens[1]) {
|
|
2284
|
+
const job = agentUpdates.cancel(tokens[1]);
|
|
2285
|
+
const rendered = renderAgentUpdateJobAction(job);
|
|
2286
|
+
await safeReply(ctx, rendered.html, {
|
|
2287
|
+
fallbackText: rendered.plain,
|
|
2288
|
+
replyMarkup: actionKeyboard(rendered.buttons),
|
|
2289
|
+
});
|
|
2290
|
+
return;
|
|
2291
|
+
}
|
|
2292
|
+
if ((subcommand === "input" || subcommand === "send") && tokens[1] && tokens.slice(2).join(" ").trim()) {
|
|
2293
|
+
const job = agentUpdates.sendInput(tokens[1], tokens.slice(2).join(" "));
|
|
2294
|
+
const rendered = renderAgentUpdateJobAction(job);
|
|
2295
|
+
await safeReply(ctx, rendered.html, {
|
|
2296
|
+
fallbackText: rendered.plain,
|
|
2297
|
+
replyMarkup: actionKeyboard(rendered.buttons),
|
|
2298
|
+
});
|
|
2299
|
+
return;
|
|
2300
|
+
}
|
|
2301
|
+
const requestedAgent = parseAgentUpdateId(subcommand);
|
|
2302
|
+
if (requestedAgent) {
|
|
2303
|
+
await startTelegramAgentUpdate(ctx, requestedAgent);
|
|
2304
|
+
return;
|
|
2305
|
+
}
|
|
2306
|
+
if (subcommand) {
|
|
2307
|
+
const usage = "Unknown update target. Use /update, /update agents, /update jobs, /update <agent>, /update log <id>, /update cancel <id>, or /update input <id> <text>.";
|
|
2308
|
+
await safeReply(ctx, escapeHTML(usage), { fallbackText: usage });
|
|
2309
|
+
return;
|
|
2310
|
+
}
|
|
2155
2311
|
const update = spawnSelfUpdate();
|
|
2156
|
-
const
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2312
|
+
const rendered = renderSelfUpdateStartedAction(update);
|
|
2313
|
+
await safeReply(ctx, rendered.html, { fallbackText: rendered.plain });
|
|
2314
|
+
});
|
|
2315
|
+
bot.callbackQuery("upd_jobs", async (ctx) => {
|
|
2316
|
+
await ctx.answerCallbackQuery();
|
|
2317
|
+
const rendered = renderAgentUpdateJobsAction(agentUpdates.list());
|
|
2318
|
+
await safeReply(ctx, rendered.html, { fallbackText: rendered.plain });
|
|
2319
|
+
});
|
|
2320
|
+
bot.callbackQuery(/^upd_agent:(codex|pi|hermes|openclaw|claude-code)$/, async (ctx) => {
|
|
2321
|
+
const agentId = ctx.match?.[1];
|
|
2322
|
+
if (!agentId) {
|
|
2323
|
+
await ctx.answerCallbackQuery();
|
|
2324
|
+
return;
|
|
2325
|
+
}
|
|
2326
|
+
await ctx.answerCallbackQuery({ text: `Starting ${agentLabel(agentId)} update...` });
|
|
2327
|
+
await startTelegramAgentUpdate(ctx, agentId);
|
|
2328
|
+
});
|
|
2329
|
+
bot.callbackQuery(/^upd_log:(.+)$/, async (ctx) => {
|
|
2330
|
+
const id = ctx.match?.[1];
|
|
2331
|
+
await ctx.answerCallbackQuery();
|
|
2332
|
+
if (!id) {
|
|
2333
|
+
return;
|
|
2334
|
+
}
|
|
2335
|
+
const rendered = renderAgentUpdateLogAction(agentUpdates.readLog(id));
|
|
2336
|
+
await safeReply(ctx, rendered.html, { fallbackText: rendered.plain });
|
|
2337
|
+
});
|
|
2338
|
+
bot.callbackQuery(/^upd_cancel:(.+)$/, async (ctx) => {
|
|
2339
|
+
const id = ctx.match?.[1];
|
|
2340
|
+
await ctx.answerCallbackQuery({ text: "Cancelling update..." });
|
|
2341
|
+
if (!id) {
|
|
2342
|
+
return;
|
|
2343
|
+
}
|
|
2344
|
+
const job = agentUpdates.cancel(id);
|
|
2345
|
+
const rendered = renderAgentUpdateJobAction(job);
|
|
2346
|
+
await safeReply(ctx, rendered.html, {
|
|
2347
|
+
fallbackText: rendered.plain,
|
|
2348
|
+
replyMarkup: actionKeyboard(rendered.buttons),
|
|
2349
|
+
});
|
|
2173
2350
|
});
|
|
2174
2351
|
bot.command("new", async (ctx) => {
|
|
2175
2352
|
const chatId = ctx.chat?.id;
|
|
@@ -2230,8 +2407,14 @@ export function createBot(config, registry) {
|
|
|
2230
2407
|
if (!contextSession) {
|
|
2231
2408
|
return;
|
|
2232
2409
|
}
|
|
2233
|
-
const { session } = contextSession;
|
|
2410
|
+
const { contextKey, session } = contextSession;
|
|
2234
2411
|
try {
|
|
2412
|
+
const busy = getBusyReason(contextKey);
|
|
2413
|
+
if (busy.kind === "external") {
|
|
2414
|
+
const text = `Cannot abort the external ${busy.activity.agentLabel} CLI task from NordRelay. Stop it in the terminal where it is running; queued Telegram messages will wait.`;
|
|
2415
|
+
await safeReply(ctx, escapeHTML(text), { fallbackText: text });
|
|
2416
|
+
return;
|
|
2417
|
+
}
|
|
2235
2418
|
await session.abort();
|
|
2236
2419
|
await safeReply(ctx, escapeHTML("Aborted current operation"), {
|
|
2237
2420
|
fallbackText: "Aborted current operation",
|
|
@@ -2311,7 +2494,7 @@ export function createBot(config, registry) {
|
|
|
2311
2494
|
});
|
|
2312
2495
|
return;
|
|
2313
2496
|
}
|
|
2314
|
-
const rendered =
|
|
2497
|
+
const rendered = renderQueuedPromptDetailAction(item);
|
|
2315
2498
|
await safeReply(ctx, rendered.html, { fallbackText: rendered.plain });
|
|
2316
2499
|
return;
|
|
2317
2500
|
}
|
|
@@ -2469,7 +2652,7 @@ export function createBot(config, registry) {
|
|
|
2469
2652
|
});
|
|
2470
2653
|
return;
|
|
2471
2654
|
}
|
|
2472
|
-
const rendered =
|
|
2655
|
+
const rendered = renderArtifactReportsAction(filtered);
|
|
2473
2656
|
await safeReply(ctx, rendered.html, {
|
|
2474
2657
|
fallbackText: rendered.plain,
|
|
2475
2658
|
replyMarkup: buildArtifactActionsKeyboard(filtered),
|
|
@@ -2495,7 +2678,7 @@ export function createBot(config, registry) {
|
|
|
2495
2678
|
}
|
|
2496
2679
|
return;
|
|
2497
2680
|
}
|
|
2498
|
-
const { html, plain } =
|
|
2681
|
+
const { html, plain } = renderArtifactReportsAction(reports);
|
|
2499
2682
|
await safeReply(ctx, html, {
|
|
2500
2683
|
fallbackText: plain,
|
|
2501
2684
|
replyMarkup: buildArtifactActionsKeyboard(reports),
|
|
@@ -2536,28 +2719,29 @@ export function createBot(config, registry) {
|
|
|
2536
2719
|
});
|
|
2537
2720
|
return;
|
|
2538
2721
|
}
|
|
2539
|
-
const
|
|
2540
|
-
const
|
|
2541
|
-
|
|
2722
|
+
const profiles = session.listLaunchProfiles();
|
|
2723
|
+
const selectedLaunchProfile = session.getInfo();
|
|
2724
|
+
const launchButtons = profiles.map((profile, index) => ({
|
|
2725
|
+
label: formatAgentLaunchProfileLabel(profile, profile.id === selectedLaunchProfile.launchProfileId),
|
|
2542
2726
|
callbackData: `launch_${index}`,
|
|
2543
2727
|
}));
|
|
2544
|
-
pendingLaunchPicks.set(contextKey,
|
|
2728
|
+
pendingLaunchPicks.set(contextKey, profiles.map((profile) => profile.id));
|
|
2545
2729
|
pendingLaunchButtons.set(contextKey, launchButtons);
|
|
2546
2730
|
pendingUnsafeLaunchConfirmations.delete(contextKey);
|
|
2547
2731
|
const keyboard = paginateKeyboard(launchButtons, 0, "launch");
|
|
2548
2732
|
const htmlLines = [
|
|
2549
|
-
`<b>Selected launch profile:</b> <code>${escapeHTML(selectedLaunchProfile.
|
|
2550
|
-
`<b>Behavior:</b> <code>${escapeHTML(
|
|
2733
|
+
`<b>Selected launch profile:</b> <code>${escapeHTML(selectedLaunchProfile.launchProfileLabel)}</code>`,
|
|
2734
|
+
`<b>Behavior:</b> <code>${escapeHTML(selectedLaunchProfile.launchProfileBehavior)}</code>`,
|
|
2551
2735
|
"",
|
|
2552
2736
|
"Select a profile for new or reattached threads:",
|
|
2553
2737
|
];
|
|
2554
2738
|
const plainLines = [
|
|
2555
|
-
`Selected launch profile: ${selectedLaunchProfile.
|
|
2556
|
-
`Behavior: ${
|
|
2739
|
+
`Selected launch profile: ${selectedLaunchProfile.launchProfileLabel}`,
|
|
2740
|
+
`Behavior: ${selectedLaunchProfile.launchProfileBehavior}`,
|
|
2557
2741
|
"",
|
|
2558
2742
|
"Select a profile for new or reattached threads:",
|
|
2559
2743
|
];
|
|
2560
|
-
if (selectedLaunchProfile.
|
|
2744
|
+
if (selectedLaunchProfile.unsafeLaunch) {
|
|
2561
2745
|
htmlLines.splice(2, 0, "⚠️ <i>Selected profile uses danger-full-access.</i>");
|
|
2562
2746
|
plainLines.splice(2, 0, "⚠️ Selected profile uses danger-full-access.");
|
|
2563
2747
|
}
|
|
@@ -2897,6 +3081,10 @@ export function createBot(config, registry) {
|
|
|
2897
3081
|
});
|
|
2898
3082
|
return;
|
|
2899
3083
|
}
|
|
3084
|
+
const info = session.getInfo();
|
|
3085
|
+
await session.refreshModels({ force: true }).catch((error) => {
|
|
3086
|
+
console.warn(`Failed to refresh ${labelOf(info)} models: ${error instanceof Error ? error.message : String(error)}`);
|
|
3087
|
+
});
|
|
2900
3088
|
const models = session.listModels();
|
|
2901
3089
|
if (models.length === 0) {
|
|
2902
3090
|
await safeReply(ctx, escapeHTML("No models available."), {
|
|
@@ -2906,7 +3094,7 @@ export function createBot(config, registry) {
|
|
|
2906
3094
|
}
|
|
2907
3095
|
const currentModel = session.getInfo().model ?? "(default)";
|
|
2908
3096
|
const modelButtons = models.map((model) => ({
|
|
2909
|
-
label:
|
|
3097
|
+
label: formatModelButtonLabel(model, model.slug === currentModel),
|
|
2910
3098
|
callbackData: `model_${model.slug}`,
|
|
2911
3099
|
}));
|
|
2912
3100
|
pendingModelButtons.set(contextKey, modelButtons);
|
|
@@ -2993,7 +3181,7 @@ export function createBot(config, registry) {
|
|
|
2993
3181
|
await safeReply(ctx, escapeHTML(text), { fallbackText: text });
|
|
2994
3182
|
return;
|
|
2995
3183
|
}
|
|
2996
|
-
const efforts = idOf(info)
|
|
3184
|
+
const efforts = agentReasoningOptions(idOf(info));
|
|
2997
3185
|
const current = info.reasoningEffort;
|
|
2998
3186
|
const effortButtons = efforts.map((effort) => ({
|
|
2999
3187
|
label: effort === current ? `${effort} ✓` : effort,
|
|
@@ -3011,7 +3199,7 @@ export function createBot(config, registry) {
|
|
|
3011
3199
|
});
|
|
3012
3200
|
};
|
|
3013
3201
|
bot.command(["effort", "reasoning"], openReasoningPicker);
|
|
3014
|
-
bot.callbackQuery(/^agent_(codex|pi)$/, async (ctx) => {
|
|
3202
|
+
bot.callbackQuery(/^agent_(codex|pi|hermes|openclaw|claude-code)$/, async (ctx) => {
|
|
3015
3203
|
const chatId = ctx.chat?.id;
|
|
3016
3204
|
const messageId = ctx.callbackQuery.message?.message_id;
|
|
3017
3205
|
const selectedAgent = ctx.match?.[1];
|
|
@@ -3365,7 +3553,7 @@ export function createBot(config, registry) {
|
|
|
3365
3553
|
await ctx.answerCallbackQuery({ text: "Wait for the current prompt to finish" });
|
|
3366
3554
|
return;
|
|
3367
3555
|
}
|
|
3368
|
-
const profile =
|
|
3556
|
+
const profile = session.listLaunchProfiles().find((candidate) => candidate.id === profileId);
|
|
3369
3557
|
if (!profile) {
|
|
3370
3558
|
clearLaunchSelectionState(contextKey);
|
|
3371
3559
|
await ctx.answerCallbackQuery({ text: "Launch profile no longer exists" });
|
|
@@ -3382,14 +3570,14 @@ export function createBot(config, registry) {
|
|
|
3382
3570
|
.text("Cancel", `launchconfirm_no:${profile.id}`);
|
|
3383
3571
|
const html = [
|
|
3384
3572
|
`<b>Confirm launch profile:</b> <code>${escapeHTML(profile.label)}</code>`,
|
|
3385
|
-
`<b>Behavior:</b> <code>${escapeHTML(
|
|
3573
|
+
`<b>Behavior:</b> <code>${escapeHTML(profile.behavior)}</code>`,
|
|
3386
3574
|
"",
|
|
3387
3575
|
"⚠️ <b>This profile uses danger-full-access.</b>",
|
|
3388
3576
|
"It will apply to new or reattached threads in this Telegram context.",
|
|
3389
3577
|
].join("\n");
|
|
3390
3578
|
const plain = [
|
|
3391
3579
|
`Confirm launch profile: ${profile.label}`,
|
|
3392
|
-
`Behavior: ${
|
|
3580
|
+
`Behavior: ${profile.behavior}`,
|
|
3393
3581
|
"",
|
|
3394
3582
|
"WARNING: This profile uses danger-full-access.",
|
|
3395
3583
|
"It will apply to new or reattached threads in this Telegram context.",
|
|
@@ -3410,17 +3598,18 @@ export function createBot(config, registry) {
|
|
|
3410
3598
|
}
|
|
3411
3599
|
await ctx.answerCallbackQuery({ text: `Launch set to ${profile.label}` });
|
|
3412
3600
|
clearLaunchSelectionState(contextKey);
|
|
3413
|
-
|
|
3601
|
+
session.setLaunchProfile(profile.id);
|
|
3414
3602
|
updateSessionMetadata(contextKey, session);
|
|
3603
|
+
const info = session.getInfo();
|
|
3415
3604
|
const html = [
|
|
3416
|
-
`<b>Launch profile set to</b> <code>${escapeHTML(
|
|
3417
|
-
`<b>Behavior:</b> <code>${escapeHTML(
|
|
3605
|
+
`<b>Launch profile set to</b> <code>${escapeHTML(info.launchProfileLabel)}</code>`,
|
|
3606
|
+
`<b>Behavior:</b> <code>${escapeHTML(info.launchProfileBehavior)}</code>`,
|
|
3418
3607
|
"",
|
|
3419
3608
|
"Applies to new or reattached threads.",
|
|
3420
3609
|
].join("\n");
|
|
3421
3610
|
const plain = [
|
|
3422
|
-
`Launch profile set to ${
|
|
3423
|
-
`Behavior: ${
|
|
3611
|
+
`Launch profile set to ${info.launchProfileLabel}`,
|
|
3612
|
+
`Behavior: ${info.launchProfileBehavior}`,
|
|
3424
3613
|
"",
|
|
3425
3614
|
"Applies to new or reattached threads.",
|
|
3426
3615
|
].join("\n");
|
|
@@ -3461,7 +3650,7 @@ export function createBot(config, registry) {
|
|
|
3461
3650
|
await ctx.answerCallbackQuery({ text: "Wait for the current prompt to finish" });
|
|
3462
3651
|
return;
|
|
3463
3652
|
}
|
|
3464
|
-
const profile =
|
|
3653
|
+
const profile = session.listLaunchProfiles().find((candidate) => candidate.id === profileId);
|
|
3465
3654
|
if (!profile) {
|
|
3466
3655
|
clearLaunchSelectionState(contextKey);
|
|
3467
3656
|
await ctx.answerCallbackQuery({ text: "Launch profile no longer exists" });
|
|
@@ -3471,18 +3660,19 @@ export function createBot(config, registry) {
|
|
|
3471
3660
|
return;
|
|
3472
3661
|
}
|
|
3473
3662
|
clearLaunchSelectionState(contextKey);
|
|
3474
|
-
|
|
3663
|
+
session.setLaunchProfile(profile.id);
|
|
3475
3664
|
updateSessionMetadata(contextKey, session);
|
|
3476
|
-
|
|
3665
|
+
const info = session.getInfo();
|
|
3666
|
+
await ctx.answerCallbackQuery({ text: `Launch set to ${info.launchProfileLabel}` });
|
|
3477
3667
|
const html = [
|
|
3478
|
-
`<b>Launch profile set to</b> <code>${escapeHTML(
|
|
3479
|
-
`<b>Behavior:</b> <code>${escapeHTML(
|
|
3668
|
+
`<b>Launch profile set to</b> <code>${escapeHTML(info.launchProfileLabel)}</code>`,
|
|
3669
|
+
`<b>Behavior:</b> <code>${escapeHTML(info.launchProfileBehavior)}</code>`,
|
|
3480
3670
|
"",
|
|
3481
3671
|
"⚠️ <i>danger-full-access confirmed for new or reattached threads.</i>",
|
|
3482
3672
|
].join("\n");
|
|
3483
3673
|
const plain = [
|
|
3484
|
-
`Launch profile set to ${
|
|
3485
|
-
`Behavior: ${
|
|
3674
|
+
`Launch profile set to ${info.launchProfileLabel}`,
|
|
3675
|
+
`Behavior: ${info.launchProfileBehavior}`,
|
|
3486
3676
|
"",
|
|
3487
3677
|
"danger-full-access confirmed for new or reattached threads.",
|
|
3488
3678
|
].join("\n");
|
|
@@ -3519,9 +3709,7 @@ export function createBot(config, registry) {
|
|
|
3519
3709
|
try {
|
|
3520
3710
|
const result = await session.setModelForCurrentSession(slug);
|
|
3521
3711
|
updateSessionMetadata(contextKey, session);
|
|
3522
|
-
const scope = result.appliedToActiveThread
|
|
3523
|
-
? "applied to the current idle thread and future threads"
|
|
3524
|
-
: "applies to new threads";
|
|
3712
|
+
const scope = formatAgentSettingScope(session.getInfo(), result.appliedToActiveThread);
|
|
3525
3713
|
const html = `<b>Model set to</b> <code>${escapeHTML(result.value)}</code> — ${escapeHTML(scope)}.`;
|
|
3526
3714
|
const plainText = `Model set to ${result.value} — ${scope}.`;
|
|
3527
3715
|
if (messageId) {
|
|
@@ -3542,7 +3730,7 @@ export function createBot(config, registry) {
|
|
|
3542
3730
|
}
|
|
3543
3731
|
}
|
|
3544
3732
|
});
|
|
3545
|
-
bot.callbackQuery(/^effort_(off|minimal|low|medium|high|xhigh)$/, async (ctx) => {
|
|
3733
|
+
bot.callbackQuery(/^effort_(off|none|minimal|low|medium|high|xhigh)$/, async (ctx) => {
|
|
3546
3734
|
const chatId = ctx.chat?.id;
|
|
3547
3735
|
const messageId = ctx.callbackQuery.message?.message_id;
|
|
3548
3736
|
const effort = ctx.match?.[1];
|
|
@@ -3568,9 +3756,7 @@ export function createBot(config, registry) {
|
|
|
3568
3756
|
const result = await session.setReasoningEffortForCurrentSession(effort);
|
|
3569
3757
|
updateSessionMetadata(contextKey, session);
|
|
3570
3758
|
const label = agentReasoningLabel(idOf(session.getInfo()));
|
|
3571
|
-
const scope = result.appliedToActiveThread
|
|
3572
|
-
? "applied to the current idle thread and future threads"
|
|
3573
|
-
: "applies to new threads";
|
|
3759
|
+
const scope = formatAgentSettingScope(session.getInfo(), result.appliedToActiveThread);
|
|
3574
3760
|
const html = `⚡ ${escapeHTML(label)} set to <code>${escapeHTML(effort)}</code> — ${escapeHTML(scope)}.`;
|
|
3575
3761
|
await safeEditMessage(bot, chatId, messageId, html, {
|
|
3576
3762
|
fallbackText: `⚡ ${label} set to ${effort} — ${scope}.`,
|
|
@@ -3901,7 +4087,7 @@ export async function registerCommands(bot) {
|
|
|
3901
4087
|
{ command: "help", description: "Command reference" },
|
|
3902
4088
|
{ command: "channels", description: "Messaging adapter status" },
|
|
3903
4089
|
{ command: "agents", description: "Agent adapter status" },
|
|
3904
|
-
{ command: "agent", description: "Select
|
|
4090
|
+
{ command: "agent", description: "Select agent" },
|
|
3905
4091
|
{ command: "new", description: "Start a new thread" },
|
|
3906
4092
|
{ command: "session", description: "Current thread details" },
|
|
3907
4093
|
{ command: "sessions", description: "Browse & switch threads" },
|
|
@@ -3940,23 +4126,12 @@ export async function registerCommands(bot) {
|
|
|
3940
4126
|
{ command: "unlock", description: "Release session write lock" },
|
|
3941
4127
|
{ command: "locks", description: "List session write locks" },
|
|
3942
4128
|
{ command: "restart", description: "Admin: restart connector" },
|
|
3943
|
-
{ command: "update", description: "Admin: update connector" },
|
|
4129
|
+
{ command: "update", description: "Admin: update connector or agents" },
|
|
3944
4130
|
{ command: "handback", description: "Hand session back to CLI" },
|
|
3945
4131
|
{ command: "attach", description: "Bind a session to this topic" },
|
|
3946
4132
|
{ command: "switch", description: "Switch to a thread by ID" },
|
|
3947
4133
|
]);
|
|
3948
4134
|
}
|
|
3949
|
-
function renderArtifactReports(reports) {
|
|
3950
|
-
const lines = reports.slice(0, 5).map((report, index) => {
|
|
3951
|
-
const size = formatFileSize(totalArtifactSize(report.artifacts));
|
|
3952
|
-
const skipped = report.skippedCount > 0 ? `, ${report.skippedCount} skipped` : "";
|
|
3953
|
-
return `${index + 1}. ${report.turnId} · ${formatRelativeTime(report.updatedAt)} · ${report.artifacts.length} file${report.artifacts.length === 1 ? "" : "s"} · ${size}${skipped}`;
|
|
3954
|
-
});
|
|
3955
|
-
const usage = "Tap an action below, or use /artifacts latest, /artifacts zip latest, /artifacts images, /artifacts docs, /artifacts search <text>, or /artifacts delete <turn-id>.";
|
|
3956
|
-
const plain = ["Recent artifacts:", ...lines, "", usage].join("\n");
|
|
3957
|
-
const html = ["<b>Recent artifacts:</b>", ...lines.map(escapeHTML), "", escapeHTML(usage)].join("\n");
|
|
3958
|
-
return { html, plain };
|
|
3959
|
-
}
|
|
3960
4135
|
function renderVersionCheckPlain(check) {
|
|
3961
4136
|
const icon = versionStatusIcon(check);
|
|
3962
4137
|
const label = check.label === "NordRelay" ? "NordRelay" : `${check.label} version`;
|
|
@@ -4002,73 +4177,6 @@ function formatVersionCheckDetailHTML(check) {
|
|
|
4002
4177
|
function versionStatusIcon(check) {
|
|
4003
4178
|
return check.status === "current" ? "✅" : "⚠️";
|
|
4004
4179
|
}
|
|
4005
|
-
function parseLogsCommand(argument) {
|
|
4006
|
-
const tokens = argument.split(/\s+/).filter(Boolean);
|
|
4007
|
-
let target = "connector";
|
|
4008
|
-
let lines = 80;
|
|
4009
|
-
for (const token of tokens) {
|
|
4010
|
-
const normalized = token.toLowerCase();
|
|
4011
|
-
if (normalized === "connector" || normalized === "main") {
|
|
4012
|
-
target = "connector";
|
|
4013
|
-
continue;
|
|
4014
|
-
}
|
|
4015
|
-
if (normalized === "update" || normalized === "updates") {
|
|
4016
|
-
target = "update";
|
|
4017
|
-
continue;
|
|
4018
|
-
}
|
|
4019
|
-
if (normalized === "all") {
|
|
4020
|
-
target = "all";
|
|
4021
|
-
continue;
|
|
4022
|
-
}
|
|
4023
|
-
const parsedLines = Number.parseInt(token, 10);
|
|
4024
|
-
if (!Number.isNaN(parsedLines)) {
|
|
4025
|
-
lines = parsedLines;
|
|
4026
|
-
}
|
|
4027
|
-
}
|
|
4028
|
-
return { target, lines };
|
|
4029
|
-
}
|
|
4030
|
-
function renderLogTailPlain(title, tail) {
|
|
4031
|
-
return [
|
|
4032
|
-
`${title} log tail`,
|
|
4033
|
-
`File: ${tail.filePath}`,
|
|
4034
|
-
`Updated: ${tail.updatedAt ? formatLogDate(tail.updatedAt) : "-"}`,
|
|
4035
|
-
`Lines: ${tail.lineCount}/${tail.requestedLines}`,
|
|
4036
|
-
"",
|
|
4037
|
-
tail.plain || "(empty)",
|
|
4038
|
-
].join("\n");
|
|
4039
|
-
}
|
|
4040
|
-
function renderLogTailHTML(title, tail) {
|
|
4041
|
-
const body = tail.plain
|
|
4042
|
-
? tail.plain.split("\n").map(renderLogLineHTML).join("\n")
|
|
4043
|
-
: "<code>(empty)</code>";
|
|
4044
|
-
return [
|
|
4045
|
-
`<b>${escapeHTML(title)} log tail</b>`,
|
|
4046
|
-
`<b>File:</b> <code>${escapeHTML(tail.filePath)}</code>`,
|
|
4047
|
-
`<b>Updated:</b> <code>${escapeHTML(tail.updatedAt ? formatLogDate(tail.updatedAt) : "-")}</code>`,
|
|
4048
|
-
`<b>Lines:</b> <code>${tail.lineCount}/${tail.requestedLines}</code>`,
|
|
4049
|
-
"",
|
|
4050
|
-
body,
|
|
4051
|
-
].join("\n");
|
|
4052
|
-
}
|
|
4053
|
-
function formatLogDate(date) {
|
|
4054
|
-
return [
|
|
4055
|
-
`${date.getFullYear()}-${pad2(date.getMonth() + 1)}-${pad2(date.getDate())}`,
|
|
4056
|
-
`${pad2(date.getHours())}:${pad2(date.getMinutes())}:${pad2(date.getSeconds())}`,
|
|
4057
|
-
].join(" ");
|
|
4058
|
-
}
|
|
4059
|
-
function renderLogLineHTML(line) {
|
|
4060
|
-
const structured = line.match(/^(?<timestamp>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}|unknown time\s*)\s+(?<level>INFO|WARN|ERROR)\s+(?<message>.*)$/);
|
|
4061
|
-
if (structured?.groups) {
|
|
4062
|
-
const level = structured.groups.level;
|
|
4063
|
-
const levelHtml = level === "INFO" ? escapeHTML(level) : `<b>${escapeHTML(level)}</b>`;
|
|
4064
|
-
return [
|
|
4065
|
-
`<code>${escapeHTML(structured.groups.timestamp.trim())}</code>`,
|
|
4066
|
-
levelHtml,
|
|
4067
|
-
escapeHTML(structured.groups.message),
|
|
4068
|
-
].join(" ");
|
|
4069
|
-
}
|
|
4070
|
-
return escapeHTML(line);
|
|
4071
|
-
}
|
|
4072
4180
|
function renderAuditEvents(events) {
|
|
4073
4181
|
if (events.length === 0) {
|
|
4074
4182
|
return {
|
|
@@ -4108,29 +4216,6 @@ function renderSessionLocks(locks) {
|
|
|
4108
4216
|
html: ["<b>Session locks:</b>", ...lines.map((line) => escapeHTML(line))].join("\n"),
|
|
4109
4217
|
};
|
|
4110
4218
|
}
|
|
4111
|
-
function renderQueuedPromptDetail(item) {
|
|
4112
|
-
const lines = [
|
|
4113
|
-
"Queued prompt:",
|
|
4114
|
-
`ID: ${item.id}`,
|
|
4115
|
-
`Created: ${formatLocalDateTime(new Date(item.createdAt))}`,
|
|
4116
|
-
item.notBefore ? `Scheduled: ${formatLocalDateTime(new Date(item.notBefore))}` : undefined,
|
|
4117
|
-
`Attempts: ${item.attempts ?? 0}`,
|
|
4118
|
-
item.lastError ? `Last error: ${item.lastError}` : undefined,
|
|
4119
|
-
`Description: ${item.description}`,
|
|
4120
|
-
].filter((line) => Boolean(line));
|
|
4121
|
-
return {
|
|
4122
|
-
plain: lines.join("\n"),
|
|
4123
|
-
html: [
|
|
4124
|
-
"<b>Queued prompt:</b>",
|
|
4125
|
-
`<b>ID:</b> <code>${escapeHTML(item.id)}</code>`,
|
|
4126
|
-
`<b>Created:</b> <code>${escapeHTML(formatLocalDateTime(new Date(item.createdAt)))}</code>`,
|
|
4127
|
-
item.notBefore ? `<b>Scheduled:</b> <code>${escapeHTML(formatLocalDateTime(new Date(item.notBefore)))}</code>` : undefined,
|
|
4128
|
-
`<b>Attempts:</b> <code>${item.attempts ?? 0}</code>`,
|
|
4129
|
-
item.lastError ? `<b>Last error:</b> ${escapeHTML(item.lastError)}` : undefined,
|
|
4130
|
-
`<b>Description:</b> ${escapeHTML(item.description)}`,
|
|
4131
|
-
].filter((line) => Boolean(line)).join("\n"),
|
|
4132
|
-
};
|
|
4133
|
-
}
|
|
4134
4219
|
function formatLockOwner(lock) {
|
|
4135
4220
|
if (!lock) {
|
|
4136
4221
|
return "nobody";
|
|
@@ -4259,7 +4344,7 @@ function renderExternalMirrorStatus(snapshot, queueLength) {
|
|
|
4259
4344
|
? formatDurationSeconds((Date.now() - snapshot.activity.startedAt.getTime()) / 1000)
|
|
4260
4345
|
: "-";
|
|
4261
4346
|
const lines = [
|
|
4262
|
-
|
|
4347
|
+
`${snapshot.agentLabel} CLI task running.`,
|
|
4263
4348
|
`Thread: ${snapshot.threadId}`,
|
|
4264
4349
|
`Elapsed: ${elapsed}`,
|
|
4265
4350
|
`Prompt: ${prompt}`,
|
|
@@ -4269,7 +4354,7 @@ function renderExternalMirrorStatus(snapshot, queueLength) {
|
|
|
4269
4354
|
return {
|
|
4270
4355
|
plain: lines.join("\n"),
|
|
4271
4356
|
html: [
|
|
4272
|
-
|
|
4357
|
+
`<b>${escapeHTML(snapshot.agentLabel)} CLI task running.</b>`,
|
|
4273
4358
|
`<b>Thread:</b> <code>${escapeHTML(snapshot.threadId)}</code>`,
|
|
4274
4359
|
`<b>Elapsed:</b> <code>${escapeHTML(elapsed)}</code>`,
|
|
4275
4360
|
`<b>Prompt:</b> <code>${escapeHTML(prompt)}</code>`,
|
|
@@ -4302,8 +4387,8 @@ function renderExternalMirrorEvent(event) {
|
|
|
4302
4387
|
function renderActivityTimeline(threadId, events, options = { limit: 16, filter: "all", exportFile: false }) {
|
|
4303
4388
|
if (events.length === 0) {
|
|
4304
4389
|
return {
|
|
4305
|
-
plain: `Activity:\nThread: ${threadId}\nFilter: ${options.filter}\nNo
|
|
4306
|
-
html: `<b>Activity:</b>\n<b>Thread:</b> <code>${escapeHTML(threadId)}</code>\n<b>Filter:</b> <code>${escapeHTML(options.filter)}</code>\n<code>No
|
|
4390
|
+
plain: `Activity:\nThread: ${threadId}\nFilter: ${options.filter}\nNo activity events found.`,
|
|
4391
|
+
html: `<b>Activity:</b>\n<b>Thread:</b> <code>${escapeHTML(threadId)}</code>\n<b>Filter:</b> <code>${escapeHTML(options.filter)}</code>\n<code>No activity events found.</code>`,
|
|
4307
4392
|
};
|
|
4308
4393
|
}
|
|
4309
4394
|
const lines = events.map((event) => {
|
|
@@ -4379,50 +4464,36 @@ function filterActivityEvents(events, options) {
|
|
|
4379
4464
|
function isActivityFilter(value) {
|
|
4380
4465
|
return value === "all" || value === "tools" || value === "errors" || value === "user" || value === "agent" || value === "tasks";
|
|
4381
4466
|
}
|
|
4382
|
-
function
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
}
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
: activity.stale
|
|
4405
|
-
? "open task exceeded stale timeout"
|
|
4406
|
-
: "no open task";
|
|
4407
|
-
const lines = [
|
|
4408
|
-
"Rollout:",
|
|
4409
|
-
`Path: ${snapshot.rolloutPath}`,
|
|
4410
|
-
`Status: ${status}`,
|
|
4411
|
-
`Reason: ${reason}`,
|
|
4412
|
-
`Turn: ${activity.turnId ?? "-"}`,
|
|
4413
|
-
`Lines: ${snapshot.lineCount}`,
|
|
4414
|
-
`Updated: ${activity.updatedAt?.toISOString() ?? "-"}`,
|
|
4415
|
-
];
|
|
4467
|
+
function formatAgentLaunchProfileLabel(profile, selected) {
|
|
4468
|
+
const prefix = selected ? "✅" : profile.unsafe ? "⚠️" : "🚀";
|
|
4469
|
+
return `${prefix} ${profile.label} · ${trimLine(profile.behavior, 24)}`;
|
|
4470
|
+
}
|
|
4471
|
+
function formatModelButtonLabel(model, selected) {
|
|
4472
|
+
const meta = [
|
|
4473
|
+
model.contextWindow ? formatCompactNumber(model.contextWindow) : undefined,
|
|
4474
|
+
model.supportsImages === true ? "img" : model.supportsImages === false ? "text" : undefined,
|
|
4475
|
+
model.supportsThinking === true ? "think" : undefined,
|
|
4476
|
+
].filter(Boolean).join(" ");
|
|
4477
|
+
return trimLine(`${selected ? "✅ " : ""}${model.displayName}${meta ? ` · ${meta}` : ""}`, 58);
|
|
4478
|
+
}
|
|
4479
|
+
function formatCompactNumber(value) {
|
|
4480
|
+
if (value >= 1_000_000_000)
|
|
4481
|
+
return `${Math.round(value / 100_000_000) / 10}B`;
|
|
4482
|
+
if (value >= 1_000_000)
|
|
4483
|
+
return `${Math.round(value / 100_000) / 10}M`;
|
|
4484
|
+
if (value >= 1_000)
|
|
4485
|
+
return `${Math.round(value / 100) / 10}K`;
|
|
4486
|
+
return String(value);
|
|
4487
|
+
}
|
|
4488
|
+
function renderAgentDiagnostics(diagnostics) {
|
|
4416
4489
|
return {
|
|
4417
|
-
plain:
|
|
4490
|
+
plain: [
|
|
4491
|
+
`${diagnostics.agentLabel} state:`,
|
|
4492
|
+
...diagnostics.lines.map((line) => `${line.label}: ${line.value}`),
|
|
4493
|
+
].join("\n"),
|
|
4418
4494
|
html: [
|
|
4419
|
-
|
|
4420
|
-
`<b
|
|
4421
|
-
`<b>Status:</b> <code>${escapeHTML(status)}</code>`,
|
|
4422
|
-
`<b>Reason:</b> <code>${escapeHTML(reason)}</code>`,
|
|
4423
|
-
`<b>Turn:</b> <code>${escapeHTML(activity.turnId ?? "-")}</code>`,
|
|
4424
|
-
`<b>Lines:</b> <code>${snapshot.lineCount}</code>`,
|
|
4425
|
-
`<b>Updated:</b> <code>${escapeHTML(activity.updatedAt?.toISOString() ?? "-")}</code>`,
|
|
4495
|
+
`<b>${escapeHTML(diagnostics.agentLabel)} state:</b>`,
|
|
4496
|
+
...diagnostics.lines.map((line) => `<b>${escapeHTML(line.label)}:</b> <code>${escapeHTML(line.value)}</code>`),
|
|
4426
4497
|
].join("\n"),
|
|
4427
4498
|
};
|
|
4428
4499
|
}
|
|
@@ -4462,6 +4533,11 @@ function renderDiagnosticsPlain(config, registry, health, authenticated, role, q
|
|
|
4462
4533
|
`Telegram transport: ${config.telegramTransport}`,
|
|
4463
4534
|
`Codex CLI: ${health.codexCli}`,
|
|
4464
4535
|
`Pi CLI: ${health.piCli}`,
|
|
4536
|
+
`Hermes CLI: ${health.hermesCli}`,
|
|
4537
|
+
`OpenClaw CLI: ${health.openClawCli}`,
|
|
4538
|
+
`Claude Code CLI: ${health.claudeCodeCli}`,
|
|
4539
|
+
`Hermes API: ${config.hermesApiBaseUrl}`,
|
|
4540
|
+
`OpenClaw Gateway: ${config.openClawGatewayUrl}`,
|
|
4465
4541
|
`Enabled agents/default: ${enabledAgents(config).join(", ")} / ${config.defaultAgent}`,
|
|
4466
4542
|
`State DB: ${health.databasePath ?? "-"}`,
|
|
4467
4543
|
`Log file: ${health.logFile}`,
|
|
@@ -4502,6 +4578,11 @@ function renderDiagnosticsHTML(config, registry, health, authenticated, role, qu
|
|
|
4502
4578
|
`<b>Telegram transport:</b> <code>${escapeHTML(config.telegramTransport)}</code>`,
|
|
4503
4579
|
`<b>Codex CLI:</b> <code>${escapeHTML(health.codexCli)}</code>`,
|
|
4504
4580
|
`<b>Pi CLI:</b> <code>${escapeHTML(health.piCli)}</code>`,
|
|
4581
|
+
`<b>Hermes CLI:</b> <code>${escapeHTML(health.hermesCli)}</code>`,
|
|
4582
|
+
`<b>OpenClaw CLI:</b> <code>${escapeHTML(health.openClawCli)}</code>`,
|
|
4583
|
+
`<b>Claude Code CLI:</b> <code>${escapeHTML(health.claudeCodeCli)}</code>`,
|
|
4584
|
+
`<b>Hermes API:</b> <code>${escapeHTML(config.hermesApiBaseUrl)}</code>`,
|
|
4585
|
+
`<b>OpenClaw Gateway:</b> <code>${escapeHTML(config.openClawGatewayUrl)}</code>`,
|
|
4505
4586
|
`<b>Enabled agents/default:</b> <code>${escapeHTML(`${enabledAgents(config).join(", ")} / ${config.defaultAgent}`)}</code>`,
|
|
4506
4587
|
`<b>State DB:</b> <code>${escapeHTML(health.databasePath ?? "-")}</code>`,
|
|
4507
4588
|
`<b>Log file:</b> <code>${escapeHTML(health.logFile)}</code>`,
|
|
@@ -4539,6 +4620,9 @@ function renderHealthPlain(health, authenticated, role) {
|
|
|
4539
4620
|
`Workspace: ${health.state.workspace ?? "-"}`,
|
|
4540
4621
|
`Codex CLI: ${health.codexCli}`,
|
|
4541
4622
|
`Pi CLI: ${health.piCli}`,
|
|
4623
|
+
`Hermes CLI: ${health.hermesCli}`,
|
|
4624
|
+
`OpenClaw CLI: ${health.openClawCli}`,
|
|
4625
|
+
`Claude Code CLI: ${health.claudeCodeCli}`,
|
|
4542
4626
|
`Codex state DB: ${health.databasePath ?? "-"}`,
|
|
4543
4627
|
`Log: ${health.logFile}`,
|
|
4544
4628
|
].join("\n");
|
|
@@ -4555,6 +4639,9 @@ function renderHealthHTML(health, authenticated, role) {
|
|
|
4555
4639
|
`<b>Workspace:</b> <code>${escapeHTML(health.state.workspace ?? "-")}</code>`,
|
|
4556
4640
|
`<b>Codex CLI:</b> <code>${escapeHTML(health.codexCli)}</code>`,
|
|
4557
4641
|
`<b>Pi CLI:</b> <code>${escapeHTML(health.piCli)}</code>`,
|
|
4642
|
+
`<b>Hermes CLI:</b> <code>${escapeHTML(health.hermesCli)}</code>`,
|
|
4643
|
+
`<b>OpenClaw CLI:</b> <code>${escapeHTML(health.openClawCli)}</code>`,
|
|
4644
|
+
`<b>Claude Code CLI:</b> <code>${escapeHTML(health.claudeCodeCli)}</code>`,
|
|
4558
4645
|
`<b>Codex state DB:</b> <code>${escapeHTML(health.databasePath ?? "-")}</code>`,
|
|
4559
4646
|
`<b>Log:</b> <code>${escapeHTML(health.logFile)}</code>`,
|
|
4560
4647
|
].join("\n");
|
|
@@ -4622,6 +4709,48 @@ function labelOf(info) {
|
|
|
4622
4709
|
function idOf(info) {
|
|
4623
4710
|
return info.agentId ?? "codex";
|
|
4624
4711
|
}
|
|
4712
|
+
function authHelpText(info) {
|
|
4713
|
+
const agentId = idOf(info);
|
|
4714
|
+
if (agentId === "pi") {
|
|
4715
|
+
return "Configure the required Pi provider environment variable on the host.";
|
|
4716
|
+
}
|
|
4717
|
+
if (agentId === "hermes") {
|
|
4718
|
+
return "Start the Hermes API Server, configure HERMES_API_KEY when required, or use /login to start Hermes CLI auth.";
|
|
4719
|
+
}
|
|
4720
|
+
if (agentId === "openclaw") {
|
|
4721
|
+
return "Start the OpenClaw Gateway and configure OPENCLAW_GATEWAY_TOKEN or OPENCLAW_GATEWAY_PASSWORD when the gateway requires one.";
|
|
4722
|
+
}
|
|
4723
|
+
if (agentId === "claude-code") {
|
|
4724
|
+
return "Use /login to start Claude Code CLI auth, or run 'claude auth login' on the host.";
|
|
4725
|
+
}
|
|
4726
|
+
return "Use /login to start authentication, or set CODEX_API_KEY on the host.";
|
|
4727
|
+
}
|
|
4728
|
+
function formatAgentSettingScope(info, appliedToActiveThread) {
|
|
4729
|
+
const agentId = idOf(info);
|
|
4730
|
+
if (agentId === "hermes") {
|
|
4731
|
+
return appliedToActiveThread
|
|
4732
|
+
? "applies to the next Hermes run in this session"
|
|
4733
|
+
: "applies to new Hermes sessions";
|
|
4734
|
+
}
|
|
4735
|
+
if (agentId === "pi") {
|
|
4736
|
+
return appliedToActiveThread
|
|
4737
|
+
? "applied to the current idle Pi session and future turns"
|
|
4738
|
+
: "applies to new Pi sessions";
|
|
4739
|
+
}
|
|
4740
|
+
if (agentId === "openclaw") {
|
|
4741
|
+
return appliedToActiveThread
|
|
4742
|
+
? "applies to the next OpenClaw run in this session"
|
|
4743
|
+
: "applies to new OpenClaw sessions";
|
|
4744
|
+
}
|
|
4745
|
+
if (agentId === "claude-code") {
|
|
4746
|
+
return appliedToActiveThread
|
|
4747
|
+
? "applies to the next Claude Code run in this session"
|
|
4748
|
+
: "applies to new Claude Code sessions";
|
|
4749
|
+
}
|
|
4750
|
+
return appliedToActiveThread
|
|
4751
|
+
? "applied to the current idle thread and future threads"
|
|
4752
|
+
: "applies to new threads";
|
|
4753
|
+
}
|
|
4625
4754
|
function requiresTurnApproval(info) {
|
|
4626
4755
|
return info.unsafeLaunch || info.approvalPolicy !== "never";
|
|
4627
4756
|
}
|