@nordbyte/nordrelay 0.3.0 → 0.4.0
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 +227 -47
- package/dist/agent-activity.js +300 -0
- package/dist/agent-adapter.js +17 -30
- package/dist/agent-factory.js +27 -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 +333 -161
- 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 +15 -2
- package/dist/config.js +113 -9
- package/dist/context-key.js +23 -0
- 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 +84 -3
- 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 +1073 -22
- package/dist/session-format.js +28 -18
- package/dist/session-registry.js +43 -18
- package/dist/settings-service.js +80 -26
- package/dist/state-backend.js +17 -8
- package/dist/web-dashboard-ui.js +18 -0
- package/dist/web-dashboard.js +463 -55
- package/dist/web-state.js +131 -0
- package/docker-compose.yml +1 -1
- 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 +173 -20
- package/plugins/nordrelay/skills/telegram-remote/SKILL.md +2 -2
- package/CHANGELOG.md +0 -17
package/dist/bot.js
CHANGED
|
@@ -12,16 +12,20 @@ import { AuditLogStore } from "./audit-log.js";
|
|
|
12
12
|
import { formatSessionLabel, renderHelpMessage, renderWelcomeFirstTime, renderWelcomeReturning, } from "./bot-ui.js";
|
|
13
13
|
import { BotPreferencesStore, formatQuietHours, isQuietNow, parseMirrorMode, parseNotifyMode, parseQuietHours, parseVoiceBackendPreference, } from "./bot-preferences.js";
|
|
14
14
|
import { listChannelDescriptors } from "./channel-adapter.js";
|
|
15
|
-
import {
|
|
15
|
+
import { CODEX_AGENT_CAPABILITIES, agentLabel, agentReasoningLabel, agentReasoningOptions, } from "./agent.js";
|
|
16
|
+
import { getAgentActivityLog, getAgentDiagnostics, getExternalActivityForSession, getExternalSnapshotForSession, } from "./agent-activity.js";
|
|
16
17
|
import { enabledAgents } from "./agent-factory.js";
|
|
17
|
-
import { checkAuthStatus, clearAuthCache, startLogin, startLogout } from "./codex-auth.js";
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import { contextKeyFromCtx, isTopicContextKey, parseContextKey } from "./context-key.js";
|
|
18
|
+
import { checkAuthStatus, clearAuthCache, startLogin as startCodexLogin, startLogout as startCodexLogout } from "./codex-auth.js";
|
|
19
|
+
import { checkClaudeCodeAuthStatus, startClaudeCodeLogin, startClaudeCodeLogout } from "./claude-code-auth.js";
|
|
20
|
+
import { formatLaunchProfileBehavior } from "./codex-launch.js";
|
|
21
|
+
import { contextKeyFromCtx, isTelegramContextKey, isTopicContextKey, parseContextKey } from "./context-key.js";
|
|
21
22
|
import { friendlyErrorText } from "./error-messages.js";
|
|
22
23
|
import { escapeHTML, formatTelegramHTML } from "./format.js";
|
|
23
24
|
import { getConnectorHealth, getUpdateLogPath, getVersionChecks, readConnectorState, readFormattedLogTail, spawnConnectorRestart, spawnSelfUpdate, } from "./operations.js";
|
|
24
25
|
import { PromptStore, toPromptEnvelope } from "./prompt-store.js";
|
|
26
|
+
import { checkHermesAuthStatus, startHermesLogin, startHermesLogout } from "./hermes-auth.js";
|
|
27
|
+
import { checkOpenClawAuthStatus } from "./openclaw-auth.js";
|
|
28
|
+
import { checkPiAuthStatus } from "./pi-auth.js";
|
|
25
29
|
import { configureRedaction, redactText } from "./redaction.js";
|
|
26
30
|
import { canWriteWithLock, SessionLockStore } from "./session-locks.js";
|
|
27
31
|
import { formatFileSize, renderLaunchSummaryHTML, renderLaunchSummaryPlain, renderSessionInfoHTML, renderSessionInfoPlain, } from "./session-format.js";
|
|
@@ -97,23 +101,23 @@ export function createBot(config, registry) {
|
|
|
97
101
|
const syncInterval = config.codexSyncIntervalMs > 0
|
|
98
102
|
? setInterval(() => {
|
|
99
103
|
try {
|
|
100
|
-
registry.
|
|
104
|
+
registry.syncAllFromAgentState({ reattach: true });
|
|
101
105
|
}
|
|
102
106
|
catch (error) {
|
|
103
|
-
console.error("Failed to sync sessions from
|
|
107
|
+
console.error("Failed to sync sessions from agent state:", error);
|
|
104
108
|
}
|
|
105
109
|
}, config.codexSyncIntervalMs)
|
|
106
110
|
: undefined;
|
|
107
111
|
syncInterval?.unref?.();
|
|
108
112
|
const externalMonitorInterval = setInterval(() => {
|
|
109
113
|
void monitorExternalContexts().catch((error) => {
|
|
110
|
-
console.error("Failed to monitor external
|
|
114
|
+
console.error("Failed to monitor external agent activity:", error);
|
|
111
115
|
});
|
|
112
116
|
}, config.codexExternalBusyCheckMs);
|
|
113
117
|
externalMonitorInterval.unref?.();
|
|
114
118
|
setTimeout(() => {
|
|
115
119
|
void monitorExternalContexts().catch((error) => {
|
|
116
|
-
console.error("Failed to run initial external
|
|
120
|
+
console.error("Failed to run initial external agent monitor:", error);
|
|
117
121
|
});
|
|
118
122
|
}, 0).unref?.();
|
|
119
123
|
registry.onRemove((key) => {
|
|
@@ -152,19 +156,7 @@ export function createBot(config, registry) {
|
|
|
152
156
|
}
|
|
153
157
|
return state;
|
|
154
158
|
};
|
|
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
|
-
};
|
|
159
|
+
const getExternalActivity = (session) => getExternalActivityForSession(session, config);
|
|
168
160
|
const getBusyReason = (contextKey) => {
|
|
169
161
|
const state = contextBusy.get(contextKey);
|
|
170
162
|
const session = registry.get(contextKey);
|
|
@@ -191,6 +183,83 @@ export function createBot(config, registry) {
|
|
|
191
183
|
const updateSessionMetadata = (contextKey, session) => {
|
|
192
184
|
registry.updateMetadata(contextKey, session);
|
|
193
185
|
};
|
|
186
|
+
const checkAgentAuthStatus = async (info) => {
|
|
187
|
+
if (idOf(info) === "pi") {
|
|
188
|
+
return checkPiAuthStatus(info.model);
|
|
189
|
+
}
|
|
190
|
+
if (idOf(info) === "hermes") {
|
|
191
|
+
return checkHermesAuthStatus({
|
|
192
|
+
baseUrl: config.hermesApiBaseUrl,
|
|
193
|
+
apiKey: config.hermesApiKey,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
if (idOf(info) === "openclaw") {
|
|
197
|
+
return checkOpenClawAuthStatus({
|
|
198
|
+
gatewayUrl: config.openClawGatewayUrl,
|
|
199
|
+
token: config.openClawGatewayToken,
|
|
200
|
+
password: config.openClawGatewayPassword,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
if (idOf(info) === "claude-code") {
|
|
204
|
+
return checkClaudeCodeAuthStatus(config.claudeCodeCliPath);
|
|
205
|
+
}
|
|
206
|
+
return checkAuthStatus(config.codexApiKey);
|
|
207
|
+
};
|
|
208
|
+
const agentIdForAuth = (info) => info ? idOf(info) : "codex";
|
|
209
|
+
const labelForAuth = (info) => info ? labelOf(info) : "Codex";
|
|
210
|
+
const checkLoginAuthStatus = async (info) => {
|
|
211
|
+
const agentId = agentIdForAuth(info);
|
|
212
|
+
if (agentId === "hermes") {
|
|
213
|
+
return checkHermesAuthStatus({
|
|
214
|
+
baseUrl: config.hermesApiBaseUrl,
|
|
215
|
+
apiKey: config.hermesApiKey,
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
if (agentId === "claude-code") {
|
|
219
|
+
return checkClaudeCodeAuthStatus(config.claudeCodeCliPath);
|
|
220
|
+
}
|
|
221
|
+
return checkAuthStatus(config.codexApiKey);
|
|
222
|
+
};
|
|
223
|
+
const startAgentLogin = (info) => {
|
|
224
|
+
const agentId = agentIdForAuth(info);
|
|
225
|
+
if (agentId === "hermes") {
|
|
226
|
+
return startHermesLogin(config.hermesCliPath);
|
|
227
|
+
}
|
|
228
|
+
if (agentId === "claude-code") {
|
|
229
|
+
return startClaudeCodeLogin(config.claudeCodeCliPath);
|
|
230
|
+
}
|
|
231
|
+
return startCodexLogin();
|
|
232
|
+
};
|
|
233
|
+
const startAgentLogout = (info) => {
|
|
234
|
+
const agentId = agentIdForAuth(info);
|
|
235
|
+
if (agentId === "hermes") {
|
|
236
|
+
return startHermesLogout(config.hermesCliPath);
|
|
237
|
+
}
|
|
238
|
+
if (agentId === "claude-code") {
|
|
239
|
+
return startClaudeCodeLogout(config.claudeCodeCliPath);
|
|
240
|
+
}
|
|
241
|
+
return startCodexLogout();
|
|
242
|
+
};
|
|
243
|
+
const hostLoginCommand = (info) => {
|
|
244
|
+
const agentId = agentIdForAuth(info);
|
|
245
|
+
if (agentId === "hermes") {
|
|
246
|
+
return `${config.hermesCliPath ?? "hermes"} login --no-browser`;
|
|
247
|
+
}
|
|
248
|
+
if (agentId === "claude-code") {
|
|
249
|
+
return `${config.claudeCodeCliPath ?? "claude"} auth login`;
|
|
250
|
+
}
|
|
251
|
+
return "codex login --device-auth";
|
|
252
|
+
};
|
|
253
|
+
const hostLogoutCommand = (info) => {
|
|
254
|
+
const agentId = agentIdForAuth(info);
|
|
255
|
+
if (agentId === "hermes") {
|
|
256
|
+
return `${config.hermesCliPath ?? "hermes"} logout`;
|
|
257
|
+
}
|
|
258
|
+
if (agentId === "claude-code") {
|
|
259
|
+
return `${config.claudeCodeCliPath ?? "claude"} auth logout`;
|
|
260
|
+
}
|
|
261
|
+
return "codex logout";
|
|
262
|
+
};
|
|
194
263
|
const isTopicContext = (contextKey) => isTopicContextKey(contextKey);
|
|
195
264
|
const getPreferences = (contextKey) => preferencesStore.get(contextKey);
|
|
196
265
|
const getEffectiveMirrorMode = (contextKey) => getPreferences(contextKey).mirrorMode ?? config.telegramMirrorMode;
|
|
@@ -331,12 +400,15 @@ export function createBot(config, registry) {
|
|
|
331
400
|
const contextKeys = new Set([
|
|
332
401
|
...registry.listContexts().map((context) => context.contextKey),
|
|
333
402
|
...promptStore.listContextKeys(),
|
|
334
|
-
]);
|
|
403
|
+
].filter(isTelegramContextKey));
|
|
335
404
|
for (const contextKey of contextKeys) {
|
|
336
405
|
await monitorExternalContext(contextKey);
|
|
337
406
|
}
|
|
338
407
|
};
|
|
339
408
|
const monitorExternalContext = async (contextKey) => {
|
|
409
|
+
if (!isTelegramContextKey(contextKey)) {
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
340
412
|
const session = await registry.getOrCreate(contextKey, { deferThreadStart: true }).catch(() => null);
|
|
341
413
|
if (!session) {
|
|
342
414
|
return;
|
|
@@ -361,11 +433,9 @@ export function createBot(config, registry) {
|
|
|
361
433
|
return;
|
|
362
434
|
}
|
|
363
435
|
const previous = externalMirrors.get(contextKey);
|
|
364
|
-
const snapshot =
|
|
436
|
+
const snapshot = getExternalSnapshotForSession(session, config, {
|
|
365
437
|
afterLine: previous?.lastLine ?? Number.MAX_SAFE_INTEGER,
|
|
366
|
-
|
|
367
|
-
}) ?? getThreadRolloutSnapshot(threadId, {
|
|
368
|
-
staleAfterMs: config.codexExternalBusyStaleMs,
|
|
438
|
+
}) ?? getExternalSnapshotForSession(session, config, {
|
|
369
439
|
maxEvents: 0,
|
|
370
440
|
});
|
|
371
441
|
if (!snapshot) {
|
|
@@ -379,7 +449,7 @@ export function createBot(config, registry) {
|
|
|
379
449
|
}
|
|
380
450
|
const activity = snapshot.activity;
|
|
381
451
|
if (activity.active && queueLength > 0) {
|
|
382
|
-
await updateQueueStatusMessage(contextKey, `Waiting for
|
|
452
|
+
await updateQueueStatusMessage(contextKey, `Waiting for ${info.agentLabel} CLI task... ${queueLength} queued${paused ? " (paused)" : ""}.`);
|
|
383
453
|
return;
|
|
384
454
|
}
|
|
385
455
|
if (!activity.active && queueLength > 0 && !paused && !session.isProcessing()) {
|
|
@@ -391,10 +461,10 @@ export function createBot(config, registry) {
|
|
|
391
461
|
const parsed = parseContextKey(contextKey);
|
|
392
462
|
const previous = externalMirrors.get(contextKey);
|
|
393
463
|
let state = previous;
|
|
394
|
-
if (!state || state.threadId !== snapshot.threadId || state.rolloutPath !== snapshot.
|
|
464
|
+
if (!state || state.threadId !== snapshot.threadId || state.rolloutPath !== snapshot.sourcePath) {
|
|
395
465
|
state = {
|
|
396
466
|
threadId: snapshot.threadId,
|
|
397
|
-
rolloutPath: snapshot.
|
|
467
|
+
rolloutPath: snapshot.sourcePath,
|
|
398
468
|
lastLine: snapshot.lineCount,
|
|
399
469
|
turnId: snapshot.activity.turnId,
|
|
400
470
|
startedAt: snapshot.activity.startedAt,
|
|
@@ -455,7 +525,7 @@ export function createBot(config, registry) {
|
|
|
455
525
|
const terminalEvent = [...snapshot.events].reverse().find((event) => event.kind === "task" && event.status && event.status !== "started");
|
|
456
526
|
if (terminalEvent) {
|
|
457
527
|
if (mirrorMode !== "off") {
|
|
458
|
-
const doneText =
|
|
528
|
+
const doneText = `${snapshot.agentLabel} CLI task ${terminalEvent.status}.`;
|
|
459
529
|
if (state.statusMessageId) {
|
|
460
530
|
await safeEditMessage(bot, chatId, state.statusMessageId, escapeHTML(doneText), {
|
|
461
531
|
fallbackText: doneText,
|
|
@@ -470,8 +540,8 @@ export function createBot(config, registry) {
|
|
|
470
540
|
}
|
|
471
541
|
const finalAgent = snapshot.events.filter((event) => event.kind === "agent" && event.text).at(-1);
|
|
472
542
|
if (mirrorMode !== "off" && mirrorMode !== "status" && finalAgent?.text && finalAgent.lineNumber !== state.latestAgentLine) {
|
|
473
|
-
await sendTextMessage(bot.api, chatId,
|
|
474
|
-
fallbackText:
|
|
543
|
+
await sendTextMessage(bot.api, chatId, `<b>${escapeHTML(snapshot.agentLabel)} CLI final answer:</b>`, {
|
|
544
|
+
fallbackText: `${snapshot.agentLabel} CLI final answer:`,
|
|
475
545
|
messageThreadId: parsed.messageThreadId,
|
|
476
546
|
});
|
|
477
547
|
for (const chunk of splitMarkdownForTelegram(finalAgent.text)) {
|
|
@@ -541,7 +611,8 @@ export function createBot(config, registry) {
|
|
|
541
611
|
}
|
|
542
612
|
const busy = getBusyReason(contextKey);
|
|
543
613
|
if (busy.kind === "external") {
|
|
544
|
-
|
|
614
|
+
const label = busy.activity.agentLabel;
|
|
615
|
+
await updateQueueStatusMessage(contextKey, `Waiting for ${label} CLI task... ${promptStore.list(contextKey).length} queued${promptStore.isPaused(contextKey) ? " (paused)" : ""}.`);
|
|
545
616
|
scheduleExternalQueueDrain(ctx, contextKey, chatId, session);
|
|
546
617
|
return;
|
|
547
618
|
}
|
|
@@ -551,7 +622,7 @@ export function createBot(config, registry) {
|
|
|
551
622
|
await updateQueueStatusMessage(contextKey, `CLI task finished, running queued prompt 1/${promptStore.list(contextKey).length}.`);
|
|
552
623
|
await drainQueuedPrompts(ctx, contextKey, chatId, session);
|
|
553
624
|
})().catch((error) => {
|
|
554
|
-
console.error("Failed to drain queue after external
|
|
625
|
+
console.error("Failed to drain queue after external CLI activity:", error);
|
|
555
626
|
});
|
|
556
627
|
}, config.codexExternalBusyCheckMs);
|
|
557
628
|
timer.unref?.();
|
|
@@ -1106,21 +1177,21 @@ export function createBot(config, registry) {
|
|
|
1106
1177
|
try {
|
|
1107
1178
|
const sessionInfo = session.getInfo();
|
|
1108
1179
|
if (capabilitiesOf(sessionInfo).auth) {
|
|
1109
|
-
const authStatus = await
|
|
1180
|
+
const authStatus = await checkAgentAuthStatus(sessionInfo);
|
|
1110
1181
|
if (!authStatus.authenticated) {
|
|
1111
1182
|
await safeReply(ctx, [
|
|
1112
1183
|
`<b>⚠️ ${escapeHTML(labelOf(sessionInfo))} is not authenticated.</b>`,
|
|
1113
1184
|
"",
|
|
1114
1185
|
`<code>${escapeHTML(authStatus.detail)}</code>`,
|
|
1115
1186
|
"",
|
|
1116
|
-
|
|
1187
|
+
authHelpText(sessionInfo),
|
|
1117
1188
|
].join("\n"), {
|
|
1118
1189
|
fallbackText: [
|
|
1119
1190
|
`⚠️ ${labelOf(sessionInfo)} is not authenticated.`,
|
|
1120
1191
|
"",
|
|
1121
1192
|
authStatus.detail,
|
|
1122
1193
|
"",
|
|
1123
|
-
|
|
1194
|
+
authHelpText(sessionInfo),
|
|
1124
1195
|
].join("\n"),
|
|
1125
1196
|
});
|
|
1126
1197
|
return;
|
|
@@ -1132,6 +1203,24 @@ export function createBot(config, registry) {
|
|
|
1132
1203
|
});
|
|
1133
1204
|
return;
|
|
1134
1205
|
}
|
|
1206
|
+
if (idOf(sessionInfo) === "hermes" && !config.hermesEnabled) {
|
|
1207
|
+
await safeReply(ctx, "<b>⚠️ Hermes is disabled.</b>\nEnable it with <code>NORDRELAY_HERMES_ENABLED=true</code>.", {
|
|
1208
|
+
fallbackText: "⚠️ Hermes is disabled.\nEnable it with NORDRELAY_HERMES_ENABLED=true.",
|
|
1209
|
+
});
|
|
1210
|
+
return;
|
|
1211
|
+
}
|
|
1212
|
+
if (idOf(sessionInfo) === "openclaw" && !config.openClawEnabled) {
|
|
1213
|
+
await safeReply(ctx, "<b>⚠️ OpenClaw is disabled.</b>\nEnable it with <code>NORDRELAY_OPENCLAW_ENABLED=true</code>.", {
|
|
1214
|
+
fallbackText: "⚠️ OpenClaw is disabled.\nEnable it with NORDRELAY_OPENCLAW_ENABLED=true.",
|
|
1215
|
+
});
|
|
1216
|
+
return;
|
|
1217
|
+
}
|
|
1218
|
+
if (idOf(sessionInfo) === "claude-code" && !config.claudeCodeEnabled) {
|
|
1219
|
+
await safeReply(ctx, "<b>⚠️ Claude Code is disabled.</b>\nEnable it with <code>NORDRELAY_CLAUDE_CODE_ENABLED=true</code>.", {
|
|
1220
|
+
fallbackText: "⚠️ Claude Code is disabled.\nEnable it with NORDRELAY_CLAUDE_CODE_ENABLED=true.",
|
|
1221
|
+
});
|
|
1222
|
+
return;
|
|
1223
|
+
}
|
|
1135
1224
|
if (!(await ensureActiveThread(ctx, contextKey, session))) {
|
|
1136
1225
|
return;
|
|
1137
1226
|
}
|
|
@@ -1145,12 +1234,13 @@ export function createBot(config, registry) {
|
|
|
1145
1234
|
const finalExternalActivity = getExternalActivity(session);
|
|
1146
1235
|
if (finalExternalActivity?.active) {
|
|
1147
1236
|
const item = maybeRequeuePromptAtFront(contextKey, envelope);
|
|
1148
|
-
const
|
|
1237
|
+
const label = finalExternalActivity.agentLabel;
|
|
1238
|
+
const message = `Queued prompt ${item.id} at position 1. The ${label} session became active in ${label} CLI and is processing another task.`;
|
|
1149
1239
|
await safeReply(ctx, escapeHTML(message), {
|
|
1150
1240
|
fallbackText: message,
|
|
1151
1241
|
replyMarkup: createQueuedPromptCancelKeyboard(contextKey, item.id),
|
|
1152
1242
|
});
|
|
1153
|
-
await updateQueueStatusMessage(contextKey, `Waiting for
|
|
1243
|
+
await updateQueueStatusMessage(contextKey, `Waiting for ${label} CLI task... ${promptStore.list(contextKey).length} queued.`);
|
|
1154
1244
|
scheduleExternalQueueDrain(ctx, contextKey, chatId, session);
|
|
1155
1245
|
turnProgress.delete(contextKey);
|
|
1156
1246
|
return;
|
|
@@ -1161,9 +1251,14 @@ export function createBot(config, registry) {
|
|
|
1161
1251
|
status: "ok",
|
|
1162
1252
|
description: envelope.description,
|
|
1163
1253
|
});
|
|
1254
|
+
const artifactStartedAt = new Date();
|
|
1255
|
+
const artifactTurnId = envelope.artifactOutDir
|
|
1256
|
+
? path.basename(path.dirname(envelope.artifactOutDir))
|
|
1257
|
+
: randomUUID().slice(0, 12);
|
|
1164
1258
|
await session.prompt(envelope.input, callbacks);
|
|
1165
1259
|
updateSessionMetadata(contextKey, session);
|
|
1166
1260
|
await finalizeResponse();
|
|
1261
|
+
await deliverCliGeneratedArtifacts(contextKey, chatId, session, artifactStartedAt, artifactTurnId, messageThreadId);
|
|
1167
1262
|
if (envelope.artifactOutDir) {
|
|
1168
1263
|
if (config.telegramAutoSendArtifacts) {
|
|
1169
1264
|
await deliverArtifacts(ctx, chatId, envelope.artifactOutDir, session.getInfo().workspace, messageThreadId);
|
|
@@ -1318,7 +1413,7 @@ export function createBot(config, registry) {
|
|
|
1318
1413
|
const deliverArtifactReportZip = async (ctx, chatId, report, messageThreadId) => {
|
|
1319
1414
|
const bundle = await createArtifactZipBundle(report.artifacts, report.outDir, {
|
|
1320
1415
|
maxFileSize: config.maxFileSize,
|
|
1321
|
-
bundleName: `
|
|
1416
|
+
bundleName: `nordrelay-artifacts-${report.turnId}.zip`,
|
|
1322
1417
|
});
|
|
1323
1418
|
if (!bundle) {
|
|
1324
1419
|
await safeReply(ctx, escapeHTML("Could not create a ZIP bundle for this artifact turn."), {
|
|
@@ -1513,9 +1608,9 @@ export function createBot(config, registry) {
|
|
|
1513
1608
|
}
|
|
1514
1609
|
const { contextKey, session } = contextSession;
|
|
1515
1610
|
const info = session.getInfo();
|
|
1516
|
-
const authStatus = capabilitiesOf(info).auth ? await
|
|
1611
|
+
const authStatus = capabilitiesOf(info).auth ? await checkAgentAuthStatus(info) : null;
|
|
1517
1612
|
const authWarning = authStatus && !authStatus.authenticated
|
|
1518
|
-
?
|
|
1613
|
+
? [`${labelOf(info)} is not authenticated.`, authStatus.detail, authHelpText(info)].filter(Boolean).join(" ")
|
|
1519
1614
|
: undefined;
|
|
1520
1615
|
const isReturning = registry.hasMetadata(contextKey);
|
|
1521
1616
|
if (isReturning) {
|
|
@@ -1558,7 +1653,13 @@ export function createBot(config, registry) {
|
|
|
1558
1653
|
? config.codexEnabled
|
|
1559
1654
|
: descriptor.id === "pi"
|
|
1560
1655
|
? config.piEnabled
|
|
1561
|
-
:
|
|
1656
|
+
: descriptor.id === "hermes"
|
|
1657
|
+
? config.hermesEnabled
|
|
1658
|
+
: descriptor.id === "openclaw"
|
|
1659
|
+
? config.openClawEnabled
|
|
1660
|
+
: descriptor.id === "claude-code"
|
|
1661
|
+
? config.claudeCodeEnabled
|
|
1662
|
+
: false;
|
|
1562
1663
|
return `${descriptor.label}: ${descriptor.status}${descriptor.status === "available" ? ` · ${enabled ? "enabled" : "disabled"}` : ""}`;
|
|
1563
1664
|
}),
|
|
1564
1665
|
].join("\n");
|
|
@@ -1569,7 +1670,13 @@ export function createBot(config, registry) {
|
|
|
1569
1670
|
? config.codexEnabled
|
|
1570
1671
|
: descriptor.id === "pi"
|
|
1571
1672
|
? config.piEnabled
|
|
1572
|
-
:
|
|
1673
|
+
: descriptor.id === "hermes"
|
|
1674
|
+
? config.hermesEnabled
|
|
1675
|
+
: descriptor.id === "openclaw"
|
|
1676
|
+
? config.openClawEnabled
|
|
1677
|
+
: descriptor.id === "claude-code"
|
|
1678
|
+
? config.claudeCodeEnabled
|
|
1679
|
+
: false;
|
|
1573
1680
|
const status = descriptor.status === "available" ? `${enabled ? "enabled" : "disabled"}` : "planned";
|
|
1574
1681
|
const notes = descriptor.notes ? `\n ${escapeHTML(descriptor.notes)}` : "";
|
|
1575
1682
|
return `${descriptor.status === "available" ? "✅" : "🟡"} <b>${escapeHTML(descriptor.label)}</b> <code>${escapeHTML(status)}</code>${notes}`;
|
|
@@ -1583,11 +1690,6 @@ export function createBot(config, registry) {
|
|
|
1583
1690
|
return;
|
|
1584
1691
|
}
|
|
1585
1692
|
const { contextKey, session } = contextSession;
|
|
1586
|
-
if (!capabilitiesOf(session.getInfo()).modelSelection) {
|
|
1587
|
-
const text = `Model selection is not supported for ${labelOf(session.getInfo())}.`;
|
|
1588
|
-
await safeReply(ctx, escapeHTML(text), { fallbackText: text });
|
|
1589
|
-
return;
|
|
1590
|
-
}
|
|
1591
1693
|
if (isBusy(contextKey)) {
|
|
1592
1694
|
await safeReply(ctx, escapeHTML("Cannot switch agent while a prompt is running."), {
|
|
1593
1695
|
fallbackText: "Cannot switch agent while a prompt is running.",
|
|
@@ -1624,7 +1726,7 @@ export function createBot(config, registry) {
|
|
|
1624
1726
|
await safeReply(ctx, escapeHTML(text), { fallbackText: text });
|
|
1625
1727
|
return;
|
|
1626
1728
|
}
|
|
1627
|
-
const authStatus = await checkAuthStatus(config.codexApiKey);
|
|
1729
|
+
const authStatus = info ? await checkAgentAuthStatus(info) : await checkAuthStatus(config.codexApiKey);
|
|
1628
1730
|
const icon = authStatus.authenticated ? "✅" : "❌";
|
|
1629
1731
|
const html = [
|
|
1630
1732
|
`<b>${icon} Auth status:</b> ${authStatus.authenticated ? "authenticated" : "not authenticated"}`,
|
|
@@ -1649,8 +1751,8 @@ export function createBot(config, registry) {
|
|
|
1649
1751
|
await safeReply(ctx, escapeHTML(text), { fallbackText: text });
|
|
1650
1752
|
return;
|
|
1651
1753
|
}
|
|
1652
|
-
const authStatus = await
|
|
1653
|
-
if (authStatus.authenticated) {
|
|
1754
|
+
const authStatus = await checkLoginAuthStatus(info);
|
|
1755
|
+
if (agentIdForAuth(info) !== "hermes" && authStatus.authenticated) {
|
|
1654
1756
|
await safeReply(ctx, `<b>✅ Already authenticated</b> via <code>${escapeHTML(authStatus.method)}</code>.`, {
|
|
1655
1757
|
fallbackText: `✅ Already authenticated via ${authStatus.method}.`,
|
|
1656
1758
|
});
|
|
@@ -1660,17 +1762,17 @@ export function createBot(config, registry) {
|
|
|
1660
1762
|
await safeReply(ctx, [
|
|
1661
1763
|
"<b>Telegram-initiated login is disabled.</b>",
|
|
1662
1764
|
"",
|
|
1663
|
-
|
|
1765
|
+
`Run <code>${escapeHTML(hostLoginCommand(info))}</code> on the host.`,
|
|
1664
1766
|
].join("\n"), {
|
|
1665
1767
|
fallbackText: [
|
|
1666
1768
|
"Telegram-initiated login is disabled.",
|
|
1667
1769
|
"",
|
|
1668
|
-
|
|
1770
|
+
`Run '${hostLoginCommand(info)}' on the host.`,
|
|
1669
1771
|
].join("\n"),
|
|
1670
1772
|
});
|
|
1671
1773
|
return;
|
|
1672
1774
|
}
|
|
1673
|
-
const result = await
|
|
1775
|
+
const result = await startAgentLogin(info);
|
|
1674
1776
|
if (result.success) {
|
|
1675
1777
|
await safeReply(ctx, `<b>🔑 Login initiated.</b>\n\n<code>${escapeHTML(result.message)}</code>`, {
|
|
1676
1778
|
fallbackText: `🔑 Login initiated.\n\n${result.message}`,
|
|
@@ -1692,17 +1794,17 @@ export function createBot(config, registry) {
|
|
|
1692
1794
|
await safeReply(ctx, escapeHTML(text), { fallbackText: text });
|
|
1693
1795
|
return;
|
|
1694
1796
|
}
|
|
1695
|
-
const authStatus = await
|
|
1797
|
+
const authStatus = await checkLoginAuthStatus(info);
|
|
1696
1798
|
if (authStatus.method === "api-key") {
|
|
1697
1799
|
await safeReply(ctx, [
|
|
1698
|
-
|
|
1800
|
+
`<b>Cannot logout via Telegram when ${escapeHTML(labelForAuth(info))} uses API-key authentication.</b>`,
|
|
1699
1801
|
"",
|
|
1700
|
-
"Remove
|
|
1802
|
+
"Remove the API key from .env to use CLI-based auth instead.",
|
|
1701
1803
|
].join("\n"), {
|
|
1702
1804
|
fallbackText: [
|
|
1703
|
-
|
|
1805
|
+
`Cannot logout via Telegram when ${labelForAuth(info)} uses API-key authentication.`,
|
|
1704
1806
|
"",
|
|
1705
|
-
"Remove
|
|
1807
|
+
"Remove the API key from .env to use CLI-based auth instead.",
|
|
1706
1808
|
].join("\n"),
|
|
1707
1809
|
});
|
|
1708
1810
|
return;
|
|
@@ -1711,23 +1813,23 @@ export function createBot(config, registry) {
|
|
|
1711
1813
|
await safeReply(ctx, [
|
|
1712
1814
|
"<b>Telegram-initiated auth management is disabled.</b>",
|
|
1713
1815
|
"",
|
|
1714
|
-
|
|
1816
|
+
`Run <code>${escapeHTML(hostLogoutCommand(info))}</code> on the host.`,
|
|
1715
1817
|
].join("\n"), {
|
|
1716
1818
|
fallbackText: [
|
|
1717
1819
|
"Telegram-initiated auth management is disabled.",
|
|
1718
1820
|
"",
|
|
1719
|
-
|
|
1821
|
+
`Run '${hostLogoutCommand(info)}' on the host.`,
|
|
1720
1822
|
].join("\n"),
|
|
1721
1823
|
});
|
|
1722
1824
|
return;
|
|
1723
1825
|
}
|
|
1724
|
-
if (!authStatus.authenticated) {
|
|
1826
|
+
if (agentIdForAuth(info) !== "hermes" && !authStatus.authenticated) {
|
|
1725
1827
|
await safeReply(ctx, escapeHTML("Not currently authenticated."), {
|
|
1726
1828
|
fallbackText: "Not currently authenticated.",
|
|
1727
1829
|
});
|
|
1728
1830
|
return;
|
|
1729
1831
|
}
|
|
1730
|
-
const result = await
|
|
1832
|
+
const result = await startAgentLogout(info);
|
|
1731
1833
|
if (result.success) {
|
|
1732
1834
|
await safeReply(ctx, `<b>🔓 Logged out.</b>\n\n${escapeHTML(result.message)}`, {
|
|
1733
1835
|
fallbackText: `🔓 Logged out.\n\n${result.message}`,
|
|
@@ -1928,16 +2030,19 @@ export function createBot(config, registry) {
|
|
|
1928
2030
|
});
|
|
1929
2031
|
});
|
|
1930
2032
|
bot.command(["status", "health"], async (ctx) => {
|
|
1931
|
-
const health = await getConnectorHealth();
|
|
1932
|
-
const
|
|
2033
|
+
const health = await getConnectorHealth({ piCliPath: config.piCliPath, hermesCliPath: config.hermesCliPath, openClawCliPath: config.openClawCliPath, claudeCodeCliPath: config.claudeCodeCliPath });
|
|
2034
|
+
const contextSession = await getContextSession(ctx, { deferThreadStart: true });
|
|
2035
|
+
const authStatus = contextSession
|
|
2036
|
+
? await checkAgentAuthStatus(contextSession.session.getInfo())
|
|
2037
|
+
: await checkAuthStatus(config.codexApiKey);
|
|
1933
2038
|
const html = renderHealthHTML(health, authStatus.authenticated, getUserRole(ctx));
|
|
1934
2039
|
const plain = renderHealthPlain(health, authStatus.authenticated, getUserRole(ctx));
|
|
1935
2040
|
await safeReply(ctx, html, { fallbackText: plain });
|
|
1936
2041
|
});
|
|
1937
2042
|
bot.command("version", async (ctx) => {
|
|
1938
|
-
const health = await getConnectorHealth();
|
|
2043
|
+
const health = await getConnectorHealth({ piCliPath: config.piCliPath, hermesCliPath: config.hermesCliPath, openClawCliPath: config.openClawCliPath, claudeCodeCliPath: config.claudeCodeCliPath });
|
|
1939
2044
|
const state = await readConnectorState();
|
|
1940
|
-
const versions = await getVersionChecks({ piCliPath: config.piCliPath });
|
|
2045
|
+
const versions = await getVersionChecks({ piCliPath: config.piCliPath, hermesCliPath: config.hermesCliPath, openClawCliPath: config.openClawCliPath, claudeCodeCliPath: config.claudeCodeCliPath });
|
|
1941
2046
|
const plain = [
|
|
1942
2047
|
renderVersionCheckPlain(versions.nordrelay),
|
|
1943
2048
|
`Runtime status: ${state.status ?? "unknown"}`,
|
|
@@ -1945,6 +2050,12 @@ export function createBot(config, registry) {
|
|
|
1945
2050
|
renderVersionCheckPlain(versions.codex),
|
|
1946
2051
|
formatCliPathPlain("Pi CLI", health.piCliPath, health.piCli),
|
|
1947
2052
|
renderVersionCheckPlain(versions.pi),
|
|
2053
|
+
formatCliPathPlain("Hermes CLI", health.hermesCliPath, health.hermesCli),
|
|
2054
|
+
renderVersionCheckPlain(versions.hermes),
|
|
2055
|
+
formatCliPathPlain("OpenClaw CLI", health.openClawCliPath, health.openClawCli),
|
|
2056
|
+
renderVersionCheckPlain(versions.openclaw),
|
|
2057
|
+
formatCliPathPlain("Claude Code CLI", health.claudeCodeCliPath, health.claudeCodeCli),
|
|
2058
|
+
renderVersionCheckPlain(versions.claudeCode),
|
|
1948
2059
|
].join("\n");
|
|
1949
2060
|
const html = [
|
|
1950
2061
|
renderVersionCheckHTML(versions.nordrelay),
|
|
@@ -1953,6 +2064,12 @@ export function createBot(config, registry) {
|
|
|
1953
2064
|
renderVersionCheckHTML(versions.codex),
|
|
1954
2065
|
formatCliPathHTML("Pi CLI", health.piCliPath, health.piCli),
|
|
1955
2066
|
renderVersionCheckHTML(versions.pi),
|
|
2067
|
+
formatCliPathHTML("Hermes CLI", health.hermesCliPath, health.hermesCli),
|
|
2068
|
+
renderVersionCheckHTML(versions.hermes),
|
|
2069
|
+
formatCliPathHTML("OpenClaw CLI", health.openClawCliPath, health.openClawCli),
|
|
2070
|
+
renderVersionCheckHTML(versions.openclaw),
|
|
2071
|
+
formatCliPathHTML("Claude Code CLI", health.claudeCodeCliPath, health.claudeCodeCli),
|
|
2072
|
+
renderVersionCheckHTML(versions.claudeCode),
|
|
1956
2073
|
].join("\n");
|
|
1957
2074
|
await safeReply(ctx, html, { fallbackText: plain });
|
|
1958
2075
|
});
|
|
@@ -1990,10 +2107,10 @@ export function createBot(config, registry) {
|
|
|
1990
2107
|
return;
|
|
1991
2108
|
}
|
|
1992
2109
|
const options = parseActivityOptions((ctx.message?.text ?? "").replace(/^\/activity(?:@\w+)?\s*/i, "").trim());
|
|
1993
|
-
const events = filterActivityEvents(
|
|
2110
|
+
const events = filterActivityEvents(getAgentActivityLog(contextSession.session, config, options.exportFile ? 200 : options.limit), options);
|
|
1994
2111
|
const rendered = renderActivityTimeline(threadId, events, options);
|
|
1995
2112
|
if (options.exportFile && ctx.chat) {
|
|
1996
|
-
const exportPath = path.join(tmpdir(), `
|
|
2113
|
+
const exportPath = path.join(tmpdir(), `nordrelay-activity-${threadId}-${randomUUID().slice(0, 8)}.txt`);
|
|
1997
2114
|
await writeFile(exportPath, rendered.plain, "utf8");
|
|
1998
2115
|
try {
|
|
1999
2116
|
await telegramRateLimiter.run(chatBucket(ctx.chat.id), "sendDocument", () => ctx.api.sendDocument(ctx.chat.id, new InputFile(exportPath, path.basename(exportPath)), {
|
|
@@ -2063,15 +2180,17 @@ export function createBot(config, registry) {
|
|
|
2063
2180
|
await safeReply(ctx, rendered.html, { fallbackText: rendered.plain });
|
|
2064
2181
|
});
|
|
2065
2182
|
bot.command("diagnostics", async (ctx) => {
|
|
2066
|
-
const health = await getConnectorHealth();
|
|
2067
|
-
const authStatus = await checkAuthStatus(config.codexApiKey);
|
|
2183
|
+
const health = await getConnectorHealth({ piCliPath: config.piCliPath, hermesCliPath: config.hermesCliPath, openClawCliPath: config.openClawCliPath, claudeCodeCliPath: config.claudeCodeCliPath });
|
|
2068
2184
|
const contextKey = contextKeyFromCtx(ctx);
|
|
2069
2185
|
const queueLength = contextKey ? promptStore.list(contextKey).length : 0;
|
|
2070
2186
|
const progress = contextKey ? turnProgress.get(contextKey) : undefined;
|
|
2071
2187
|
const contextSession = contextKey ? await getContextSession(ctx, { deferThreadStart: true }) : null;
|
|
2072
|
-
const
|
|
2073
|
-
?
|
|
2074
|
-
:
|
|
2188
|
+
const authStatus = contextSession
|
|
2189
|
+
? await checkAgentAuthStatus(contextSession.session.getInfo())
|
|
2190
|
+
: await checkAuthStatus(config.codexApiKey);
|
|
2191
|
+
const agentDiagnostics = contextSession
|
|
2192
|
+
? renderAgentDiagnostics(getAgentDiagnostics(contextSession.session, config))
|
|
2193
|
+
: { plain: "Agent state: no context", html: "<b>Agent state:</b> <code>no context</code>" };
|
|
2075
2194
|
const runtime = {
|
|
2076
2195
|
rateLimit: getTelegramRateLimitMetrics(),
|
|
2077
2196
|
externalMirrors: externalMirrors.size,
|
|
@@ -2084,8 +2203,8 @@ export function createBot(config, registry) {
|
|
|
2084
2203
|
voiceLanguage: contextKey ? getEffectiveVoiceLanguage(contextKey) ?? "auto" : config.voiceDefaultLanguage ?? "auto",
|
|
2085
2204
|
voiceTranscribeOnly: contextKey ? isVoiceTranscribeOnly(contextKey) : config.voiceTranscribeOnly,
|
|
2086
2205
|
};
|
|
2087
|
-
const plain = `${renderDiagnosticsPlain(config, registry, health, authStatus.authenticated, getUserRole(ctx), queueLength, progress, runtime)}\n${
|
|
2088
|
-
const html = `${renderDiagnosticsHTML(config, registry, health, authStatus.authenticated, getUserRole(ctx), queueLength, progress, runtime)}\n${
|
|
2206
|
+
const plain = `${renderDiagnosticsPlain(config, registry, health, authStatus.authenticated, getUserRole(ctx), queueLength, progress, runtime)}\n${agentDiagnostics.plain}`;
|
|
2207
|
+
const html = `${renderDiagnosticsHTML(config, registry, health, authStatus.authenticated, getUserRole(ctx), queueLength, progress, runtime)}\n${agentDiagnostics.html}`;
|
|
2089
2208
|
await safeReply(ctx, html, { fallbackText: plain });
|
|
2090
2209
|
});
|
|
2091
2210
|
bot.command("sync", async (ctx) => {
|
|
@@ -2100,7 +2219,7 @@ export function createBot(config, registry) {
|
|
|
2100
2219
|
await safeReply(ctx, html, { fallbackText: plain });
|
|
2101
2220
|
return;
|
|
2102
2221
|
}
|
|
2103
|
-
const result = contextSession.session.
|
|
2222
|
+
const result = contextSession.session.syncFromAgentState({ reattach: true });
|
|
2104
2223
|
if (result.changed) {
|
|
2105
2224
|
updateSessionMetadata(contextSession.contextKey, contextSession.session);
|
|
2106
2225
|
}
|
|
@@ -2227,8 +2346,14 @@ export function createBot(config, registry) {
|
|
|
2227
2346
|
if (!contextSession) {
|
|
2228
2347
|
return;
|
|
2229
2348
|
}
|
|
2230
|
-
const { session } = contextSession;
|
|
2349
|
+
const { contextKey, session } = contextSession;
|
|
2231
2350
|
try {
|
|
2351
|
+
const busy = getBusyReason(contextKey);
|
|
2352
|
+
if (busy.kind === "external") {
|
|
2353
|
+
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.`;
|
|
2354
|
+
await safeReply(ctx, escapeHTML(text), { fallbackText: text });
|
|
2355
|
+
return;
|
|
2356
|
+
}
|
|
2232
2357
|
await session.abort();
|
|
2233
2358
|
await safeReply(ctx, escapeHTML("Aborted current operation"), {
|
|
2234
2359
|
fallbackText: "Aborted current operation",
|
|
@@ -2533,28 +2658,29 @@ export function createBot(config, registry) {
|
|
|
2533
2658
|
});
|
|
2534
2659
|
return;
|
|
2535
2660
|
}
|
|
2536
|
-
const
|
|
2537
|
-
const
|
|
2538
|
-
|
|
2661
|
+
const profiles = session.listLaunchProfiles();
|
|
2662
|
+
const selectedLaunchProfile = session.getInfo();
|
|
2663
|
+
const launchButtons = profiles.map((profile, index) => ({
|
|
2664
|
+
label: formatAgentLaunchProfileLabel(profile, profile.id === selectedLaunchProfile.launchProfileId),
|
|
2539
2665
|
callbackData: `launch_${index}`,
|
|
2540
2666
|
}));
|
|
2541
|
-
pendingLaunchPicks.set(contextKey,
|
|
2667
|
+
pendingLaunchPicks.set(contextKey, profiles.map((profile) => profile.id));
|
|
2542
2668
|
pendingLaunchButtons.set(contextKey, launchButtons);
|
|
2543
2669
|
pendingUnsafeLaunchConfirmations.delete(contextKey);
|
|
2544
2670
|
const keyboard = paginateKeyboard(launchButtons, 0, "launch");
|
|
2545
2671
|
const htmlLines = [
|
|
2546
|
-
`<b>Selected launch profile:</b> <code>${escapeHTML(selectedLaunchProfile.
|
|
2547
|
-
`<b>Behavior:</b> <code>${escapeHTML(
|
|
2672
|
+
`<b>Selected launch profile:</b> <code>${escapeHTML(selectedLaunchProfile.launchProfileLabel)}</code>`,
|
|
2673
|
+
`<b>Behavior:</b> <code>${escapeHTML(selectedLaunchProfile.launchProfileBehavior)}</code>`,
|
|
2548
2674
|
"",
|
|
2549
2675
|
"Select a profile for new or reattached threads:",
|
|
2550
2676
|
];
|
|
2551
2677
|
const plainLines = [
|
|
2552
|
-
`Selected launch profile: ${selectedLaunchProfile.
|
|
2553
|
-
`Behavior: ${
|
|
2678
|
+
`Selected launch profile: ${selectedLaunchProfile.launchProfileLabel}`,
|
|
2679
|
+
`Behavior: ${selectedLaunchProfile.launchProfileBehavior}`,
|
|
2554
2680
|
"",
|
|
2555
2681
|
"Select a profile for new or reattached threads:",
|
|
2556
2682
|
];
|
|
2557
|
-
if (selectedLaunchProfile.
|
|
2683
|
+
if (selectedLaunchProfile.unsafeLaunch) {
|
|
2558
2684
|
htmlLines.splice(2, 0, "⚠️ <i>Selected profile uses danger-full-access.</i>");
|
|
2559
2685
|
plainLines.splice(2, 0, "⚠️ Selected profile uses danger-full-access.");
|
|
2560
2686
|
}
|
|
@@ -2894,6 +3020,10 @@ export function createBot(config, registry) {
|
|
|
2894
3020
|
});
|
|
2895
3021
|
return;
|
|
2896
3022
|
}
|
|
3023
|
+
const info = session.getInfo();
|
|
3024
|
+
await session.refreshModels({ force: true }).catch((error) => {
|
|
3025
|
+
console.warn(`Failed to refresh ${labelOf(info)} models: ${error instanceof Error ? error.message : String(error)}`);
|
|
3026
|
+
});
|
|
2897
3027
|
const models = session.listModels();
|
|
2898
3028
|
if (models.length === 0) {
|
|
2899
3029
|
await safeReply(ctx, escapeHTML("No models available."), {
|
|
@@ -2903,7 +3033,7 @@ export function createBot(config, registry) {
|
|
|
2903
3033
|
}
|
|
2904
3034
|
const currentModel = session.getInfo().model ?? "(default)";
|
|
2905
3035
|
const modelButtons = models.map((model) => ({
|
|
2906
|
-
label:
|
|
3036
|
+
label: formatModelButtonLabel(model, model.slug === currentModel),
|
|
2907
3037
|
callbackData: `model_${model.slug}`,
|
|
2908
3038
|
}));
|
|
2909
3039
|
pendingModelButtons.set(contextKey, modelButtons);
|
|
@@ -2990,7 +3120,7 @@ export function createBot(config, registry) {
|
|
|
2990
3120
|
await safeReply(ctx, escapeHTML(text), { fallbackText: text });
|
|
2991
3121
|
return;
|
|
2992
3122
|
}
|
|
2993
|
-
const efforts = idOf(info)
|
|
3123
|
+
const efforts = agentReasoningOptions(idOf(info));
|
|
2994
3124
|
const current = info.reasoningEffort;
|
|
2995
3125
|
const effortButtons = efforts.map((effort) => ({
|
|
2996
3126
|
label: effort === current ? `${effort} ✓` : effort,
|
|
@@ -3008,7 +3138,7 @@ export function createBot(config, registry) {
|
|
|
3008
3138
|
});
|
|
3009
3139
|
};
|
|
3010
3140
|
bot.command(["effort", "reasoning"], openReasoningPicker);
|
|
3011
|
-
bot.callbackQuery(/^agent_(codex|pi)$/, async (ctx) => {
|
|
3141
|
+
bot.callbackQuery(/^agent_(codex|pi|hermes|openclaw|claude-code)$/, async (ctx) => {
|
|
3012
3142
|
const chatId = ctx.chat?.id;
|
|
3013
3143
|
const messageId = ctx.callbackQuery.message?.message_id;
|
|
3014
3144
|
const selectedAgent = ctx.match?.[1];
|
|
@@ -3362,7 +3492,7 @@ export function createBot(config, registry) {
|
|
|
3362
3492
|
await ctx.answerCallbackQuery({ text: "Wait for the current prompt to finish" });
|
|
3363
3493
|
return;
|
|
3364
3494
|
}
|
|
3365
|
-
const profile =
|
|
3495
|
+
const profile = session.listLaunchProfiles().find((candidate) => candidate.id === profileId);
|
|
3366
3496
|
if (!profile) {
|
|
3367
3497
|
clearLaunchSelectionState(contextKey);
|
|
3368
3498
|
await ctx.answerCallbackQuery({ text: "Launch profile no longer exists" });
|
|
@@ -3379,14 +3509,14 @@ export function createBot(config, registry) {
|
|
|
3379
3509
|
.text("Cancel", `launchconfirm_no:${profile.id}`);
|
|
3380
3510
|
const html = [
|
|
3381
3511
|
`<b>Confirm launch profile:</b> <code>${escapeHTML(profile.label)}</code>`,
|
|
3382
|
-
`<b>Behavior:</b> <code>${escapeHTML(
|
|
3512
|
+
`<b>Behavior:</b> <code>${escapeHTML(profile.behavior)}</code>`,
|
|
3383
3513
|
"",
|
|
3384
3514
|
"⚠️ <b>This profile uses danger-full-access.</b>",
|
|
3385
3515
|
"It will apply to new or reattached threads in this Telegram context.",
|
|
3386
3516
|
].join("\n");
|
|
3387
3517
|
const plain = [
|
|
3388
3518
|
`Confirm launch profile: ${profile.label}`,
|
|
3389
|
-
`Behavior: ${
|
|
3519
|
+
`Behavior: ${profile.behavior}`,
|
|
3390
3520
|
"",
|
|
3391
3521
|
"WARNING: This profile uses danger-full-access.",
|
|
3392
3522
|
"It will apply to new or reattached threads in this Telegram context.",
|
|
@@ -3407,17 +3537,18 @@ export function createBot(config, registry) {
|
|
|
3407
3537
|
}
|
|
3408
3538
|
await ctx.answerCallbackQuery({ text: `Launch set to ${profile.label}` });
|
|
3409
3539
|
clearLaunchSelectionState(contextKey);
|
|
3410
|
-
|
|
3540
|
+
session.setLaunchProfile(profile.id);
|
|
3411
3541
|
updateSessionMetadata(contextKey, session);
|
|
3542
|
+
const info = session.getInfo();
|
|
3412
3543
|
const html = [
|
|
3413
|
-
`<b>Launch profile set to</b> <code>${escapeHTML(
|
|
3414
|
-
`<b>Behavior:</b> <code>${escapeHTML(
|
|
3544
|
+
`<b>Launch profile set to</b> <code>${escapeHTML(info.launchProfileLabel)}</code>`,
|
|
3545
|
+
`<b>Behavior:</b> <code>${escapeHTML(info.launchProfileBehavior)}</code>`,
|
|
3415
3546
|
"",
|
|
3416
3547
|
"Applies to new or reattached threads.",
|
|
3417
3548
|
].join("\n");
|
|
3418
3549
|
const plain = [
|
|
3419
|
-
`Launch profile set to ${
|
|
3420
|
-
`Behavior: ${
|
|
3550
|
+
`Launch profile set to ${info.launchProfileLabel}`,
|
|
3551
|
+
`Behavior: ${info.launchProfileBehavior}`,
|
|
3421
3552
|
"",
|
|
3422
3553
|
"Applies to new or reattached threads.",
|
|
3423
3554
|
].join("\n");
|
|
@@ -3458,7 +3589,7 @@ export function createBot(config, registry) {
|
|
|
3458
3589
|
await ctx.answerCallbackQuery({ text: "Wait for the current prompt to finish" });
|
|
3459
3590
|
return;
|
|
3460
3591
|
}
|
|
3461
|
-
const profile =
|
|
3592
|
+
const profile = session.listLaunchProfiles().find((candidate) => candidate.id === profileId);
|
|
3462
3593
|
if (!profile) {
|
|
3463
3594
|
clearLaunchSelectionState(contextKey);
|
|
3464
3595
|
await ctx.answerCallbackQuery({ text: "Launch profile no longer exists" });
|
|
@@ -3468,18 +3599,19 @@ export function createBot(config, registry) {
|
|
|
3468
3599
|
return;
|
|
3469
3600
|
}
|
|
3470
3601
|
clearLaunchSelectionState(contextKey);
|
|
3471
|
-
|
|
3602
|
+
session.setLaunchProfile(profile.id);
|
|
3472
3603
|
updateSessionMetadata(contextKey, session);
|
|
3473
|
-
|
|
3604
|
+
const info = session.getInfo();
|
|
3605
|
+
await ctx.answerCallbackQuery({ text: `Launch set to ${info.launchProfileLabel}` });
|
|
3474
3606
|
const html = [
|
|
3475
|
-
`<b>Launch profile set to</b> <code>${escapeHTML(
|
|
3476
|
-
`<b>Behavior:</b> <code>${escapeHTML(
|
|
3607
|
+
`<b>Launch profile set to</b> <code>${escapeHTML(info.launchProfileLabel)}</code>`,
|
|
3608
|
+
`<b>Behavior:</b> <code>${escapeHTML(info.launchProfileBehavior)}</code>`,
|
|
3477
3609
|
"",
|
|
3478
3610
|
"⚠️ <i>danger-full-access confirmed for new or reattached threads.</i>",
|
|
3479
3611
|
].join("\n");
|
|
3480
3612
|
const plain = [
|
|
3481
|
-
`Launch profile set to ${
|
|
3482
|
-
`Behavior: ${
|
|
3613
|
+
`Launch profile set to ${info.launchProfileLabel}`,
|
|
3614
|
+
`Behavior: ${info.launchProfileBehavior}`,
|
|
3483
3615
|
"",
|
|
3484
3616
|
"danger-full-access confirmed for new or reattached threads.",
|
|
3485
3617
|
].join("\n");
|
|
@@ -3516,9 +3648,7 @@ export function createBot(config, registry) {
|
|
|
3516
3648
|
try {
|
|
3517
3649
|
const result = await session.setModelForCurrentSession(slug);
|
|
3518
3650
|
updateSessionMetadata(contextKey, session);
|
|
3519
|
-
const scope = result.appliedToActiveThread
|
|
3520
|
-
? "applied to the current idle thread and future threads"
|
|
3521
|
-
: "applies to new threads";
|
|
3651
|
+
const scope = formatAgentSettingScope(session.getInfo(), result.appliedToActiveThread);
|
|
3522
3652
|
const html = `<b>Model set to</b> <code>${escapeHTML(result.value)}</code> — ${escapeHTML(scope)}.`;
|
|
3523
3653
|
const plainText = `Model set to ${result.value} — ${scope}.`;
|
|
3524
3654
|
if (messageId) {
|
|
@@ -3539,7 +3669,7 @@ export function createBot(config, registry) {
|
|
|
3539
3669
|
}
|
|
3540
3670
|
}
|
|
3541
3671
|
});
|
|
3542
|
-
bot.callbackQuery(/^effort_(off|minimal|low|medium|high|xhigh)$/, async (ctx) => {
|
|
3672
|
+
bot.callbackQuery(/^effort_(off|none|minimal|low|medium|high|xhigh)$/, async (ctx) => {
|
|
3543
3673
|
const chatId = ctx.chat?.id;
|
|
3544
3674
|
const messageId = ctx.callbackQuery.message?.message_id;
|
|
3545
3675
|
const effort = ctx.match?.[1];
|
|
@@ -3565,9 +3695,7 @@ export function createBot(config, registry) {
|
|
|
3565
3695
|
const result = await session.setReasoningEffortForCurrentSession(effort);
|
|
3566
3696
|
updateSessionMetadata(contextKey, session);
|
|
3567
3697
|
const label = agentReasoningLabel(idOf(session.getInfo()));
|
|
3568
|
-
const scope = result.appliedToActiveThread
|
|
3569
|
-
? "applied to the current idle thread and future threads"
|
|
3570
|
-
: "applies to new threads";
|
|
3698
|
+
const scope = formatAgentSettingScope(session.getInfo(), result.appliedToActiveThread);
|
|
3571
3699
|
const html = `⚡ ${escapeHTML(label)} set to <code>${escapeHTML(effort)}</code> — ${escapeHTML(scope)}.`;
|
|
3572
3700
|
await safeEditMessage(bot, chatId, messageId, html, {
|
|
3573
3701
|
fallbackText: `⚡ ${label} set to ${effort} — ${scope}.`,
|
|
@@ -3898,7 +4026,7 @@ export async function registerCommands(bot) {
|
|
|
3898
4026
|
{ command: "help", description: "Command reference" },
|
|
3899
4027
|
{ command: "channels", description: "Messaging adapter status" },
|
|
3900
4028
|
{ command: "agents", description: "Agent adapter status" },
|
|
3901
|
-
{ command: "agent", description: "Select
|
|
4029
|
+
{ command: "agent", description: "Select agent" },
|
|
3902
4030
|
{ command: "new", description: "Start a new thread" },
|
|
3903
4031
|
{ command: "session", description: "Current thread details" },
|
|
3904
4032
|
{ command: "sessions", description: "Browse & switch threads" },
|
|
@@ -4256,7 +4384,7 @@ function renderExternalMirrorStatus(snapshot, queueLength) {
|
|
|
4256
4384
|
? formatDurationSeconds((Date.now() - snapshot.activity.startedAt.getTime()) / 1000)
|
|
4257
4385
|
: "-";
|
|
4258
4386
|
const lines = [
|
|
4259
|
-
|
|
4387
|
+
`${snapshot.agentLabel} CLI task running.`,
|
|
4260
4388
|
`Thread: ${snapshot.threadId}`,
|
|
4261
4389
|
`Elapsed: ${elapsed}`,
|
|
4262
4390
|
`Prompt: ${prompt}`,
|
|
@@ -4266,7 +4394,7 @@ function renderExternalMirrorStatus(snapshot, queueLength) {
|
|
|
4266
4394
|
return {
|
|
4267
4395
|
plain: lines.join("\n"),
|
|
4268
4396
|
html: [
|
|
4269
|
-
|
|
4397
|
+
`<b>${escapeHTML(snapshot.agentLabel)} CLI task running.</b>`,
|
|
4270
4398
|
`<b>Thread:</b> <code>${escapeHTML(snapshot.threadId)}</code>`,
|
|
4271
4399
|
`<b>Elapsed:</b> <code>${escapeHTML(elapsed)}</code>`,
|
|
4272
4400
|
`<b>Prompt:</b> <code>${escapeHTML(prompt)}</code>`,
|
|
@@ -4299,8 +4427,8 @@ function renderExternalMirrorEvent(event) {
|
|
|
4299
4427
|
function renderActivityTimeline(threadId, events, options = { limit: 16, filter: "all", exportFile: false }) {
|
|
4300
4428
|
if (events.length === 0) {
|
|
4301
4429
|
return {
|
|
4302
|
-
plain: `Activity:\nThread: ${threadId}\nFilter: ${options.filter}\nNo
|
|
4303
|
-
html: `<b>Activity:</b>\n<b>Thread:</b> <code>${escapeHTML(threadId)}</code>\n<b>Filter:</b> <code>${escapeHTML(options.filter)}</code>\n<code>No
|
|
4430
|
+
plain: `Activity:\nThread: ${threadId}\nFilter: ${options.filter}\nNo activity events found.`,
|
|
4431
|
+
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>`,
|
|
4304
4432
|
};
|
|
4305
4433
|
}
|
|
4306
4434
|
const lines = events.map((event) => {
|
|
@@ -4376,50 +4504,36 @@ function filterActivityEvents(events, options) {
|
|
|
4376
4504
|
function isActivityFilter(value) {
|
|
4377
4505
|
return value === "all" || value === "tools" || value === "errors" || value === "user" || value === "agent" || value === "tasks";
|
|
4378
4506
|
}
|
|
4379
|
-
function
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
}
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
: activity.stale
|
|
4402
|
-
? "open task exceeded stale timeout"
|
|
4403
|
-
: "no open task";
|
|
4404
|
-
const lines = [
|
|
4405
|
-
"Rollout:",
|
|
4406
|
-
`Path: ${snapshot.rolloutPath}`,
|
|
4407
|
-
`Status: ${status}`,
|
|
4408
|
-
`Reason: ${reason}`,
|
|
4409
|
-
`Turn: ${activity.turnId ?? "-"}`,
|
|
4410
|
-
`Lines: ${snapshot.lineCount}`,
|
|
4411
|
-
`Updated: ${activity.updatedAt?.toISOString() ?? "-"}`,
|
|
4412
|
-
];
|
|
4507
|
+
function formatAgentLaunchProfileLabel(profile, selected) {
|
|
4508
|
+
const prefix = selected ? "✅" : profile.unsafe ? "⚠️" : "🚀";
|
|
4509
|
+
return `${prefix} ${profile.label} · ${trimLine(profile.behavior, 24)}`;
|
|
4510
|
+
}
|
|
4511
|
+
function formatModelButtonLabel(model, selected) {
|
|
4512
|
+
const meta = [
|
|
4513
|
+
model.contextWindow ? formatCompactNumber(model.contextWindow) : undefined,
|
|
4514
|
+
model.supportsImages === true ? "img" : model.supportsImages === false ? "text" : undefined,
|
|
4515
|
+
model.supportsThinking === true ? "think" : undefined,
|
|
4516
|
+
].filter(Boolean).join(" ");
|
|
4517
|
+
return trimLine(`${selected ? "✅ " : ""}${model.displayName}${meta ? ` · ${meta}` : ""}`, 58);
|
|
4518
|
+
}
|
|
4519
|
+
function formatCompactNumber(value) {
|
|
4520
|
+
if (value >= 1_000_000_000)
|
|
4521
|
+
return `${Math.round(value / 100_000_000) / 10}B`;
|
|
4522
|
+
if (value >= 1_000_000)
|
|
4523
|
+
return `${Math.round(value / 100_000) / 10}M`;
|
|
4524
|
+
if (value >= 1_000)
|
|
4525
|
+
return `${Math.round(value / 100) / 10}K`;
|
|
4526
|
+
return String(value);
|
|
4527
|
+
}
|
|
4528
|
+
function renderAgentDiagnostics(diagnostics) {
|
|
4413
4529
|
return {
|
|
4414
|
-
plain:
|
|
4530
|
+
plain: [
|
|
4531
|
+
`${diagnostics.agentLabel} state:`,
|
|
4532
|
+
...diagnostics.lines.map((line) => `${line.label}: ${line.value}`),
|
|
4533
|
+
].join("\n"),
|
|
4415
4534
|
html: [
|
|
4416
|
-
|
|
4417
|
-
`<b
|
|
4418
|
-
`<b>Status:</b> <code>${escapeHTML(status)}</code>`,
|
|
4419
|
-
`<b>Reason:</b> <code>${escapeHTML(reason)}</code>`,
|
|
4420
|
-
`<b>Turn:</b> <code>${escapeHTML(activity.turnId ?? "-")}</code>`,
|
|
4421
|
-
`<b>Lines:</b> <code>${snapshot.lineCount}</code>`,
|
|
4422
|
-
`<b>Updated:</b> <code>${escapeHTML(activity.updatedAt?.toISOString() ?? "-")}</code>`,
|
|
4535
|
+
`<b>${escapeHTML(diagnostics.agentLabel)} state:</b>`,
|
|
4536
|
+
...diagnostics.lines.map((line) => `<b>${escapeHTML(line.label)}:</b> <code>${escapeHTML(line.value)}</code>`),
|
|
4423
4537
|
].join("\n"),
|
|
4424
4538
|
};
|
|
4425
4539
|
}
|
|
@@ -4459,6 +4573,11 @@ function renderDiagnosticsPlain(config, registry, health, authenticated, role, q
|
|
|
4459
4573
|
`Telegram transport: ${config.telegramTransport}`,
|
|
4460
4574
|
`Codex CLI: ${health.codexCli}`,
|
|
4461
4575
|
`Pi CLI: ${health.piCli}`,
|
|
4576
|
+
`Hermes CLI: ${health.hermesCli}`,
|
|
4577
|
+
`OpenClaw CLI: ${health.openClawCli}`,
|
|
4578
|
+
`Claude Code CLI: ${health.claudeCodeCli}`,
|
|
4579
|
+
`Hermes API: ${config.hermesApiBaseUrl}`,
|
|
4580
|
+
`OpenClaw Gateway: ${config.openClawGatewayUrl}`,
|
|
4462
4581
|
`Enabled agents/default: ${enabledAgents(config).join(", ")} / ${config.defaultAgent}`,
|
|
4463
4582
|
`State DB: ${health.databasePath ?? "-"}`,
|
|
4464
4583
|
`Log file: ${health.logFile}`,
|
|
@@ -4499,6 +4618,11 @@ function renderDiagnosticsHTML(config, registry, health, authenticated, role, qu
|
|
|
4499
4618
|
`<b>Telegram transport:</b> <code>${escapeHTML(config.telegramTransport)}</code>`,
|
|
4500
4619
|
`<b>Codex CLI:</b> <code>${escapeHTML(health.codexCli)}</code>`,
|
|
4501
4620
|
`<b>Pi CLI:</b> <code>${escapeHTML(health.piCli)}</code>`,
|
|
4621
|
+
`<b>Hermes CLI:</b> <code>${escapeHTML(health.hermesCli)}</code>`,
|
|
4622
|
+
`<b>OpenClaw CLI:</b> <code>${escapeHTML(health.openClawCli)}</code>`,
|
|
4623
|
+
`<b>Claude Code CLI:</b> <code>${escapeHTML(health.claudeCodeCli)}</code>`,
|
|
4624
|
+
`<b>Hermes API:</b> <code>${escapeHTML(config.hermesApiBaseUrl)}</code>`,
|
|
4625
|
+
`<b>OpenClaw Gateway:</b> <code>${escapeHTML(config.openClawGatewayUrl)}</code>`,
|
|
4502
4626
|
`<b>Enabled agents/default:</b> <code>${escapeHTML(`${enabledAgents(config).join(", ")} / ${config.defaultAgent}`)}</code>`,
|
|
4503
4627
|
`<b>State DB:</b> <code>${escapeHTML(health.databasePath ?? "-")}</code>`,
|
|
4504
4628
|
`<b>Log file:</b> <code>${escapeHTML(health.logFile)}</code>`,
|
|
@@ -4536,6 +4660,9 @@ function renderHealthPlain(health, authenticated, role) {
|
|
|
4536
4660
|
`Workspace: ${health.state.workspace ?? "-"}`,
|
|
4537
4661
|
`Codex CLI: ${health.codexCli}`,
|
|
4538
4662
|
`Pi CLI: ${health.piCli}`,
|
|
4663
|
+
`Hermes CLI: ${health.hermesCli}`,
|
|
4664
|
+
`OpenClaw CLI: ${health.openClawCli}`,
|
|
4665
|
+
`Claude Code CLI: ${health.claudeCodeCli}`,
|
|
4539
4666
|
`Codex state DB: ${health.databasePath ?? "-"}`,
|
|
4540
4667
|
`Log: ${health.logFile}`,
|
|
4541
4668
|
].join("\n");
|
|
@@ -4552,6 +4679,9 @@ function renderHealthHTML(health, authenticated, role) {
|
|
|
4552
4679
|
`<b>Workspace:</b> <code>${escapeHTML(health.state.workspace ?? "-")}</code>`,
|
|
4553
4680
|
`<b>Codex CLI:</b> <code>${escapeHTML(health.codexCli)}</code>`,
|
|
4554
4681
|
`<b>Pi CLI:</b> <code>${escapeHTML(health.piCli)}</code>`,
|
|
4682
|
+
`<b>Hermes CLI:</b> <code>${escapeHTML(health.hermesCli)}</code>`,
|
|
4683
|
+
`<b>OpenClaw CLI:</b> <code>${escapeHTML(health.openClawCli)}</code>`,
|
|
4684
|
+
`<b>Claude Code CLI:</b> <code>${escapeHTML(health.claudeCodeCli)}</code>`,
|
|
4555
4685
|
`<b>Codex state DB:</b> <code>${escapeHTML(health.databasePath ?? "-")}</code>`,
|
|
4556
4686
|
`<b>Log:</b> <code>${escapeHTML(health.logFile)}</code>`,
|
|
4557
4687
|
].join("\n");
|
|
@@ -4619,6 +4749,48 @@ function labelOf(info) {
|
|
|
4619
4749
|
function idOf(info) {
|
|
4620
4750
|
return info.agentId ?? "codex";
|
|
4621
4751
|
}
|
|
4752
|
+
function authHelpText(info) {
|
|
4753
|
+
const agentId = idOf(info);
|
|
4754
|
+
if (agentId === "pi") {
|
|
4755
|
+
return "Configure the required Pi provider environment variable on the host.";
|
|
4756
|
+
}
|
|
4757
|
+
if (agentId === "hermes") {
|
|
4758
|
+
return "Start the Hermes API Server, configure HERMES_API_KEY when required, or use /login to start Hermes CLI auth.";
|
|
4759
|
+
}
|
|
4760
|
+
if (agentId === "openclaw") {
|
|
4761
|
+
return "Start the OpenClaw Gateway and configure OPENCLAW_GATEWAY_TOKEN or OPENCLAW_GATEWAY_PASSWORD when the gateway requires one.";
|
|
4762
|
+
}
|
|
4763
|
+
if (agentId === "claude-code") {
|
|
4764
|
+
return "Use /login to start Claude Code CLI auth, or run 'claude auth login' on the host.";
|
|
4765
|
+
}
|
|
4766
|
+
return "Use /login to start authentication, or set CODEX_API_KEY on the host.";
|
|
4767
|
+
}
|
|
4768
|
+
function formatAgentSettingScope(info, appliedToActiveThread) {
|
|
4769
|
+
const agentId = idOf(info);
|
|
4770
|
+
if (agentId === "hermes") {
|
|
4771
|
+
return appliedToActiveThread
|
|
4772
|
+
? "applies to the next Hermes run in this session"
|
|
4773
|
+
: "applies to new Hermes sessions";
|
|
4774
|
+
}
|
|
4775
|
+
if (agentId === "pi") {
|
|
4776
|
+
return appliedToActiveThread
|
|
4777
|
+
? "applied to the current idle Pi session and future turns"
|
|
4778
|
+
: "applies to new Pi sessions";
|
|
4779
|
+
}
|
|
4780
|
+
if (agentId === "openclaw") {
|
|
4781
|
+
return appliedToActiveThread
|
|
4782
|
+
? "applies to the next OpenClaw run in this session"
|
|
4783
|
+
: "applies to new OpenClaw sessions";
|
|
4784
|
+
}
|
|
4785
|
+
if (agentId === "claude-code") {
|
|
4786
|
+
return appliedToActiveThread
|
|
4787
|
+
? "applies to the next Claude Code run in this session"
|
|
4788
|
+
: "applies to new Claude Code sessions";
|
|
4789
|
+
}
|
|
4790
|
+
return appliedToActiveThread
|
|
4791
|
+
? "applied to the current idle thread and future threads"
|
|
4792
|
+
: "applies to new threads";
|
|
4793
|
+
}
|
|
4622
4794
|
function requiresTurnApproval(info) {
|
|
4623
4795
|
return info.unsafeLaunch || info.approvalPolicy !== "never";
|
|
4624
4796
|
}
|