@nordbyte/nordrelay 0.5.2 → 0.7.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 +80 -11
- package/README.md +154 -22
- package/dist/access-control.js +7 -1
- package/dist/activity-events.js +44 -0
- package/dist/audit-log.js +40 -2
- package/dist/bot-preferences.js +1 -0
- package/dist/bot-rendering.js +10 -7
- package/dist/bot.js +535 -11
- package/dist/channel-actions.js +7 -2
- package/dist/channel-adapter.js +40 -7
- package/dist/channel-command-catalog.js +88 -0
- package/dist/channel-command-service.js +369 -0
- package/dist/channel-mirror-registry.js +77 -0
- package/dist/channel-peer-prompt.js +95 -0
- package/dist/channel-runtime.js +12 -5
- package/dist/channel-turn-service.js +237 -0
- package/dist/codex-state.js +114 -78
- package/dist/config-metadata.js +93 -13
- package/dist/config.js +103 -8
- package/dist/context-key.js +87 -5
- package/dist/discord-artifacts.js +165 -0
- package/dist/discord-bot.js +2073 -0
- package/dist/discord-channel-runtime.js +133 -0
- package/dist/discord-command-surface.js +57 -0
- package/dist/discord-rate-limit.js +141 -0
- package/dist/index.js +36 -5
- package/dist/job-store.js +127 -0
- package/dist/metrics.js +87 -0
- package/dist/peer-auth.js +85 -0
- package/dist/peer-client.js +256 -0
- package/dist/peer-context.js +21 -0
- package/dist/peer-identity.js +127 -0
- package/dist/peer-runtime-service.js +636 -0
- package/dist/peer-server.js +220 -0
- package/dist/peer-store.js +294 -0
- package/dist/peer-types.js +52 -0
- package/dist/relay-external-activity-monitor.js +47 -6
- package/dist/relay-runtime-helpers.js +208 -0
- package/dist/relay-runtime.js +897 -394
- package/dist/remote-prompt.js +98 -0
- package/dist/runtime-cache.js +57 -0
- package/dist/session-locks.js +10 -7
- package/dist/support-bundle.js +1 -0
- package/dist/telegram-access-commands.js +15 -2
- package/dist/telegram-access-middleware.js +16 -3
- package/dist/telegram-agent-commands.js +25 -0
- package/dist/telegram-artifact-commands.js +46 -0
- package/dist/telegram-command-menu.js +3 -53
- package/dist/telegram-diagnostics-command.js +5 -50
- package/dist/telegram-general-commands.js +16 -6
- package/dist/telegram-operational-commands.js +14 -6
- package/dist/telegram-preference-commands.js +23 -127
- package/dist/telegram-queue-commands.js +74 -4
- package/dist/telegram-support-command.js +7 -0
- package/dist/telegram-update-commands.js +27 -0
- package/dist/user-management.js +208 -0
- package/dist/web-api-contract.js +17 -0
- package/dist/web-dashboard-access-routes.js +74 -1
- package/dist/web-dashboard-artifact-routes.js +3 -3
- package/dist/web-dashboard-assets.js +2 -0
- package/dist/web-dashboard-pages.js +109 -13
- package/dist/web-dashboard-peer-routes.js +204 -0
- package/dist/web-dashboard-runtime-routes.js +53 -8
- package/dist/web-dashboard-session-routes.js +27 -20
- package/dist/web-dashboard-ui.js +2 -0
- package/dist/web-dashboard.js +160 -6
- package/dist/web-state.js +33 -2
- package/dist/webui-assets/dashboard.css +75 -1
- package/dist/webui-assets/dashboard.js +779 -55
- package/package.json +5 -2
- package/plugins/nordrelay/scripts/nordrelay.mjs +578 -19
package/dist/bot.js
CHANGED
|
@@ -11,6 +11,8 @@ import { AuditLogStore } from "./audit-log.js";
|
|
|
11
11
|
import { formatSessionLabel } from "./bot-ui.js";
|
|
12
12
|
import { BotPreferencesStore, isQuietNow, } from "./bot-preferences.js";
|
|
13
13
|
import { renderAgentUpdateJobAction } from "./channel-actions.js";
|
|
14
|
+
import { ChannelCommandService } from "./channel-command-service.js";
|
|
15
|
+
import { runChannelPeerPrompt } from "./channel-peer-prompt.js";
|
|
14
16
|
import { deliverChannelAction } from "./channel-runtime.js";
|
|
15
17
|
import { agentLabel, agentReasoningLabel, agentReasoningOptions, } from "./agent.js";
|
|
16
18
|
import { getExternalActivityForSession, getExternalSnapshotForSession, } from "./agent-activity.js";
|
|
@@ -23,6 +25,7 @@ import { escapeHTML } from "./format.js";
|
|
|
23
25
|
import { PromptStore, toPromptEnvelope } from "./prompt-store.js";
|
|
24
26
|
import { checkHermesAuthStatus, startHermesLogin, startHermesLogout } from "./hermes-auth.js";
|
|
25
27
|
import { checkOpenClawAuthStatus } from "./openclaw-auth.js";
|
|
28
|
+
import { RemoteRelayClient } from "./peer-client.js";
|
|
26
29
|
import { checkPiAuthStatus } from "./pi-auth.js";
|
|
27
30
|
import { configureRedaction, redactText } from "./redaction.js";
|
|
28
31
|
import { canWriteWithLock, SessionLockStore } from "./session-locks.js";
|
|
@@ -45,6 +48,7 @@ import { registerTelegramSupportCommands } from "./telegram-support-command.js";
|
|
|
45
48
|
import { registerTelegramUpdateCommands } from "./telegram-update-commands.js";
|
|
46
49
|
import { appendWithCap, authHelpText, buildStreamingPreview, capabilitiesOf, filterSessions, formatAgentLaunchProfileLabel, formatAgentSettingScope, formatDurationSeconds, formatError, formatLocalDateTime, formatLockOwner, formatModelButtonLabel, formatRelativeTime, formatTelegramName, formatToolSummaryLine, formatTurnUsageLine, getWorkspaceShortName, idOf, isEmptyArtifactReport, isPromptEnvelopeLike, isQueuedPromptLike, labelOf, orderPinnedSessions, parseFastModeArgument, renderExternalMirrorEvent, renderExternalMirrorStatus, renderPromptFailure, renderTodoList, renderToolEndMessage, renderToolStartMessage, requiresTurnApproval, trimLine, } from "./bot-rendering.js";
|
|
47
50
|
import { UserStore } from "./user-management.js";
|
|
51
|
+
import { WebActivityStore } from "./web-state.js";
|
|
48
52
|
import { evaluateWorkspacePolicy, filterAllowedWorkspaces, renderWorkspacePolicyLine, } from "./workspace-policy.js";
|
|
49
53
|
export { formatToolSummaryLine, formatTurnUsageLine, summarizeToolName } from "./bot-rendering.js";
|
|
50
54
|
export { registerCommands } from "./telegram-command-menu.js";
|
|
@@ -54,6 +58,10 @@ const TOOL_OUTPUT_PREVIEW_LIMIT = 500;
|
|
|
54
58
|
const MAX_AUDIO_FILE_SIZE = 25 * 1024 * 1024;
|
|
55
59
|
const MEDIA_GROUP_FLUSH_MS = 1200;
|
|
56
60
|
const LAUNCH_PROFILES_COMMAND = "/launch_profiles";
|
|
61
|
+
const CLI_ACTIVITY_ACTOR = {
|
|
62
|
+
channel: "cli",
|
|
63
|
+
label: "CLI",
|
|
64
|
+
};
|
|
57
65
|
export function createBot(config, registry) {
|
|
58
66
|
configureRedaction(config.telegramRedactPatterns);
|
|
59
67
|
telegramRateLimiter.configure({
|
|
@@ -80,11 +88,17 @@ export function createBot(config, registry) {
|
|
|
80
88
|
const turnProgress = new Map();
|
|
81
89
|
const promptStore = new PromptStore(config.workspace, config.stateBackend);
|
|
82
90
|
const preferencesStore = new BotPreferencesStore(config.workspace, config.stateBackend);
|
|
91
|
+
const activityStore = new WebActivityStore(config.workspace, config.stateBackend, config.auditMaxEvents);
|
|
83
92
|
const auditLog = new AuditLogStore(config.workspace, config.stateBackend, config.auditMaxEvents);
|
|
84
93
|
const lockStore = new SessionLockStore(config.workspace, config.stateBackend);
|
|
85
94
|
const userStore = new UserStore();
|
|
86
95
|
const contextUsers = new WeakMap();
|
|
87
|
-
const
|
|
96
|
+
const agentUpdateActors = new Map();
|
|
97
|
+
const agentUpdateStates = new Map();
|
|
98
|
+
const commandService = new ChannelCommandService(config);
|
|
99
|
+
const agentUpdates = new AgentUpdateManager({
|
|
100
|
+
onUpdate: (job) => recordTelegramAgentUpdateLifecycle(job),
|
|
101
|
+
});
|
|
88
102
|
const linkAttempts = new Map();
|
|
89
103
|
const drainingQueues = new Set();
|
|
90
104
|
const externalQueueTimers = new Map();
|
|
@@ -228,6 +242,19 @@ export function createBot(config, registry) {
|
|
|
228
242
|
const startTelegramAgentUpdate = async (ctx, agentId, operation = "update") => {
|
|
229
243
|
try {
|
|
230
244
|
const job = agentUpdates.start(agentId, agentUpdateContext(), operation);
|
|
245
|
+
const actor = telegramActivityActor(ctx);
|
|
246
|
+
agentUpdateActors.set(job.id, actor);
|
|
247
|
+
agentUpdateStates.set(job.id, { status: job.status, needsInput: job.needsInput });
|
|
248
|
+
appendActivity({
|
|
249
|
+
source: "telegram",
|
|
250
|
+
status: "info",
|
|
251
|
+
type: operation === "install" ? "agent_install_started" : "agent_update_started",
|
|
252
|
+
threadId: null,
|
|
253
|
+
workspace: config.workspace,
|
|
254
|
+
agentId,
|
|
255
|
+
actor,
|
|
256
|
+
detail: `${job.method}: ${job.summary}`,
|
|
257
|
+
});
|
|
231
258
|
const contextKey = contextKeyFromCtx(ctx);
|
|
232
259
|
if (contextKey) {
|
|
233
260
|
audit({
|
|
@@ -235,6 +262,9 @@ export function createBot(config, registry) {
|
|
|
235
262
|
status: "ok",
|
|
236
263
|
contextKey,
|
|
237
264
|
agentId,
|
|
265
|
+
actor,
|
|
266
|
+
actorId: getAuthenticatedUser(ctx)?.user.id ?? ctx.from?.id,
|
|
267
|
+
actorRole: getUserRole(ctx),
|
|
238
268
|
description: `${operation} ${agentId}`,
|
|
239
269
|
detail: job.summary,
|
|
240
270
|
});
|
|
@@ -495,6 +525,26 @@ export function createBot(config, registry) {
|
|
|
495
525
|
if (snapshot.activity.active) {
|
|
496
526
|
state.turnId = snapshot.activity.turnId;
|
|
497
527
|
state.startedAt = snapshot.activity.startedAt;
|
|
528
|
+
const turnKey = snapshot.activity.turnId ?? snapshot.activity.startedAt?.toISOString() ?? "unknown";
|
|
529
|
+
if (state.activityStartedTurnKey !== turnKey) {
|
|
530
|
+
const info = session.getInfo();
|
|
531
|
+
appendActivity({
|
|
532
|
+
source: "cli",
|
|
533
|
+
status: "running",
|
|
534
|
+
type: "cli_turn_started",
|
|
535
|
+
contextKey,
|
|
536
|
+
threadId: snapshot.threadId,
|
|
537
|
+
workspace: info.workspace,
|
|
538
|
+
agentId: info.agentId,
|
|
539
|
+
actor: CLI_ACTIVITY_ACTOR,
|
|
540
|
+
prompt: snapshot.latestUserMessage ?? `${snapshot.agentLabel} CLI task`,
|
|
541
|
+
detail: `${snapshot.sourceLabel}: ${snapshot.sourcePath}`,
|
|
542
|
+
});
|
|
543
|
+
state.activityStartedTurnKey = turnKey;
|
|
544
|
+
state.activityFinishedTurnKey = undefined;
|
|
545
|
+
state.activityToolStartLines = [];
|
|
546
|
+
state.activityToolEndLines = [];
|
|
547
|
+
}
|
|
498
548
|
if (mirrorMode !== "off") {
|
|
499
549
|
await sendExternalMirrorTyping(chatId, parsed.messageThreadId, state);
|
|
500
550
|
}
|
|
@@ -542,6 +592,43 @@ export function createBot(config, registry) {
|
|
|
542
592
|
state.latestMirroredEventLine = event.lineNumber;
|
|
543
593
|
}
|
|
544
594
|
}
|
|
595
|
+
const info = session.getInfo();
|
|
596
|
+
const loggedStartLines = new Set(state.activityToolStartLines ?? []);
|
|
597
|
+
const loggedEndLines = new Set(state.activityToolEndLines ?? []);
|
|
598
|
+
for (const event of snapshot.events.filter((event) => event.lineNumber > state.lastLine && event.kind === "tool")) {
|
|
599
|
+
if (event.status === "started" && !loggedStartLines.has(event.lineNumber)) {
|
|
600
|
+
appendActivity({
|
|
601
|
+
source: "cli",
|
|
602
|
+
status: "running",
|
|
603
|
+
type: "cli_tool_started",
|
|
604
|
+
contextKey,
|
|
605
|
+
threadId: snapshot.threadId,
|
|
606
|
+
workspace: info.workspace,
|
|
607
|
+
agentId: info.agentId,
|
|
608
|
+
actor: CLI_ACTIVITY_ACTOR,
|
|
609
|
+
prompt: snapshot.latestUserMessage ?? undefined,
|
|
610
|
+
detail: event.toolName ?? "tool",
|
|
611
|
+
});
|
|
612
|
+
loggedStartLines.add(event.lineNumber);
|
|
613
|
+
}
|
|
614
|
+
if ((event.status === "finished" || event.status === "failed") && !loggedEndLines.has(event.lineNumber)) {
|
|
615
|
+
appendActivity({
|
|
616
|
+
source: "cli",
|
|
617
|
+
status: event.status === "failed" ? "failed" : "completed",
|
|
618
|
+
type: event.status === "failed" ? "cli_tool_failed" : "cli_tool_completed",
|
|
619
|
+
contextKey,
|
|
620
|
+
threadId: snapshot.threadId,
|
|
621
|
+
workspace: info.workspace,
|
|
622
|
+
agentId: info.agentId,
|
|
623
|
+
actor: CLI_ACTIVITY_ACTOR,
|
|
624
|
+
prompt: snapshot.latestUserMessage ?? undefined,
|
|
625
|
+
detail: event.toolName ?? "tool",
|
|
626
|
+
});
|
|
627
|
+
loggedEndLines.add(event.lineNumber);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
state.activityToolStartLines = [...loggedStartLines].slice(-200);
|
|
631
|
+
state.activityToolEndLines = [...loggedEndLines].slice(-200);
|
|
545
632
|
state.lastLine = Math.max(state.lastLine, snapshot.lineCount);
|
|
546
633
|
return;
|
|
547
634
|
}
|
|
@@ -551,6 +638,25 @@ export function createBot(config, registry) {
|
|
|
551
638
|
}
|
|
552
639
|
const terminalEvent = [...snapshot.events].reverse().find((event) => event.kind === "task" && event.status && event.status !== "started");
|
|
553
640
|
if (terminalEvent) {
|
|
641
|
+
const turnKey = terminalEvent.turnId ?? snapshot.activity.turnId ?? state.startedAt?.toString() ?? "unknown";
|
|
642
|
+
if (state.activityFinishedTurnKey !== turnKey) {
|
|
643
|
+
const info = session.getInfo();
|
|
644
|
+
const startedAt = state.startedAt instanceof Date ? state.startedAt : state.startedAt ? new Date(state.startedAt) : snapshot.activity.startedAt;
|
|
645
|
+
appendActivity({
|
|
646
|
+
source: "cli",
|
|
647
|
+
status: terminalEvent.status === "aborted" ? "aborted" : terminalEvent.status === "failed" ? "failed" : "completed",
|
|
648
|
+
type: "cli_turn_finished",
|
|
649
|
+
contextKey,
|
|
650
|
+
threadId: snapshot.threadId,
|
|
651
|
+
workspace: info.workspace,
|
|
652
|
+
agentId: info.agentId,
|
|
653
|
+
actor: CLI_ACTIVITY_ACTOR,
|
|
654
|
+
prompt: snapshot.latestUserMessage ?? undefined,
|
|
655
|
+
detail: `${snapshot.agentLabel} CLI task ${terminalEvent.status ?? "finished"}.`,
|
|
656
|
+
durationMs: startedAt && terminalEvent.timestamp ? Math.max(0, terminalEvent.timestamp.getTime() - startedAt.getTime()) : undefined,
|
|
657
|
+
});
|
|
658
|
+
state.activityFinishedTurnKey = turnKey;
|
|
659
|
+
}
|
|
554
660
|
if (mirrorMode !== "off") {
|
|
555
661
|
const doneText = `${snapshot.agentLabel} CLI task ${terminalEvent.status}.`;
|
|
556
662
|
if (state.statusMessageId) {
|
|
@@ -637,6 +743,18 @@ export function createBot(config, registry) {
|
|
|
637
743
|
for (const artifact of (persistedReport?.artifacts ?? report.artifacts)) {
|
|
638
744
|
await sendArtifactFileByApi(bot.api, chatId, artifact, messageThreadId);
|
|
639
745
|
}
|
|
746
|
+
const info = session.getInfo();
|
|
747
|
+
appendActivity({
|
|
748
|
+
source: "cli",
|
|
749
|
+
status: "info",
|
|
750
|
+
type: "artifacts_sent",
|
|
751
|
+
contextKey,
|
|
752
|
+
threadId: info.threadId,
|
|
753
|
+
workspace: info.workspace,
|
|
754
|
+
agentId: info.agentId,
|
|
755
|
+
actor: CLI_ACTIVITY_ACTOR,
|
|
756
|
+
detail: summary,
|
|
757
|
+
});
|
|
640
758
|
if (state)
|
|
641
759
|
state.artifactsDeliveredForTurnId = turnId;
|
|
642
760
|
};
|
|
@@ -688,9 +806,11 @@ export function createBot(config, registry) {
|
|
|
688
806
|
};
|
|
689
807
|
const auditContext = (ctx, contextKey, session, patch) => {
|
|
690
808
|
const info = session.getInfo();
|
|
809
|
+
const authUser = getAuthenticatedUser(ctx);
|
|
691
810
|
audit({
|
|
692
811
|
contextKey,
|
|
693
|
-
|
|
812
|
+
actor: telegramActivityActor(ctx),
|
|
813
|
+
actorId: authUser?.user.id ?? ctx.from?.id,
|
|
694
814
|
actorRole: getUserRole(ctx),
|
|
695
815
|
agentId: idOf(info),
|
|
696
816
|
threadId: info.threadId,
|
|
@@ -698,10 +818,68 @@ export function createBot(config, registry) {
|
|
|
698
818
|
...patch,
|
|
699
819
|
});
|
|
700
820
|
};
|
|
821
|
+
function telegramActivityActor(ctx) {
|
|
822
|
+
const user = ctx.from;
|
|
823
|
+
const authUser = getAuthenticatedUser(ctx);
|
|
824
|
+
const label = authUser?.user.displayName || formatTelegramName(ctx) || user?.username || (user?.id ? String(user.id) : "Telegram user");
|
|
825
|
+
return {
|
|
826
|
+
channel: "telegram",
|
|
827
|
+
id: authUser?.user.id ?? (user?.id !== undefined ? `telegram:${user.id}` : undefined),
|
|
828
|
+
label,
|
|
829
|
+
username: authUser?.user.email ?? user?.username,
|
|
830
|
+
channelUserId: user?.id !== undefined ? String(user.id) : undefined,
|
|
831
|
+
};
|
|
832
|
+
}
|
|
833
|
+
function appendActivity(input) {
|
|
834
|
+
return activityStore.append(input);
|
|
835
|
+
}
|
|
836
|
+
function appendTelegramActivity(ctx, contextKey, session, input) {
|
|
837
|
+
const info = session.getInfo();
|
|
838
|
+
return appendActivity({
|
|
839
|
+
source: "telegram",
|
|
840
|
+
contextKey,
|
|
841
|
+
...input,
|
|
842
|
+
threadId: input.threadId ?? info.threadId,
|
|
843
|
+
workspace: input.workspace ?? info.workspace,
|
|
844
|
+
agentId: input.agentId ?? idOf(info),
|
|
845
|
+
actor: input.actor ?? telegramActivityActor(ctx),
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
function recordTelegramAgentUpdateLifecycle(job) {
|
|
849
|
+
const previous = agentUpdateStates.get(job.id);
|
|
850
|
+
const actor = agentUpdateActors.get(job.id);
|
|
851
|
+
if (job.needsInput && !previous?.needsInput) {
|
|
852
|
+
appendActivity({
|
|
853
|
+
source: "telegram",
|
|
854
|
+
status: "info",
|
|
855
|
+
type: "agent_update_input_required",
|
|
856
|
+
threadId: null,
|
|
857
|
+
workspace: config.workspace,
|
|
858
|
+
agentId: job.agentId,
|
|
859
|
+
actor,
|
|
860
|
+
detail: `${job.agentLabel} ${job.operation} may require input.`,
|
|
861
|
+
});
|
|
862
|
+
}
|
|
863
|
+
if (job.status !== "running" && previous?.status === "running") {
|
|
864
|
+
appendActivity({
|
|
865
|
+
source: "telegram",
|
|
866
|
+
status: job.status === "completed" ? "completed" : job.status === "cancelled" ? "aborted" : "failed",
|
|
867
|
+
type: job.operation === "install" ? `agent_install_${job.status}` : `agent_update_${job.status}`,
|
|
868
|
+
threadId: null,
|
|
869
|
+
workspace: config.workspace,
|
|
870
|
+
agentId: job.agentId,
|
|
871
|
+
actor,
|
|
872
|
+
detail: job.error ?? `${job.agentLabel} ${job.operation} ${job.status}.`,
|
|
873
|
+
durationMs: Math.max(0, Date.parse(job.finishedAt ?? job.updatedAt) - Date.parse(job.startedAt)),
|
|
874
|
+
});
|
|
875
|
+
agentUpdateActors.delete(job.id);
|
|
876
|
+
}
|
|
877
|
+
agentUpdateStates.set(job.id, { status: job.status, needsInput: job.needsInput });
|
|
878
|
+
}
|
|
701
879
|
const denyIfLocked = async (ctx, contextKey, session) => {
|
|
702
880
|
const lock = lockStore.get(contextKey);
|
|
703
881
|
const isAdmin = isAdminUser(ctx);
|
|
704
|
-
if (canWriteWithLock(lock, ctx.
|
|
882
|
+
if (canWriteWithLock(lock, getAuthenticatedUser(ctx)?.user.id, isAdmin)) {
|
|
705
883
|
return false;
|
|
706
884
|
}
|
|
707
885
|
const owner = formatLockOwner(lock);
|
|
@@ -711,6 +889,11 @@ export function createBot(config, registry) {
|
|
|
711
889
|
status: "denied",
|
|
712
890
|
detail: text,
|
|
713
891
|
});
|
|
892
|
+
appendTelegramActivity(ctx, contextKey, session, {
|
|
893
|
+
status: "failed",
|
|
894
|
+
type: "lock_denied",
|
|
895
|
+
detail: text,
|
|
896
|
+
});
|
|
714
897
|
await safeReply(ctx, escapeHTML(text), { fallbackText: text });
|
|
715
898
|
return true;
|
|
716
899
|
};
|
|
@@ -803,13 +986,90 @@ export function createBot(config, registry) {
|
|
|
803
986
|
].join("\n");
|
|
804
987
|
await safeReply(ctx, html, { fallbackText: plain, replyMarkup: keyboard });
|
|
805
988
|
};
|
|
989
|
+
const remoteClient = new RemoteRelayClient();
|
|
990
|
+
const handleRemoteUserPrompt = async (ctx, contextKey, chatId, prompt) => {
|
|
991
|
+
const targetPeerId = preferencesStore.get(contextKey).targetPeerId ?? undefined;
|
|
992
|
+
const parsed = parseContextKey(contextKey);
|
|
993
|
+
const messageThreadId = parsed.messageThreadId;
|
|
994
|
+
return runChannelPeerPrompt({
|
|
995
|
+
targetPeerId,
|
|
996
|
+
contextKey,
|
|
997
|
+
prompt,
|
|
998
|
+
remoteClient,
|
|
999
|
+
editMinIntervalMs: config.telegramEditMinIntervalMs,
|
|
1000
|
+
typingIntervalMs: TYPING_INTERVAL_MS,
|
|
1001
|
+
sendTyping: () => sendChatActionSafe(ctx.api, chatId, "typing", messageThreadId),
|
|
1002
|
+
sendResponse: async (text) => {
|
|
1003
|
+
const message = await sendTextMessage(ctx.api, chatId, escapeHTML(text), {
|
|
1004
|
+
fallbackText: text,
|
|
1005
|
+
messageThreadId,
|
|
1006
|
+
});
|
|
1007
|
+
return message.message_id;
|
|
1008
|
+
},
|
|
1009
|
+
editResponse: (messageId, text) => safeEditMessage(bot, chatId, messageId, escapeHTML(text), {
|
|
1010
|
+
fallbackText: text,
|
|
1011
|
+
}),
|
|
1012
|
+
sendTurnStart: (remotePrompt) => safeReply(ctx, `<b>Remote peer working on:</b>\n${escapeHTML(remotePrompt)}`, {
|
|
1013
|
+
fallbackText: `Remote peer working on:\n${remotePrompt}`,
|
|
1014
|
+
}),
|
|
1015
|
+
sendToolStart: (toolName) => safeReply(ctx, `<b>Remote tool:</b> <code>${escapeHTML(toolName)}</code>`, {
|
|
1016
|
+
fallbackText: `Remote tool: ${toolName}`,
|
|
1017
|
+
}),
|
|
1018
|
+
sendQueued: async (queueId) => {
|
|
1019
|
+
const keyboard = queueId ? new InlineKeyboard().text("Cancel queued message", `peer_queue_cancel:${targetPeerId}:${queueId}`) : undefined;
|
|
1020
|
+
await safeReply(ctx, escapeHTML(`Remote prompt queued${queueId ? `: ${queueId}` : ""}.`), {
|
|
1021
|
+
fallbackText: `Remote prompt queued${queueId ? `: ${queueId}` : ""}.`,
|
|
1022
|
+
replyMarkup: keyboard,
|
|
1023
|
+
});
|
|
1024
|
+
},
|
|
1025
|
+
sendCompleted: () => safeReply(ctx, escapeHTML("Remote turn completed."), { fallbackText: "Remote turn completed." }),
|
|
1026
|
+
sendFailure: (message) => safeReply(ctx, escapeHTML(`Remote peer failed: ${message}`), {
|
|
1027
|
+
fallbackText: `Remote peer failed: ${message}`,
|
|
1028
|
+
}),
|
|
1029
|
+
});
|
|
1030
|
+
};
|
|
1031
|
+
bot.callbackQuery(/^peer_queue_cancel:([^:]+):([a-z0-9]+)$/, async (ctx) => {
|
|
1032
|
+
const targetPeerId = ctx.match?.[1];
|
|
1033
|
+
const queueId = ctx.match?.[2];
|
|
1034
|
+
const contextKey = contextKeyFromCtx(ctx);
|
|
1035
|
+
if (!targetPeerId || !queueId || !contextKey) {
|
|
1036
|
+
await ctx.answerCallbackQuery();
|
|
1037
|
+
return;
|
|
1038
|
+
}
|
|
1039
|
+
try {
|
|
1040
|
+
await remoteClient.webProxy(targetPeerId, {
|
|
1041
|
+
method: "POST",
|
|
1042
|
+
path: "/api/queue",
|
|
1043
|
+
body: { action: "cancel", id: queueId },
|
|
1044
|
+
contextKey,
|
|
1045
|
+
}, telegramActivityActor(ctx), contextKey);
|
|
1046
|
+
await ctx.answerCallbackQuery({ text: `Cancelled remote queued prompt ${queueId}.` });
|
|
1047
|
+
const chatId = ctx.chat?.id;
|
|
1048
|
+
const messageId = ctx.callbackQuery.message?.message_id;
|
|
1049
|
+
if (chatId && messageId) {
|
|
1050
|
+
await safeEditMessage(bot, chatId, messageId, escapeHTML(`Cancelled remote queued prompt ${queueId}.`), {
|
|
1051
|
+
fallbackText: `Cancelled remote queued prompt ${queueId}.`,
|
|
1052
|
+
});
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
catch (error) {
|
|
1056
|
+
await ctx.answerCallbackQuery({ text: friendlyErrorText(error), show_alert: true });
|
|
1057
|
+
}
|
|
1058
|
+
});
|
|
806
1059
|
const handleUserPrompt = async (ctx, contextKey, chatId, session, prompt, options = {}) => {
|
|
807
1060
|
if (!canSendSystemMessagesToContext(contextKey)) {
|
|
808
1061
|
return;
|
|
809
1062
|
}
|
|
810
1063
|
const parsed = parseContextKey(contextKey);
|
|
811
1064
|
const messageThreadId = parsed.messageThreadId;
|
|
812
|
-
const
|
|
1065
|
+
const rawEnvelope = isPromptEnvelopeLike(prompt) ? prompt : toPromptEnvelope(prompt);
|
|
1066
|
+
const envelope = {
|
|
1067
|
+
...rawEnvelope,
|
|
1068
|
+
activityActor: rawEnvelope.activityActor ?? telegramActivityActor(ctx),
|
|
1069
|
+
};
|
|
1070
|
+
if (!options.fromQueue && await handleRemoteUserPrompt(ctx, contextKey, chatId, envelope)) {
|
|
1071
|
+
return;
|
|
1072
|
+
}
|
|
813
1073
|
if (!options.fromQueue && await denyIfLocked(ctx, contextKey, session)) {
|
|
814
1074
|
return;
|
|
815
1075
|
}
|
|
@@ -840,6 +1100,13 @@ export function createBot(config, registry) {
|
|
|
840
1100
|
description: item.description,
|
|
841
1101
|
detail: busy.kind,
|
|
842
1102
|
});
|
|
1103
|
+
appendTelegramActivity(ctx, contextKey, session, {
|
|
1104
|
+
status: "queued",
|
|
1105
|
+
type: "prompt_queued",
|
|
1106
|
+
prompt: item.description,
|
|
1107
|
+
detail: `Queued prompt ${item.id} at position ${position}; busy=${busy.kind}`,
|
|
1108
|
+
actor: envelope.activityActor,
|
|
1109
|
+
});
|
|
843
1110
|
if (busy.kind === "external") {
|
|
844
1111
|
scheduleExternalQueueDrain(ctx, contextKey, chatId, session);
|
|
845
1112
|
}
|
|
@@ -877,6 +1144,9 @@ export function createBot(config, registry) {
|
|
|
877
1144
|
let lastRenderedPlan = "";
|
|
878
1145
|
let planMessageSending = false;
|
|
879
1146
|
let lastTurnUsage;
|
|
1147
|
+
let promptStartedAt;
|
|
1148
|
+
const toolActivityNames = new Map();
|
|
1149
|
+
const toolActivityStartedAt = new Map();
|
|
880
1150
|
const typingInterval = setInterval(() => {
|
|
881
1151
|
void sendChatActionSafe(bot.api, chatId, "typing", messageThreadId).catch(() => { });
|
|
882
1152
|
}, TYPING_INTERVAL_MS);
|
|
@@ -1080,6 +1350,15 @@ export function createBot(config, registry) {
|
|
|
1080
1350
|
progress.lastTool = toolName;
|
|
1081
1351
|
progress.updatedAt = Date.now();
|
|
1082
1352
|
progress.toolCounts.set(toolName, (progress.toolCounts.get(toolName) ?? 0) + 1);
|
|
1353
|
+
toolActivityNames.set(toolCallId, toolName);
|
|
1354
|
+
toolActivityStartedAt.set(toolCallId, Date.now());
|
|
1355
|
+
appendTelegramActivity(ctx, contextKey, session, {
|
|
1356
|
+
status: "running",
|
|
1357
|
+
type: "tool_started",
|
|
1358
|
+
prompt: envelope.description,
|
|
1359
|
+
detail: toolName,
|
|
1360
|
+
actor: envelope.activityActor,
|
|
1361
|
+
});
|
|
1083
1362
|
if (toolVerbosity === "summary") {
|
|
1084
1363
|
toolCounts.set(toolName, (toolCounts.get(toolName) ?? 0) + 1);
|
|
1085
1364
|
return;
|
|
@@ -1127,6 +1406,18 @@ export function createBot(config, registry) {
|
|
|
1127
1406
|
onToolEnd: (toolCallId, isError) => {
|
|
1128
1407
|
progress.currentTool = undefined;
|
|
1129
1408
|
progress.updatedAt = Date.now();
|
|
1409
|
+
const activityToolName = toolActivityNames.get(toolCallId) ?? "tool";
|
|
1410
|
+
const activityStartedAt = toolActivityStartedAt.get(toolCallId);
|
|
1411
|
+
appendTelegramActivity(ctx, contextKey, session, {
|
|
1412
|
+
status: isError ? "failed" : "completed",
|
|
1413
|
+
type: isError ? "tool_failed" : "tool_completed",
|
|
1414
|
+
prompt: envelope.description,
|
|
1415
|
+
detail: activityToolName,
|
|
1416
|
+
actor: envelope.activityActor,
|
|
1417
|
+
durationMs: activityStartedAt ? Date.now() - activityStartedAt : undefined,
|
|
1418
|
+
});
|
|
1419
|
+
toolActivityNames.delete(toolCallId);
|
|
1420
|
+
toolActivityStartedAt.delete(toolCallId);
|
|
1130
1421
|
if (toolVerbosity === "none" || toolVerbosity === "summary") {
|
|
1131
1422
|
return;
|
|
1132
1423
|
}
|
|
@@ -1266,11 +1557,25 @@ export function createBot(config, registry) {
|
|
|
1266
1557
|
replyMarkup: createQueuedPromptCancelKeyboard(contextKey, item.id),
|
|
1267
1558
|
});
|
|
1268
1559
|
await updateQueueStatusMessage(contextKey, `Waiting for ${label} CLI task... ${promptStore.list(contextKey).length} queued.`);
|
|
1560
|
+
appendTelegramActivity(ctx, contextKey, session, {
|
|
1561
|
+
status: "queued",
|
|
1562
|
+
type: "prompt_queued",
|
|
1563
|
+
prompt: item.description,
|
|
1564
|
+
detail: `Queued prompt ${item.id} at position 1; external ${label} CLI task active`,
|
|
1565
|
+
actor: envelope.activityActor,
|
|
1566
|
+
});
|
|
1269
1567
|
scheduleExternalQueueDrain(ctx, contextKey, chatId, session);
|
|
1270
1568
|
turnProgress.delete(contextKey);
|
|
1271
1569
|
return;
|
|
1272
1570
|
}
|
|
1273
1571
|
promptStore.setLastPrompt(contextKey, envelope);
|
|
1572
|
+
promptStartedAt = Date.now();
|
|
1573
|
+
appendTelegramActivity(ctx, contextKey, session, {
|
|
1574
|
+
status: "running",
|
|
1575
|
+
type: "prompt_started",
|
|
1576
|
+
prompt: envelope.description,
|
|
1577
|
+
actor: envelope.activityActor,
|
|
1578
|
+
});
|
|
1274
1579
|
auditContext(ctx, contextKey, session, {
|
|
1275
1580
|
action: "prompt_started",
|
|
1276
1581
|
status: "ok",
|
|
@@ -1300,6 +1605,13 @@ export function createBot(config, registry) {
|
|
|
1300
1605
|
status: "ok",
|
|
1301
1606
|
description: envelope.description,
|
|
1302
1607
|
});
|
|
1608
|
+
appendTelegramActivity(ctx, contextKey, session, {
|
|
1609
|
+
status: "completed",
|
|
1610
|
+
type: "prompt_completed",
|
|
1611
|
+
prompt: envelope.description,
|
|
1612
|
+
actor: envelope.activityActor,
|
|
1613
|
+
durationMs: promptStartedAt ? Date.now() - promptStartedAt : undefined,
|
|
1614
|
+
});
|
|
1303
1615
|
}
|
|
1304
1616
|
catch (error) {
|
|
1305
1617
|
progress.status = "failed";
|
|
@@ -1310,6 +1622,16 @@ export function createBot(config, registry) {
|
|
|
1310
1622
|
description: envelope.description,
|
|
1311
1623
|
detail: progress.error,
|
|
1312
1624
|
});
|
|
1625
|
+
if (promptStartedAt) {
|
|
1626
|
+
appendTelegramActivity(ctx, contextKey, session, {
|
|
1627
|
+
status: "failed",
|
|
1628
|
+
type: "prompt_failed",
|
|
1629
|
+
prompt: envelope.description,
|
|
1630
|
+
detail: progress.error,
|
|
1631
|
+
actor: envelope.activityActor,
|
|
1632
|
+
durationMs: Date.now() - promptStartedAt,
|
|
1633
|
+
});
|
|
1634
|
+
}
|
|
1313
1635
|
progress.completedAt = Date.now();
|
|
1314
1636
|
progress.updatedAt = progress.completedAt;
|
|
1315
1637
|
stopTyping();
|
|
@@ -1400,6 +1722,15 @@ export function createBot(config, registry) {
|
|
|
1400
1722
|
source: "turn",
|
|
1401
1723
|
};
|
|
1402
1724
|
await deliverArtifactReport(ctx, chatId, report, messageThreadId);
|
|
1725
|
+
const contextKey = contextKeyFromCtx(ctx);
|
|
1726
|
+
const session = contextKey ? registry.get(contextKey) : undefined;
|
|
1727
|
+
if (contextKey && session) {
|
|
1728
|
+
appendTelegramActivity(ctx, contextKey, session, {
|
|
1729
|
+
status: "info",
|
|
1730
|
+
type: "artifacts_sent",
|
|
1731
|
+
detail: formatArtifactSummary(report.artifacts, report.skippedCount, report.omittedCount),
|
|
1732
|
+
});
|
|
1733
|
+
}
|
|
1403
1734
|
await pruneArtifacts(workspace);
|
|
1404
1735
|
};
|
|
1405
1736
|
const deliverArtifactReport = async (ctx, chatId, report, messageThreadId) => {
|
|
@@ -1596,6 +1927,11 @@ export function createBot(config, registry) {
|
|
|
1596
1927
|
}
|
|
1597
1928
|
const receivedText = `Received ${stagedFiles.length} media group file${stagedFiles.length === 1 ? "" : "s"}${skippedCount > 0 ? ` (${skippedCount} skipped)` : ""}.`;
|
|
1598
1929
|
await safeReply(pending.ctx, escapeHTML(receivedText), { fallbackText: receivedText });
|
|
1930
|
+
appendTelegramActivity(pending.ctx, pending.contextKey, pending.session, {
|
|
1931
|
+
status: "info",
|
|
1932
|
+
type: "attachment_staged",
|
|
1933
|
+
detail: receivedText,
|
|
1934
|
+
});
|
|
1599
1935
|
await sendChatActionSafe(pending.ctx.api, pending.chatId, "typing", pending.messageThreadId).catch(() => { });
|
|
1600
1936
|
const promptInput = {
|
|
1601
1937
|
stagedFileInstructions: buildFileInstructions(stagedFiles, outDir),
|
|
@@ -1625,6 +1961,8 @@ export function createBot(config, registry) {
|
|
|
1625
1961
|
checkAgentAuthStatus,
|
|
1626
1962
|
isTopicContext,
|
|
1627
1963
|
replyChannelAction,
|
|
1964
|
+
commandService,
|
|
1965
|
+
preferencesStore,
|
|
1628
1966
|
});
|
|
1629
1967
|
registerTelegramAgentCommands({
|
|
1630
1968
|
bot,
|
|
@@ -1641,18 +1979,20 @@ export function createBot(config, registry) {
|
|
|
1641
1979
|
startAgentLogout,
|
|
1642
1980
|
hostLoginCommand,
|
|
1643
1981
|
hostLogoutCommand,
|
|
1982
|
+
appendActivity: (ctx, input) => appendActivity({
|
|
1983
|
+
source: "telegram",
|
|
1984
|
+
...input,
|
|
1985
|
+
threadId: input.threadId ?? null,
|
|
1986
|
+
workspace: input.workspace ?? config.workspace,
|
|
1987
|
+
actor: input.actor ?? telegramActivityActor(ctx),
|
|
1988
|
+
}),
|
|
1644
1989
|
});
|
|
1645
1990
|
registerTelegramPreferenceCommands({
|
|
1646
1991
|
bot,
|
|
1647
1992
|
config,
|
|
1993
|
+
commandService,
|
|
1648
1994
|
preferencesStore,
|
|
1649
1995
|
getContextSession,
|
|
1650
|
-
getEffectiveMirrorMode,
|
|
1651
|
-
getEffectiveNotifyMode,
|
|
1652
|
-
getEffectiveQuietHours,
|
|
1653
|
-
getEffectiveVoiceBackend,
|
|
1654
|
-
getEffectiveVoiceLanguage,
|
|
1655
|
-
isVoiceTranscribeOnly,
|
|
1656
1996
|
});
|
|
1657
1997
|
registerTelegramDiagnosticsCommands({
|
|
1658
1998
|
bot,
|
|
@@ -1673,6 +2013,7 @@ export function createBot(config, registry) {
|
|
|
1673
2013
|
getEffectiveVoiceLanguage,
|
|
1674
2014
|
isVoiceTranscribeOnly,
|
|
1675
2015
|
replyChannelAction,
|
|
2016
|
+
commandService,
|
|
1676
2017
|
});
|
|
1677
2018
|
registerTelegramOperationalCommands({
|
|
1678
2019
|
bot,
|
|
@@ -1686,10 +2027,34 @@ export function createBot(config, registry) {
|
|
|
1686
2027
|
getExternalActivity,
|
|
1687
2028
|
isAdminUser,
|
|
1688
2029
|
auditContext,
|
|
2030
|
+
getLockOwner: (ctx) => {
|
|
2031
|
+
const authUser = getAuthenticatedUser(ctx);
|
|
2032
|
+
if (!authUser) {
|
|
2033
|
+
return null;
|
|
2034
|
+
}
|
|
2035
|
+
return {
|
|
2036
|
+
userId: authUser.user.id,
|
|
2037
|
+
label: authUser.user.displayName || authUser.user.email,
|
|
2038
|
+
channel: "telegram",
|
|
2039
|
+
channelUserId: ctx.from?.id !== undefined ? String(ctx.from.id) : undefined,
|
|
2040
|
+
};
|
|
2041
|
+
},
|
|
1689
2042
|
updateSessionMetadata,
|
|
1690
2043
|
});
|
|
1691
2044
|
registerTelegramSupportCommands({ bot, config, auditLog, agentUpdates, getUserRole, audit });
|
|
1692
|
-
registerTelegramUpdateCommands({
|
|
2045
|
+
registerTelegramUpdateCommands({
|
|
2046
|
+
bot,
|
|
2047
|
+
agentUpdates,
|
|
2048
|
+
replyChannelAction,
|
|
2049
|
+
startTelegramAgentUpdate,
|
|
2050
|
+
appendActivity: (ctx, input) => appendActivity({
|
|
2051
|
+
source: "telegram",
|
|
2052
|
+
...input,
|
|
2053
|
+
threadId: input.threadId ?? null,
|
|
2054
|
+
workspace: input.workspace ?? config.workspace,
|
|
2055
|
+
actor: input.actor ?? telegramActivityActor(ctx),
|
|
2056
|
+
}),
|
|
2057
|
+
});
|
|
1693
2058
|
bot.command("new", async (ctx) => {
|
|
1694
2059
|
const chatId = ctx.chat?.id;
|
|
1695
2060
|
if (!chatId) {
|
|
@@ -1718,6 +2083,14 @@ export function createBot(config, registry) {
|
|
|
1718
2083
|
try {
|
|
1719
2084
|
const info = await session.newThread();
|
|
1720
2085
|
updateSessionMetadata(contextKey, session);
|
|
2086
|
+
appendTelegramActivity(ctx, contextKey, session, {
|
|
2087
|
+
status: "info",
|
|
2088
|
+
type: "session_new",
|
|
2089
|
+
threadId: info.threadId,
|
|
2090
|
+
workspace: info.workspace,
|
|
2091
|
+
agentId: info.agentId,
|
|
2092
|
+
detail: info.workspace,
|
|
2093
|
+
});
|
|
1721
2094
|
const label = isTopicContext(contextKey) ? "New thread created for this topic." : "New thread created.";
|
|
1722
2095
|
const policyLine = renderWorkspacePolicyLine(info.workspace, config);
|
|
1723
2096
|
const plainText = [label, policyLine, "", renderSessionInfoPlain(info)].filter((line) => line !== undefined).join("\n");
|
|
@@ -1755,12 +2128,22 @@ export function createBot(config, registry) {
|
|
|
1755
2128
|
if (busy.kind === "external") {
|
|
1756
2129
|
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.`;
|
|
1757
2130
|
await safeReply(ctx, escapeHTML(text), { fallbackText: text });
|
|
2131
|
+
appendTelegramActivity(ctx, contextKey, session, {
|
|
2132
|
+
status: "failed",
|
|
2133
|
+
type: "prompt_abort_rejected",
|
|
2134
|
+
detail: text,
|
|
2135
|
+
});
|
|
1758
2136
|
return;
|
|
1759
2137
|
}
|
|
1760
2138
|
await session.abort();
|
|
1761
2139
|
await safeReply(ctx, escapeHTML("Aborted current operation"), {
|
|
1762
2140
|
fallbackText: "Aborted current operation",
|
|
1763
2141
|
});
|
|
2142
|
+
appendTelegramActivity(ctx, contextKey, session, {
|
|
2143
|
+
status: "aborted",
|
|
2144
|
+
type: "prompt_aborted",
|
|
2145
|
+
detail: "Abort requested from Telegram.",
|
|
2146
|
+
});
|
|
1764
2147
|
}
|
|
1765
2148
|
catch (error) {
|
|
1766
2149
|
await safeReply(ctx, `<b>Failed:</b> ${escapeHTML(friendlyErrorText(error))}`, {
|
|
@@ -1809,6 +2192,8 @@ export function createBot(config, registry) {
|
|
|
1809
2192
|
drainQueuedPrompts,
|
|
1810
2193
|
handleUserPrompt,
|
|
1811
2194
|
auditContext,
|
|
2195
|
+
activityActor: telegramActivityActor,
|
|
2196
|
+
appendActivity: appendTelegramActivity,
|
|
1812
2197
|
});
|
|
1813
2198
|
registerTelegramArtifactCommands({
|
|
1814
2199
|
bot,
|
|
@@ -1816,6 +2201,13 @@ export function createBot(config, registry) {
|
|
|
1816
2201
|
getContextSession,
|
|
1817
2202
|
deliverArtifactReport,
|
|
1818
2203
|
deliverArtifactReportZip,
|
|
2204
|
+
appendActivity: (ctx, input) => appendActivity({
|
|
2205
|
+
source: "telegram",
|
|
2206
|
+
...input,
|
|
2207
|
+
threadId: input.threadId ?? null,
|
|
2208
|
+
workspace: input.workspace ?? config.workspace,
|
|
2209
|
+
actor: input.actor ?? telegramActivityActor(ctx),
|
|
2210
|
+
}),
|
|
1819
2211
|
});
|
|
1820
2212
|
bot.command("session", async (ctx) => {
|
|
1821
2213
|
const contextSession = await getContextSession(ctx, { deferThreadStart: true });
|
|
@@ -1910,6 +2302,14 @@ export function createBot(config, registry) {
|
|
|
1910
2302
|
try {
|
|
1911
2303
|
const info = session.handback();
|
|
1912
2304
|
updateSessionMetadata(contextKey, session);
|
|
2305
|
+
appendTelegramActivity(ctx, contextKey, session, {
|
|
2306
|
+
status: "info",
|
|
2307
|
+
type: "handback",
|
|
2308
|
+
threadId: info.threadId,
|
|
2309
|
+
workspace: info.workspace,
|
|
2310
|
+
agentId: idOf(session.getInfo()),
|
|
2311
|
+
detail: info.command ?? info.threadId ?? "handback",
|
|
2312
|
+
});
|
|
1913
2313
|
if (!info.threadId) {
|
|
1914
2314
|
await safeReply(ctx, escapeHTML("This thread has not started yet, so there is no resumable thread ID. Send a message to create one, or use /new to start fresh."), {
|
|
1915
2315
|
fallbackText: "This thread has not started yet, so there is no resumable thread ID. Send a message to create one, or use /new to start fresh.",
|
|
@@ -2005,6 +2405,14 @@ export function createBot(config, registry) {
|
|
|
2005
2405
|
try {
|
|
2006
2406
|
const info = await session.switchSession(threadId);
|
|
2007
2407
|
updateSessionMetadata(contextKey, session);
|
|
2408
|
+
appendTelegramActivity(ctx, contextKey, session, {
|
|
2409
|
+
status: "info",
|
|
2410
|
+
type: "session_attach",
|
|
2411
|
+
threadId: info.threadId,
|
|
2412
|
+
workspace: info.workspace,
|
|
2413
|
+
agentId: info.agentId,
|
|
2414
|
+
detail: threadId,
|
|
2415
|
+
});
|
|
2008
2416
|
const policyLine = renderWorkspacePolicyLine(info.workspace, config);
|
|
2009
2417
|
const html = ["<b>Attached to thread.</b>", policyLine ? `<i>${escapeHTML(policyLine)}</i>` : undefined, "", renderSessionInfoHTML(info)].filter((line) => line !== undefined).join("\n");
|
|
2010
2418
|
const plain = ["Attached to thread.", policyLine, "", renderSessionInfoPlain(info)].filter((line) => line !== undefined).join("\n");
|
|
@@ -2051,6 +2459,14 @@ export function createBot(config, registry) {
|
|
|
2051
2459
|
try {
|
|
2052
2460
|
const info = await session.switchSession(threadId);
|
|
2053
2461
|
updateSessionMetadata(contextKey, session);
|
|
2462
|
+
appendTelegramActivity(ctx, contextKey, session, {
|
|
2463
|
+
status: "info",
|
|
2464
|
+
type: "session_switch",
|
|
2465
|
+
threadId: info.threadId,
|
|
2466
|
+
workspace: info.workspace,
|
|
2467
|
+
agentId: info.agentId,
|
|
2468
|
+
detail: threadId,
|
|
2469
|
+
});
|
|
2054
2470
|
const policyLine = renderWorkspacePolicyLine(info.workspace, config);
|
|
2055
2471
|
const html = ["<b>Switched thread.</b>", policyLine ? `<i>${escapeHTML(policyLine)}</i>` : undefined, "", renderSessionInfoHTML(info)].filter((line) => line !== undefined).join("\n");
|
|
2056
2472
|
const plain = ["Switched thread.", policyLine, "", renderSessionInfoPlain(info)].filter((line) => line !== undefined).join("\n");
|
|
@@ -2137,6 +2553,12 @@ export function createBot(config, registry) {
|
|
|
2137
2553
|
return;
|
|
2138
2554
|
}
|
|
2139
2555
|
const pinned = registry.pinThread(contextKey, threadId);
|
|
2556
|
+
appendTelegramActivity(ctx, contextKey, session, {
|
|
2557
|
+
status: "info",
|
|
2558
|
+
type: "session_pinned",
|
|
2559
|
+
threadId,
|
|
2560
|
+
detail: threadId,
|
|
2561
|
+
});
|
|
2140
2562
|
await safeReply(ctx, `<b>Pinned thread:</b> <code>${escapeHTML(threadId)}</code>\n<b>Total pinned:</b> <code>${pinned.length}</code>`, {
|
|
2141
2563
|
fallbackText: `Pinned thread: ${threadId}\nTotal pinned: ${pinned.length}`,
|
|
2142
2564
|
});
|
|
@@ -2157,6 +2579,12 @@ export function createBot(config, registry) {
|
|
|
2157
2579
|
return;
|
|
2158
2580
|
}
|
|
2159
2581
|
const pinned = registry.unpinThread(contextKey, threadId);
|
|
2582
|
+
appendTelegramActivity(ctx, contextKey, session, {
|
|
2583
|
+
status: "info",
|
|
2584
|
+
type: "session_unpinned",
|
|
2585
|
+
threadId,
|
|
2586
|
+
detail: threadId,
|
|
2587
|
+
});
|
|
2160
2588
|
await safeReply(ctx, `<b>Unpinned thread:</b> <code>${escapeHTML(threadId)}</code>\n<b>Total pinned:</b> <code>${pinned.length}</code>`, {
|
|
2161
2589
|
fallbackText: `Unpinned thread: ${threadId}\nTotal pinned: ${pinned.length}`,
|
|
2162
2590
|
});
|
|
@@ -2272,6 +2700,14 @@ export function createBot(config, registry) {
|
|
|
2272
2700
|
const result = session.setFastMode(nextFastMode);
|
|
2273
2701
|
updateSessionMetadata(contextKey, session);
|
|
2274
2702
|
const info = session.getInfo();
|
|
2703
|
+
appendTelegramActivity(ctx, contextKey, session, {
|
|
2704
|
+
status: "info",
|
|
2705
|
+
type: "fast_mode_changed",
|
|
2706
|
+
threadId: info.threadId,
|
|
2707
|
+
workspace: info.workspace,
|
|
2708
|
+
agentId: info.agentId,
|
|
2709
|
+
detail: result.enabled ? "on" : "off",
|
|
2710
|
+
});
|
|
2275
2711
|
const plain = [
|
|
2276
2712
|
`Fast mode: ${result.enabled ? "on" : "off"}`,
|
|
2277
2713
|
`Launch profile: ${result.profile.label} (${formatLaunchProfileBehavior(result.profile)})`,
|
|
@@ -2384,6 +2820,15 @@ export function createBot(config, registry) {
|
|
|
2384
2820
|
});
|
|
2385
2821
|
}
|
|
2386
2822
|
const session = registry.get(pending.contextKey);
|
|
2823
|
+
if (session) {
|
|
2824
|
+
appendTelegramActivity(ctx, pending.contextKey, session, {
|
|
2825
|
+
status: "aborted",
|
|
2826
|
+
type: "prompt_approval_denied",
|
|
2827
|
+
prompt: pending.prompt.description,
|
|
2828
|
+
detail: approvalId,
|
|
2829
|
+
actor: pending.prompt.activityActor,
|
|
2830
|
+
});
|
|
2831
|
+
}
|
|
2387
2832
|
if (chatId && session) {
|
|
2388
2833
|
void drainQueuedPrompts(ctx, pending.contextKey, chatId, session).catch((error) => {
|
|
2389
2834
|
console.error("Failed to drain queue after approval denial:", error);
|
|
@@ -2402,6 +2847,13 @@ export function createBot(config, registry) {
|
|
|
2402
2847
|
fallbackText: `Approved prompt ${approvalId}.`,
|
|
2403
2848
|
});
|
|
2404
2849
|
}
|
|
2850
|
+
appendTelegramActivity(ctx, pending.contextKey, contextSession.session, {
|
|
2851
|
+
status: "info",
|
|
2852
|
+
type: "prompt_approval_approved",
|
|
2853
|
+
prompt: pending.prompt.description,
|
|
2854
|
+
detail: approvalId,
|
|
2855
|
+
actor: pending.prompt.activityActor,
|
|
2856
|
+
});
|
|
2405
2857
|
await handleUserPrompt(ctx, pending.contextKey, chatId ?? parseContextKey(pending.contextKey).chatId, contextSession.session, pending.prompt, {
|
|
2406
2858
|
approved: true,
|
|
2407
2859
|
});
|
|
@@ -2445,6 +2897,14 @@ export function createBot(config, registry) {
|
|
|
2445
2897
|
try {
|
|
2446
2898
|
const info = await session.switchSession(threadId);
|
|
2447
2899
|
updateSessionMetadata(contextKey, session);
|
|
2900
|
+
appendTelegramActivity(ctx, contextKey, session, {
|
|
2901
|
+
status: "info",
|
|
2902
|
+
type: "session_switch",
|
|
2903
|
+
threadId: info.threadId,
|
|
2904
|
+
workspace: info.workspace,
|
|
2905
|
+
agentId: info.agentId,
|
|
2906
|
+
detail: threadId,
|
|
2907
|
+
});
|
|
2448
2908
|
const policyLine = renderWorkspacePolicyLine(info.workspace, config);
|
|
2449
2909
|
const plainText = ["Switched session.", policyLine, "", renderSessionInfoPlain(info)].filter((line) => line !== undefined).join("\n");
|
|
2450
2910
|
const html = ["<b>Switched session.</b>", policyLine ? `<i>${escapeHTML(policyLine)}</i>` : undefined, "", renderSessionInfoHTML(info)].filter((line) => line !== undefined).join("\n");
|
|
@@ -2507,6 +2967,14 @@ export function createBot(config, registry) {
|
|
|
2507
2967
|
try {
|
|
2508
2968
|
const info = await session.newThread(workspace);
|
|
2509
2969
|
updateSessionMetadata(contextKey, session);
|
|
2970
|
+
appendTelegramActivity(ctx, contextKey, session, {
|
|
2971
|
+
status: "info",
|
|
2972
|
+
type: "session_new",
|
|
2973
|
+
threadId: info.threadId,
|
|
2974
|
+
workspace: info.workspace,
|
|
2975
|
+
agentId: info.agentId,
|
|
2976
|
+
detail: workspace,
|
|
2977
|
+
});
|
|
2510
2978
|
const label = isTopicContext(contextKey) ? "New thread created for this topic." : "New thread created.";
|
|
2511
2979
|
const policyLine = renderWorkspacePolicyLine(info.workspace, config);
|
|
2512
2980
|
const plainText = [label, policyLine, "", renderSessionInfoPlain(info)].filter((line) => line !== undefined).join("\n");
|
|
@@ -2602,6 +3070,14 @@ export function createBot(config, registry) {
|
|
|
2602
3070
|
session.setLaunchProfile(profile.id);
|
|
2603
3071
|
updateSessionMetadata(contextKey, session);
|
|
2604
3072
|
const info = session.getInfo();
|
|
3073
|
+
appendTelegramActivity(ctx, contextKey, session, {
|
|
3074
|
+
status: "info",
|
|
3075
|
+
type: "launch_profile_changed",
|
|
3076
|
+
threadId: info.threadId,
|
|
3077
|
+
workspace: info.workspace,
|
|
3078
|
+
agentId: info.agentId,
|
|
3079
|
+
detail: info.launchProfileLabel,
|
|
3080
|
+
});
|
|
2605
3081
|
const html = [
|
|
2606
3082
|
`<b>Launch profile set to</b> <code>${escapeHTML(info.launchProfileLabel)}</code>`,
|
|
2607
3083
|
`<b>Behavior:</b> <code>${escapeHTML(info.launchProfileBehavior)}</code>`,
|
|
@@ -2664,6 +3140,14 @@ export function createBot(config, registry) {
|
|
|
2664
3140
|
session.setLaunchProfile(profile.id);
|
|
2665
3141
|
updateSessionMetadata(contextKey, session);
|
|
2666
3142
|
const info = session.getInfo();
|
|
3143
|
+
appendTelegramActivity(ctx, contextKey, session, {
|
|
3144
|
+
status: "info",
|
|
3145
|
+
type: "launch_profile_changed",
|
|
3146
|
+
threadId: info.threadId,
|
|
3147
|
+
workspace: info.workspace,
|
|
3148
|
+
agentId: info.agentId,
|
|
3149
|
+
detail: info.launchProfileLabel,
|
|
3150
|
+
});
|
|
2667
3151
|
await ctx.answerCallbackQuery({ text: `Launch set to ${info.launchProfileLabel}` });
|
|
2668
3152
|
const html = [
|
|
2669
3153
|
`<b>Launch profile set to</b> <code>${escapeHTML(info.launchProfileLabel)}</code>`,
|
|
@@ -2710,6 +3194,15 @@ export function createBot(config, registry) {
|
|
|
2710
3194
|
try {
|
|
2711
3195
|
const result = await session.setModelForCurrentSession(slug);
|
|
2712
3196
|
updateSessionMetadata(contextKey, session);
|
|
3197
|
+
const info = session.getInfo();
|
|
3198
|
+
appendTelegramActivity(ctx, contextKey, session, {
|
|
3199
|
+
status: "info",
|
|
3200
|
+
type: "model_changed",
|
|
3201
|
+
threadId: info.threadId,
|
|
3202
|
+
workspace: info.workspace,
|
|
3203
|
+
agentId: info.agentId,
|
|
3204
|
+
detail: result.value,
|
|
3205
|
+
});
|
|
2713
3206
|
const scope = formatAgentSettingScope(session.getInfo(), result.appliedToActiveThread);
|
|
2714
3207
|
const html = `<b>Model set to</b> <code>${escapeHTML(result.value)}</code> — ${escapeHTML(scope)}.`;
|
|
2715
3208
|
const plainText = `Model set to ${result.value} — ${scope}.`;
|
|
@@ -2756,6 +3249,15 @@ export function createBot(config, registry) {
|
|
|
2756
3249
|
pendingEffortButtons.delete(contextKey);
|
|
2757
3250
|
const result = await session.setReasoningEffortForCurrentSession(effort);
|
|
2758
3251
|
updateSessionMetadata(contextKey, session);
|
|
3252
|
+
const info = session.getInfo();
|
|
3253
|
+
appendTelegramActivity(ctx, contextKey, session, {
|
|
3254
|
+
status: "info",
|
|
3255
|
+
type: "reasoning_changed",
|
|
3256
|
+
threadId: info.threadId,
|
|
3257
|
+
workspace: info.workspace,
|
|
3258
|
+
agentId: info.agentId,
|
|
3259
|
+
detail: result.value,
|
|
3260
|
+
});
|
|
2759
3261
|
const label = agentReasoningLabel(idOf(session.getInfo()));
|
|
2760
3262
|
const scope = formatAgentSettingScope(session.getInfo(), result.appliedToActiveThread);
|
|
2761
3263
|
const html = `⚡ ${escapeHTML(label)} set to <code>${escapeHTML(effort)}</code> — ${escapeHTML(scope)}.`;
|
|
@@ -2815,12 +3317,24 @@ export function createBot(config, registry) {
|
|
|
2815
3317
|
}
|
|
2816
3318
|
const preview = trimLine(transcript.replace(/\s+/g, " "), 100);
|
|
2817
3319
|
await safeReply(ctx, `🎙️ <b>Transcribed:</b> ${escapeHTML(preview)} <i>(via ${escapeHTML(result.backend)}, ${formatDurationSeconds(result.durationMs / 1000)})</i>`, { fallbackText: `🎙️ Transcribed: ${preview} (via ${result.backend}, ${formatDurationSeconds(result.durationMs / 1000)})` });
|
|
3320
|
+
appendTelegramActivity(ctx, contextKey, session, {
|
|
3321
|
+
status: "info",
|
|
3322
|
+
type: "voice_transcribed",
|
|
3323
|
+
prompt: preview,
|
|
3324
|
+
detail: result.backend,
|
|
3325
|
+
durationMs: result.durationMs,
|
|
3326
|
+
});
|
|
2818
3327
|
}
|
|
2819
3328
|
catch (error) {
|
|
2820
3329
|
const note = "Voice uses faster-whisper/parakeet locally or OPENAI_API_KEY for cloud transcription, not CODEX_API_KEY.";
|
|
2821
3330
|
await safeReply(ctx, `<b>Transcription failed:</b>\n${escapeHTML(friendlyErrorText(error))}\n\n<i>${escapeHTML(note)}</i>`, {
|
|
2822
3331
|
fallbackText: `Transcription failed:\n${friendlyErrorText(error)}\n\n${note}`,
|
|
2823
3332
|
});
|
|
3333
|
+
appendTelegramActivity(ctx, contextKey, session, {
|
|
3334
|
+
status: "failed",
|
|
3335
|
+
type: "voice_transcription_failed",
|
|
3336
|
+
detail: friendlyErrorText(error),
|
|
3337
|
+
});
|
|
2824
3338
|
return;
|
|
2825
3339
|
}
|
|
2826
3340
|
finally {
|
|
@@ -2915,6 +3429,11 @@ export function createBot(config, registry) {
|
|
|
2915
3429
|
if (caption) {
|
|
2916
3430
|
promptInput.text = caption;
|
|
2917
3431
|
}
|
|
3432
|
+
appendTelegramActivity(ctx, contextKey, session, {
|
|
3433
|
+
status: "info",
|
|
3434
|
+
type: "attachment_staged",
|
|
3435
|
+
detail: stagedPhoto.safeName,
|
|
3436
|
+
});
|
|
2918
3437
|
await setReaction(ctx, "👀");
|
|
2919
3438
|
try {
|
|
2920
3439
|
await handleUserPrompt(ctx, contextKey, chatId, session, toPromptEnvelope(promptInput, outDir));
|
|
@@ -2997,6 +3516,11 @@ export function createBot(config, registry) {
|
|
|
2997
3516
|
await safeReply(ctx, `📎 <b>Received:</b> <code>${escapeHTML(stagedFile.safeName)}</code>`, {
|
|
2998
3517
|
fallbackText: `📎 Received: ${stagedFile.safeName}`,
|
|
2999
3518
|
});
|
|
3519
|
+
appendTelegramActivity(ctx, contextKey, session, {
|
|
3520
|
+
status: "info",
|
|
3521
|
+
type: "attachment_staged",
|
|
3522
|
+
detail: stagedFile.safeName,
|
|
3523
|
+
});
|
|
3000
3524
|
// Keep typing visible during the gap between staging and prompt execution
|
|
3001
3525
|
await sendChatActionSafe(ctx.api, chatId, "typing", ctx.message?.message_thread_id).catch(() => { });
|
|
3002
3526
|
const outDir = outboxPath(workspace, turnId);
|