Skip to content

Commit 5f5bf41

Browse files
committed
telegram: lazy-init tool traces, add typing indicator, remove budget
Three changes to the Telegram integration: 1. Typing indicator (SendChatAction every 4s) replaces the premature '🤔 Thinking...' message. Telegram shows the native '...' status while the agent runs. 2. Tool trace message is now created lazily — only when the first tool_call fires — not at conversation start. 3. Remove daily token budget enforcement. The bot package retains the CheckDailyBudget API for programmatic use but the Telegram integration no longer blocks users mid- conversation after accumulated usage crosses the threshold.
1 parent b3a5643 commit 5f5bf41

11 files changed

Lines changed: 3410 additions & 22 deletions

File tree

cmd/odek/telegram.go

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ func telegramCmd(args []string) error {
5151

5252
// 4. Create bot client.
5353
bot := telegram.NewBot(cfg.Token)
54-
bot.SetDailyTokenBudget(cfg.DailyTokenBudget)
5554

5655
// 4b. Create logger.
5756
level := telegram.ParseLogLevel(cfg.LogLevel)
@@ -294,17 +293,32 @@ func handleChatMessage(
294293

295294
rend := render.New(os.Stderr, false).WithModel(modelLabel)
296295

296+
// ── Typing Indicator ────────────────────────────────────────────
297+
// Send "typing" action every 4s while the agent runs (Telegram shows
298+
// it for ~5s). Stops when the goroutine's context is cancelled.
299+
typingDone := make(chan struct{})
300+
defer close(typingDone)
301+
go func() {
302+
ticker := time.NewTicker(4 * time.Second)
303+
defer ticker.Stop()
304+
for {
305+
select {
306+
case <-ticker.C:
307+
bot.SendChatAction(chatID, "typing")
308+
case <-typingDone:
309+
return
310+
}
311+
}
312+
}()
313+
297314
// ── Tool Tracing ───────────────────────────────────────────────
298315
// Single editable message showing live tool execution progress.
316+
// The message is created lazily — only when the first tool call
317+
// fires, not before. This avoids the premature "🤔 Thinking…" spam.
299318
var traceMsgID int
300319
var traceMu sync.Mutex
301320
traceLines := make([]string, 0, 8)
302321

303-
// Send initial thinking message.
304-
if initMsg, err := bot.SendMessage(chatID, "🤔 Thinking...", nil); err == nil {
305-
traceMsgID = initMsg.ID
306-
}
307-
308322
// truncate shortens a string for display, appending "…" if trimmed.
309323
truncate := func(s string, max int) string {
310324
if len(s) > max {
@@ -331,6 +345,15 @@ func handleChatMessage(
331345
ToolEventHandler: func(event string, name string, data string) {
332346
traceMu.Lock()
333347
defer traceMu.Unlock()
348+
349+
// Lazy-init: create the trace message on the first tool call.
350+
if traceMsgID == 0 && event == "tool_call" {
351+
if msg, err := bot.SendMessage(chatID, "🔧 …", nil); err == nil {
352+
traceMsgID = msg.ID
353+
} else {
354+
return
355+
}
356+
}
334357
if traceMsgID == 0 {
335358
return
336359
}
@@ -343,8 +366,6 @@ func handleChatMessage(
343366
bot.EditMessageText(chatID, traceMsgID, strings.Join(traceLines, "\n"), nil)
344367

345368
case "tool_result":
346-
// Replace the last line's ⏳ with a completion marker
347-
// and the result size instead of the actual content.
348369
sizeLabel := fmt.Sprintf("%dB", len(data))
349370
if len(data) > 1024 {
350371
sizeLabel = fmt.Sprintf("%dKB", len(data)/1024)
@@ -386,20 +407,6 @@ func handleChatMessage(
386407
return
387408
}
388409

389-
// Check daily token budget.
390-
totalTokens := int64(agent.TotalInputTokens() + agent.TotalOutputTokens())
391-
if err := bot.CheckDailyBudget(totalTokens); err != nil {
392-
fmt.Fprintf(os.Stderr, "odek telegram: %v\n", err)
393-
reportError(bot, chatID, "Daily token budget exceeded. Usage for today has been tracked and will be enforced going forward.")
394-
// Still save the session so the conversation isn't lost.
395-
cs.Messages = updatedMessages
396-
cs.TurnCount++
397-
if err := sessionManager.Save(chatID, cs.Messages); err != nil {
398-
fmt.Fprintf(os.Stderr, "odek telegram: session save: %v\n", err)
399-
}
400-
return
401-
}
402-
403410
// Save the updated session messages.
404411
cs.Messages = updatedMessages
405412
cs.TurnCount++

coverage2.out

Lines changed: 624 additions & 0 deletions
Large diffs are not rendered by default.

coverage3.out

Lines changed: 624 additions & 0 deletions
Large diffs are not rendered by default.

internal/telegram/approver_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,33 @@ func TestResetTrust(t *testing.T) {
168168

169169
// ── Test newID uniqueness ──────────────────────────────────────────────────
170170

171+
// ── Test SetLogger ────────────────────────────────────────────────────────
172+
173+
func TestTelegramApprover_SetLogger_Nil(t *testing.T) {
174+
ts := testServer(t, nil)
175+
defer ts.Close()
176+
bot := testBot(t, ts)
177+
178+
a := NewTelegramApprover(bot, 1)
179+
// Initially uses NopLogger.
180+
a.SetLogger(nil)
181+
// After nil, should use NopLogger (no panic).
182+
a.SetLogger(nil)
183+
}
184+
185+
func TestTelegramApprover_SetLogger_Valid(t *testing.T) {
186+
ts := testServer(t, nil)
187+
defer ts.Close()
188+
bot := testBot(t, ts)
189+
190+
a := NewTelegramApprover(bot, 1)
191+
logger := NewFileLogger(LogDebug, "")
192+
a.SetLogger(logger)
193+
// Just verify no panic — the logger is set internally.
194+
}
195+
196+
// ── Test newID uniqueness ──────────────────────────────────────────────────
197+
171198
func TestNewID_Unique(t *testing.T) {
172199
ts := testServer(t, nil)
173200
defer ts.Close()

internal/telegram/bot.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,3 +420,15 @@ func (b *Bot) GetMe() (*User, error) {
420420
}
421421
return &user, nil
422422
}
423+
424+
// SendChatAction tells the user that the bot is doing something on their
425+
// behalf (e.g., "typing"). The action is shown as a status in the chat for
426+
// ~5 seconds or until the next message is sent. Callers should re-send every
427+
// 4 seconds for long-running operations.
428+
func (b *Bot) SendChatAction(chatID int64, action string) error {
429+
params := map[string]any{
430+
"chat_id": chatID,
431+
"action": action,
432+
}
433+
return b.doJSON("sendChatAction", params, nil)
434+
}

0 commit comments

Comments
 (0)