grok-telegram-bot 2.5.0 → 2.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.
@@ -18,6 +18,7 @@ import { estimateProgress } from "../render/progress-estimate.js";
18
18
  import { stripTelegramActionFences } from "../render/telegram-bridge.js";
19
19
  import { truncateMiddle } from "../render/truncate.js";
20
20
  import { safeEdit, safeSend } from "../bot/telegram-io.js";
21
+ import { outboundThreadExtra } from "../forum/thread.js";
21
22
 
22
23
  const SOFT_LIMIT = 3500;
23
24
  /** Display budget for a thinking block (middle-truncated; session context keeps all). */
@@ -31,6 +32,18 @@ interface Seg {
31
32
  toolId?: string;
32
33
  }
33
34
 
35
+ export interface StreamerOptions {
36
+ /** Chat-like mode: drop thoughts/tools/plan; only stream agent prose. */
37
+ proseOnly?: boolean;
38
+ /** When false, never render a progress bar (manager chat). Default true. */
39
+ showProgressBar?: boolean;
40
+ /**
41
+ * Pre-posted message id to edit in place (e.g. General "Thinking…" placeholder).
42
+ * Avoids a separate bubble when the first real tokens arrive.
43
+ */
44
+ seedMessageId?: number;
45
+ }
46
+
34
47
  export class ResponseStreamer {
35
48
  private readonly segs: Seg[] = [];
36
49
  private sealedIdx = 0;
@@ -54,6 +67,8 @@ export class ResponseStreamer {
54
67
  * the progress bar when set — done / in-progress / pending steps.
55
68
  */
56
69
  private planMarkdown: string | undefined;
70
+ private readonly proseOnly: boolean;
71
+ private readonly showProgressBar: boolean;
57
72
 
58
73
  constructor(
59
74
  private readonly api: Api,
@@ -68,7 +83,12 @@ export class ResponseStreamer {
68
83
  private readonly turnStartedAt = Date.now(),
69
84
  /** Forum topic thread — required so stream edits land in the right topic. */
70
85
  private readonly messageThreadId?: number,
71
- ) {}
86
+ opts?: StreamerOptions,
87
+ ) {
88
+ this.proseOnly = !!opts?.proseOnly;
89
+ this.showProgressBar = opts?.showProgressBar !== false;
90
+ if (opts?.seedMessageId !== undefined) this.liveId = opts.seedMessageId;
91
+ }
72
92
 
73
93
  /** Replace the hashtag footer (used after a logical fork swaps the session id
74
94
  * mid-turn, so the streamed response carries the NEW session's tags). */
@@ -76,6 +96,16 @@ export class ResponseStreamer {
76
96
  this.footer = footer;
77
97
  }
78
98
 
99
+ /** Seed/replace the live bubble id (General Thinking… placeholder). */
100
+ seedLiveMessage(messageId: number): void {
101
+ this.liveId = messageId;
102
+ }
103
+
104
+ /** Current live Telegram message id (for attaching suggestions after finalize). */
105
+ get liveMessageId(): number | undefined {
106
+ return this.liveId;
107
+ }
108
+
79
109
  /** "\n\n<footer>" appended to every finished message bubble (e.g. hashtags). */
80
110
  private footerSuffix(): string {
81
111
  return this.footer ? `\n\n${this.footer}` : "";
@@ -94,6 +124,7 @@ export class ResponseStreamer {
94
124
  * and notifying the owner on change. Agent markers are authoritative: once
95
125
  * one arrives, the bot fallback stops contributing. */
96
126
  private setProgressValue(pct: number, fromAgent: boolean): void {
127
+ if (!this.showProgressBar) return;
97
128
  if (fromAgent) this.agentReported = true;
98
129
  const next = Math.max(this.progress ?? 0, Math.round(pct));
99
130
  if (next === this.progress) return;
@@ -108,7 +139,7 @@ export class ResponseStreamer {
108
139
  /** Advance the fallback estimate from real activity signals, but only while
109
140
  * the agent itself hasn't reported a value. No-op when fallback is off. */
110
141
  private applyFallback(): void {
111
- if (!this.fallbackEnabled || this.agentReported) return;
142
+ if (!this.showProgressBar || !this.fallbackEnabled || this.agentReported) return;
112
143
  const est = estimateProgress({
113
144
  toolCalls: this.toolCalls,
114
145
  outputChars: this.outChars,
@@ -121,12 +152,13 @@ export class ResponseStreamer {
121
152
  /** Called when the turn finishes successfully: if the agent never reported
122
153
  * its own progress, fill the fallback bar to 100. No-op otherwise. */
123
154
  completeFallback(): void {
124
- if (!this.fallbackEnabled || this.agentReported) return;
155
+ if (!this.showProgressBar || !this.fallbackEnabled || this.agentReported) return;
125
156
  this.setProgressValue(100, false);
126
157
  }
127
158
 
128
159
  private threadExtra(): Record<string, unknown> {
129
- return this.messageThreadId !== undefined ? { message_thread_id: this.messageThreadId } : {};
160
+ // Never send message_thread_id=1 (General) — Telegram rejects it.
161
+ return outboundThreadExtra(this.messageThreadId);
130
162
  }
131
163
 
132
164
  /** reply_parameters threading EVERY message of the turn to the user's prompt,
@@ -148,7 +180,7 @@ export class ResponseStreamer {
148
180
  }
149
181
 
150
182
  appendThought(text: string): void {
151
- if (!text) return;
183
+ if (!text || this.proseOnly) return;
152
184
  this.thoughtChars += text.length;
153
185
  this.merge("think", text);
154
186
  this.schedule();
@@ -159,7 +191,7 @@ export class ResponseStreamer {
159
191
  * for ACP tool calls that stream progress/output under a stable toolCallId.
160
192
  */
161
193
  addTool(rawMarkdown: string): void {
162
- if (!rawMarkdown) return;
194
+ if (!rawMarkdown || this.proseOnly) return;
163
195
  this.toolCalls += 1;
164
196
  this.segs.push({ kind: "tool", text: rawMarkdown });
165
197
  this.schedule();
@@ -172,6 +204,7 @@ export class ResponseStreamer {
172
204
  */
173
205
  /** Replace the live plan board (or clear with empty/undefined). */
174
206
  setPlan(markdown: string | undefined): void {
207
+ if (this.proseOnly) return;
175
208
  const next = markdown?.trim() ? markdown.trim() : undefined;
176
209
  if (next === this.planMarkdown) return;
177
210
  this.planMarkdown = next;
@@ -179,7 +212,7 @@ export class ResponseStreamer {
179
212
  }
180
213
 
181
214
  upsertTool(toolId: string | undefined, rawMarkdown: string): void {
182
- if (!rawMarkdown) return;
215
+ if (!rawMarkdown || this.proseOnly) return;
183
216
  const id = (toolId || "").trim();
184
217
  if (id) {
185
218
  // Replace any existing segment with this id (newest first; includes rare
@@ -204,8 +237,9 @@ export class ResponseStreamer {
204
237
  this.schedule();
205
238
  }
206
239
 
240
+ /** True when agent prose/tools/thoughts were appended (not just a seed bubble). */
207
241
  get hasOutput(): boolean {
208
- return this.liveId !== undefined || this.segs.some((s) => s.text.trim().length > 0);
242
+ return this.segs.some((s) => s.text.trim().length > 0);
209
243
  }
210
244
 
211
245
  async finalize(): Promise<void> {
@@ -213,6 +247,8 @@ export class ResponseStreamer {
213
247
  if (this.timer) clearTimeout(this.timer);
214
248
  this.timer = undefined;
215
249
  await this.flush(true);
250
+ // Seeded Thinking… with zero agent text: leave the placeholder for the
251
+ // owner to replace (fallback reply) or keep while bridge results chain.
216
252
  }
217
253
 
218
254
  // ── internals ──────────────────────────────────────────────────────────────
@@ -251,8 +287,8 @@ export class ResponseStreamer {
251
287
  // Live bubble: body → plan (always above progress) → progress bar → footer.
252
288
  const parts: string[] = [];
253
289
  if (base.trim()) parts.push(base);
254
- if (this.planMarkdown) parts.push(this.planMarkdown);
255
- if (this.progress !== undefined) parts.push(progressBar(this.progress));
290
+ if (!this.proseOnly && this.planMarkdown) parts.push(this.planMarkdown);
291
+ if (this.showProgressBar && this.progress !== undefined) parts.push(progressBar(this.progress));
256
292
  if (parts.length === 0) return;
257
293
  const src = `${parts.join("\n\n")}${this.footerSuffix()}`;
258
294
  const rendered = toTelegramMarkdown(src);