15 Commits

Author SHA1 Message Date
wassname a2cfde2b25 Translate dashes to underscores for Telegram bot commands
Pi command names like compact-now were silently dropped from
setMyCommands because Telegram requires names to match
^[a-z0-9_]{1,32}$ - no dashes. They showed up in /help (which
iterates pi.getCommands() directly) but were untypable via
autocomplete and, even when typed manually, fell through to the
agent as plain text rather than dispatching.

Now: register the underscore-translated form with Telegram, show the
underscore form in /help so it matches what the user sees in the
menu, and on dispatch rewrite the message text from /foo_bar back to
/foo-bar before enqueueing so the harness's slash-command parser
finds the registered command.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-01 18:58:32 +08:00
wassname b2444fd3cd Stop fetch retry from killing the polling loop
Two real bugs surfaced after the original retry helper:

1. Healthy long-polls were tripping our 15s per-attempt timeout. The
   getUpdates request asks Telegram for a 30s server-side long-poll, so
   our internal timeout aborted every healthy connection and turned it
   into ABORT_ERR -> retry -> exhausted -> "disconnected", with no
   auto-reconnect. The fix: long-poll bypasses the retry helper and uses
   a 60s per-attempt timeout, since the poll loop already retries by
   re-entering after sleep().

2. Our own internal AbortController timeout produced a DOMException
   AbortError indistinguishable from a caller-abort. The poll loop's
   shouldStopTelegramPolling treated that as "user wants to stop" and
   exited. Now fetchWithRetry normalizes its own timeout into a tagged
   Error with code ATTEMPT_TIMEOUT, so only real caller-aborts surface
   as AbortError upstream.

Also: per-attempt timeout default dropped 15s -> 5s, retry budget
dropped from [500, 2000] to [500] (so 2 attempts, not 3) for outbound
sends, since they serialize and a long retry tail makes the bridge feel
hung.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-01 18:51:35 +08:00
wassname b866807112 Survive transient Telegram fetch failures
Wrap fetch calls in callTelegram, callTelegramMultipart, and
downloadTelegramFile with bounded retry (max 3 attempts, 500ms/2000ms
backoff with jitter) on transient network codes and a per-attempt 15s
AbortController timeout so a stuck connection can't wedge the bridge
indefinitely. HTTP 4xx/5xx still surface as before.

Add a process-level unhandledRejection handler so a stray rejection from
a fire-and-forget void f() (e.g. timer-driven preview flush) can't crash
the host under Node 22's default unhandledRejection=throw - which is how
a remote session got dropped. Also tighten the two timer-callback voids
that can fire after their turn's try/catch is gone.
2026-04-29 22:34:59 +08:00
wassname 4677aea56b Show tool results in Telegram trace 2026-04-24 22:24:12 +08:00
wassname 3ce880d5bc Register pi commands in Telegram menu 2026-04-24 21:56:07 +08:00
wassname 14798607c6 Fix Telegram stop recovery and trace output 2026-04-24 21:23:50 +08:00
wassname 39da73ce3c feat: improve model selection UX and fix queue/status behaviors
- Add search/filtering to the `/model` command with multi-word matching
- Finalize partial stream previews (e.g. thinking blocks) on turn abort instead of clearing them
- Dynamically format low-cost `$ value` metrics up to 5 decimal places in status outputs
- Update queue tests to expect text-turn plans for aborted turns with partial text
2026-04-23 12:42:35 +08:00
wassname 29ac175e79 refactor: per-block Telegram emission, displayMode replaces traceVisible
- Each pi output block (thinking, tool_call, tool_result) emits a
  separate Telegram message as it arrives; text content still streams
  in a single edit-in-place preview message
- Replace traceVisible bool with displayMode: "full" | "compact" | "text"
  (/trace cycles through all three; compact is default)
- Add renderBlockMessage() to lib/rendering.ts; delete old transcript
  aggregation helpers (buildTelegramAssistantTranscriptMarkdown, etc.)
- registerTelegramBotCommands now pulls extension commands from
  pi.getCommands() in addition to local bot commands
- Update rendering tests for new block rendering API

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 15:29:05 +08:00
wassname 15fa661b7a security: require pre-configured allowedUserId, remove auto-pair
The first-DM auto-pair behavior combined with ! shell passthrough meant
the first account to DM the bot gained arbitrary shell access. This
removes that footgun entirely.

- allowedUserId must be set before polling starts; missing config blocks
  polling with a TUI warning rather than silently accepting any sender
- TELEGRAM_ALLOWED_USER_ID env var is read on session start and
  overwrites the saved config value, so rotating the allowed user is a
  restart away
- /telegram-setup now prompts for a numeric user ID after the bot token
  if one is not already configured
- Denied senders receive an auth error reply; their numeric ID is also
  logged to the pi TUI as a warning so operators can identify themselves
  on a fresh install without needing @userinfobot
- Dropped the {kind: "pair"} authorization state entirely; undefined
  allowedUserId now produces deny, not pair
- Removed pairTelegramUserIfNeeded, shouldPair, shouldNotifyPaired

Existing installs with allowedUserId already in telegram.json are
unaffected. Fresh installs require explicit configuration.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 06:04:56 +08:00
wassname c28436503f slash 2026-04-21 18:09:09 +08:00
wassname 5da34c33a2 skip [telegram] prefix for ! commands so pi handles them natively as shell
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 21:38:51 +08:00
wassname c2d25b31fe wip 2026-04-19 16:08:23 +08:00
wassname 5aa37b7a99 tool verbose 2026-04-19 15:41:15 +08:00
LLB e7e3e86550 0.2.7: priority symbol 2026-04-17 14:33:55 +04:00
LLB 8dcf761937 0.2.0: refactor into domain modules 2026-04-11 11:32:44 +04:00