@grinev/opencode-telegram-bot 0.21.2 → 0.22.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +0 -15
- package/README.md +15 -8
- package/dist/app/formatters/summary-formatter.js +16 -0
- package/dist/app/managers/summary-aggregation-manager.js +174 -10
- package/dist/app/services/file-download-service.js +51 -2
- package/dist/app/services/scheduled-task-executor-service.js +22 -7
- package/dist/app/stores/settings-store.js +46 -4
- package/dist/bot/callbacks/callback-router.js +4 -1
- package/dist/bot/callbacks/scheduled-task-callback-handler.js +20 -1
- package/dist/bot/callbacks/settings-callback-handler.js +105 -0
- package/dist/bot/commands/definitions.js +1 -1
- package/dist/bot/commands/settings-command.js +10 -0
- package/dist/bot/commands/status-command.js +7 -2
- package/dist/bot/handlers/document-handler.js +1 -1
- package/dist/bot/handlers/media-group-handler.js +1 -1
- package/dist/bot/handlers/prompt.js +3 -3
- package/dist/bot/handlers/voice-handler.js +5 -2
- package/dist/bot/menus/inline-menu.js +3 -1
- package/dist/bot/menus/settings-menu.js +52 -0
- package/dist/bot/messages/thinking-message.js +1 -4
- package/dist/bot/messages/thinking-rendering.js +66 -0
- package/dist/bot/routers/command-router.js +2 -2
- package/dist/bot/services/event-subscription-service.js +304 -30
- package/dist/bot/streaming/compact-progress-streamer.js +168 -0
- package/dist/bot/streaming/finalize-assistant-response.js +9 -3
- package/dist/bot/streaming/response-streamer.js +16 -1
- package/dist/config.js +0 -15
- package/dist/i18n/ar.js +31 -6
- package/dist/i18n/de.js +32 -7
- package/dist/i18n/en.js +32 -7
- package/dist/i18n/es.js +32 -7
- package/dist/i18n/fr.js +32 -7
- package/dist/i18n/ru.js +32 -7
- package/dist/i18n/zh.js +32 -7
- package/dist/opencode/process.js +40 -3
- package/package.json +1 -1
- package/dist/bot/commands/tts-command.js +0 -13
|
@@ -5,9 +5,10 @@ import { InputFile } from "grammy";
|
|
|
5
5
|
import { config } from "../../config.js";
|
|
6
6
|
import { t } from "../../i18n/index.js";
|
|
7
7
|
import { summaryAggregator } from "../../app/managers/summary-aggregation-manager.js";
|
|
8
|
-
import { formatToolInfo } from "../../app/formatters/summary-formatter.js";
|
|
8
|
+
import { formatCompactToolActivity, formatToolInfo } from "../../app/formatters/summary-formatter.js";
|
|
9
9
|
import { renderSubagentCards } from "../../app/formatters/subagent-formatter.js";
|
|
10
10
|
import { ToolMessageBatcher } from "../../app/formatters/tool-message-batcher.js";
|
|
11
|
+
import { getCompactOutputMode, getResponseStreamingMode, getSendDiffFileAttachments, getShowAssistantRunFooter, getShowThinkingContent, } from "../../app/stores/settings-store.js";
|
|
11
12
|
import { getCurrentSession } from "../../app/services/session-service.js";
|
|
12
13
|
import { ingestSessionInfoForCache } from "../../app/services/session-cache-service.js";
|
|
13
14
|
import { logger } from "../../utils/logger.js";
|
|
@@ -27,10 +28,12 @@ import { scheduledTaskRuntime } from "../../app/services/scheduled-task-runtime-
|
|
|
27
28
|
import { assistantRunState } from "../../app/managers/assistant-run-state-manager.js";
|
|
28
29
|
import { ResponseStreamer } from "../streaming/response-streamer.js";
|
|
29
30
|
import { ToolCallStreamer } from "../streaming/tool-call-streamer.js";
|
|
31
|
+
import { CompactProgressStreamer } from "../streaming/compact-progress-streamer.js";
|
|
30
32
|
import { attachManager } from "../../app/managers/attach-manager.js";
|
|
31
33
|
import { markAttachedSessionBusy, markAttachedSessionIdle, } from "../../app/services/attach-service.js";
|
|
32
34
|
import { externalUserInputSuppressionManager } from "../../app/managers/external-input-suppression-manager.js";
|
|
33
35
|
import { prepareAssistantFinalStreamingPayload, prepareAssistantStreamingPayload, renderAssistantFinalPartsSafe, } from "../messages/assistant-rendering.js";
|
|
36
|
+
import { makeThinkingPayloadExpandable, prepareThinkingStreamingPayload, } from "../messages/thinking-rendering.js";
|
|
34
37
|
import { deliverExternalUserInputNotification } from "../messages/external-user-input-notification.js";
|
|
35
38
|
import { backgroundSessionTracker, } from "../../app/managers/background-session-manager.js";
|
|
36
39
|
import { buildBackgroundSessionOpenKeyboard } from "../menus/session-selection-menu.js";
|
|
@@ -41,13 +44,15 @@ import { clearAllInteractionState } from "../../app/managers/interaction-manager
|
|
|
41
44
|
import { stopEventListening, subscribeToEvents } from "../../opencode/events.js";
|
|
42
45
|
const TELEGRAM_DOCUMENT_CAPTION_MAX_LENGTH = 1024;
|
|
43
46
|
const RESPONSE_STREAM_THROTTLE_MS = config.bot.responseStreamThrottleMs;
|
|
44
|
-
const RESPONSE_STREAMING_MODE = config.bot.responseStreamingMode;
|
|
45
47
|
const RESPONSE_STREAM_TEXT_LIMIT = 3800;
|
|
46
48
|
const SESSION_RETRY_PREFIX = "🔁";
|
|
47
49
|
const SUBAGENT_STREAM_PREFIX = "🧩";
|
|
48
50
|
const __filename = fileURLToPath(import.meta.url);
|
|
49
51
|
const __dirname = path.dirname(__filename);
|
|
50
52
|
const TEMP_DIR = path.join(__dirname, "..", "..", ".tmp");
|
|
53
|
+
function isCompactProgressMode() {
|
|
54
|
+
return getCompactOutputMode();
|
|
55
|
+
}
|
|
51
56
|
export function createEventSubscriptionService() {
|
|
52
57
|
return new EventSubscriptionService();
|
|
53
58
|
}
|
|
@@ -55,10 +60,16 @@ class EventSubscriptionService {
|
|
|
55
60
|
botInstance = null;
|
|
56
61
|
chatIdInstance = null;
|
|
57
62
|
nextDraftId = 1;
|
|
63
|
+
thinkingStreamingPayloads = new Map();
|
|
58
64
|
sessionCompletionTasks = new Map();
|
|
59
|
-
|
|
65
|
+
compactProgressFinalizationTasks = new Map();
|
|
66
|
+
assistantEditResponseStreamer;
|
|
67
|
+
assistantDraftResponseStreamer;
|
|
68
|
+
thinkingResponseStreamer;
|
|
69
|
+
assistantResponseStreamModes = new Map();
|
|
60
70
|
toolCallStreamer;
|
|
61
71
|
toolMessageBatcher;
|
|
72
|
+
compactProgressStreamer;
|
|
62
73
|
constructor() {
|
|
63
74
|
this.toolMessageBatcher = new ToolMessageBatcher({
|
|
64
75
|
sendText: async (sessionId, text) => {
|
|
@@ -100,9 +111,48 @@ class EventSubscriptionService {
|
|
|
100
111
|
}
|
|
101
112
|
},
|
|
102
113
|
});
|
|
103
|
-
this.
|
|
104
|
-
|
|
114
|
+
this.assistantEditResponseStreamer = this.createResponseStreamer("edit");
|
|
115
|
+
this.assistantDraftResponseStreamer = this.createResponseStreamer("draft");
|
|
116
|
+
this.thinkingResponseStreamer = this.createResponseStreamer("edit");
|
|
117
|
+
setResponseStreamerForReconciliation({
|
|
118
|
+
hasActiveStream: (sessionId) => this.hasActiveAssistantResponseStream(sessionId),
|
|
119
|
+
});
|
|
105
120
|
setPromptResponseModeClearerForReconciliation(clearPromptResponseMode);
|
|
121
|
+
this.compactProgressStreamer = new CompactProgressStreamer({
|
|
122
|
+
throttleMs: RESPONSE_STREAM_THROTTLE_MS,
|
|
123
|
+
sendText: async (sessionId, text) => {
|
|
124
|
+
if (!this.botInstance || !this.chatIdInstance || this.chatIdInstance <= 0) {
|
|
125
|
+
throw new Error("Bot context missing for compact progress send");
|
|
126
|
+
}
|
|
127
|
+
const currentSession = getCurrentSession();
|
|
128
|
+
if (!currentSession || currentSession.id !== sessionId) {
|
|
129
|
+
throw new Error(`Compact progress session mismatch for send: ${sessionId}`);
|
|
130
|
+
}
|
|
131
|
+
const sentMessage = await this.botInstance.api.sendMessage(this.chatIdInstance, text, {
|
|
132
|
+
disable_notification: true,
|
|
133
|
+
});
|
|
134
|
+
return sentMessage.message_id;
|
|
135
|
+
},
|
|
136
|
+
editText: async (sessionId, messageId, text) => {
|
|
137
|
+
if (!this.botInstance || !this.chatIdInstance || this.chatIdInstance <= 0) {
|
|
138
|
+
throw new Error("Bot context missing for compact progress edit");
|
|
139
|
+
}
|
|
140
|
+
const currentSession = getCurrentSession();
|
|
141
|
+
if (!currentSession || currentSession.id !== sessionId) {
|
|
142
|
+
throw new Error(`Compact progress session mismatch for edit: ${sessionId}`);
|
|
143
|
+
}
|
|
144
|
+
try {
|
|
145
|
+
await this.botInstance.api.editMessageText(this.chatIdInstance, messageId, text);
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
const errorMessage = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
|
|
149
|
+
if (errorMessage.includes("message is not modified")) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
throw error;
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
});
|
|
106
156
|
this.toolCallStreamer = new ToolCallStreamer({
|
|
107
157
|
throttleMs: RESPONSE_STREAM_THROTTLE_MS,
|
|
108
158
|
sendText: async (sessionId, text) => {
|
|
@@ -163,9 +213,12 @@ class EventSubscriptionService {
|
|
|
163
213
|
clearRuntimeState(reason) {
|
|
164
214
|
backgroundSessionTracker.clear();
|
|
165
215
|
this.nextDraftId = 1;
|
|
166
|
-
this.
|
|
216
|
+
this.clearAllResponseStreams(reason);
|
|
167
217
|
this.toolCallStreamer.clearAll(reason);
|
|
168
218
|
this.toolMessageBatcher.clearAll(reason);
|
|
219
|
+
this.compactProgressStreamer.clearAll(reason);
|
|
220
|
+
this.compactProgressFinalizationTasks.clear();
|
|
221
|
+
this.thinkingStreamingPayloads.clear();
|
|
169
222
|
this.sessionCompletionTasks.clear();
|
|
170
223
|
assistantRunState.clearAll(reason);
|
|
171
224
|
}
|
|
@@ -189,7 +242,10 @@ class EventSubscriptionService {
|
|
|
189
242
|
summaryAggregator.setOnCleared(() => {
|
|
190
243
|
this.toolMessageBatcher.clearAll("summary_aggregator_clear");
|
|
191
244
|
this.toolCallStreamer.clearAll("summary_aggregator_clear");
|
|
192
|
-
this.
|
|
245
|
+
this.clearAllResponseStreams("summary_aggregator_clear");
|
|
246
|
+
this.compactProgressStreamer.clearAll("summary_aggregator_clear");
|
|
247
|
+
this.compactProgressFinalizationTasks.clear();
|
|
248
|
+
this.thinkingStreamingPayloads.clear();
|
|
193
249
|
});
|
|
194
250
|
summaryAggregator.setOnPartial((sessionId, messageId, messageText) => {
|
|
195
251
|
if (!this.botInstance || !this.chatIdInstance) {
|
|
@@ -199,21 +255,43 @@ class EventSubscriptionService {
|
|
|
199
255
|
if (!currentSession || currentSession.id !== sessionId) {
|
|
200
256
|
return;
|
|
201
257
|
}
|
|
258
|
+
if (isCompactProgressMode()) {
|
|
259
|
+
void this.finalizeCompactProgress(sessionId)
|
|
260
|
+
.then(() => {
|
|
261
|
+
const activeSession = getCurrentSession();
|
|
262
|
+
if (!activeSession || activeSession.id !== sessionId) {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
const preparedStreamPayload = this.prepareStreamingPayload(messageText);
|
|
266
|
+
if (!preparedStreamPayload) {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
preparedStreamPayload.sendOptions = { disable_notification: true };
|
|
270
|
+
preparedStreamPayload.editOptions = undefined;
|
|
271
|
+
this.enqueueAssistantResponse(sessionId, messageId, preparedStreamPayload);
|
|
272
|
+
})
|
|
273
|
+
.catch((error) => {
|
|
274
|
+
logger.error("[Bot] Failed to finalize compact progress before assistant stream", error);
|
|
275
|
+
});
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
202
278
|
const preparedStreamPayload = this.prepareStreamingPayload(messageText);
|
|
203
279
|
if (!preparedStreamPayload) {
|
|
204
280
|
return;
|
|
205
281
|
}
|
|
206
282
|
preparedStreamPayload.sendOptions = { disable_notification: true };
|
|
207
283
|
preparedStreamPayload.editOptions = undefined;
|
|
208
|
-
this.
|
|
284
|
+
this.enqueueAssistantResponse(sessionId, messageId, preparedStreamPayload);
|
|
209
285
|
});
|
|
210
286
|
summaryAggregator.setOnComplete((sessionId, messageId, messageText, completionInfo) => {
|
|
211
287
|
void this.enqueueSessionCompletionTask(sessionId, async () => {
|
|
212
288
|
if (!this.botInstance || !this.chatIdInstance) {
|
|
213
289
|
logger.error("Bot or chat ID not available for sending message");
|
|
214
290
|
clearPromptResponseMode(sessionId);
|
|
215
|
-
this.
|
|
291
|
+
this.clearAssistantResponseStream(sessionId, messageId, "bot_context_missing");
|
|
292
|
+
this.clearThinkingStream(sessionId, messageId, "bot_context_missing");
|
|
216
293
|
this.toolCallStreamer.clearSession(sessionId, "bot_context_missing");
|
|
294
|
+
this.compactProgressStreamer.clearSession(sessionId, "bot_context_missing");
|
|
217
295
|
assistantRunState.clearRun(sessionId, "bot_context_missing");
|
|
218
296
|
foregroundSessionState.markIdle(sessionId);
|
|
219
297
|
return;
|
|
@@ -221,8 +299,10 @@ class EventSubscriptionService {
|
|
|
221
299
|
const currentSession = getCurrentSession();
|
|
222
300
|
if (currentSession?.id !== sessionId) {
|
|
223
301
|
clearPromptResponseMode(sessionId);
|
|
224
|
-
this.
|
|
302
|
+
this.clearAssistantResponseStream(sessionId, messageId, "session_mismatch");
|
|
303
|
+
this.clearThinkingStream(sessionId, messageId, "session_mismatch");
|
|
225
304
|
this.toolCallStreamer.clearSession(sessionId, "session_mismatch");
|
|
305
|
+
this.compactProgressStreamer.clearSession(sessionId, "session_mismatch");
|
|
226
306
|
assistantRunState.clearRun(sessionId, "session_mismatch");
|
|
227
307
|
foregroundSessionState.markIdle(sessionId);
|
|
228
308
|
await scheduledTaskRuntime.flushDeferredDeliveries();
|
|
@@ -236,11 +316,18 @@ class EventSubscriptionService {
|
|
|
236
316
|
providerID: completionInfo.providerID,
|
|
237
317
|
modelID: completionInfo.modelID,
|
|
238
318
|
});
|
|
319
|
+
await this.completeThinkingStream(sessionId, messageId);
|
|
320
|
+
if (isCompactProgressMode()) {
|
|
321
|
+
await this.finalizeCompactProgress(sessionId);
|
|
322
|
+
}
|
|
323
|
+
const assistantResponseMode = this.getAssistantResponseStreamMode(sessionId, messageId);
|
|
239
324
|
await finalizeAssistantResponse({
|
|
240
325
|
sessionId,
|
|
241
326
|
messageId,
|
|
242
327
|
messageText,
|
|
243
|
-
responseStreamer:
|
|
328
|
+
responseStreamer: {
|
|
329
|
+
complete: (completeSessionId, completeMessageId, payload, options) => this.completeAssistantResponse(completeSessionId, completeMessageId, payload, options),
|
|
330
|
+
},
|
|
244
331
|
flushPendingServiceMessages: () => Promise.all([
|
|
245
332
|
this.toolMessageBatcher.flushSession(sessionId, "assistant_message_completed"),
|
|
246
333
|
this.toolCallStreamer.breakSession(sessionId, "assistant_message_completed"),
|
|
@@ -248,6 +335,7 @@ class EventSubscriptionService {
|
|
|
248
335
|
prepareStreamingPayload: this.prepareFinalStreamingPayload,
|
|
249
336
|
renderFinalParts: (text) => renderAssistantFinalPartsSafe(text),
|
|
250
337
|
getReplyKeyboard: this.getCurrentReplyKeyboard,
|
|
338
|
+
notifyFirstFinalPart: assistantResponseMode === "draft" && !getShowAssistantRunFooter(),
|
|
251
339
|
sendRenderedPart: async (part, options) => {
|
|
252
340
|
await sendRenderedBotPart({
|
|
253
341
|
api: botApi,
|
|
@@ -266,6 +354,8 @@ class EventSubscriptionService {
|
|
|
266
354
|
}
|
|
267
355
|
catch (err) {
|
|
268
356
|
clearPromptResponseMode(sessionId);
|
|
357
|
+
this.clearThinkingStream(sessionId, messageId, "assistant_finalize_failed");
|
|
358
|
+
this.compactProgressStreamer.clearSession(sessionId, "assistant_finalize_failed");
|
|
269
359
|
assistantRunState.clearRun(sessionId, "assistant_finalize_failed");
|
|
270
360
|
logger.error("Failed to send message to Telegram:", err);
|
|
271
361
|
logger.error("[Bot] CRITICAL: Stopping event processing due to error");
|
|
@@ -297,6 +387,22 @@ class EventSubscriptionService {
|
|
|
297
387
|
}
|
|
298
388
|
});
|
|
299
389
|
});
|
|
390
|
+
summaryAggregator.setOnRootToolUpdate((toolInfo) => {
|
|
391
|
+
if (!isCompactProgressMode()) {
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
const currentSession = getCurrentSession();
|
|
395
|
+
if (!currentSession || currentSession.id !== toolInfo.sessionId) {
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
const activity = this.getCompactToolActivity(toolInfo);
|
|
399
|
+
if (activity) {
|
|
400
|
+
this.compactProgressStreamer.updateActivity(toolInfo.sessionId, activity);
|
|
401
|
+
}
|
|
402
|
+
if ("status" in toolInfo.state && toolInfo.state.status === "completed") {
|
|
403
|
+
this.compactProgressStreamer.addToolCall(toolInfo.sessionId, toolInfo.callId);
|
|
404
|
+
}
|
|
405
|
+
});
|
|
300
406
|
summaryAggregator.setOnTool(async (toolInfo) => {
|
|
301
407
|
if (!this.botInstance || !this.chatIdInstance) {
|
|
302
408
|
logger.error("Bot or chat ID not available for sending tool notification");
|
|
@@ -306,11 +412,13 @@ class EventSubscriptionService {
|
|
|
306
412
|
if (!currentSession || currentSession.id !== toolInfo.sessionId) {
|
|
307
413
|
return;
|
|
308
414
|
}
|
|
309
|
-
|
|
415
|
+
if (isCompactProgressMode()) {
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
const shouldSendToolFileAttachment = toolInfo.hasFileAttachment &&
|
|
419
|
+
getSendDiffFileAttachments() &&
|
|
310
420
|
(toolInfo.tool === "write" || toolInfo.tool === "edit" || toolInfo.tool === "apply_patch");
|
|
311
|
-
if (
|
|
312
|
-
shouldIncludeToolInfoInFileCaption ||
|
|
313
|
-
toolInfo.tool === "task") {
|
|
421
|
+
if (shouldSendToolFileAttachment || toolInfo.tool === "task") {
|
|
314
422
|
return;
|
|
315
423
|
}
|
|
316
424
|
try {
|
|
@@ -327,7 +435,7 @@ class EventSubscriptionService {
|
|
|
327
435
|
if (!this.botInstance || !this.chatIdInstance) {
|
|
328
436
|
return;
|
|
329
437
|
}
|
|
330
|
-
if (
|
|
438
|
+
if (isCompactProgressMode()) {
|
|
331
439
|
return;
|
|
332
440
|
}
|
|
333
441
|
const currentSession = getCurrentSession();
|
|
@@ -354,7 +462,10 @@ class EventSubscriptionService {
|
|
|
354
462
|
if (!currentSession || currentSession.id !== fileInfo.sessionId) {
|
|
355
463
|
return;
|
|
356
464
|
}
|
|
357
|
-
if (
|
|
465
|
+
if (isCompactProgressMode()) {
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
if (!getSendDiffFileAttachments()) {
|
|
358
469
|
return;
|
|
359
470
|
}
|
|
360
471
|
try {
|
|
@@ -379,6 +490,9 @@ class EventSubscriptionService {
|
|
|
379
490
|
if (!currentSession || currentSession.id !== sessionId) {
|
|
380
491
|
return;
|
|
381
492
|
}
|
|
493
|
+
if (isCompactProgressMode()) {
|
|
494
|
+
this.compactProgressStreamer.updateWaitingForQuestion(sessionId);
|
|
495
|
+
}
|
|
382
496
|
await Promise.all([
|
|
383
497
|
this.toolMessageBatcher.flushSession(currentSession.id, "question_asked"),
|
|
384
498
|
this.toolCallStreamer.flushSession(currentSession.id, "question_asked"),
|
|
@@ -418,6 +532,9 @@ class EventSubscriptionService {
|
|
|
418
532
|
if (!currentSession || (!isCurrent && !isSubagent)) {
|
|
419
533
|
return;
|
|
420
534
|
}
|
|
535
|
+
if (isCompactProgressMode()) {
|
|
536
|
+
this.compactProgressStreamer.updateWaitingForPermission(currentSession.id);
|
|
537
|
+
}
|
|
421
538
|
await Promise.all([
|
|
422
539
|
this.toolMessageBatcher.flushSession(request.sessionID, "permission_asked"),
|
|
423
540
|
this.toolCallStreamer.flushSession(request.sessionID, "permission_asked"),
|
|
@@ -425,23 +542,63 @@ class EventSubscriptionService {
|
|
|
425
542
|
logger.info(`[Bot] Received permission request from agent: type=${request.permission}, requestID=${request.id}, subagent=${isSubagent}`);
|
|
426
543
|
await showPermissionRequest(this.botInstance.api, this.chatIdInstance, request);
|
|
427
544
|
});
|
|
428
|
-
summaryAggregator.setOnThinking(async (
|
|
545
|
+
summaryAggregator.setOnThinking(async (update) => {
|
|
429
546
|
if (!this.botInstance || !this.chatIdInstance) {
|
|
430
547
|
return;
|
|
431
548
|
}
|
|
432
549
|
const currentSession = getCurrentSession();
|
|
433
|
-
if (!currentSession || currentSession.id !== sessionId) {
|
|
550
|
+
if (!currentSession || currentSession.id !== update.sessionId) {
|
|
434
551
|
return;
|
|
435
552
|
}
|
|
436
|
-
logger.debug("[Bot] Agent
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
553
|
+
logger.debug("[Bot] Agent thinking update", {
|
|
554
|
+
sessionId: update.sessionId,
|
|
555
|
+
messageId: update.messageId,
|
|
556
|
+
sectionCount: update.sections.length,
|
|
557
|
+
isFirstUpdate: update.isFirstUpdate,
|
|
440
558
|
});
|
|
441
|
-
if (
|
|
559
|
+
if (isCompactProgressMode()) {
|
|
560
|
+
this.compactProgressStreamer.updateThinking(update.sessionId);
|
|
561
|
+
if (update.isFirstUpdate && pinnedMessageManager.isInitialized()) {
|
|
562
|
+
await pinnedMessageManager.refresh();
|
|
563
|
+
}
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
if (update.isFirstUpdate) {
|
|
567
|
+
void this.toolCallStreamer.breakSession(update.sessionId, "thinking_started").catch((error) => {
|
|
568
|
+
logger.error("[Bot] Failed to break tool stream before thinking message", error);
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
if (getShowThinkingContent()) {
|
|
572
|
+
const payload = prepareThinkingStreamingPayload(update.sections, RESPONSE_STREAM_TEXT_LIMIT, {
|
|
573
|
+
expandable: false,
|
|
574
|
+
});
|
|
575
|
+
if (payload) {
|
|
576
|
+
payload.sendOptions = { disable_notification: true };
|
|
577
|
+
payload.editOptions = undefined;
|
|
578
|
+
this.thinkingStreamingPayloads.set(this.getThinkingPayloadKey(update.sessionId, update.messageId), payload);
|
|
579
|
+
this.thinkingResponseStreamer.enqueue(update.sessionId, this.getThinkingStreamId(update.messageId), payload);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
else if (update.isFirstUpdate) {
|
|
583
|
+
deliverThinkingMessage(update.sessionId, this.toolMessageBatcher);
|
|
584
|
+
}
|
|
585
|
+
if (update.isFirstUpdate && pinnedMessageManager.isInitialized()) {
|
|
442
586
|
await pinnedMessageManager.refresh();
|
|
443
587
|
}
|
|
444
588
|
});
|
|
589
|
+
summaryAggregator.setOnThinkingFinished((sessionId, messageId) => {
|
|
590
|
+
if (!this.botInstance || !this.chatIdInstance) {
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
const currentSession = getCurrentSession();
|
|
594
|
+
if (!currentSession || currentSession.id !== sessionId) {
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
logger.debug("[Bot] Agent thinking finished", { sessionId, messageId });
|
|
598
|
+
void this.completeThinkingStream(sessionId, messageId).catch((error) => {
|
|
599
|
+
logger.error("[Bot] Failed to finalize thinking stream early", error);
|
|
600
|
+
});
|
|
601
|
+
});
|
|
445
602
|
summaryAggregator.setOnTokens(async (tokens, isCompleted) => {
|
|
446
603
|
if (!pinnedMessageManager.isInitialized()) {
|
|
447
604
|
return;
|
|
@@ -510,7 +667,7 @@ class EventSubscriptionService {
|
|
|
510
667
|
this.toolMessageBatcher.flushSession(sessionId, "session_idle"),
|
|
511
668
|
this.toolCallStreamer.flushSession(sessionId, "session_idle"),
|
|
512
669
|
]);
|
|
513
|
-
if (completedRun?.hasCompletedResponse) {
|
|
670
|
+
if (getShowAssistantRunFooter() && completedRun?.hasCompletedResponse) {
|
|
514
671
|
const agent = completedRun.actualAgent || completedRun.configuredAgent;
|
|
515
672
|
const providerID = completedRun.actualProviderID || completedRun.configuredProviderID;
|
|
516
673
|
const modelID = completedRun.actualModelID || completedRun.configuredModelID;
|
|
@@ -539,6 +696,7 @@ class EventSubscriptionService {
|
|
|
539
696
|
await markAttachedSessionIdle(sessionId);
|
|
540
697
|
if (!this.botInstance || !this.chatIdInstance) {
|
|
541
698
|
clearPromptResponseMode(sessionId);
|
|
699
|
+
this.compactProgressStreamer.clearSession(sessionId, "session_error_no_bot_context");
|
|
542
700
|
assistantRunState.clearRun(sessionId, "session_error_no_bot_context");
|
|
543
701
|
foregroundSessionState.markIdle(sessionId);
|
|
544
702
|
return;
|
|
@@ -546,14 +704,16 @@ class EventSubscriptionService {
|
|
|
546
704
|
const currentSession = getCurrentSession();
|
|
547
705
|
if (!currentSession || currentSession.id !== sessionId) {
|
|
548
706
|
clearPromptResponseMode(sessionId);
|
|
549
|
-
this.
|
|
707
|
+
this.clearAssistantResponseSession(sessionId, "session_error_not_current");
|
|
550
708
|
this.toolCallStreamer.clearSession(sessionId, "session_error_not_current");
|
|
709
|
+
this.compactProgressStreamer.clearSession(sessionId, "session_error_not_current");
|
|
551
710
|
assistantRunState.clearRun(sessionId, "session_error_not_current");
|
|
552
711
|
foregroundSessionState.markIdle(sessionId);
|
|
553
712
|
await scheduledTaskRuntime.flushDeferredDeliveries();
|
|
554
713
|
return;
|
|
555
714
|
}
|
|
556
|
-
this.
|
|
715
|
+
this.clearAssistantResponseSession(sessionId, "session_error");
|
|
716
|
+
this.compactProgressStreamer.clearSession(sessionId, "session_error");
|
|
557
717
|
clearPromptResponseMode(sessionId);
|
|
558
718
|
assistantRunState.clearRun(sessionId, "session_error");
|
|
559
719
|
await Promise.all([
|
|
@@ -586,6 +746,10 @@ class EventSubscriptionService {
|
|
|
586
746
|
if (!currentSession || currentSession.id !== sessionId) {
|
|
587
747
|
return;
|
|
588
748
|
}
|
|
749
|
+
if (isCompactProgressMode()) {
|
|
750
|
+
this.compactProgressStreamer.updateActivity(sessionId, t("progress.compact.retrying"));
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
589
753
|
const normalizedMessage = message.trim() || t("common.unknown_error");
|
|
590
754
|
const truncatedMessage = normalizedMessage.length > 3500
|
|
591
755
|
? `${normalizedMessage.slice(0, 3497)}...`
|
|
@@ -593,7 +757,12 @@ class EventSubscriptionService {
|
|
|
593
757
|
const retryMessage = t("bot.session_retry", { message: truncatedMessage });
|
|
594
758
|
this.toolCallStreamer.replaceByPrefix(sessionId, SESSION_RETRY_PREFIX, retryMessage);
|
|
595
759
|
});
|
|
596
|
-
summaryAggregator.setOnSessionDiff(async (
|
|
760
|
+
summaryAggregator.setOnSessionDiff(async (sessionId, diffs) => {
|
|
761
|
+
if (isCompactProgressMode()) {
|
|
762
|
+
for (const diff of diffs) {
|
|
763
|
+
this.compactProgressStreamer.addFileChange(sessionId, diff.file);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
597
766
|
if (!pinnedMessageManager.isInitialized()) {
|
|
598
767
|
return;
|
|
599
768
|
}
|
|
@@ -605,6 +774,12 @@ class EventSubscriptionService {
|
|
|
605
774
|
}
|
|
606
775
|
});
|
|
607
776
|
summaryAggregator.setOnFileChange((change) => {
|
|
777
|
+
if (isCompactProgressMode()) {
|
|
778
|
+
const currentSession = getCurrentSession();
|
|
779
|
+
if (currentSession) {
|
|
780
|
+
this.compactProgressStreamer.addFileChange(currentSession.id, change.file);
|
|
781
|
+
}
|
|
782
|
+
}
|
|
608
783
|
if (!pinnedMessageManager.isInitialized()) {
|
|
609
784
|
return;
|
|
610
785
|
}
|
|
@@ -648,8 +823,57 @@ class EventSubscriptionService {
|
|
|
648
823
|
logger.error("Failed to subscribe to events:", err);
|
|
649
824
|
});
|
|
650
825
|
};
|
|
651
|
-
|
|
652
|
-
|
|
826
|
+
getAssistantResponseStreamKey(sessionId, messageId) {
|
|
827
|
+
return `${sessionId}:${messageId}`;
|
|
828
|
+
}
|
|
829
|
+
getAssistantResponseStreamer(mode) {
|
|
830
|
+
return mode === "draft"
|
|
831
|
+
? this.assistantDraftResponseStreamer
|
|
832
|
+
: this.assistantEditResponseStreamer;
|
|
833
|
+
}
|
|
834
|
+
getAssistantResponseStreamMode(sessionId, messageId) {
|
|
835
|
+
return (this.assistantResponseStreamModes.get(this.getAssistantResponseStreamKey(sessionId, messageId)) ??
|
|
836
|
+
getResponseStreamingMode());
|
|
837
|
+
}
|
|
838
|
+
enqueueAssistantResponse(sessionId, messageId, payload) {
|
|
839
|
+
const key = this.getAssistantResponseStreamKey(sessionId, messageId);
|
|
840
|
+
const mode = this.getAssistantResponseStreamMode(sessionId, messageId);
|
|
841
|
+
this.assistantResponseStreamModes.set(key, mode);
|
|
842
|
+
this.getAssistantResponseStreamer(mode).enqueue(sessionId, messageId, payload);
|
|
843
|
+
}
|
|
844
|
+
async completeAssistantResponse(sessionId, messageId, payload, options) {
|
|
845
|
+
const key = this.getAssistantResponseStreamKey(sessionId, messageId);
|
|
846
|
+
const mode = this.getAssistantResponseStreamMode(sessionId, messageId);
|
|
847
|
+
const result = await this.getAssistantResponseStreamer(mode).complete(sessionId, messageId, payload, options);
|
|
848
|
+
this.assistantResponseStreamModes.delete(key);
|
|
849
|
+
return result;
|
|
850
|
+
}
|
|
851
|
+
clearAssistantResponseStream(sessionId, messageId, reason) {
|
|
852
|
+
this.assistantResponseStreamModes.delete(this.getAssistantResponseStreamKey(sessionId, messageId));
|
|
853
|
+
this.assistantEditResponseStreamer.clearMessage(sessionId, messageId, reason);
|
|
854
|
+
this.assistantDraftResponseStreamer.clearMessage(sessionId, messageId, reason);
|
|
855
|
+
}
|
|
856
|
+
clearAssistantResponseSession(sessionId, reason) {
|
|
857
|
+
for (const key of this.assistantResponseStreamModes.keys()) {
|
|
858
|
+
if (key.startsWith(`${sessionId}:`)) {
|
|
859
|
+
this.assistantResponseStreamModes.delete(key);
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
this.assistantEditResponseStreamer.clearSession(sessionId, reason);
|
|
863
|
+
this.assistantDraftResponseStreamer.clearSession(sessionId, reason);
|
|
864
|
+
}
|
|
865
|
+
clearAllResponseStreams(reason) {
|
|
866
|
+
this.assistantResponseStreamModes.clear();
|
|
867
|
+
this.assistantEditResponseStreamer.clearAll(reason);
|
|
868
|
+
this.assistantDraftResponseStreamer.clearAll(reason);
|
|
869
|
+
this.thinkingResponseStreamer.clearAll(reason);
|
|
870
|
+
}
|
|
871
|
+
hasActiveAssistantResponseStream(sessionId) {
|
|
872
|
+
return (this.assistantEditResponseStreamer.hasActiveStream(sessionId) ||
|
|
873
|
+
this.assistantDraftResponseStreamer.hasActiveStream(sessionId));
|
|
874
|
+
}
|
|
875
|
+
createResponseStreamer(mode) {
|
|
876
|
+
if (mode === "draft") {
|
|
653
877
|
return new ResponseStreamer({
|
|
654
878
|
throttleMs: RESPONSE_STREAM_THROTTLE_MS,
|
|
655
879
|
sendPart: async (part) => {
|
|
@@ -763,6 +987,37 @@ class EventSubscriptionService {
|
|
|
763
987
|
prepareFinalStreamingPayload(messageText) {
|
|
764
988
|
return prepareAssistantFinalStreamingPayload(messageText, RESPONSE_STREAM_TEXT_LIMIT);
|
|
765
989
|
}
|
|
990
|
+
getThinkingStreamId(messageId) {
|
|
991
|
+
return `thinking:${messageId}`;
|
|
992
|
+
}
|
|
993
|
+
getThinkingPayloadKey(sessionId, messageId) {
|
|
994
|
+
return `${sessionId}:${messageId}`;
|
|
995
|
+
}
|
|
996
|
+
clearThinkingStream(sessionId, messageId, reason) {
|
|
997
|
+
this.thinkingResponseStreamer.clearMessage(sessionId, this.getThinkingStreamId(messageId), reason);
|
|
998
|
+
this.thinkingStreamingPayloads.delete(this.getThinkingPayloadKey(sessionId, messageId));
|
|
999
|
+
}
|
|
1000
|
+
async completeThinkingStream(sessionId, messageId) {
|
|
1001
|
+
const key = this.getThinkingPayloadKey(sessionId, messageId);
|
|
1002
|
+
const payload = this.thinkingStreamingPayloads.get(key);
|
|
1003
|
+
const finalPayload = payload ? makeThinkingPayloadExpandable(payload) : undefined;
|
|
1004
|
+
const result = await this.thinkingResponseStreamer.complete(sessionId, this.getThinkingStreamId(messageId), finalPayload);
|
|
1005
|
+
this.thinkingStreamingPayloads.delete(key);
|
|
1006
|
+
if (result.streamed || !finalPayload) {
|
|
1007
|
+
return;
|
|
1008
|
+
}
|
|
1009
|
+
if (!this.botInstance || !this.chatIdInstance) {
|
|
1010
|
+
return;
|
|
1011
|
+
}
|
|
1012
|
+
for (const part of finalPayload.parts) {
|
|
1013
|
+
await sendRenderedBotPart({
|
|
1014
|
+
api: this.botInstance.api,
|
|
1015
|
+
chatId: this.chatIdInstance,
|
|
1016
|
+
part,
|
|
1017
|
+
options: finalPayload.sendOptions,
|
|
1018
|
+
});
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
766
1021
|
enqueueSessionCompletionTask(sessionId, task) {
|
|
767
1022
|
const previousTask = this.sessionCompletionTasks.get(sessionId) ?? Promise.resolve();
|
|
768
1023
|
const nextTask = previousTask
|
|
@@ -776,6 +1031,19 @@ class EventSubscriptionService {
|
|
|
776
1031
|
this.sessionCompletionTasks.set(sessionId, nextTask);
|
|
777
1032
|
return nextTask;
|
|
778
1033
|
}
|
|
1034
|
+
finalizeCompactProgress(sessionId) {
|
|
1035
|
+
const existingTask = this.compactProgressFinalizationTasks.get(sessionId);
|
|
1036
|
+
if (existingTask) {
|
|
1037
|
+
return existingTask;
|
|
1038
|
+
}
|
|
1039
|
+
const nextTask = this.compactProgressStreamer.finalize(sessionId).finally(() => {
|
|
1040
|
+
if (this.compactProgressFinalizationTasks.get(sessionId) === nextTask) {
|
|
1041
|
+
this.compactProgressFinalizationTasks.delete(sessionId);
|
|
1042
|
+
}
|
|
1043
|
+
});
|
|
1044
|
+
this.compactProgressFinalizationTasks.set(sessionId, nextTask);
|
|
1045
|
+
return nextTask;
|
|
1046
|
+
}
|
|
779
1047
|
getNextDraftId() {
|
|
780
1048
|
const id = this.nextDraftId;
|
|
781
1049
|
this.nextDraftId += 1;
|
|
@@ -787,6 +1055,12 @@ class EventSubscriptionService {
|
|
|
787
1055
|
}
|
|
788
1056
|
return "default";
|
|
789
1057
|
}
|
|
1058
|
+
getCompactToolActivity(toolInfo) {
|
|
1059
|
+
if (toolInfo.tool === "task") {
|
|
1060
|
+
return t("progress.compact.task");
|
|
1061
|
+
}
|
|
1062
|
+
return formatCompactToolActivity(toolInfo, 128);
|
|
1063
|
+
}
|
|
790
1064
|
formatShortSessionId(sessionId) {
|
|
791
1065
|
return sessionId.length <= 8 ? sessionId : sessionId.slice(0, 8);
|
|
792
1066
|
}
|