@nordbyte/nordrelay 0.5.1 → 0.6.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 +65 -11
- package/README.md +97 -23
- package/dist/access-control.js +1 -0
- package/dist/activity-events.js +44 -0
- package/dist/agent-updates.js +18 -2
- package/dist/audit-log.js +40 -2
- package/dist/bot-rendering.js +10 -7
- package/dist/bot.js +492 -7
- package/dist/channel-actions.js +7 -2
- package/dist/channel-adapter.js +34 -7
- package/dist/channel-command-service.js +156 -0
- package/dist/channel-turn-service.js +237 -0
- package/dist/codex-cli.js +1 -1
- package/dist/config-metadata.js +80 -13
- package/dist/config.js +77 -7
- package/dist/context-key.js +77 -5
- package/dist/discord-artifacts.js +165 -0
- package/dist/discord-bot.js +2014 -0
- package/dist/discord-channel-runtime.js +133 -0
- package/dist/discord-command-surface.js +119 -0
- package/dist/discord-rate-limit.js +141 -0
- package/dist/index.js +16 -5
- package/dist/job-store.js +127 -0
- package/dist/metrics.js +41 -0
- package/dist/operations.js +176 -119
- package/dist/relay-external-activity-monitor.js +47 -6
- package/dist/relay-runtime.js +1003 -268
- package/dist/runtime-cache.js +57 -0
- package/dist/session-locks.js +10 -7
- package/dist/state-backend.js +3 -0
- package/dist/support-bundle.js +18 -1
- 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-diagnostics-command.js +5 -50
- package/dist/telegram-general-commands.js +2 -6
- package/dist/telegram-operational-commands.js +14 -6
- 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 +9 -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 +97 -13
- package/dist/web-dashboard-runtime-routes.js +53 -8
- package/dist/web-dashboard-session-routes.js +27 -20
- package/dist/web-dashboard-ui.js +1 -0
- package/dist/web-dashboard.js +149 -6
- package/dist/web-state.js +33 -2
- package/dist/webui-assets/dashboard.css +75 -1
- package/dist/webui-assets/dashboard.js +358 -47
- package/package.json +3 -1
- package/plugins/nordrelay/.codex-plugin/plugin.json +1 -1
- package/plugins/nordrelay/scripts/nordrelay.mjs +468 -22
package/dist/bot.js
CHANGED
|
@@ -11,6 +11,7 @@ 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";
|
|
14
15
|
import { deliverChannelAction } from "./channel-runtime.js";
|
|
15
16
|
import { agentLabel, agentReasoningLabel, agentReasoningOptions, } from "./agent.js";
|
|
16
17
|
import { getExternalActivityForSession, getExternalSnapshotForSession, } from "./agent-activity.js";
|
|
@@ -45,6 +46,7 @@ import { registerTelegramSupportCommands } from "./telegram-support-command.js";
|
|
|
45
46
|
import { registerTelegramUpdateCommands } from "./telegram-update-commands.js";
|
|
46
47
|
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
48
|
import { UserStore } from "./user-management.js";
|
|
49
|
+
import { WebActivityStore } from "./web-state.js";
|
|
48
50
|
import { evaluateWorkspacePolicy, filterAllowedWorkspaces, renderWorkspacePolicyLine, } from "./workspace-policy.js";
|
|
49
51
|
export { formatToolSummaryLine, formatTurnUsageLine, summarizeToolName } from "./bot-rendering.js";
|
|
50
52
|
export { registerCommands } from "./telegram-command-menu.js";
|
|
@@ -54,6 +56,10 @@ const TOOL_OUTPUT_PREVIEW_LIMIT = 500;
|
|
|
54
56
|
const MAX_AUDIO_FILE_SIZE = 25 * 1024 * 1024;
|
|
55
57
|
const MEDIA_GROUP_FLUSH_MS = 1200;
|
|
56
58
|
const LAUNCH_PROFILES_COMMAND = "/launch_profiles";
|
|
59
|
+
const CLI_ACTIVITY_ACTOR = {
|
|
60
|
+
channel: "cli",
|
|
61
|
+
label: "CLI",
|
|
62
|
+
};
|
|
57
63
|
export function createBot(config, registry) {
|
|
58
64
|
configureRedaction(config.telegramRedactPatterns);
|
|
59
65
|
telegramRateLimiter.configure({
|
|
@@ -80,11 +86,17 @@ export function createBot(config, registry) {
|
|
|
80
86
|
const turnProgress = new Map();
|
|
81
87
|
const promptStore = new PromptStore(config.workspace, config.stateBackend);
|
|
82
88
|
const preferencesStore = new BotPreferencesStore(config.workspace, config.stateBackend);
|
|
89
|
+
const activityStore = new WebActivityStore(config.workspace, config.stateBackend, config.auditMaxEvents);
|
|
83
90
|
const auditLog = new AuditLogStore(config.workspace, config.stateBackend, config.auditMaxEvents);
|
|
84
91
|
const lockStore = new SessionLockStore(config.workspace, config.stateBackend);
|
|
85
92
|
const userStore = new UserStore();
|
|
86
93
|
const contextUsers = new WeakMap();
|
|
87
|
-
const
|
|
94
|
+
const agentUpdateActors = new Map();
|
|
95
|
+
const agentUpdateStates = new Map();
|
|
96
|
+
const commandService = new ChannelCommandService(config);
|
|
97
|
+
const agentUpdates = new AgentUpdateManager({
|
|
98
|
+
onUpdate: (job) => recordTelegramAgentUpdateLifecycle(job),
|
|
99
|
+
});
|
|
88
100
|
const linkAttempts = new Map();
|
|
89
101
|
const drainingQueues = new Set();
|
|
90
102
|
const externalQueueTimers = new Map();
|
|
@@ -228,6 +240,19 @@ export function createBot(config, registry) {
|
|
|
228
240
|
const startTelegramAgentUpdate = async (ctx, agentId, operation = "update") => {
|
|
229
241
|
try {
|
|
230
242
|
const job = agentUpdates.start(agentId, agentUpdateContext(), operation);
|
|
243
|
+
const actor = telegramActivityActor(ctx);
|
|
244
|
+
agentUpdateActors.set(job.id, actor);
|
|
245
|
+
agentUpdateStates.set(job.id, { status: job.status, needsInput: job.needsInput });
|
|
246
|
+
appendActivity({
|
|
247
|
+
source: "telegram",
|
|
248
|
+
status: "info",
|
|
249
|
+
type: operation === "install" ? "agent_install_started" : "agent_update_started",
|
|
250
|
+
threadId: null,
|
|
251
|
+
workspace: config.workspace,
|
|
252
|
+
agentId,
|
|
253
|
+
actor,
|
|
254
|
+
detail: `${job.method}: ${job.summary}`,
|
|
255
|
+
});
|
|
231
256
|
const contextKey = contextKeyFromCtx(ctx);
|
|
232
257
|
if (contextKey) {
|
|
233
258
|
audit({
|
|
@@ -235,6 +260,9 @@ export function createBot(config, registry) {
|
|
|
235
260
|
status: "ok",
|
|
236
261
|
contextKey,
|
|
237
262
|
agentId,
|
|
263
|
+
actor,
|
|
264
|
+
actorId: getAuthenticatedUser(ctx)?.user.id ?? ctx.from?.id,
|
|
265
|
+
actorRole: getUserRole(ctx),
|
|
238
266
|
description: `${operation} ${agentId}`,
|
|
239
267
|
detail: job.summary,
|
|
240
268
|
});
|
|
@@ -453,6 +481,30 @@ export function createBot(config, registry) {
|
|
|
453
481
|
await drainQueuedPrompts(createSystemContext(contextKey), contextKey, parsed.chatId, session);
|
|
454
482
|
}
|
|
455
483
|
};
|
|
484
|
+
const sendExternalMirrorTyping = async (chatId, messageThreadId, state) => {
|
|
485
|
+
const now = Date.now();
|
|
486
|
+
if (state.lastTypingAt && now - state.lastTypingAt < TYPING_INTERVAL_MS) {
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
state.lastTypingAt = now;
|
|
490
|
+
await sendChatActionSafe(bot.api, chatId, "typing", messageThreadId).catch(() => { });
|
|
491
|
+
};
|
|
492
|
+
const sendExternalWorkingNotice = async (chatId, messageThreadId, state, snapshot) => {
|
|
493
|
+
const turnKey = snapshot.activity.turnId ?? snapshot.activity.startedAt?.toISOString() ?? "unknown";
|
|
494
|
+
if (state.workingNoticeTurnKey === turnKey) {
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
const prompt = trimLine(snapshot.latestUserMessage ?? "", 250);
|
|
498
|
+
const fallbackText = prompt ? `Working on ${prompt}` : `Working on external ${snapshot.agentLabel} task...`;
|
|
499
|
+
const html = prompt
|
|
500
|
+
? `<b>Working on</b> ${escapeHTML(prompt)}`
|
|
501
|
+
: `<b>Working on</b> external ${escapeHTML(snapshot.agentLabel)} task...`;
|
|
502
|
+
await sendTextMessage(bot.api, chatId, html, {
|
|
503
|
+
fallbackText,
|
|
504
|
+
messageThreadId,
|
|
505
|
+
});
|
|
506
|
+
state.workingNoticeTurnKey = turnKey;
|
|
507
|
+
};
|
|
456
508
|
const mirrorExternalSnapshot = async (contextKey, chatId, session, snapshot) => {
|
|
457
509
|
const parsed = parseContextKey(contextKey);
|
|
458
510
|
const previous = externalMirrors.get(contextKey);
|
|
@@ -471,7 +523,35 @@ export function createBot(config, registry) {
|
|
|
471
523
|
if (snapshot.activity.active) {
|
|
472
524
|
state.turnId = snapshot.activity.turnId;
|
|
473
525
|
state.startedAt = snapshot.activity.startedAt;
|
|
474
|
-
|
|
526
|
+
const turnKey = snapshot.activity.turnId ?? snapshot.activity.startedAt?.toISOString() ?? "unknown";
|
|
527
|
+
if (state.activityStartedTurnKey !== turnKey) {
|
|
528
|
+
const info = session.getInfo();
|
|
529
|
+
appendActivity({
|
|
530
|
+
source: "cli",
|
|
531
|
+
status: "running",
|
|
532
|
+
type: "cli_turn_started",
|
|
533
|
+
contextKey,
|
|
534
|
+
threadId: snapshot.threadId,
|
|
535
|
+
workspace: info.workspace,
|
|
536
|
+
agentId: info.agentId,
|
|
537
|
+
actor: CLI_ACTIVITY_ACTOR,
|
|
538
|
+
prompt: snapshot.latestUserMessage ?? `${snapshot.agentLabel} CLI task`,
|
|
539
|
+
detail: `${snapshot.sourceLabel}: ${snapshot.sourcePath}`,
|
|
540
|
+
});
|
|
541
|
+
state.activityStartedTurnKey = turnKey;
|
|
542
|
+
state.activityFinishedTurnKey = undefined;
|
|
543
|
+
state.activityToolStartLines = [];
|
|
544
|
+
state.activityToolEndLines = [];
|
|
545
|
+
}
|
|
546
|
+
if (mirrorMode !== "off") {
|
|
547
|
+
await sendExternalMirrorTyping(chatId, parsed.messageThreadId, state);
|
|
548
|
+
}
|
|
549
|
+
if (mirrorMode === "final") {
|
|
550
|
+
await sendExternalWorkingNotice(chatId, parsed.messageThreadId, state, snapshot);
|
|
551
|
+
state.lastLine = Math.max(state.lastLine, snapshot.lineCount);
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
if (mirrorMode === "off") {
|
|
475
555
|
state.lastLine = Math.max(state.lastLine, snapshot.lineCount);
|
|
476
556
|
return;
|
|
477
557
|
}
|
|
@@ -510,7 +590,43 @@ export function createBot(config, registry) {
|
|
|
510
590
|
state.latestMirroredEventLine = event.lineNumber;
|
|
511
591
|
}
|
|
512
592
|
}
|
|
513
|
-
|
|
593
|
+
const info = session.getInfo();
|
|
594
|
+
const loggedStartLines = new Set(state.activityToolStartLines ?? []);
|
|
595
|
+
const loggedEndLines = new Set(state.activityToolEndLines ?? []);
|
|
596
|
+
for (const event of snapshot.events.filter((event) => event.lineNumber > state.lastLine && event.kind === "tool")) {
|
|
597
|
+
if (event.status === "started" && !loggedStartLines.has(event.lineNumber)) {
|
|
598
|
+
appendActivity({
|
|
599
|
+
source: "cli",
|
|
600
|
+
status: "running",
|
|
601
|
+
type: "cli_tool_started",
|
|
602
|
+
contextKey,
|
|
603
|
+
threadId: snapshot.threadId,
|
|
604
|
+
workspace: info.workspace,
|
|
605
|
+
agentId: info.agentId,
|
|
606
|
+
actor: CLI_ACTIVITY_ACTOR,
|
|
607
|
+
prompt: snapshot.latestUserMessage ?? undefined,
|
|
608
|
+
detail: event.toolName ?? "tool",
|
|
609
|
+
});
|
|
610
|
+
loggedStartLines.add(event.lineNumber);
|
|
611
|
+
}
|
|
612
|
+
if ((event.status === "finished" || event.status === "failed") && !loggedEndLines.has(event.lineNumber)) {
|
|
613
|
+
appendActivity({
|
|
614
|
+
source: "cli",
|
|
615
|
+
status: event.status === "failed" ? "failed" : "completed",
|
|
616
|
+
type: event.status === "failed" ? "cli_tool_failed" : "cli_tool_completed",
|
|
617
|
+
contextKey,
|
|
618
|
+
threadId: snapshot.threadId,
|
|
619
|
+
workspace: info.workspace,
|
|
620
|
+
agentId: info.agentId,
|
|
621
|
+
actor: CLI_ACTIVITY_ACTOR,
|
|
622
|
+
prompt: snapshot.latestUserMessage ?? undefined,
|
|
623
|
+
detail: event.toolName ?? "tool",
|
|
624
|
+
});
|
|
625
|
+
loggedEndLines.add(event.lineNumber);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
state.activityToolStartLines = [...loggedStartLines].slice(-200);
|
|
629
|
+
state.activityToolEndLines = [...loggedEndLines].slice(-200);
|
|
514
630
|
state.lastLine = Math.max(state.lastLine, snapshot.lineCount);
|
|
515
631
|
return;
|
|
516
632
|
}
|
|
@@ -520,6 +636,25 @@ export function createBot(config, registry) {
|
|
|
520
636
|
}
|
|
521
637
|
const terminalEvent = [...snapshot.events].reverse().find((event) => event.kind === "task" && event.status && event.status !== "started");
|
|
522
638
|
if (terminalEvent) {
|
|
639
|
+
const turnKey = terminalEvent.turnId ?? snapshot.activity.turnId ?? state.startedAt?.toString() ?? "unknown";
|
|
640
|
+
if (state.activityFinishedTurnKey !== turnKey) {
|
|
641
|
+
const info = session.getInfo();
|
|
642
|
+
const startedAt = state.startedAt instanceof Date ? state.startedAt : state.startedAt ? new Date(state.startedAt) : snapshot.activity.startedAt;
|
|
643
|
+
appendActivity({
|
|
644
|
+
source: "cli",
|
|
645
|
+
status: terminalEvent.status === "aborted" ? "aborted" : terminalEvent.status === "failed" ? "failed" : "completed",
|
|
646
|
+
type: "cli_turn_finished",
|
|
647
|
+
contextKey,
|
|
648
|
+
threadId: snapshot.threadId,
|
|
649
|
+
workspace: info.workspace,
|
|
650
|
+
agentId: info.agentId,
|
|
651
|
+
actor: CLI_ACTIVITY_ACTOR,
|
|
652
|
+
prompt: snapshot.latestUserMessage ?? undefined,
|
|
653
|
+
detail: `${snapshot.agentLabel} CLI task ${terminalEvent.status ?? "finished"}.`,
|
|
654
|
+
durationMs: startedAt && terminalEvent.timestamp ? Math.max(0, terminalEvent.timestamp.getTime() - startedAt.getTime()) : undefined,
|
|
655
|
+
});
|
|
656
|
+
state.activityFinishedTurnKey = turnKey;
|
|
657
|
+
}
|
|
523
658
|
if (mirrorMode !== "off") {
|
|
524
659
|
const doneText = `${snapshot.agentLabel} CLI task ${terminalEvent.status}.`;
|
|
525
660
|
if (state.statusMessageId) {
|
|
@@ -551,6 +686,7 @@ export function createBot(config, registry) {
|
|
|
551
686
|
}
|
|
552
687
|
await deliverCliGeneratedArtifacts(contextKey, chatId, session, state.startedAt, terminalEvent.turnId, parsed.messageThreadId);
|
|
553
688
|
}
|
|
689
|
+
state.workingNoticeTurnKey = undefined;
|
|
554
690
|
state.lastLine = Math.max(state.lastLine, snapshot.lineCount);
|
|
555
691
|
};
|
|
556
692
|
const canSendSystemMessagesToContext = (contextKey) => {
|
|
@@ -605,6 +741,18 @@ export function createBot(config, registry) {
|
|
|
605
741
|
for (const artifact of (persistedReport?.artifacts ?? report.artifacts)) {
|
|
606
742
|
await sendArtifactFileByApi(bot.api, chatId, artifact, messageThreadId);
|
|
607
743
|
}
|
|
744
|
+
const info = session.getInfo();
|
|
745
|
+
appendActivity({
|
|
746
|
+
source: "cli",
|
|
747
|
+
status: "info",
|
|
748
|
+
type: "artifacts_sent",
|
|
749
|
+
contextKey,
|
|
750
|
+
threadId: info.threadId,
|
|
751
|
+
workspace: info.workspace,
|
|
752
|
+
agentId: info.agentId,
|
|
753
|
+
actor: CLI_ACTIVITY_ACTOR,
|
|
754
|
+
detail: summary,
|
|
755
|
+
});
|
|
608
756
|
if (state)
|
|
609
757
|
state.artifactsDeliveredForTurnId = turnId;
|
|
610
758
|
};
|
|
@@ -656,9 +804,11 @@ export function createBot(config, registry) {
|
|
|
656
804
|
};
|
|
657
805
|
const auditContext = (ctx, contextKey, session, patch) => {
|
|
658
806
|
const info = session.getInfo();
|
|
807
|
+
const authUser = getAuthenticatedUser(ctx);
|
|
659
808
|
audit({
|
|
660
809
|
contextKey,
|
|
661
|
-
|
|
810
|
+
actor: telegramActivityActor(ctx),
|
|
811
|
+
actorId: authUser?.user.id ?? ctx.from?.id,
|
|
662
812
|
actorRole: getUserRole(ctx),
|
|
663
813
|
agentId: idOf(info),
|
|
664
814
|
threadId: info.threadId,
|
|
@@ -666,10 +816,68 @@ export function createBot(config, registry) {
|
|
|
666
816
|
...patch,
|
|
667
817
|
});
|
|
668
818
|
};
|
|
819
|
+
function telegramActivityActor(ctx) {
|
|
820
|
+
const user = ctx.from;
|
|
821
|
+
const authUser = getAuthenticatedUser(ctx);
|
|
822
|
+
const label = authUser?.user.displayName || formatTelegramName(ctx) || user?.username || (user?.id ? String(user.id) : "Telegram user");
|
|
823
|
+
return {
|
|
824
|
+
channel: "telegram",
|
|
825
|
+
id: authUser?.user.id ?? (user?.id !== undefined ? `telegram:${user.id}` : undefined),
|
|
826
|
+
label,
|
|
827
|
+
username: authUser?.user.email ?? user?.username,
|
|
828
|
+
channelUserId: user?.id !== undefined ? String(user.id) : undefined,
|
|
829
|
+
};
|
|
830
|
+
}
|
|
831
|
+
function appendActivity(input) {
|
|
832
|
+
return activityStore.append(input);
|
|
833
|
+
}
|
|
834
|
+
function appendTelegramActivity(ctx, contextKey, session, input) {
|
|
835
|
+
const info = session.getInfo();
|
|
836
|
+
return appendActivity({
|
|
837
|
+
source: "telegram",
|
|
838
|
+
contextKey,
|
|
839
|
+
...input,
|
|
840
|
+
threadId: input.threadId ?? info.threadId,
|
|
841
|
+
workspace: input.workspace ?? info.workspace,
|
|
842
|
+
agentId: input.agentId ?? idOf(info),
|
|
843
|
+
actor: input.actor ?? telegramActivityActor(ctx),
|
|
844
|
+
});
|
|
845
|
+
}
|
|
846
|
+
function recordTelegramAgentUpdateLifecycle(job) {
|
|
847
|
+
const previous = agentUpdateStates.get(job.id);
|
|
848
|
+
const actor = agentUpdateActors.get(job.id);
|
|
849
|
+
if (job.needsInput && !previous?.needsInput) {
|
|
850
|
+
appendActivity({
|
|
851
|
+
source: "telegram",
|
|
852
|
+
status: "info",
|
|
853
|
+
type: "agent_update_input_required",
|
|
854
|
+
threadId: null,
|
|
855
|
+
workspace: config.workspace,
|
|
856
|
+
agentId: job.agentId,
|
|
857
|
+
actor,
|
|
858
|
+
detail: `${job.agentLabel} ${job.operation} may require input.`,
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
if (job.status !== "running" && previous?.status === "running") {
|
|
862
|
+
appendActivity({
|
|
863
|
+
source: "telegram",
|
|
864
|
+
status: job.status === "completed" ? "completed" : job.status === "cancelled" ? "aborted" : "failed",
|
|
865
|
+
type: job.operation === "install" ? `agent_install_${job.status}` : `agent_update_${job.status}`,
|
|
866
|
+
threadId: null,
|
|
867
|
+
workspace: config.workspace,
|
|
868
|
+
agentId: job.agentId,
|
|
869
|
+
actor,
|
|
870
|
+
detail: job.error ?? `${job.agentLabel} ${job.operation} ${job.status}.`,
|
|
871
|
+
durationMs: Math.max(0, Date.parse(job.finishedAt ?? job.updatedAt) - Date.parse(job.startedAt)),
|
|
872
|
+
});
|
|
873
|
+
agentUpdateActors.delete(job.id);
|
|
874
|
+
}
|
|
875
|
+
agentUpdateStates.set(job.id, { status: job.status, needsInput: job.needsInput });
|
|
876
|
+
}
|
|
669
877
|
const denyIfLocked = async (ctx, contextKey, session) => {
|
|
670
878
|
const lock = lockStore.get(contextKey);
|
|
671
879
|
const isAdmin = isAdminUser(ctx);
|
|
672
|
-
if (canWriteWithLock(lock, ctx.
|
|
880
|
+
if (canWriteWithLock(lock, getAuthenticatedUser(ctx)?.user.id, isAdmin)) {
|
|
673
881
|
return false;
|
|
674
882
|
}
|
|
675
883
|
const owner = formatLockOwner(lock);
|
|
@@ -679,6 +887,11 @@ export function createBot(config, registry) {
|
|
|
679
887
|
status: "denied",
|
|
680
888
|
detail: text,
|
|
681
889
|
});
|
|
890
|
+
appendTelegramActivity(ctx, contextKey, session, {
|
|
891
|
+
status: "failed",
|
|
892
|
+
type: "lock_denied",
|
|
893
|
+
detail: text,
|
|
894
|
+
});
|
|
682
895
|
await safeReply(ctx, escapeHTML(text), { fallbackText: text });
|
|
683
896
|
return true;
|
|
684
897
|
};
|
|
@@ -777,7 +990,11 @@ export function createBot(config, registry) {
|
|
|
777
990
|
}
|
|
778
991
|
const parsed = parseContextKey(contextKey);
|
|
779
992
|
const messageThreadId = parsed.messageThreadId;
|
|
780
|
-
const
|
|
993
|
+
const rawEnvelope = isPromptEnvelopeLike(prompt) ? prompt : toPromptEnvelope(prompt);
|
|
994
|
+
const envelope = {
|
|
995
|
+
...rawEnvelope,
|
|
996
|
+
activityActor: rawEnvelope.activityActor ?? telegramActivityActor(ctx),
|
|
997
|
+
};
|
|
781
998
|
if (!options.fromQueue && await denyIfLocked(ctx, contextKey, session)) {
|
|
782
999
|
return;
|
|
783
1000
|
}
|
|
@@ -808,6 +1025,13 @@ export function createBot(config, registry) {
|
|
|
808
1025
|
description: item.description,
|
|
809
1026
|
detail: busy.kind,
|
|
810
1027
|
});
|
|
1028
|
+
appendTelegramActivity(ctx, contextKey, session, {
|
|
1029
|
+
status: "queued",
|
|
1030
|
+
type: "prompt_queued",
|
|
1031
|
+
prompt: item.description,
|
|
1032
|
+
detail: `Queued prompt ${item.id} at position ${position}; busy=${busy.kind}`,
|
|
1033
|
+
actor: envelope.activityActor,
|
|
1034
|
+
});
|
|
811
1035
|
if (busy.kind === "external") {
|
|
812
1036
|
scheduleExternalQueueDrain(ctx, contextKey, chatId, session);
|
|
813
1037
|
}
|
|
@@ -845,6 +1069,9 @@ export function createBot(config, registry) {
|
|
|
845
1069
|
let lastRenderedPlan = "";
|
|
846
1070
|
let planMessageSending = false;
|
|
847
1071
|
let lastTurnUsage;
|
|
1072
|
+
let promptStartedAt;
|
|
1073
|
+
const toolActivityNames = new Map();
|
|
1074
|
+
const toolActivityStartedAt = new Map();
|
|
848
1075
|
const typingInterval = setInterval(() => {
|
|
849
1076
|
void sendChatActionSafe(bot.api, chatId, "typing", messageThreadId).catch(() => { });
|
|
850
1077
|
}, TYPING_INTERVAL_MS);
|
|
@@ -1048,6 +1275,15 @@ export function createBot(config, registry) {
|
|
|
1048
1275
|
progress.lastTool = toolName;
|
|
1049
1276
|
progress.updatedAt = Date.now();
|
|
1050
1277
|
progress.toolCounts.set(toolName, (progress.toolCounts.get(toolName) ?? 0) + 1);
|
|
1278
|
+
toolActivityNames.set(toolCallId, toolName);
|
|
1279
|
+
toolActivityStartedAt.set(toolCallId, Date.now());
|
|
1280
|
+
appendTelegramActivity(ctx, contextKey, session, {
|
|
1281
|
+
status: "running",
|
|
1282
|
+
type: "tool_started",
|
|
1283
|
+
prompt: envelope.description,
|
|
1284
|
+
detail: toolName,
|
|
1285
|
+
actor: envelope.activityActor,
|
|
1286
|
+
});
|
|
1051
1287
|
if (toolVerbosity === "summary") {
|
|
1052
1288
|
toolCounts.set(toolName, (toolCounts.get(toolName) ?? 0) + 1);
|
|
1053
1289
|
return;
|
|
@@ -1095,6 +1331,18 @@ export function createBot(config, registry) {
|
|
|
1095
1331
|
onToolEnd: (toolCallId, isError) => {
|
|
1096
1332
|
progress.currentTool = undefined;
|
|
1097
1333
|
progress.updatedAt = Date.now();
|
|
1334
|
+
const activityToolName = toolActivityNames.get(toolCallId) ?? "tool";
|
|
1335
|
+
const activityStartedAt = toolActivityStartedAt.get(toolCallId);
|
|
1336
|
+
appendTelegramActivity(ctx, contextKey, session, {
|
|
1337
|
+
status: isError ? "failed" : "completed",
|
|
1338
|
+
type: isError ? "tool_failed" : "tool_completed",
|
|
1339
|
+
prompt: envelope.description,
|
|
1340
|
+
detail: activityToolName,
|
|
1341
|
+
actor: envelope.activityActor,
|
|
1342
|
+
durationMs: activityStartedAt ? Date.now() - activityStartedAt : undefined,
|
|
1343
|
+
});
|
|
1344
|
+
toolActivityNames.delete(toolCallId);
|
|
1345
|
+
toolActivityStartedAt.delete(toolCallId);
|
|
1098
1346
|
if (toolVerbosity === "none" || toolVerbosity === "summary") {
|
|
1099
1347
|
return;
|
|
1100
1348
|
}
|
|
@@ -1234,11 +1482,25 @@ export function createBot(config, registry) {
|
|
|
1234
1482
|
replyMarkup: createQueuedPromptCancelKeyboard(contextKey, item.id),
|
|
1235
1483
|
});
|
|
1236
1484
|
await updateQueueStatusMessage(contextKey, `Waiting for ${label} CLI task... ${promptStore.list(contextKey).length} queued.`);
|
|
1485
|
+
appendTelegramActivity(ctx, contextKey, session, {
|
|
1486
|
+
status: "queued",
|
|
1487
|
+
type: "prompt_queued",
|
|
1488
|
+
prompt: item.description,
|
|
1489
|
+
detail: `Queued prompt ${item.id} at position 1; external ${label} CLI task active`,
|
|
1490
|
+
actor: envelope.activityActor,
|
|
1491
|
+
});
|
|
1237
1492
|
scheduleExternalQueueDrain(ctx, contextKey, chatId, session);
|
|
1238
1493
|
turnProgress.delete(contextKey);
|
|
1239
1494
|
return;
|
|
1240
1495
|
}
|
|
1241
1496
|
promptStore.setLastPrompt(contextKey, envelope);
|
|
1497
|
+
promptStartedAt = Date.now();
|
|
1498
|
+
appendTelegramActivity(ctx, contextKey, session, {
|
|
1499
|
+
status: "running",
|
|
1500
|
+
type: "prompt_started",
|
|
1501
|
+
prompt: envelope.description,
|
|
1502
|
+
actor: envelope.activityActor,
|
|
1503
|
+
});
|
|
1242
1504
|
auditContext(ctx, contextKey, session, {
|
|
1243
1505
|
action: "prompt_started",
|
|
1244
1506
|
status: "ok",
|
|
@@ -1268,6 +1530,13 @@ export function createBot(config, registry) {
|
|
|
1268
1530
|
status: "ok",
|
|
1269
1531
|
description: envelope.description,
|
|
1270
1532
|
});
|
|
1533
|
+
appendTelegramActivity(ctx, contextKey, session, {
|
|
1534
|
+
status: "completed",
|
|
1535
|
+
type: "prompt_completed",
|
|
1536
|
+
prompt: envelope.description,
|
|
1537
|
+
actor: envelope.activityActor,
|
|
1538
|
+
durationMs: promptStartedAt ? Date.now() - promptStartedAt : undefined,
|
|
1539
|
+
});
|
|
1271
1540
|
}
|
|
1272
1541
|
catch (error) {
|
|
1273
1542
|
progress.status = "failed";
|
|
@@ -1278,6 +1547,16 @@ export function createBot(config, registry) {
|
|
|
1278
1547
|
description: envelope.description,
|
|
1279
1548
|
detail: progress.error,
|
|
1280
1549
|
});
|
|
1550
|
+
if (promptStartedAt) {
|
|
1551
|
+
appendTelegramActivity(ctx, contextKey, session, {
|
|
1552
|
+
status: "failed",
|
|
1553
|
+
type: "prompt_failed",
|
|
1554
|
+
prompt: envelope.description,
|
|
1555
|
+
detail: progress.error,
|
|
1556
|
+
actor: envelope.activityActor,
|
|
1557
|
+
durationMs: Date.now() - promptStartedAt,
|
|
1558
|
+
});
|
|
1559
|
+
}
|
|
1281
1560
|
progress.completedAt = Date.now();
|
|
1282
1561
|
progress.updatedAt = progress.completedAt;
|
|
1283
1562
|
stopTyping();
|
|
@@ -1368,6 +1647,15 @@ export function createBot(config, registry) {
|
|
|
1368
1647
|
source: "turn",
|
|
1369
1648
|
};
|
|
1370
1649
|
await deliverArtifactReport(ctx, chatId, report, messageThreadId);
|
|
1650
|
+
const contextKey = contextKeyFromCtx(ctx);
|
|
1651
|
+
const session = contextKey ? registry.get(contextKey) : undefined;
|
|
1652
|
+
if (contextKey && session) {
|
|
1653
|
+
appendTelegramActivity(ctx, contextKey, session, {
|
|
1654
|
+
status: "info",
|
|
1655
|
+
type: "artifacts_sent",
|
|
1656
|
+
detail: formatArtifactSummary(report.artifacts, report.skippedCount, report.omittedCount),
|
|
1657
|
+
});
|
|
1658
|
+
}
|
|
1371
1659
|
await pruneArtifacts(workspace);
|
|
1372
1660
|
};
|
|
1373
1661
|
const deliverArtifactReport = async (ctx, chatId, report, messageThreadId) => {
|
|
@@ -1564,6 +1852,11 @@ export function createBot(config, registry) {
|
|
|
1564
1852
|
}
|
|
1565
1853
|
const receivedText = `Received ${stagedFiles.length} media group file${stagedFiles.length === 1 ? "" : "s"}${skippedCount > 0 ? ` (${skippedCount} skipped)` : ""}.`;
|
|
1566
1854
|
await safeReply(pending.ctx, escapeHTML(receivedText), { fallbackText: receivedText });
|
|
1855
|
+
appendTelegramActivity(pending.ctx, pending.contextKey, pending.session, {
|
|
1856
|
+
status: "info",
|
|
1857
|
+
type: "attachment_staged",
|
|
1858
|
+
detail: receivedText,
|
|
1859
|
+
});
|
|
1567
1860
|
await sendChatActionSafe(pending.ctx.api, pending.chatId, "typing", pending.messageThreadId).catch(() => { });
|
|
1568
1861
|
const promptInput = {
|
|
1569
1862
|
stagedFileInstructions: buildFileInstructions(stagedFiles, outDir),
|
|
@@ -1593,6 +1886,7 @@ export function createBot(config, registry) {
|
|
|
1593
1886
|
checkAgentAuthStatus,
|
|
1594
1887
|
isTopicContext,
|
|
1595
1888
|
replyChannelAction,
|
|
1889
|
+
commandService,
|
|
1596
1890
|
});
|
|
1597
1891
|
registerTelegramAgentCommands({
|
|
1598
1892
|
bot,
|
|
@@ -1609,6 +1903,13 @@ export function createBot(config, registry) {
|
|
|
1609
1903
|
startAgentLogout,
|
|
1610
1904
|
hostLoginCommand,
|
|
1611
1905
|
hostLogoutCommand,
|
|
1906
|
+
appendActivity: (ctx, input) => appendActivity({
|
|
1907
|
+
source: "telegram",
|
|
1908
|
+
...input,
|
|
1909
|
+
threadId: input.threadId ?? null,
|
|
1910
|
+
workspace: input.workspace ?? config.workspace,
|
|
1911
|
+
actor: input.actor ?? telegramActivityActor(ctx),
|
|
1912
|
+
}),
|
|
1612
1913
|
});
|
|
1613
1914
|
registerTelegramPreferenceCommands({
|
|
1614
1915
|
bot,
|
|
@@ -1641,6 +1942,7 @@ export function createBot(config, registry) {
|
|
|
1641
1942
|
getEffectiveVoiceLanguage,
|
|
1642
1943
|
isVoiceTranscribeOnly,
|
|
1643
1944
|
replyChannelAction,
|
|
1945
|
+
commandService,
|
|
1644
1946
|
});
|
|
1645
1947
|
registerTelegramOperationalCommands({
|
|
1646
1948
|
bot,
|
|
@@ -1654,10 +1956,34 @@ export function createBot(config, registry) {
|
|
|
1654
1956
|
getExternalActivity,
|
|
1655
1957
|
isAdminUser,
|
|
1656
1958
|
auditContext,
|
|
1959
|
+
getLockOwner: (ctx) => {
|
|
1960
|
+
const authUser = getAuthenticatedUser(ctx);
|
|
1961
|
+
if (!authUser) {
|
|
1962
|
+
return null;
|
|
1963
|
+
}
|
|
1964
|
+
return {
|
|
1965
|
+
userId: authUser.user.id,
|
|
1966
|
+
label: authUser.user.displayName || authUser.user.email,
|
|
1967
|
+
channel: "telegram",
|
|
1968
|
+
channelUserId: ctx.from?.id !== undefined ? String(ctx.from.id) : undefined,
|
|
1969
|
+
};
|
|
1970
|
+
},
|
|
1657
1971
|
updateSessionMetadata,
|
|
1658
1972
|
});
|
|
1659
1973
|
registerTelegramSupportCommands({ bot, config, auditLog, agentUpdates, getUserRole, audit });
|
|
1660
|
-
registerTelegramUpdateCommands({
|
|
1974
|
+
registerTelegramUpdateCommands({
|
|
1975
|
+
bot,
|
|
1976
|
+
agentUpdates,
|
|
1977
|
+
replyChannelAction,
|
|
1978
|
+
startTelegramAgentUpdate,
|
|
1979
|
+
appendActivity: (ctx, input) => appendActivity({
|
|
1980
|
+
source: "telegram",
|
|
1981
|
+
...input,
|
|
1982
|
+
threadId: input.threadId ?? null,
|
|
1983
|
+
workspace: input.workspace ?? config.workspace,
|
|
1984
|
+
actor: input.actor ?? telegramActivityActor(ctx),
|
|
1985
|
+
}),
|
|
1986
|
+
});
|
|
1661
1987
|
bot.command("new", async (ctx) => {
|
|
1662
1988
|
const chatId = ctx.chat?.id;
|
|
1663
1989
|
if (!chatId) {
|
|
@@ -1686,6 +2012,14 @@ export function createBot(config, registry) {
|
|
|
1686
2012
|
try {
|
|
1687
2013
|
const info = await session.newThread();
|
|
1688
2014
|
updateSessionMetadata(contextKey, session);
|
|
2015
|
+
appendTelegramActivity(ctx, contextKey, session, {
|
|
2016
|
+
status: "info",
|
|
2017
|
+
type: "session_new",
|
|
2018
|
+
threadId: info.threadId,
|
|
2019
|
+
workspace: info.workspace,
|
|
2020
|
+
agentId: info.agentId,
|
|
2021
|
+
detail: info.workspace,
|
|
2022
|
+
});
|
|
1689
2023
|
const label = isTopicContext(contextKey) ? "New thread created for this topic." : "New thread created.";
|
|
1690
2024
|
const policyLine = renderWorkspacePolicyLine(info.workspace, config);
|
|
1691
2025
|
const plainText = [label, policyLine, "", renderSessionInfoPlain(info)].filter((line) => line !== undefined).join("\n");
|
|
@@ -1723,12 +2057,22 @@ export function createBot(config, registry) {
|
|
|
1723
2057
|
if (busy.kind === "external") {
|
|
1724
2058
|
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.`;
|
|
1725
2059
|
await safeReply(ctx, escapeHTML(text), { fallbackText: text });
|
|
2060
|
+
appendTelegramActivity(ctx, contextKey, session, {
|
|
2061
|
+
status: "failed",
|
|
2062
|
+
type: "prompt_abort_rejected",
|
|
2063
|
+
detail: text,
|
|
2064
|
+
});
|
|
1726
2065
|
return;
|
|
1727
2066
|
}
|
|
1728
2067
|
await session.abort();
|
|
1729
2068
|
await safeReply(ctx, escapeHTML("Aborted current operation"), {
|
|
1730
2069
|
fallbackText: "Aborted current operation",
|
|
1731
2070
|
});
|
|
2071
|
+
appendTelegramActivity(ctx, contextKey, session, {
|
|
2072
|
+
status: "aborted",
|
|
2073
|
+
type: "prompt_aborted",
|
|
2074
|
+
detail: "Abort requested from Telegram.",
|
|
2075
|
+
});
|
|
1732
2076
|
}
|
|
1733
2077
|
catch (error) {
|
|
1734
2078
|
await safeReply(ctx, `<b>Failed:</b> ${escapeHTML(friendlyErrorText(error))}`, {
|
|
@@ -1777,6 +2121,8 @@ export function createBot(config, registry) {
|
|
|
1777
2121
|
drainQueuedPrompts,
|
|
1778
2122
|
handleUserPrompt,
|
|
1779
2123
|
auditContext,
|
|
2124
|
+
activityActor: telegramActivityActor,
|
|
2125
|
+
appendActivity: appendTelegramActivity,
|
|
1780
2126
|
});
|
|
1781
2127
|
registerTelegramArtifactCommands({
|
|
1782
2128
|
bot,
|
|
@@ -1784,6 +2130,13 @@ export function createBot(config, registry) {
|
|
|
1784
2130
|
getContextSession,
|
|
1785
2131
|
deliverArtifactReport,
|
|
1786
2132
|
deliverArtifactReportZip,
|
|
2133
|
+
appendActivity: (ctx, input) => appendActivity({
|
|
2134
|
+
source: "telegram",
|
|
2135
|
+
...input,
|
|
2136
|
+
threadId: input.threadId ?? null,
|
|
2137
|
+
workspace: input.workspace ?? config.workspace,
|
|
2138
|
+
actor: input.actor ?? telegramActivityActor(ctx),
|
|
2139
|
+
}),
|
|
1787
2140
|
});
|
|
1788
2141
|
bot.command("session", async (ctx) => {
|
|
1789
2142
|
const contextSession = await getContextSession(ctx, { deferThreadStart: true });
|
|
@@ -1878,6 +2231,14 @@ export function createBot(config, registry) {
|
|
|
1878
2231
|
try {
|
|
1879
2232
|
const info = session.handback();
|
|
1880
2233
|
updateSessionMetadata(contextKey, session);
|
|
2234
|
+
appendTelegramActivity(ctx, contextKey, session, {
|
|
2235
|
+
status: "info",
|
|
2236
|
+
type: "handback",
|
|
2237
|
+
threadId: info.threadId,
|
|
2238
|
+
workspace: info.workspace,
|
|
2239
|
+
agentId: idOf(session.getInfo()),
|
|
2240
|
+
detail: info.command ?? info.threadId ?? "handback",
|
|
2241
|
+
});
|
|
1881
2242
|
if (!info.threadId) {
|
|
1882
2243
|
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."), {
|
|
1883
2244
|
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.",
|
|
@@ -1973,6 +2334,14 @@ export function createBot(config, registry) {
|
|
|
1973
2334
|
try {
|
|
1974
2335
|
const info = await session.switchSession(threadId);
|
|
1975
2336
|
updateSessionMetadata(contextKey, session);
|
|
2337
|
+
appendTelegramActivity(ctx, contextKey, session, {
|
|
2338
|
+
status: "info",
|
|
2339
|
+
type: "session_attach",
|
|
2340
|
+
threadId: info.threadId,
|
|
2341
|
+
workspace: info.workspace,
|
|
2342
|
+
agentId: info.agentId,
|
|
2343
|
+
detail: threadId,
|
|
2344
|
+
});
|
|
1976
2345
|
const policyLine = renderWorkspacePolicyLine(info.workspace, config);
|
|
1977
2346
|
const html = ["<b>Attached to thread.</b>", policyLine ? `<i>${escapeHTML(policyLine)}</i>` : undefined, "", renderSessionInfoHTML(info)].filter((line) => line !== undefined).join("\n");
|
|
1978
2347
|
const plain = ["Attached to thread.", policyLine, "", renderSessionInfoPlain(info)].filter((line) => line !== undefined).join("\n");
|
|
@@ -2019,6 +2388,14 @@ export function createBot(config, registry) {
|
|
|
2019
2388
|
try {
|
|
2020
2389
|
const info = await session.switchSession(threadId);
|
|
2021
2390
|
updateSessionMetadata(contextKey, session);
|
|
2391
|
+
appendTelegramActivity(ctx, contextKey, session, {
|
|
2392
|
+
status: "info",
|
|
2393
|
+
type: "session_switch",
|
|
2394
|
+
threadId: info.threadId,
|
|
2395
|
+
workspace: info.workspace,
|
|
2396
|
+
agentId: info.agentId,
|
|
2397
|
+
detail: threadId,
|
|
2398
|
+
});
|
|
2022
2399
|
const policyLine = renderWorkspacePolicyLine(info.workspace, config);
|
|
2023
2400
|
const html = ["<b>Switched thread.</b>", policyLine ? `<i>${escapeHTML(policyLine)}</i>` : undefined, "", renderSessionInfoHTML(info)].filter((line) => line !== undefined).join("\n");
|
|
2024
2401
|
const plain = ["Switched thread.", policyLine, "", renderSessionInfoPlain(info)].filter((line) => line !== undefined).join("\n");
|
|
@@ -2105,6 +2482,12 @@ export function createBot(config, registry) {
|
|
|
2105
2482
|
return;
|
|
2106
2483
|
}
|
|
2107
2484
|
const pinned = registry.pinThread(contextKey, threadId);
|
|
2485
|
+
appendTelegramActivity(ctx, contextKey, session, {
|
|
2486
|
+
status: "info",
|
|
2487
|
+
type: "session_pinned",
|
|
2488
|
+
threadId,
|
|
2489
|
+
detail: threadId,
|
|
2490
|
+
});
|
|
2108
2491
|
await safeReply(ctx, `<b>Pinned thread:</b> <code>${escapeHTML(threadId)}</code>\n<b>Total pinned:</b> <code>${pinned.length}</code>`, {
|
|
2109
2492
|
fallbackText: `Pinned thread: ${threadId}\nTotal pinned: ${pinned.length}`,
|
|
2110
2493
|
});
|
|
@@ -2125,6 +2508,12 @@ export function createBot(config, registry) {
|
|
|
2125
2508
|
return;
|
|
2126
2509
|
}
|
|
2127
2510
|
const pinned = registry.unpinThread(contextKey, threadId);
|
|
2511
|
+
appendTelegramActivity(ctx, contextKey, session, {
|
|
2512
|
+
status: "info",
|
|
2513
|
+
type: "session_unpinned",
|
|
2514
|
+
threadId,
|
|
2515
|
+
detail: threadId,
|
|
2516
|
+
});
|
|
2128
2517
|
await safeReply(ctx, `<b>Unpinned thread:</b> <code>${escapeHTML(threadId)}</code>\n<b>Total pinned:</b> <code>${pinned.length}</code>`, {
|
|
2129
2518
|
fallbackText: `Unpinned thread: ${threadId}\nTotal pinned: ${pinned.length}`,
|
|
2130
2519
|
});
|
|
@@ -2240,6 +2629,14 @@ export function createBot(config, registry) {
|
|
|
2240
2629
|
const result = session.setFastMode(nextFastMode);
|
|
2241
2630
|
updateSessionMetadata(contextKey, session);
|
|
2242
2631
|
const info = session.getInfo();
|
|
2632
|
+
appendTelegramActivity(ctx, contextKey, session, {
|
|
2633
|
+
status: "info",
|
|
2634
|
+
type: "fast_mode_changed",
|
|
2635
|
+
threadId: info.threadId,
|
|
2636
|
+
workspace: info.workspace,
|
|
2637
|
+
agentId: info.agentId,
|
|
2638
|
+
detail: result.enabled ? "on" : "off",
|
|
2639
|
+
});
|
|
2243
2640
|
const plain = [
|
|
2244
2641
|
`Fast mode: ${result.enabled ? "on" : "off"}`,
|
|
2245
2642
|
`Launch profile: ${result.profile.label} (${formatLaunchProfileBehavior(result.profile)})`,
|
|
@@ -2352,6 +2749,15 @@ export function createBot(config, registry) {
|
|
|
2352
2749
|
});
|
|
2353
2750
|
}
|
|
2354
2751
|
const session = registry.get(pending.contextKey);
|
|
2752
|
+
if (session) {
|
|
2753
|
+
appendTelegramActivity(ctx, pending.contextKey, session, {
|
|
2754
|
+
status: "aborted",
|
|
2755
|
+
type: "prompt_approval_denied",
|
|
2756
|
+
prompt: pending.prompt.description,
|
|
2757
|
+
detail: approvalId,
|
|
2758
|
+
actor: pending.prompt.activityActor,
|
|
2759
|
+
});
|
|
2760
|
+
}
|
|
2355
2761
|
if (chatId && session) {
|
|
2356
2762
|
void drainQueuedPrompts(ctx, pending.contextKey, chatId, session).catch((error) => {
|
|
2357
2763
|
console.error("Failed to drain queue after approval denial:", error);
|
|
@@ -2370,6 +2776,13 @@ export function createBot(config, registry) {
|
|
|
2370
2776
|
fallbackText: `Approved prompt ${approvalId}.`,
|
|
2371
2777
|
});
|
|
2372
2778
|
}
|
|
2779
|
+
appendTelegramActivity(ctx, pending.contextKey, contextSession.session, {
|
|
2780
|
+
status: "info",
|
|
2781
|
+
type: "prompt_approval_approved",
|
|
2782
|
+
prompt: pending.prompt.description,
|
|
2783
|
+
detail: approvalId,
|
|
2784
|
+
actor: pending.prompt.activityActor,
|
|
2785
|
+
});
|
|
2373
2786
|
await handleUserPrompt(ctx, pending.contextKey, chatId ?? parseContextKey(pending.contextKey).chatId, contextSession.session, pending.prompt, {
|
|
2374
2787
|
approved: true,
|
|
2375
2788
|
});
|
|
@@ -2413,6 +2826,14 @@ export function createBot(config, registry) {
|
|
|
2413
2826
|
try {
|
|
2414
2827
|
const info = await session.switchSession(threadId);
|
|
2415
2828
|
updateSessionMetadata(contextKey, session);
|
|
2829
|
+
appendTelegramActivity(ctx, contextKey, session, {
|
|
2830
|
+
status: "info",
|
|
2831
|
+
type: "session_switch",
|
|
2832
|
+
threadId: info.threadId,
|
|
2833
|
+
workspace: info.workspace,
|
|
2834
|
+
agentId: info.agentId,
|
|
2835
|
+
detail: threadId,
|
|
2836
|
+
});
|
|
2416
2837
|
const policyLine = renderWorkspacePolicyLine(info.workspace, config);
|
|
2417
2838
|
const plainText = ["Switched session.", policyLine, "", renderSessionInfoPlain(info)].filter((line) => line !== undefined).join("\n");
|
|
2418
2839
|
const html = ["<b>Switched session.</b>", policyLine ? `<i>${escapeHTML(policyLine)}</i>` : undefined, "", renderSessionInfoHTML(info)].filter((line) => line !== undefined).join("\n");
|
|
@@ -2475,6 +2896,14 @@ export function createBot(config, registry) {
|
|
|
2475
2896
|
try {
|
|
2476
2897
|
const info = await session.newThread(workspace);
|
|
2477
2898
|
updateSessionMetadata(contextKey, session);
|
|
2899
|
+
appendTelegramActivity(ctx, contextKey, session, {
|
|
2900
|
+
status: "info",
|
|
2901
|
+
type: "session_new",
|
|
2902
|
+
threadId: info.threadId,
|
|
2903
|
+
workspace: info.workspace,
|
|
2904
|
+
agentId: info.agentId,
|
|
2905
|
+
detail: workspace,
|
|
2906
|
+
});
|
|
2478
2907
|
const label = isTopicContext(contextKey) ? "New thread created for this topic." : "New thread created.";
|
|
2479
2908
|
const policyLine = renderWorkspacePolicyLine(info.workspace, config);
|
|
2480
2909
|
const plainText = [label, policyLine, "", renderSessionInfoPlain(info)].filter((line) => line !== undefined).join("\n");
|
|
@@ -2570,6 +2999,14 @@ export function createBot(config, registry) {
|
|
|
2570
2999
|
session.setLaunchProfile(profile.id);
|
|
2571
3000
|
updateSessionMetadata(contextKey, session);
|
|
2572
3001
|
const info = session.getInfo();
|
|
3002
|
+
appendTelegramActivity(ctx, contextKey, session, {
|
|
3003
|
+
status: "info",
|
|
3004
|
+
type: "launch_profile_changed",
|
|
3005
|
+
threadId: info.threadId,
|
|
3006
|
+
workspace: info.workspace,
|
|
3007
|
+
agentId: info.agentId,
|
|
3008
|
+
detail: info.launchProfileLabel,
|
|
3009
|
+
});
|
|
2573
3010
|
const html = [
|
|
2574
3011
|
`<b>Launch profile set to</b> <code>${escapeHTML(info.launchProfileLabel)}</code>`,
|
|
2575
3012
|
`<b>Behavior:</b> <code>${escapeHTML(info.launchProfileBehavior)}</code>`,
|
|
@@ -2632,6 +3069,14 @@ export function createBot(config, registry) {
|
|
|
2632
3069
|
session.setLaunchProfile(profile.id);
|
|
2633
3070
|
updateSessionMetadata(contextKey, session);
|
|
2634
3071
|
const info = session.getInfo();
|
|
3072
|
+
appendTelegramActivity(ctx, contextKey, session, {
|
|
3073
|
+
status: "info",
|
|
3074
|
+
type: "launch_profile_changed",
|
|
3075
|
+
threadId: info.threadId,
|
|
3076
|
+
workspace: info.workspace,
|
|
3077
|
+
agentId: info.agentId,
|
|
3078
|
+
detail: info.launchProfileLabel,
|
|
3079
|
+
});
|
|
2635
3080
|
await ctx.answerCallbackQuery({ text: `Launch set to ${info.launchProfileLabel}` });
|
|
2636
3081
|
const html = [
|
|
2637
3082
|
`<b>Launch profile set to</b> <code>${escapeHTML(info.launchProfileLabel)}</code>`,
|
|
@@ -2678,6 +3123,15 @@ export function createBot(config, registry) {
|
|
|
2678
3123
|
try {
|
|
2679
3124
|
const result = await session.setModelForCurrentSession(slug);
|
|
2680
3125
|
updateSessionMetadata(contextKey, session);
|
|
3126
|
+
const info = session.getInfo();
|
|
3127
|
+
appendTelegramActivity(ctx, contextKey, session, {
|
|
3128
|
+
status: "info",
|
|
3129
|
+
type: "model_changed",
|
|
3130
|
+
threadId: info.threadId,
|
|
3131
|
+
workspace: info.workspace,
|
|
3132
|
+
agentId: info.agentId,
|
|
3133
|
+
detail: result.value,
|
|
3134
|
+
});
|
|
2681
3135
|
const scope = formatAgentSettingScope(session.getInfo(), result.appliedToActiveThread);
|
|
2682
3136
|
const html = `<b>Model set to</b> <code>${escapeHTML(result.value)}</code> — ${escapeHTML(scope)}.`;
|
|
2683
3137
|
const plainText = `Model set to ${result.value} — ${scope}.`;
|
|
@@ -2724,6 +3178,15 @@ export function createBot(config, registry) {
|
|
|
2724
3178
|
pendingEffortButtons.delete(contextKey);
|
|
2725
3179
|
const result = await session.setReasoningEffortForCurrentSession(effort);
|
|
2726
3180
|
updateSessionMetadata(contextKey, session);
|
|
3181
|
+
const info = session.getInfo();
|
|
3182
|
+
appendTelegramActivity(ctx, contextKey, session, {
|
|
3183
|
+
status: "info",
|
|
3184
|
+
type: "reasoning_changed",
|
|
3185
|
+
threadId: info.threadId,
|
|
3186
|
+
workspace: info.workspace,
|
|
3187
|
+
agentId: info.agentId,
|
|
3188
|
+
detail: result.value,
|
|
3189
|
+
});
|
|
2727
3190
|
const label = agentReasoningLabel(idOf(session.getInfo()));
|
|
2728
3191
|
const scope = formatAgentSettingScope(session.getInfo(), result.appliedToActiveThread);
|
|
2729
3192
|
const html = `⚡ ${escapeHTML(label)} set to <code>${escapeHTML(effort)}</code> — ${escapeHTML(scope)}.`;
|
|
@@ -2783,12 +3246,24 @@ export function createBot(config, registry) {
|
|
|
2783
3246
|
}
|
|
2784
3247
|
const preview = trimLine(transcript.replace(/\s+/g, " "), 100);
|
|
2785
3248
|
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)})` });
|
|
3249
|
+
appendTelegramActivity(ctx, contextKey, session, {
|
|
3250
|
+
status: "info",
|
|
3251
|
+
type: "voice_transcribed",
|
|
3252
|
+
prompt: preview,
|
|
3253
|
+
detail: result.backend,
|
|
3254
|
+
durationMs: result.durationMs,
|
|
3255
|
+
});
|
|
2786
3256
|
}
|
|
2787
3257
|
catch (error) {
|
|
2788
3258
|
const note = "Voice uses faster-whisper/parakeet locally or OPENAI_API_KEY for cloud transcription, not CODEX_API_KEY.";
|
|
2789
3259
|
await safeReply(ctx, `<b>Transcription failed:</b>\n${escapeHTML(friendlyErrorText(error))}\n\n<i>${escapeHTML(note)}</i>`, {
|
|
2790
3260
|
fallbackText: `Transcription failed:\n${friendlyErrorText(error)}\n\n${note}`,
|
|
2791
3261
|
});
|
|
3262
|
+
appendTelegramActivity(ctx, contextKey, session, {
|
|
3263
|
+
status: "failed",
|
|
3264
|
+
type: "voice_transcription_failed",
|
|
3265
|
+
detail: friendlyErrorText(error),
|
|
3266
|
+
});
|
|
2792
3267
|
return;
|
|
2793
3268
|
}
|
|
2794
3269
|
finally {
|
|
@@ -2883,6 +3358,11 @@ export function createBot(config, registry) {
|
|
|
2883
3358
|
if (caption) {
|
|
2884
3359
|
promptInput.text = caption;
|
|
2885
3360
|
}
|
|
3361
|
+
appendTelegramActivity(ctx, contextKey, session, {
|
|
3362
|
+
status: "info",
|
|
3363
|
+
type: "attachment_staged",
|
|
3364
|
+
detail: stagedPhoto.safeName,
|
|
3365
|
+
});
|
|
2886
3366
|
await setReaction(ctx, "👀");
|
|
2887
3367
|
try {
|
|
2888
3368
|
await handleUserPrompt(ctx, contextKey, chatId, session, toPromptEnvelope(promptInput, outDir));
|
|
@@ -2965,6 +3445,11 @@ export function createBot(config, registry) {
|
|
|
2965
3445
|
await safeReply(ctx, `📎 <b>Received:</b> <code>${escapeHTML(stagedFile.safeName)}</code>`, {
|
|
2966
3446
|
fallbackText: `📎 Received: ${stagedFile.safeName}`,
|
|
2967
3447
|
});
|
|
3448
|
+
appendTelegramActivity(ctx, contextKey, session, {
|
|
3449
|
+
status: "info",
|
|
3450
|
+
type: "attachment_staged",
|
|
3451
|
+
detail: stagedFile.safeName,
|
|
3452
|
+
});
|
|
2968
3453
|
// Keep typing visible during the gap between staging and prompt execution
|
|
2969
3454
|
await sendChatActionSafe(ctx.api, chatId, "typing", ctx.message?.message_thread_id).catch(() => { });
|
|
2970
3455
|
const outDir = outboxPath(workspace, turnId);
|