@nordbyte/nordrelay 0.3.1 → 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 +204 -30
- 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 +328 -159
- 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 +83 -2
- 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 +673 -51
- package/dist/session-format.js +28 -18
- package/dist/session-registry.js +40 -15
- package/dist/settings-service.js +35 -4
- package/dist/web-dashboard-ui.js +18 -0
- package/dist/web-dashboard.js +329 -47
- 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 +131 -3
- package/plugins/nordrelay/skills/telegram-remote/SKILL.md +2 -2
- package/CHANGELOG.md +0 -26
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 {
|
|
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";
|
|
20
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;
|
|
@@ -364,11 +433,9 @@ export function createBot(config, registry) {
|
|
|
364
433
|
return;
|
|
365
434
|
}
|
|
366
435
|
const previous = externalMirrors.get(contextKey);
|
|
367
|
-
const snapshot =
|
|
436
|
+
const snapshot = getExternalSnapshotForSession(session, config, {
|
|
368
437
|
afterLine: previous?.lastLine ?? Number.MAX_SAFE_INTEGER,
|
|
369
|
-
|
|
370
|
-
}) ?? getThreadRolloutSnapshot(threadId, {
|
|
371
|
-
staleAfterMs: config.codexExternalBusyStaleMs,
|
|
438
|
+
}) ?? getExternalSnapshotForSession(session, config, {
|
|
372
439
|
maxEvents: 0,
|
|
373
440
|
});
|
|
374
441
|
if (!snapshot) {
|
|
@@ -382,7 +449,7 @@ export function createBot(config, registry) {
|
|
|
382
449
|
}
|
|
383
450
|
const activity = snapshot.activity;
|
|
384
451
|
if (activity.active && queueLength > 0) {
|
|
385
|
-
await updateQueueStatusMessage(contextKey, `Waiting for
|
|
452
|
+
await updateQueueStatusMessage(contextKey, `Waiting for ${info.agentLabel} CLI task... ${queueLength} queued${paused ? " (paused)" : ""}.`);
|
|
386
453
|
return;
|
|
387
454
|
}
|
|
388
455
|
if (!activity.active && queueLength > 0 && !paused && !session.isProcessing()) {
|
|
@@ -394,10 +461,10 @@ export function createBot(config, registry) {
|
|
|
394
461
|
const parsed = parseContextKey(contextKey);
|
|
395
462
|
const previous = externalMirrors.get(contextKey);
|
|
396
463
|
let state = previous;
|
|
397
|
-
if (!state || state.threadId !== snapshot.threadId || state.rolloutPath !== snapshot.
|
|
464
|
+
if (!state || state.threadId !== snapshot.threadId || state.rolloutPath !== snapshot.sourcePath) {
|
|
398
465
|
state = {
|
|
399
466
|
threadId: snapshot.threadId,
|
|
400
|
-
rolloutPath: snapshot.
|
|
467
|
+
rolloutPath: snapshot.sourcePath,
|
|
401
468
|
lastLine: snapshot.lineCount,
|
|
402
469
|
turnId: snapshot.activity.turnId,
|
|
403
470
|
startedAt: snapshot.activity.startedAt,
|
|
@@ -458,7 +525,7 @@ export function createBot(config, registry) {
|
|
|
458
525
|
const terminalEvent = [...snapshot.events].reverse().find((event) => event.kind === "task" && event.status && event.status !== "started");
|
|
459
526
|
if (terminalEvent) {
|
|
460
527
|
if (mirrorMode !== "off") {
|
|
461
|
-
const doneText =
|
|
528
|
+
const doneText = `${snapshot.agentLabel} CLI task ${terminalEvent.status}.`;
|
|
462
529
|
if (state.statusMessageId) {
|
|
463
530
|
await safeEditMessage(bot, chatId, state.statusMessageId, escapeHTML(doneText), {
|
|
464
531
|
fallbackText: doneText,
|
|
@@ -473,8 +540,8 @@ export function createBot(config, registry) {
|
|
|
473
540
|
}
|
|
474
541
|
const finalAgent = snapshot.events.filter((event) => event.kind === "agent" && event.text).at(-1);
|
|
475
542
|
if (mirrorMode !== "off" && mirrorMode !== "status" && finalAgent?.text && finalAgent.lineNumber !== state.latestAgentLine) {
|
|
476
|
-
await sendTextMessage(bot.api, chatId,
|
|
477
|
-
fallbackText:
|
|
543
|
+
await sendTextMessage(bot.api, chatId, `<b>${escapeHTML(snapshot.agentLabel)} CLI final answer:</b>`, {
|
|
544
|
+
fallbackText: `${snapshot.agentLabel} CLI final answer:`,
|
|
478
545
|
messageThreadId: parsed.messageThreadId,
|
|
479
546
|
});
|
|
480
547
|
for (const chunk of splitMarkdownForTelegram(finalAgent.text)) {
|
|
@@ -544,7 +611,8 @@ export function createBot(config, registry) {
|
|
|
544
611
|
}
|
|
545
612
|
const busy = getBusyReason(contextKey);
|
|
546
613
|
if (busy.kind === "external") {
|
|
547
|
-
|
|
614
|
+
const label = busy.activity.agentLabel;
|
|
615
|
+
await updateQueueStatusMessage(contextKey, `Waiting for ${label} CLI task... ${promptStore.list(contextKey).length} queued${promptStore.isPaused(contextKey) ? " (paused)" : ""}.`);
|
|
548
616
|
scheduleExternalQueueDrain(ctx, contextKey, chatId, session);
|
|
549
617
|
return;
|
|
550
618
|
}
|
|
@@ -554,7 +622,7 @@ export function createBot(config, registry) {
|
|
|
554
622
|
await updateQueueStatusMessage(contextKey, `CLI task finished, running queued prompt 1/${promptStore.list(contextKey).length}.`);
|
|
555
623
|
await drainQueuedPrompts(ctx, contextKey, chatId, session);
|
|
556
624
|
})().catch((error) => {
|
|
557
|
-
console.error("Failed to drain queue after external
|
|
625
|
+
console.error("Failed to drain queue after external CLI activity:", error);
|
|
558
626
|
});
|
|
559
627
|
}, config.codexExternalBusyCheckMs);
|
|
560
628
|
timer.unref?.();
|
|
@@ -1109,21 +1177,21 @@ export function createBot(config, registry) {
|
|
|
1109
1177
|
try {
|
|
1110
1178
|
const sessionInfo = session.getInfo();
|
|
1111
1179
|
if (capabilitiesOf(sessionInfo).auth) {
|
|
1112
|
-
const authStatus = await
|
|
1180
|
+
const authStatus = await checkAgentAuthStatus(sessionInfo);
|
|
1113
1181
|
if (!authStatus.authenticated) {
|
|
1114
1182
|
await safeReply(ctx, [
|
|
1115
1183
|
`<b>⚠️ ${escapeHTML(labelOf(sessionInfo))} is not authenticated.</b>`,
|
|
1116
1184
|
"",
|
|
1117
1185
|
`<code>${escapeHTML(authStatus.detail)}</code>`,
|
|
1118
1186
|
"",
|
|
1119
|
-
|
|
1187
|
+
authHelpText(sessionInfo),
|
|
1120
1188
|
].join("\n"), {
|
|
1121
1189
|
fallbackText: [
|
|
1122
1190
|
`⚠️ ${labelOf(sessionInfo)} is not authenticated.`,
|
|
1123
1191
|
"",
|
|
1124
1192
|
authStatus.detail,
|
|
1125
1193
|
"",
|
|
1126
|
-
|
|
1194
|
+
authHelpText(sessionInfo),
|
|
1127
1195
|
].join("\n"),
|
|
1128
1196
|
});
|
|
1129
1197
|
return;
|
|
@@ -1135,6 +1203,24 @@ export function createBot(config, registry) {
|
|
|
1135
1203
|
});
|
|
1136
1204
|
return;
|
|
1137
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
|
+
}
|
|
1138
1224
|
if (!(await ensureActiveThread(ctx, contextKey, session))) {
|
|
1139
1225
|
return;
|
|
1140
1226
|
}
|
|
@@ -1148,12 +1234,13 @@ export function createBot(config, registry) {
|
|
|
1148
1234
|
const finalExternalActivity = getExternalActivity(session);
|
|
1149
1235
|
if (finalExternalActivity?.active) {
|
|
1150
1236
|
const item = maybeRequeuePromptAtFront(contextKey, envelope);
|
|
1151
|
-
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.`;
|
|
1152
1239
|
await safeReply(ctx, escapeHTML(message), {
|
|
1153
1240
|
fallbackText: message,
|
|
1154
1241
|
replyMarkup: createQueuedPromptCancelKeyboard(contextKey, item.id),
|
|
1155
1242
|
});
|
|
1156
|
-
await updateQueueStatusMessage(contextKey, `Waiting for
|
|
1243
|
+
await updateQueueStatusMessage(contextKey, `Waiting for ${label} CLI task... ${promptStore.list(contextKey).length} queued.`);
|
|
1157
1244
|
scheduleExternalQueueDrain(ctx, contextKey, chatId, session);
|
|
1158
1245
|
turnProgress.delete(contextKey);
|
|
1159
1246
|
return;
|
|
@@ -1164,9 +1251,14 @@ export function createBot(config, registry) {
|
|
|
1164
1251
|
status: "ok",
|
|
1165
1252
|
description: envelope.description,
|
|
1166
1253
|
});
|
|
1254
|
+
const artifactStartedAt = new Date();
|
|
1255
|
+
const artifactTurnId = envelope.artifactOutDir
|
|
1256
|
+
? path.basename(path.dirname(envelope.artifactOutDir))
|
|
1257
|
+
: randomUUID().slice(0, 12);
|
|
1167
1258
|
await session.prompt(envelope.input, callbacks);
|
|
1168
1259
|
updateSessionMetadata(contextKey, session);
|
|
1169
1260
|
await finalizeResponse();
|
|
1261
|
+
await deliverCliGeneratedArtifacts(contextKey, chatId, session, artifactStartedAt, artifactTurnId, messageThreadId);
|
|
1170
1262
|
if (envelope.artifactOutDir) {
|
|
1171
1263
|
if (config.telegramAutoSendArtifacts) {
|
|
1172
1264
|
await deliverArtifacts(ctx, chatId, envelope.artifactOutDir, session.getInfo().workspace, messageThreadId);
|
|
@@ -1321,7 +1413,7 @@ export function createBot(config, registry) {
|
|
|
1321
1413
|
const deliverArtifactReportZip = async (ctx, chatId, report, messageThreadId) => {
|
|
1322
1414
|
const bundle = await createArtifactZipBundle(report.artifacts, report.outDir, {
|
|
1323
1415
|
maxFileSize: config.maxFileSize,
|
|
1324
|
-
bundleName: `
|
|
1416
|
+
bundleName: `nordrelay-artifacts-${report.turnId}.zip`,
|
|
1325
1417
|
});
|
|
1326
1418
|
if (!bundle) {
|
|
1327
1419
|
await safeReply(ctx, escapeHTML("Could not create a ZIP bundle for this artifact turn."), {
|
|
@@ -1516,9 +1608,9 @@ export function createBot(config, registry) {
|
|
|
1516
1608
|
}
|
|
1517
1609
|
const { contextKey, session } = contextSession;
|
|
1518
1610
|
const info = session.getInfo();
|
|
1519
|
-
const authStatus = capabilitiesOf(info).auth ? await
|
|
1611
|
+
const authStatus = capabilitiesOf(info).auth ? await checkAgentAuthStatus(info) : null;
|
|
1520
1612
|
const authWarning = authStatus && !authStatus.authenticated
|
|
1521
|
-
?
|
|
1613
|
+
? [`${labelOf(info)} is not authenticated.`, authStatus.detail, authHelpText(info)].filter(Boolean).join(" ")
|
|
1522
1614
|
: undefined;
|
|
1523
1615
|
const isReturning = registry.hasMetadata(contextKey);
|
|
1524
1616
|
if (isReturning) {
|
|
@@ -1561,7 +1653,13 @@ export function createBot(config, registry) {
|
|
|
1561
1653
|
? config.codexEnabled
|
|
1562
1654
|
: descriptor.id === "pi"
|
|
1563
1655
|
? config.piEnabled
|
|
1564
|
-
:
|
|
1656
|
+
: descriptor.id === "hermes"
|
|
1657
|
+
? config.hermesEnabled
|
|
1658
|
+
: descriptor.id === "openclaw"
|
|
1659
|
+
? config.openClawEnabled
|
|
1660
|
+
: descriptor.id === "claude-code"
|
|
1661
|
+
? config.claudeCodeEnabled
|
|
1662
|
+
: false;
|
|
1565
1663
|
return `${descriptor.label}: ${descriptor.status}${descriptor.status === "available" ? ` · ${enabled ? "enabled" : "disabled"}` : ""}`;
|
|
1566
1664
|
}),
|
|
1567
1665
|
].join("\n");
|
|
@@ -1572,7 +1670,13 @@ export function createBot(config, registry) {
|
|
|
1572
1670
|
? config.codexEnabled
|
|
1573
1671
|
: descriptor.id === "pi"
|
|
1574
1672
|
? config.piEnabled
|
|
1575
|
-
:
|
|
1673
|
+
: descriptor.id === "hermes"
|
|
1674
|
+
? config.hermesEnabled
|
|
1675
|
+
: descriptor.id === "openclaw"
|
|
1676
|
+
? config.openClawEnabled
|
|
1677
|
+
: descriptor.id === "claude-code"
|
|
1678
|
+
? config.claudeCodeEnabled
|
|
1679
|
+
: false;
|
|
1576
1680
|
const status = descriptor.status === "available" ? `${enabled ? "enabled" : "disabled"}` : "planned";
|
|
1577
1681
|
const notes = descriptor.notes ? `\n ${escapeHTML(descriptor.notes)}` : "";
|
|
1578
1682
|
return `${descriptor.status === "available" ? "✅" : "🟡"} <b>${escapeHTML(descriptor.label)}</b> <code>${escapeHTML(status)}</code>${notes}`;
|
|
@@ -1586,11 +1690,6 @@ export function createBot(config, registry) {
|
|
|
1586
1690
|
return;
|
|
1587
1691
|
}
|
|
1588
1692
|
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
1693
|
if (isBusy(contextKey)) {
|
|
1595
1694
|
await safeReply(ctx, escapeHTML("Cannot switch agent while a prompt is running."), {
|
|
1596
1695
|
fallbackText: "Cannot switch agent while a prompt is running.",
|
|
@@ -1627,7 +1726,7 @@ export function createBot(config, registry) {
|
|
|
1627
1726
|
await safeReply(ctx, escapeHTML(text), { fallbackText: text });
|
|
1628
1727
|
return;
|
|
1629
1728
|
}
|
|
1630
|
-
const authStatus = await checkAuthStatus(config.codexApiKey);
|
|
1729
|
+
const authStatus = info ? await checkAgentAuthStatus(info) : await checkAuthStatus(config.codexApiKey);
|
|
1631
1730
|
const icon = authStatus.authenticated ? "✅" : "❌";
|
|
1632
1731
|
const html = [
|
|
1633
1732
|
`<b>${icon} Auth status:</b> ${authStatus.authenticated ? "authenticated" : "not authenticated"}`,
|
|
@@ -1652,8 +1751,8 @@ export function createBot(config, registry) {
|
|
|
1652
1751
|
await safeReply(ctx, escapeHTML(text), { fallbackText: text });
|
|
1653
1752
|
return;
|
|
1654
1753
|
}
|
|
1655
|
-
const authStatus = await
|
|
1656
|
-
if (authStatus.authenticated) {
|
|
1754
|
+
const authStatus = await checkLoginAuthStatus(info);
|
|
1755
|
+
if (agentIdForAuth(info) !== "hermes" && authStatus.authenticated) {
|
|
1657
1756
|
await safeReply(ctx, `<b>✅ Already authenticated</b> via <code>${escapeHTML(authStatus.method)}</code>.`, {
|
|
1658
1757
|
fallbackText: `✅ Already authenticated via ${authStatus.method}.`,
|
|
1659
1758
|
});
|
|
@@ -1663,17 +1762,17 @@ export function createBot(config, registry) {
|
|
|
1663
1762
|
await safeReply(ctx, [
|
|
1664
1763
|
"<b>Telegram-initiated login is disabled.</b>",
|
|
1665
1764
|
"",
|
|
1666
|
-
|
|
1765
|
+
`Run <code>${escapeHTML(hostLoginCommand(info))}</code> on the host.`,
|
|
1667
1766
|
].join("\n"), {
|
|
1668
1767
|
fallbackText: [
|
|
1669
1768
|
"Telegram-initiated login is disabled.",
|
|
1670
1769
|
"",
|
|
1671
|
-
|
|
1770
|
+
`Run '${hostLoginCommand(info)}' on the host.`,
|
|
1672
1771
|
].join("\n"),
|
|
1673
1772
|
});
|
|
1674
1773
|
return;
|
|
1675
1774
|
}
|
|
1676
|
-
const result = await
|
|
1775
|
+
const result = await startAgentLogin(info);
|
|
1677
1776
|
if (result.success) {
|
|
1678
1777
|
await safeReply(ctx, `<b>🔑 Login initiated.</b>\n\n<code>${escapeHTML(result.message)}</code>`, {
|
|
1679
1778
|
fallbackText: `🔑 Login initiated.\n\n${result.message}`,
|
|
@@ -1695,17 +1794,17 @@ export function createBot(config, registry) {
|
|
|
1695
1794
|
await safeReply(ctx, escapeHTML(text), { fallbackText: text });
|
|
1696
1795
|
return;
|
|
1697
1796
|
}
|
|
1698
|
-
const authStatus = await
|
|
1797
|
+
const authStatus = await checkLoginAuthStatus(info);
|
|
1699
1798
|
if (authStatus.method === "api-key") {
|
|
1700
1799
|
await safeReply(ctx, [
|
|
1701
|
-
|
|
1800
|
+
`<b>Cannot logout via Telegram when ${escapeHTML(labelForAuth(info))} uses API-key authentication.</b>`,
|
|
1702
1801
|
"",
|
|
1703
|
-
"Remove
|
|
1802
|
+
"Remove the API key from .env to use CLI-based auth instead.",
|
|
1704
1803
|
].join("\n"), {
|
|
1705
1804
|
fallbackText: [
|
|
1706
|
-
|
|
1805
|
+
`Cannot logout via Telegram when ${labelForAuth(info)} uses API-key authentication.`,
|
|
1707
1806
|
"",
|
|
1708
|
-
"Remove
|
|
1807
|
+
"Remove the API key from .env to use CLI-based auth instead.",
|
|
1709
1808
|
].join("\n"),
|
|
1710
1809
|
});
|
|
1711
1810
|
return;
|
|
@@ -1714,23 +1813,23 @@ export function createBot(config, registry) {
|
|
|
1714
1813
|
await safeReply(ctx, [
|
|
1715
1814
|
"<b>Telegram-initiated auth management is disabled.</b>",
|
|
1716
1815
|
"",
|
|
1717
|
-
|
|
1816
|
+
`Run <code>${escapeHTML(hostLogoutCommand(info))}</code> on the host.`,
|
|
1718
1817
|
].join("\n"), {
|
|
1719
1818
|
fallbackText: [
|
|
1720
1819
|
"Telegram-initiated auth management is disabled.",
|
|
1721
1820
|
"",
|
|
1722
|
-
|
|
1821
|
+
`Run '${hostLogoutCommand(info)}' on the host.`,
|
|
1723
1822
|
].join("\n"),
|
|
1724
1823
|
});
|
|
1725
1824
|
return;
|
|
1726
1825
|
}
|
|
1727
|
-
if (!authStatus.authenticated) {
|
|
1826
|
+
if (agentIdForAuth(info) !== "hermes" && !authStatus.authenticated) {
|
|
1728
1827
|
await safeReply(ctx, escapeHTML("Not currently authenticated."), {
|
|
1729
1828
|
fallbackText: "Not currently authenticated.",
|
|
1730
1829
|
});
|
|
1731
1830
|
return;
|
|
1732
1831
|
}
|
|
1733
|
-
const result = await
|
|
1832
|
+
const result = await startAgentLogout(info);
|
|
1734
1833
|
if (result.success) {
|
|
1735
1834
|
await safeReply(ctx, `<b>🔓 Logged out.</b>\n\n${escapeHTML(result.message)}`, {
|
|
1736
1835
|
fallbackText: `🔓 Logged out.\n\n${result.message}`,
|
|
@@ -1931,16 +2030,19 @@ export function createBot(config, registry) {
|
|
|
1931
2030
|
});
|
|
1932
2031
|
});
|
|
1933
2032
|
bot.command(["status", "health"], async (ctx) => {
|
|
1934
|
-
const health = await getConnectorHealth();
|
|
1935
|
-
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);
|
|
1936
2038
|
const html = renderHealthHTML(health, authStatus.authenticated, getUserRole(ctx));
|
|
1937
2039
|
const plain = renderHealthPlain(health, authStatus.authenticated, getUserRole(ctx));
|
|
1938
2040
|
await safeReply(ctx, html, { fallbackText: plain });
|
|
1939
2041
|
});
|
|
1940
2042
|
bot.command("version", async (ctx) => {
|
|
1941
|
-
const health = await getConnectorHealth();
|
|
2043
|
+
const health = await getConnectorHealth({ piCliPath: config.piCliPath, hermesCliPath: config.hermesCliPath, openClawCliPath: config.openClawCliPath, claudeCodeCliPath: config.claudeCodeCliPath });
|
|
1942
2044
|
const state = await readConnectorState();
|
|
1943
|
-
const versions = await getVersionChecks({ piCliPath: config.piCliPath });
|
|
2045
|
+
const versions = await getVersionChecks({ piCliPath: config.piCliPath, hermesCliPath: config.hermesCliPath, openClawCliPath: config.openClawCliPath, claudeCodeCliPath: config.claudeCodeCliPath });
|
|
1944
2046
|
const plain = [
|
|
1945
2047
|
renderVersionCheckPlain(versions.nordrelay),
|
|
1946
2048
|
`Runtime status: ${state.status ?? "unknown"}`,
|
|
@@ -1948,6 +2050,12 @@ export function createBot(config, registry) {
|
|
|
1948
2050
|
renderVersionCheckPlain(versions.codex),
|
|
1949
2051
|
formatCliPathPlain("Pi CLI", health.piCliPath, health.piCli),
|
|
1950
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),
|
|
1951
2059
|
].join("\n");
|
|
1952
2060
|
const html = [
|
|
1953
2061
|
renderVersionCheckHTML(versions.nordrelay),
|
|
@@ -1956,6 +2064,12 @@ export function createBot(config, registry) {
|
|
|
1956
2064
|
renderVersionCheckHTML(versions.codex),
|
|
1957
2065
|
formatCliPathHTML("Pi CLI", health.piCliPath, health.piCli),
|
|
1958
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),
|
|
1959
2073
|
].join("\n");
|
|
1960
2074
|
await safeReply(ctx, html, { fallbackText: plain });
|
|
1961
2075
|
});
|
|
@@ -1993,10 +2107,10 @@ export function createBot(config, registry) {
|
|
|
1993
2107
|
return;
|
|
1994
2108
|
}
|
|
1995
2109
|
const options = parseActivityOptions((ctx.message?.text ?? "").replace(/^\/activity(?:@\w+)?\s*/i, "").trim());
|
|
1996
|
-
const events = filterActivityEvents(
|
|
2110
|
+
const events = filterActivityEvents(getAgentActivityLog(contextSession.session, config, options.exportFile ? 200 : options.limit), options);
|
|
1997
2111
|
const rendered = renderActivityTimeline(threadId, events, options);
|
|
1998
2112
|
if (options.exportFile && ctx.chat) {
|
|
1999
|
-
const exportPath = path.join(tmpdir(), `
|
|
2113
|
+
const exportPath = path.join(tmpdir(), `nordrelay-activity-${threadId}-${randomUUID().slice(0, 8)}.txt`);
|
|
2000
2114
|
await writeFile(exportPath, rendered.plain, "utf8");
|
|
2001
2115
|
try {
|
|
2002
2116
|
await telegramRateLimiter.run(chatBucket(ctx.chat.id), "sendDocument", () => ctx.api.sendDocument(ctx.chat.id, new InputFile(exportPath, path.basename(exportPath)), {
|
|
@@ -2066,15 +2180,17 @@ export function createBot(config, registry) {
|
|
|
2066
2180
|
await safeReply(ctx, rendered.html, { fallbackText: rendered.plain });
|
|
2067
2181
|
});
|
|
2068
2182
|
bot.command("diagnostics", async (ctx) => {
|
|
2069
|
-
const health = await getConnectorHealth();
|
|
2070
|
-
const authStatus = await checkAuthStatus(config.codexApiKey);
|
|
2183
|
+
const health = await getConnectorHealth({ piCliPath: config.piCliPath, hermesCliPath: config.hermesCliPath, openClawCliPath: config.openClawCliPath, claudeCodeCliPath: config.claudeCodeCliPath });
|
|
2071
2184
|
const contextKey = contextKeyFromCtx(ctx);
|
|
2072
2185
|
const queueLength = contextKey ? promptStore.list(contextKey).length : 0;
|
|
2073
2186
|
const progress = contextKey ? turnProgress.get(contextKey) : undefined;
|
|
2074
2187
|
const contextSession = contextKey ? await getContextSession(ctx, { deferThreadStart: true }) : null;
|
|
2075
|
-
const
|
|
2076
|
-
?
|
|
2077
|
-
:
|
|
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>" };
|
|
2078
2194
|
const runtime = {
|
|
2079
2195
|
rateLimit: getTelegramRateLimitMetrics(),
|
|
2080
2196
|
externalMirrors: externalMirrors.size,
|
|
@@ -2087,8 +2203,8 @@ export function createBot(config, registry) {
|
|
|
2087
2203
|
voiceLanguage: contextKey ? getEffectiveVoiceLanguage(contextKey) ?? "auto" : config.voiceDefaultLanguage ?? "auto",
|
|
2088
2204
|
voiceTranscribeOnly: contextKey ? isVoiceTranscribeOnly(contextKey) : config.voiceTranscribeOnly,
|
|
2089
2205
|
};
|
|
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${
|
|
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}`;
|
|
2092
2208
|
await safeReply(ctx, html, { fallbackText: plain });
|
|
2093
2209
|
});
|
|
2094
2210
|
bot.command("sync", async (ctx) => {
|
|
@@ -2103,7 +2219,7 @@ export function createBot(config, registry) {
|
|
|
2103
2219
|
await safeReply(ctx, html, { fallbackText: plain });
|
|
2104
2220
|
return;
|
|
2105
2221
|
}
|
|
2106
|
-
const result = contextSession.session.
|
|
2222
|
+
const result = contextSession.session.syncFromAgentState({ reattach: true });
|
|
2107
2223
|
if (result.changed) {
|
|
2108
2224
|
updateSessionMetadata(contextSession.contextKey, contextSession.session);
|
|
2109
2225
|
}
|
|
@@ -2230,8 +2346,14 @@ export function createBot(config, registry) {
|
|
|
2230
2346
|
if (!contextSession) {
|
|
2231
2347
|
return;
|
|
2232
2348
|
}
|
|
2233
|
-
const { session } = contextSession;
|
|
2349
|
+
const { contextKey, session } = contextSession;
|
|
2234
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
|
+
}
|
|
2235
2357
|
await session.abort();
|
|
2236
2358
|
await safeReply(ctx, escapeHTML("Aborted current operation"), {
|
|
2237
2359
|
fallbackText: "Aborted current operation",
|
|
@@ -2536,28 +2658,29 @@ export function createBot(config, registry) {
|
|
|
2536
2658
|
});
|
|
2537
2659
|
return;
|
|
2538
2660
|
}
|
|
2539
|
-
const
|
|
2540
|
-
const
|
|
2541
|
-
|
|
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),
|
|
2542
2665
|
callbackData: `launch_${index}`,
|
|
2543
2666
|
}));
|
|
2544
|
-
pendingLaunchPicks.set(contextKey,
|
|
2667
|
+
pendingLaunchPicks.set(contextKey, profiles.map((profile) => profile.id));
|
|
2545
2668
|
pendingLaunchButtons.set(contextKey, launchButtons);
|
|
2546
2669
|
pendingUnsafeLaunchConfirmations.delete(contextKey);
|
|
2547
2670
|
const keyboard = paginateKeyboard(launchButtons, 0, "launch");
|
|
2548
2671
|
const htmlLines = [
|
|
2549
|
-
`<b>Selected launch profile:</b> <code>${escapeHTML(selectedLaunchProfile.
|
|
2550
|
-
`<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>`,
|
|
2551
2674
|
"",
|
|
2552
2675
|
"Select a profile for new or reattached threads:",
|
|
2553
2676
|
];
|
|
2554
2677
|
const plainLines = [
|
|
2555
|
-
`Selected launch profile: ${selectedLaunchProfile.
|
|
2556
|
-
`Behavior: ${
|
|
2678
|
+
`Selected launch profile: ${selectedLaunchProfile.launchProfileLabel}`,
|
|
2679
|
+
`Behavior: ${selectedLaunchProfile.launchProfileBehavior}`,
|
|
2557
2680
|
"",
|
|
2558
2681
|
"Select a profile for new or reattached threads:",
|
|
2559
2682
|
];
|
|
2560
|
-
if (selectedLaunchProfile.
|
|
2683
|
+
if (selectedLaunchProfile.unsafeLaunch) {
|
|
2561
2684
|
htmlLines.splice(2, 0, "⚠️ <i>Selected profile uses danger-full-access.</i>");
|
|
2562
2685
|
plainLines.splice(2, 0, "⚠️ Selected profile uses danger-full-access.");
|
|
2563
2686
|
}
|
|
@@ -2897,6 +3020,10 @@ export function createBot(config, registry) {
|
|
|
2897
3020
|
});
|
|
2898
3021
|
return;
|
|
2899
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
|
+
});
|
|
2900
3027
|
const models = session.listModels();
|
|
2901
3028
|
if (models.length === 0) {
|
|
2902
3029
|
await safeReply(ctx, escapeHTML("No models available."), {
|
|
@@ -2906,7 +3033,7 @@ export function createBot(config, registry) {
|
|
|
2906
3033
|
}
|
|
2907
3034
|
const currentModel = session.getInfo().model ?? "(default)";
|
|
2908
3035
|
const modelButtons = models.map((model) => ({
|
|
2909
|
-
label:
|
|
3036
|
+
label: formatModelButtonLabel(model, model.slug === currentModel),
|
|
2910
3037
|
callbackData: `model_${model.slug}`,
|
|
2911
3038
|
}));
|
|
2912
3039
|
pendingModelButtons.set(contextKey, modelButtons);
|
|
@@ -2993,7 +3120,7 @@ export function createBot(config, registry) {
|
|
|
2993
3120
|
await safeReply(ctx, escapeHTML(text), { fallbackText: text });
|
|
2994
3121
|
return;
|
|
2995
3122
|
}
|
|
2996
|
-
const efforts = idOf(info)
|
|
3123
|
+
const efforts = agentReasoningOptions(idOf(info));
|
|
2997
3124
|
const current = info.reasoningEffort;
|
|
2998
3125
|
const effortButtons = efforts.map((effort) => ({
|
|
2999
3126
|
label: effort === current ? `${effort} ✓` : effort,
|
|
@@ -3011,7 +3138,7 @@ export function createBot(config, registry) {
|
|
|
3011
3138
|
});
|
|
3012
3139
|
};
|
|
3013
3140
|
bot.command(["effort", "reasoning"], openReasoningPicker);
|
|
3014
|
-
bot.callbackQuery(/^agent_(codex|pi)$/, async (ctx) => {
|
|
3141
|
+
bot.callbackQuery(/^agent_(codex|pi|hermes|openclaw|claude-code)$/, async (ctx) => {
|
|
3015
3142
|
const chatId = ctx.chat?.id;
|
|
3016
3143
|
const messageId = ctx.callbackQuery.message?.message_id;
|
|
3017
3144
|
const selectedAgent = ctx.match?.[1];
|
|
@@ -3365,7 +3492,7 @@ export function createBot(config, registry) {
|
|
|
3365
3492
|
await ctx.answerCallbackQuery({ text: "Wait for the current prompt to finish" });
|
|
3366
3493
|
return;
|
|
3367
3494
|
}
|
|
3368
|
-
const profile =
|
|
3495
|
+
const profile = session.listLaunchProfiles().find((candidate) => candidate.id === profileId);
|
|
3369
3496
|
if (!profile) {
|
|
3370
3497
|
clearLaunchSelectionState(contextKey);
|
|
3371
3498
|
await ctx.answerCallbackQuery({ text: "Launch profile no longer exists" });
|
|
@@ -3382,14 +3509,14 @@ export function createBot(config, registry) {
|
|
|
3382
3509
|
.text("Cancel", `launchconfirm_no:${profile.id}`);
|
|
3383
3510
|
const html = [
|
|
3384
3511
|
`<b>Confirm launch profile:</b> <code>${escapeHTML(profile.label)}</code>`,
|
|
3385
|
-
`<b>Behavior:</b> <code>${escapeHTML(
|
|
3512
|
+
`<b>Behavior:</b> <code>${escapeHTML(profile.behavior)}</code>`,
|
|
3386
3513
|
"",
|
|
3387
3514
|
"⚠️ <b>This profile uses danger-full-access.</b>",
|
|
3388
3515
|
"It will apply to new or reattached threads in this Telegram context.",
|
|
3389
3516
|
].join("\n");
|
|
3390
3517
|
const plain = [
|
|
3391
3518
|
`Confirm launch profile: ${profile.label}`,
|
|
3392
|
-
`Behavior: ${
|
|
3519
|
+
`Behavior: ${profile.behavior}`,
|
|
3393
3520
|
"",
|
|
3394
3521
|
"WARNING: This profile uses danger-full-access.",
|
|
3395
3522
|
"It will apply to new or reattached threads in this Telegram context.",
|
|
@@ -3410,17 +3537,18 @@ export function createBot(config, registry) {
|
|
|
3410
3537
|
}
|
|
3411
3538
|
await ctx.answerCallbackQuery({ text: `Launch set to ${profile.label}` });
|
|
3412
3539
|
clearLaunchSelectionState(contextKey);
|
|
3413
|
-
|
|
3540
|
+
session.setLaunchProfile(profile.id);
|
|
3414
3541
|
updateSessionMetadata(contextKey, session);
|
|
3542
|
+
const info = session.getInfo();
|
|
3415
3543
|
const html = [
|
|
3416
|
-
`<b>Launch profile set to</b> <code>${escapeHTML(
|
|
3417
|
-
`<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>`,
|
|
3418
3546
|
"",
|
|
3419
3547
|
"Applies to new or reattached threads.",
|
|
3420
3548
|
].join("\n");
|
|
3421
3549
|
const plain = [
|
|
3422
|
-
`Launch profile set to ${
|
|
3423
|
-
`Behavior: ${
|
|
3550
|
+
`Launch profile set to ${info.launchProfileLabel}`,
|
|
3551
|
+
`Behavior: ${info.launchProfileBehavior}`,
|
|
3424
3552
|
"",
|
|
3425
3553
|
"Applies to new or reattached threads.",
|
|
3426
3554
|
].join("\n");
|
|
@@ -3461,7 +3589,7 @@ export function createBot(config, registry) {
|
|
|
3461
3589
|
await ctx.answerCallbackQuery({ text: "Wait for the current prompt to finish" });
|
|
3462
3590
|
return;
|
|
3463
3591
|
}
|
|
3464
|
-
const profile =
|
|
3592
|
+
const profile = session.listLaunchProfiles().find((candidate) => candidate.id === profileId);
|
|
3465
3593
|
if (!profile) {
|
|
3466
3594
|
clearLaunchSelectionState(contextKey);
|
|
3467
3595
|
await ctx.answerCallbackQuery({ text: "Launch profile no longer exists" });
|
|
@@ -3471,18 +3599,19 @@ export function createBot(config, registry) {
|
|
|
3471
3599
|
return;
|
|
3472
3600
|
}
|
|
3473
3601
|
clearLaunchSelectionState(contextKey);
|
|
3474
|
-
|
|
3602
|
+
session.setLaunchProfile(profile.id);
|
|
3475
3603
|
updateSessionMetadata(contextKey, session);
|
|
3476
|
-
|
|
3604
|
+
const info = session.getInfo();
|
|
3605
|
+
await ctx.answerCallbackQuery({ text: `Launch set to ${info.launchProfileLabel}` });
|
|
3477
3606
|
const html = [
|
|
3478
|
-
`<b>Launch profile set to</b> <code>${escapeHTML(
|
|
3479
|
-
`<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>`,
|
|
3480
3609
|
"",
|
|
3481
3610
|
"⚠️ <i>danger-full-access confirmed for new or reattached threads.</i>",
|
|
3482
3611
|
].join("\n");
|
|
3483
3612
|
const plain = [
|
|
3484
|
-
`Launch profile set to ${
|
|
3485
|
-
`Behavior: ${
|
|
3613
|
+
`Launch profile set to ${info.launchProfileLabel}`,
|
|
3614
|
+
`Behavior: ${info.launchProfileBehavior}`,
|
|
3486
3615
|
"",
|
|
3487
3616
|
"danger-full-access confirmed for new or reattached threads.",
|
|
3488
3617
|
].join("\n");
|
|
@@ -3519,9 +3648,7 @@ export function createBot(config, registry) {
|
|
|
3519
3648
|
try {
|
|
3520
3649
|
const result = await session.setModelForCurrentSession(slug);
|
|
3521
3650
|
updateSessionMetadata(contextKey, session);
|
|
3522
|
-
const scope = result.appliedToActiveThread
|
|
3523
|
-
? "applied to the current idle thread and future threads"
|
|
3524
|
-
: "applies to new threads";
|
|
3651
|
+
const scope = formatAgentSettingScope(session.getInfo(), result.appliedToActiveThread);
|
|
3525
3652
|
const html = `<b>Model set to</b> <code>${escapeHTML(result.value)}</code> — ${escapeHTML(scope)}.`;
|
|
3526
3653
|
const plainText = `Model set to ${result.value} — ${scope}.`;
|
|
3527
3654
|
if (messageId) {
|
|
@@ -3542,7 +3669,7 @@ export function createBot(config, registry) {
|
|
|
3542
3669
|
}
|
|
3543
3670
|
}
|
|
3544
3671
|
});
|
|
3545
|
-
bot.callbackQuery(/^effort_(off|minimal|low|medium|high|xhigh)$/, async (ctx) => {
|
|
3672
|
+
bot.callbackQuery(/^effort_(off|none|minimal|low|medium|high|xhigh)$/, async (ctx) => {
|
|
3546
3673
|
const chatId = ctx.chat?.id;
|
|
3547
3674
|
const messageId = ctx.callbackQuery.message?.message_id;
|
|
3548
3675
|
const effort = ctx.match?.[1];
|
|
@@ -3568,9 +3695,7 @@ export function createBot(config, registry) {
|
|
|
3568
3695
|
const result = await session.setReasoningEffortForCurrentSession(effort);
|
|
3569
3696
|
updateSessionMetadata(contextKey, session);
|
|
3570
3697
|
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";
|
|
3698
|
+
const scope = formatAgentSettingScope(session.getInfo(), result.appliedToActiveThread);
|
|
3574
3699
|
const html = `⚡ ${escapeHTML(label)} set to <code>${escapeHTML(effort)}</code> — ${escapeHTML(scope)}.`;
|
|
3575
3700
|
await safeEditMessage(bot, chatId, messageId, html, {
|
|
3576
3701
|
fallbackText: `⚡ ${label} set to ${effort} — ${scope}.`,
|
|
@@ -3901,7 +4026,7 @@ export async function registerCommands(bot) {
|
|
|
3901
4026
|
{ command: "help", description: "Command reference" },
|
|
3902
4027
|
{ command: "channels", description: "Messaging adapter status" },
|
|
3903
4028
|
{ command: "agents", description: "Agent adapter status" },
|
|
3904
|
-
{ command: "agent", description: "Select
|
|
4029
|
+
{ command: "agent", description: "Select agent" },
|
|
3905
4030
|
{ command: "new", description: "Start a new thread" },
|
|
3906
4031
|
{ command: "session", description: "Current thread details" },
|
|
3907
4032
|
{ command: "sessions", description: "Browse & switch threads" },
|
|
@@ -4259,7 +4384,7 @@ function renderExternalMirrorStatus(snapshot, queueLength) {
|
|
|
4259
4384
|
? formatDurationSeconds((Date.now() - snapshot.activity.startedAt.getTime()) / 1000)
|
|
4260
4385
|
: "-";
|
|
4261
4386
|
const lines = [
|
|
4262
|
-
|
|
4387
|
+
`${snapshot.agentLabel} CLI task running.`,
|
|
4263
4388
|
`Thread: ${snapshot.threadId}`,
|
|
4264
4389
|
`Elapsed: ${elapsed}`,
|
|
4265
4390
|
`Prompt: ${prompt}`,
|
|
@@ -4269,7 +4394,7 @@ function renderExternalMirrorStatus(snapshot, queueLength) {
|
|
|
4269
4394
|
return {
|
|
4270
4395
|
plain: lines.join("\n"),
|
|
4271
4396
|
html: [
|
|
4272
|
-
|
|
4397
|
+
`<b>${escapeHTML(snapshot.agentLabel)} CLI task running.</b>`,
|
|
4273
4398
|
`<b>Thread:</b> <code>${escapeHTML(snapshot.threadId)}</code>`,
|
|
4274
4399
|
`<b>Elapsed:</b> <code>${escapeHTML(elapsed)}</code>`,
|
|
4275
4400
|
`<b>Prompt:</b> <code>${escapeHTML(prompt)}</code>`,
|
|
@@ -4302,8 +4427,8 @@ function renderExternalMirrorEvent(event) {
|
|
|
4302
4427
|
function renderActivityTimeline(threadId, events, options = { limit: 16, filter: "all", exportFile: false }) {
|
|
4303
4428
|
if (events.length === 0) {
|
|
4304
4429
|
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
|
|
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>`,
|
|
4307
4432
|
};
|
|
4308
4433
|
}
|
|
4309
4434
|
const lines = events.map((event) => {
|
|
@@ -4379,50 +4504,36 @@ function filterActivityEvents(events, options) {
|
|
|
4379
4504
|
function isActivityFilter(value) {
|
|
4380
4505
|
return value === "all" || value === "tools" || value === "errors" || value === "user" || value === "agent" || value === "tasks";
|
|
4381
4506
|
}
|
|
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
|
-
];
|
|
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) {
|
|
4416
4529
|
return {
|
|
4417
|
-
plain:
|
|
4530
|
+
plain: [
|
|
4531
|
+
`${diagnostics.agentLabel} state:`,
|
|
4532
|
+
...diagnostics.lines.map((line) => `${line.label}: ${line.value}`),
|
|
4533
|
+
].join("\n"),
|
|
4418
4534
|
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>`,
|
|
4535
|
+
`<b>${escapeHTML(diagnostics.agentLabel)} state:</b>`,
|
|
4536
|
+
...diagnostics.lines.map((line) => `<b>${escapeHTML(line.label)}:</b> <code>${escapeHTML(line.value)}</code>`),
|
|
4426
4537
|
].join("\n"),
|
|
4427
4538
|
};
|
|
4428
4539
|
}
|
|
@@ -4462,6 +4573,11 @@ function renderDiagnosticsPlain(config, registry, health, authenticated, role, q
|
|
|
4462
4573
|
`Telegram transport: ${config.telegramTransport}`,
|
|
4463
4574
|
`Codex CLI: ${health.codexCli}`,
|
|
4464
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}`,
|
|
4465
4581
|
`Enabled agents/default: ${enabledAgents(config).join(", ")} / ${config.defaultAgent}`,
|
|
4466
4582
|
`State DB: ${health.databasePath ?? "-"}`,
|
|
4467
4583
|
`Log file: ${health.logFile}`,
|
|
@@ -4502,6 +4618,11 @@ function renderDiagnosticsHTML(config, registry, health, authenticated, role, qu
|
|
|
4502
4618
|
`<b>Telegram transport:</b> <code>${escapeHTML(config.telegramTransport)}</code>`,
|
|
4503
4619
|
`<b>Codex CLI:</b> <code>${escapeHTML(health.codexCli)}</code>`,
|
|
4504
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>`,
|
|
4505
4626
|
`<b>Enabled agents/default:</b> <code>${escapeHTML(`${enabledAgents(config).join(", ")} / ${config.defaultAgent}`)}</code>`,
|
|
4506
4627
|
`<b>State DB:</b> <code>${escapeHTML(health.databasePath ?? "-")}</code>`,
|
|
4507
4628
|
`<b>Log file:</b> <code>${escapeHTML(health.logFile)}</code>`,
|
|
@@ -4539,6 +4660,9 @@ function renderHealthPlain(health, authenticated, role) {
|
|
|
4539
4660
|
`Workspace: ${health.state.workspace ?? "-"}`,
|
|
4540
4661
|
`Codex CLI: ${health.codexCli}`,
|
|
4541
4662
|
`Pi CLI: ${health.piCli}`,
|
|
4663
|
+
`Hermes CLI: ${health.hermesCli}`,
|
|
4664
|
+
`OpenClaw CLI: ${health.openClawCli}`,
|
|
4665
|
+
`Claude Code CLI: ${health.claudeCodeCli}`,
|
|
4542
4666
|
`Codex state DB: ${health.databasePath ?? "-"}`,
|
|
4543
4667
|
`Log: ${health.logFile}`,
|
|
4544
4668
|
].join("\n");
|
|
@@ -4555,6 +4679,9 @@ function renderHealthHTML(health, authenticated, role) {
|
|
|
4555
4679
|
`<b>Workspace:</b> <code>${escapeHTML(health.state.workspace ?? "-")}</code>`,
|
|
4556
4680
|
`<b>Codex CLI:</b> <code>${escapeHTML(health.codexCli)}</code>`,
|
|
4557
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>`,
|
|
4558
4685
|
`<b>Codex state DB:</b> <code>${escapeHTML(health.databasePath ?? "-")}</code>`,
|
|
4559
4686
|
`<b>Log:</b> <code>${escapeHTML(health.logFile)}</code>`,
|
|
4560
4687
|
].join("\n");
|
|
@@ -4622,6 +4749,48 @@ function labelOf(info) {
|
|
|
4622
4749
|
function idOf(info) {
|
|
4623
4750
|
return info.agentId ?? "codex";
|
|
4624
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
|
+
}
|
|
4625
4794
|
function requiresTurnApproval(info) {
|
|
4626
4795
|
return info.unsafeLaunch || info.approvalPolicy !== "never";
|
|
4627
4796
|
}
|