@pedrohnas/opencode-telegram 0.1.0 → 1.2.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.
Files changed (43) hide show
  1. package/.env.example +17 -0
  2. package/bunfig.toml +2 -0
  3. package/docs/PROGRESS.md +327 -0
  4. package/docs/mapping.md +326 -0
  5. package/docs/plans/phase-1.md +176 -0
  6. package/docs/plans/phase-2.md +235 -0
  7. package/docs/plans/phase-3.md +485 -0
  8. package/docs/plans/phase-4.md +566 -0
  9. package/docs/spec.md +2055 -0
  10. package/e2e/client.ts +24 -0
  11. package/e2e/helpers.ts +119 -0
  12. package/e2e/phase-0.test.ts +30 -0
  13. package/e2e/phase-1.test.ts +48 -0
  14. package/e2e/phase-2.test.ts +54 -0
  15. package/e2e/phase-3.test.ts +142 -0
  16. package/e2e/phase-4.test.ts +96 -0
  17. package/e2e/runner.ts +145 -0
  18. package/package.json +14 -12
  19. package/scripts/gen-session.ts +49 -0
  20. package/src/bot.test.ts +301 -0
  21. package/src/bot.ts +91 -0
  22. package/src/config.test.ts +130 -0
  23. package/src/config.ts +15 -0
  24. package/src/event-bus.test.ts +175 -0
  25. package/src/handlers/allowlist.test.ts +60 -0
  26. package/src/handlers/allowlist.ts +33 -0
  27. package/src/handlers/cancel.test.ts +105 -0
  28. package/src/handlers/permissions.test.ts +72 -0
  29. package/src/handlers/questions.test.ts +107 -0
  30. package/src/handlers/sessions.test.ts +479 -0
  31. package/src/handlers/sessions.ts +202 -0
  32. package/src/handlers/typing.test.ts +60 -0
  33. package/src/index.ts +26 -0
  34. package/src/pending-requests.test.ts +64 -0
  35. package/src/send/chunker.test.ts +74 -0
  36. package/src/send/draft-stream.test.ts +229 -0
  37. package/src/send/format.test.ts +143 -0
  38. package/src/send/tool-progress.test.ts +70 -0
  39. package/src/session-manager.test.ts +198 -0
  40. package/src/session-manager.ts +23 -0
  41. package/src/turn-manager.test.ts +155 -0
  42. package/src/turn-manager.ts +5 -0
  43. package/tsconfig.json +9 -0
package/.env.example ADDED
@@ -0,0 +1,17 @@
1
+ # Required: Telegram bot token from @BotFather
2
+ TELEGRAM_BOT_TOKEN=123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11
3
+
4
+ # Optional: OpenCode server URL (default: spawns local server)
5
+ # OPENCODE_URL=http://127.0.0.1:4096
6
+
7
+ # Optional: Project directory for OpenCode (default: cwd)
8
+ # OPENCODE_DIRECTORY=/path/to/project
9
+
10
+ # Optional: Use Telegram test environment
11
+ # TELEGRAM_TEST_ENV=1
12
+
13
+ # E2E test credentials (only needed for automated testing)
14
+ # TELEGRAM_API_ID=12345
15
+ # TELEGRAM_API_HASH=0123456789abcdef0123456789abcdef
16
+ # TELEGRAM_SESSION=saved-session-string
17
+ # TELEGRAM_BOT_USERNAME=my_bot
package/bunfig.toml ADDED
@@ -0,0 +1,2 @@
1
+ [test]
2
+ root = "./src"
@@ -0,0 +1,327 @@
1
+ # OpenCode Telegram Bot — Progress Log
2
+
3
+ ## Phase 0 — E2E Infrastructure + Bot Skeleton ✅
4
+
5
+ **Status:** Complete
6
+ **Date:** 2026-02-07
7
+
8
+ ### Delivered
9
+ - Project scaffolding inside monorepo (`packages/telegram/`)
10
+ - Grammy bot with `/start` handler
11
+ - Config from env vars with validation
12
+ - Graceful shutdown (SIGINT/SIGTERM)
13
+ - E2E infrastructure: gramjs userbot client, test helpers, runner
14
+ - Session string generation script (`scripts/gen-session.ts`)
15
+
16
+ ### Tests
17
+ | Type | Count | Status |
18
+ |------|-------|--------|
19
+ | Unit (bun test src/) | 12 | ✅ all pass |
20
+ | E2E (bun test ./e2e/phase-0.test.ts) | 2 | ✅ all pass |
21
+ | Manual (/start in Telegram) | 1 | ✅ working |
22
+
23
+ ### Files Created
24
+ ```
25
+ packages/telegram/
26
+ package.json
27
+ tsconfig.json
28
+ bunfig.toml
29
+ .env.example
30
+ .env ← credentials (gitignored)
31
+ .gitignore
32
+ src/
33
+ index.ts ← entry point
34
+ bot.ts ← Grammy bot + /start
35
+ bot.test.ts ← 4 tests
36
+ config.ts ← env parsing
37
+ config.test.ts ← 8 tests
38
+ e2e/
39
+ client.ts ← gramjs wrapper
40
+ helpers.ts ← sendAndWait, assertContains, clickInlineButton, etc.
41
+ runner.ts ← setup/teardown (spawn bot + connect client)
42
+ phase-0.test.ts ← 2 E2E tests
43
+ scripts/
44
+ gen-session.ts ← generate gramjs session string
45
+ ```
46
+
47
+ ### Lessons Learned
48
+ - `bunfig.toml`: `preload = []` is invalid, use `root = "./src"` instead
49
+ - Bun auto-loads `.env` — tests for default values need explicit `delete process.env.VAR`
50
+ - E2E runs in ~3s: gramjs connects, spawns bot, sends /start, verifies, tears down
51
+
52
+ ---
53
+
54
+ ## Phase 1 — Core Loop (MVP) ✅
55
+
56
+ **Status:** Complete
57
+ **Date:** 2026-02-07
58
+
59
+ ### Delivered
60
+ - **SDK client factory** (`sdk.ts`): spawns local OpenCode server from monorepo source, or connects to external
61
+ - **SessionManager** (`session-manager.ts`): LRU-bounded map with TTL, reverse lookup (sessionId → chatKey)
62
+ - **TurnManager** (`turn-manager.ts`): per-turn AbortController + timer tracking, clean abort
63
+ - **EventBus** (`event-bus.ts`): single SSE connection, routes events to Telegram chats
64
+ - **Markdown formatter** (`send/format.ts`): Markdown → Telegram HTML conversion
65
+ - **Message chunker** (`send/chunker.ts`): split messages at 4096 char boundary
66
+ - **Message handler**: text → SessionManager → SDK prompt → EventBus → formatted response
67
+ - **`/new` command**: removes mapping, creates fresh session
68
+ - **index.ts wiring**: SDK init → bot + managers → EventBus → graceful shutdown
69
+
70
+ ### Tests
71
+ | Type | Count | Status |
72
+ |------|-------|--------|
73
+ | Unit (bun test src/) | 76 | ✅ all pass |
74
+ | E2E Phase 0 (regression) | 2 | ✅ all pass |
75
+ | E2E Phase 1 | 3 | ✅ all pass |
76
+
77
+ ### Files Created
78
+ ```
79
+ src/
80
+ sdk.ts ← SDK client factory (spawn or connect)
81
+ session-manager.ts ← LRU Map<chatKey, SessionEntry>
82
+ session-manager.test.ts ← 13 tests
83
+ turn-manager.ts ← Per-turn lifecycle (AbortController)
84
+ turn-manager.test.ts ← 12 tests
85
+ event-bus.ts ← Single SSE connection + dispatcher
86
+ event-bus.test.ts ← 5 tests
87
+ send/
88
+ format.ts ← Markdown → Telegram HTML
89
+ format.test.ts ← 22 tests
90
+ chunker.ts ← Split messages at 4096 chars
91
+ chunker.test.ts ← 8 tests
92
+ e2e/
93
+ phase-1.test.ts ← 3 E2E tests
94
+ ```
95
+
96
+ ### Files Modified
97
+ ```
98
+ src/
99
+ bot.ts ← Added message handler, /new command, BotDeps
100
+ bot.test.ts ← Expanded to 8 tests (handleMessage, handleNew)
101
+ index.ts ← Full wiring: SDK + managers + EventBus + shutdown
102
+ e2e/
103
+ runner.ts ← Spawns server + bot separately, for-await stdout
104
+ helpers.ts ← sendAndWait uses message ID ordering
105
+ phase-0.test.ts ← Increased beforeAll timeout for server startup
106
+ ```
107
+
108
+ ### How to Run
109
+
110
+ ```bash
111
+ # From the telegram package directory:
112
+ cd packages/telegram
113
+
114
+ # Set OPENCODE_DIRECTORY to the project you want the AI to work on:
115
+ env $(grep -v '^#' .env | xargs) OPENCODE_DIRECTORY=/home/pedro/dev bun run src/index.ts
116
+
117
+ # Or connect to an existing OpenCode server:
118
+ env $(grep -v '^#' .env | xargs) OPENCODE_URL=http://127.0.0.1:4096 bun run src/index.ts
119
+ ```
120
+
121
+ **How it works:**
122
+ - Without `OPENCODE_URL`: spawns a local OpenCode server from monorepo source (`packages/opencode`)
123
+ - The server CWD stays at `packages/opencode` (for module resolution)
124
+ - `OPENCODE_DIRECTORY` is sent as `x-opencode-directory` header in every SDK request
125
+ - The OpenCode server uses that header to know which project to operate on
126
+
127
+ ### Lessons Learned
128
+ - `Bun.spawn` stdout with `getReader()` + `Promise.race` timeout breaks stream reading — use `for await` instead
129
+ - `process.execPath` resolves bun correctly; ENOENT from spawn usually means CWD doesn't exist
130
+ - E2E runner must spawn OpenCode server separately (not nested inside bot) to avoid subprocess hang
131
+ - `sendAndWait` must use message ID ordering (not timestamps) to avoid picking up stale responses
132
+ - `createOpencode({ port: 0 })` from SDK needs the `opencode` binary — dev mode uses bun source directly
133
+ - Server CWD must be `packages/opencode` (module resolution); project dir via `x-opencode-directory` header
134
+
135
+ ---
136
+
137
+ ## Phase 2 — Interactive Controls ✅
138
+
139
+ **Status:** Complete
140
+ **Date:** 2026-02-07
141
+
142
+ ### Delivered
143
+ - **Permission handling** (`handlers/permissions.ts`): Inline keyboard (Allow / Always / Deny) on `permission.asked` SSE events. Callback routes to `sdk.permission.reply()`.
144
+ - **Question handling** (`handlers/questions.ts`): Inline keyboard with option buttons + Skip on `question.asked` SSE events. Callback routes to `sdk.question.reply()` or `sdk.question.reject()`.
145
+ - **PendingRequests** (`pending-requests.ts`): Bounded Map with TTL for request state tracking — enables index→label resolution for questions and double-click protection for both.
146
+ - **`/cancel` command** (`handlers/cancel.ts`): Aborts active turn via `sdk.session.abort()`, cleans up TurnManager.
147
+ - **Typing indicator** (`handlers/typing.ts`): Sends "typing" chat action every 4s during active turns, auto-stops on turn end via AbortSignal.
148
+ - **Callback query routing** in `bot.ts`: Routes `perm:` and `q:` prefixed callbacks, always calls `answerCallbackQuery()` first, edits original message to show decision.
149
+ - **EventBus wiring** in `index.ts`: Handles `permission.asked` and `question.asked` events, stores in PendingRequests, sends formatted messages with keyboards.
150
+
151
+ ### Tests
152
+ | Type | Count | Status |
153
+ |------|-------|--------|
154
+ | Unit (bun test src/) | 108 | ✅ all pass |
155
+ | E2E Phase 0 (regression) | 2 | ✅ all pass |
156
+ | E2E Phase 1 (regression) | 3 | ✅ all pass |
157
+ | E2E Phase 2 | 3 | ✅ all pass |
158
+
159
+ ### Files Created
160
+ ```
161
+ src/
162
+ pending-requests.ts ← Bounded Map<requestID, PendingEntry> with TTL
163
+ pending-requests.test.ts ← 7 tests
164
+ handlers/
165
+ permissions.ts ← formatPermissionMessage(), parsePermissionCallback()
166
+ permissions.test.ts ← 8 tests
167
+ questions.ts ← formatQuestionMessage(), parseQuestionCallback(), resolveQuestionAnswer()
168
+ questions.test.ts ← 8 tests
169
+ cancel.ts ← handleCancel()
170
+ cancel.test.ts ← 5 tests
171
+ typing.ts ← startTypingLoop()
172
+ typing.test.ts ← 4 tests
173
+ e2e/
174
+ phase-2.test.ts ← 3 E2E tests (regression, /cancel, AI response)
175
+ ```
176
+
177
+ ### Files Modified
178
+ ```
179
+ src/
180
+ bot.ts ← Added /cancel, callback_query handler, BotDeps.pendingRequests,
181
+ handleMessage returns { turn }, typing loop start
182
+ bot.test.ts ← Updated tests for new return type
183
+ index.ts ← PendingRequests instance, permission.asked + question.asked
184
+ event handlers, periodic cleanup
185
+ e2e/
186
+ helpers.ts ← Polling interval 500ms → 1500ms (flood wait mitigation)
187
+ phase-0.test.ts ← Unknown command test adapted (bot now responds via AI)
188
+ ```
189
+
190
+ ### Key Design Decisions
191
+ - **No sessionID needed for permission/question SDK calls** — only requestID. Simplifies PendingRequests significantly.
192
+ - **Callback data fits 64-byte limit**: `perm:once:per_xxxx` (~40 bytes), `q:que_xxxx:0` (~35 bytes).
193
+ - **Single-select MVP for questions**: `multiple: true` and `custom: true` questions deferred.
194
+ - **E2E permission/cancel-during-generation tests deferred**: Timing-dependent on AI behavior and Grammy's sequential update processing.
195
+
196
+ ### Lessons Learned
197
+ - Grammy processes updates sequentially — `/cancel` can't interrupt a handler blocked on `sdk.session.prompt()`
198
+ - `answerCallbackQuery()` MUST be called immediately in callback handlers (prevents Telegram loading spinner)
199
+ - E2E polling at 500ms triggers Telegram flood waits — 1500ms is safe
200
+ - Permission/question button E2E tests are inherently flaky (depend on AI triggering specific tool calls)
201
+ - Bot now processes unknown commands via AI (Phase 1 `message:text` handler catches them), so Phase 0 regression test needed updating
202
+
203
+ ---
204
+
205
+ ## Phase 3 — Streaming + UX ✅
206
+
207
+ **Status:** Complete
208
+ **Date:** 2026-02-08
209
+
210
+ ### Delivered
211
+ - **DraftStream** (`send/draft-stream.ts`): Sends initial message on first text part, then edits it as text streams in. Throttled at 400ms, HTML with plain-text fallback, auto-stop via AbortSignal.
212
+ - **Tool progress** (`send/tool-progress.ts`): Appends `⚙ Running tool: title` suffix to draft during tool execution. Cleared on next text update.
213
+ - **Final response** (`finalizeResponse` in `index.ts`): On `session.idle`, stops draft and either edits to final HTML (single chunk) or deletes draft and sends chunked messages (>4096 chars).
214
+ - **Fire-and-forget prompt** (`bot.ts`): `sdk.session.prompt()` no longer blocks the Grammy handler — the DraftStream is created before the prompt fires, so SSE events can update the draft immediately.
215
+ - **E2E project directory** (`runner.ts`): Bot now uses `/home/pedro/dev/` as OPENCODE_DIRECTORY (configurable via env), pointing to the workspace with Opus configured.
216
+
217
+ ### Tests
218
+ | Type | Count | Status |
219
+ |------|-------|--------|
220
+ | Unit (bun test src/) | 137 | ✅ all pass |
221
+ | E2E Phase 0 (regression) | 2 | ✅ all pass |
222
+ | E2E Phase 1 (regression) | 3 | ✅ all pass |
223
+ | E2E Phase 2 (regression) | 3 | ✅ all pass |
224
+ | E2E Phase 3 | 4 | ✅ all pass |
225
+
226
+ ### Files Created
227
+ ```
228
+ src/
229
+ send/
230
+ draft-stream.ts ← DraftStream class (throttled message editing)
231
+ draft-stream.test.ts ← 18 tests
232
+ tool-progress.ts ← formatToolStatus() pure function
233
+ tool-progress.test.ts ← 8 tests
234
+ e2e/
235
+ phase-3.test.ts ← 4 E2E tests (streaming, tool progress, 2 regression)
236
+ ```
237
+
238
+ ### Files Modified
239
+ ```
240
+ src/
241
+ turn-manager.ts ← Added toolSuffix + draft fields to ActiveTurn
242
+ turn-manager.test.ts ← 3 new tests for new fields
243
+ bot.ts ← DraftStream created before prompt, fire-and-forget prompt,
244
+ draftDeps parameter for testability
245
+ bot.test.ts ← Updated for fire-and-forget prompt (microtick waits)
246
+ index.ts ← DraftStream updates on text/tool events, finalizeResponse
247
+ on session.idle, tool progress integration
248
+ e2e/
249
+ runner.ts ← OPENCODE_DIRECTORY defaults to project root (/home/pedro/dev/)
250
+ ```
251
+
252
+ ### Key Design Decisions
253
+ - **Fire-and-forget prompt**: `sdk.session.prompt()` was blocking Grammy's sequential handler — with Opus max, this could take minutes. Now it's fire-and-forget with `.catch()`, and the DraftStream is ready before any SSE events arrive.
254
+ - **DraftStream dependency injection**: `DraftStreamDeps` abstracts `bot.api.sendMessage/editMessageText` for testability without Grammy mocks.
255
+ - **Tool progress as suffix**: Tool status is appended to the draft text (not sent as separate messages), keeping the chat clean. Cleared when next text part arrives.
256
+ - **Finalization logic**: Single-chunk → edit draft; multi-chunk → delete draft + send chunked; no draft → send normally.
257
+
258
+ ### Lessons Learned
259
+ - `sdk.session.prompt()` blocks until the server responds — with slow models this blocks Grammy's entire update processing. Fire-and-forget is essential.
260
+ - DraftStream must be created BEFORE the prompt call, not after — SSE events arrive immediately and need a target.
261
+ - E2E tests with different model configs (Opus system prompt in Portuguese) may respond differently — regression tests should assert "bot responded" not specific words.
262
+ - Chaining E2E suites with `&&` can cause server port conflicts — run each suite isolated.
263
+
264
+ ---
265
+
266
+ ## Phase 4 — Session Management + Hardening ✅
267
+
268
+ **Status:** Complete
269
+ **Date:** 2026-02-08
270
+ **Plan:** See `docs/plans/phase-4.md`
271
+
272
+ ### Delivered
273
+ - **Allowlist middleware (N1)** — `TELEGRAM_ALLOWED_USERS` env var restricts bot access to specific Telegram user IDs. Grammy middleware at top of stack silently ignores non-allowed users. Empty list = accept all.
274
+ - **Streaming interruption fix (N2)** — `sdk.session.abort()` called before starting new turn when existing turn is active. Prevents stale SSE events from corrupting new turns. Generation counter as safety net.
275
+ - **Session restore on restart (N3)** — On startup, `sessionManager.restore(sdk)` pre-populates SessionManager with sessions matching "Telegram {chatId}" title pattern.
276
+ - **Telegram command menu (N4)** — `bot.api.setMyCommands()` registers 9 commands in Telegram's "/" autocomplete menu.
277
+ - **`/list`** — Shows sessions with inline keyboard, click `sess:` callback to switch.
278
+ - **`/rename <title>`** — Renames current session via `sdk.session.update()`.
279
+ - **`/delete`** — Deletes current session via `sdk.session.delete()` + removes SessionManager mapping.
280
+ - **`/info`** — Shows session info (title, directory, created, updated).
281
+ - **`/history`** — Shows last 10 messages (role: truncated text).
282
+ - **`/summarize`** — Returns guidance message (avoids requiring model selection).
283
+ - **`handleSessionCallback`** — Switches session via prefix matching on `session.list()`.
284
+
285
+ ### Tests
286
+ | Type | Count | Status |
287
+ |------|-------|--------|
288
+ | Unit (bun test src/) | 184 | ✅ all pass |
289
+ | E2E Phase 0 (regression) | 2 | ✅ all pass |
290
+ | E2E Phase 1 (regression) | 3 | ✅ all pass |
291
+ | E2E Phase 2 (regression) | 3 | ✅ all pass |
292
+ | E2E Phase 3 (regression) | 4 | ✅ all pass |
293
+ | E2E Phase 4 | 5 | ✅ all pass |
294
+
295
+ ### Files Created
296
+ ```
297
+ src/
298
+ handlers/
299
+ allowlist.ts ← Grammy middleware factory
300
+ allowlist.test.ts ← 6 tests
301
+ sessions.ts ← Session command handlers + formatting
302
+ sessions.test.ts ← 26 tests
303
+ e2e/
304
+ phase-4.test.ts ← 5 E2E tests
305
+ ```
306
+
307
+ ### Files Modified
308
+ ```
309
+ src/
310
+ config.ts ← Added allowedUsers: number[] field
311
+ config.test.ts ← 3 new tests for allowedUsers parsing
312
+ turn-manager.ts ← Added generation counter to ActiveTurn
313
+ turn-manager.test.ts ← 3 new tests for generation field
314
+ session-manager.ts ← Added restore() method
315
+ session-manager.test.ts ← 5 new tests for restore()
316
+ bot.ts ← Allowlist middleware, 6 new commands, sess: callback,
317
+ sdk.session.abort() before new turn, handleSessionCallback
318
+ bot.test.ts ← 4 new tests (abort behavior, session callback)
319
+ index.ts ← Session restore on startup, setMyCommands registration
320
+ e2e/
321
+ phase-3.test.ts ← Fixed flaky streaming assertion
322
+ ```
323
+
324
+ ### Lessons Learned
325
+ - `sdk.session.abort()` is fire-and-forget — errors logged but don't block new turn
326
+ - Phase 3 streaming E2E was flaky: finalizeResponse can shorten text (strips tool suffix), so assert "same message ID + has content" instead of "text grew"
327
+ - `sdk.session.summarize()` requires providerID + modelID — simpler to guide users to ask the AI directly
@@ -0,0 +1,326 @@
1
+ # OpenCode Telegram Bot - Feature Mapping Report
2
+
3
+ Comparative analysis between the OpenCode SDK (as consumed by the `app` web UI) and the OpenClaw Telegram integration, to plan a comprehensive Telegram bot for OpenCode.
4
+
5
+ ---
6
+
7
+ ## 1. OpenCode SDK - Complete API Surface
8
+
9
+ The SDK (`@opencode-ai/sdk`) exposes the `OpencodeClient` class with these namespaces:
10
+
11
+ | Namespace | Key Methods | Used by App |
12
+ |-----------|------------|-------------|
13
+ | `global` | `get`, `update`, `health`, `event` (SSE stream), `dispose` | Yes - bootstrap, config, SSE |
14
+ | `auth` | `remove`, `set` | Yes - provider auth |
15
+ | `project` | `list`, `current`, `update` | Yes - multi-project |
16
+ | `pty` | `list`, `create`, `remove`, `get`, `update`, `connect` | No |
17
+ | `config` | `get`, `update`, `providers` | Yes - settings |
18
+ | `tool` | `ids`, `list` | No |
19
+ | `worktree` | `list`, `remove`, `create`, `reset` | Yes - git worktrees |
20
+ | `experimental` | `resource.list` | No |
21
+ | `session` | `list`, `create`, `status`, `delete`, `get`, `update`, `children`, `todo`, `init`, `fork`, `abort`, `share`, `unshare`, `diff`, `summarize`, `messages`, `prompt`, `message`, `promptAsync`, `command`, `shell`, `revert`, `unrevert` | Yes - all core |
22
+ | `part` | `delete`, `update` | No |
23
+ | `permission` | `respond`, `reply`, `list` | Yes - permission dialogs |
24
+ | `question` | `list`, `reply`, `reject` | Yes - question dialogs |
25
+ | `provider` | `list`, `auth` | Yes - provider mgmt |
26
+ | `find` | `text`, `files`, `symbols` | No |
27
+ | `file` | `list`, `read`, `status` | Yes - file tree |
28
+ | `mcp` | `remove`, `start`, `callback`, `authenticate`, `status`, `add`, `connect`, `disconnect` | No |
29
+ | `tui` | `next`, `response`, `appendPrompt`, `openHelp/Sessions/Themes/Models`, `submitPrompt`, `clearPrompt`, `executeCommand`, `showToast`, `publish`, `selectSession` | No (TUI-specific) |
30
+ | `instance` | `dispose` | No |
31
+ | `path` | `get` | Yes - directory paths |
32
+ | `vcs` | `get` | Yes - git status |
33
+ | `command` | `list` | Yes - slash commands |
34
+ | `app` | `log`, `agents`, `skills` | Yes - agent list |
35
+ | `lsp` | `status` | Yes - LSP indicators |
36
+ | `formatter` | `status` | No |
37
+ | `event` | `subscribe` (per-instance SSE) | Yes - realtime updates |
38
+
39
+ ---
40
+
41
+ ## 2. OpenCode App (Web UI) - SDK Usage Breakdown
42
+
43
+ ### 2.1 Session Lifecycle
44
+ - `session.create()` - new conversation
45
+ - `session.list()` - sidebar session list
46
+ - `session.get()` - load specific session
47
+ - `session.update()` - rename, archive
48
+ - `session.delete()` - delete session
49
+ - `session.fork()` - fork from message
50
+ - `session.share()` / `unshare()` - sharing URLs
51
+ - `session.abort()` - cancel running response
52
+ - `session.diff()` - view file changes
53
+ - `session.todo()` - task list
54
+ - `session.children()` - sub-sessions
55
+
56
+ ### 2.2 Messaging
57
+ - `session.prompt()` - send user message (text + files + images + agent + model + variant)
58
+ - `session.shell()` - execute shell command
59
+ - `session.command()` - execute slash command (with agent, model, variant, parts)
60
+ - `session.messages()` - load message history (paginated)
61
+ - `session.message()` - get single message
62
+ - `session.summarize()` - summarize session
63
+ - `session.revert()` / `unrevert()` - undo/redo changes
64
+
65
+ ### 2.3 Events (SSE)
66
+ - `global.event()` - global SSE stream, events by directory
67
+ - Event types handled:
68
+ - `message.updated` - new/updated messages
69
+ - `message.part.updated` - tool calls, text streaming
70
+ - `session.updated` - session metadata changes
71
+ - `session.status` - busy/idle state
72
+ - `session.idle` - turn complete (triggers notifications)
73
+ - `session.error` - error events
74
+ - `permission.asked` - permission request
75
+ - `lsp.updated` - LSP status change
76
+
77
+ ### 2.4 Permissions
78
+ - `permission.list()` - pending permissions for directory
79
+ - `permission.respond()` - once/always/reject
80
+
81
+ ### 2.5 Questions
82
+ - `question.list()` - pending questions
83
+ - `question.reply()` - answer question
84
+ - `question.reject()` - reject question
85
+
86
+ ### 2.6 Config & Providers
87
+ - `global.get()` → config, providers, provider_auth, path, project list
88
+ - `global.update()` → update global config
89
+ - `config.get()` / `config.update()` - per-project config
90
+ - `provider.list()` / `provider.auth()` - provider management
91
+ - `auth.set()` / `auth.remove()` - API key management
92
+
93
+ ### 2.7 Files & VCS
94
+ - `file.list()` - directory file tree
95
+ - `file.read()` - read file content
96
+ - `file.status()` - git status
97
+ - `vcs.get()` - VCS info
98
+ - `worktree.create()` / `list()` / `remove()` / `reset()` - git worktree management
99
+
100
+ ### 2.8 Other
101
+ - `app.agents()` - list available agents
102
+ - `app.skills()` - list skills
103
+ - `command.list()` - list slash commands
104
+ - `lsp.status()` - LSP server status
105
+
106
+ ---
107
+
108
+ ## 3. OpenClaw Telegram - Feature Breakdown
109
+
110
+ ~6,500 LOC (excluding tests). Built on Grammy (Telegram Bot Framework).
111
+
112
+ ### 3.1 Core Architecture
113
+ - **bot.ts** (494 LOC) - Bot creation, Grammy setup, sequentialization, throttling
114
+ - **bot-handlers.ts** (928 LOC) - Message/callback routing, media groups, text fragment assembly, debouncing
115
+ - **bot-message.ts** (93 LOC) - Message processor factory
116
+ - **bot-message-context.ts** (700 LOC) - Rich context building (sender info, group config, history, media, permissions)
117
+ - **bot-message-dispatch.ts** (357 LOC) - Dispatches to AI agent, handles reply delivery
118
+
119
+ ### 3.2 Message Handling
120
+ - Text messages (DM + group)
121
+ - Media: photos, videos, documents, audio, voice, stickers
122
+ - Media groups (multiple photos/videos in one message)
123
+ - Text fragment assembly (long messages split across multiple Telegram messages)
124
+ - Inbound debouncing (batches rapid messages)
125
+ - Forward/reply context extraction
126
+ - Sticker image analysis via vision model
127
+ - Inline button callbacks (callback_query)
128
+
129
+ ### 3.3 Sending / Response Delivery
130
+ - **send.ts** (754 LOC) - Full send pipeline:
131
+ - Markdown → Telegram HTML conversion
132
+ - Message chunking (4096 char limit)
133
+ - Media attachments (photo, video, audio, document, voice, GIF)
134
+ - Caption splitting for media
135
+ - Inline keyboard buttons
136
+ - Reply threading (reply_to_message_id)
137
+ - Forum topic support (message_thread_id)
138
+ - Silent messages (disable_notification)
139
+ - Voice messages (via ElevenLabs TTS)
140
+ - Reactions (emoji reactions on messages)
141
+ - Proxy support (SOCKS5/HTTP)
142
+ - Retry with exponential backoff
143
+
144
+ ### 3.4 Draft Streaming
145
+ - **draft-stream.ts** (139 LOC) - Live streaming of AI response as editable message draft
146
+ - Throttled updates (300ms)
147
+ - Max 4096 chars per draft
148
+ - Falls back gracefully on failure
149
+
150
+ ### 3.5 Native Commands (/command)
151
+ - **bot-native-commands.ts** (699 LOC) - Telegram /commands:
152
+ - `/status` - bot status
153
+ - `/models` - model selection with inline keyboard pagination
154
+ - `/help` - command list
155
+ - `/commands` - list available commands
156
+ - Custom commands from config
157
+ - Skill-based commands
158
+ - Plugin commands
159
+ - Per-group/topic command gating
160
+
161
+ ### 3.6 Model Selection UI
162
+ - **model-buttons.ts** (217 LOC) - Inline keyboard for model selection:
163
+ - Provider grouping
164
+ - Paginated model list
165
+ - Per-session model override storage
166
+
167
+ ### 3.7 Group Chat Support
168
+ - Group policy (allow/deny/mentionOnly)
169
+ - Per-group configuration
170
+ - Forum/topic support (supergroups with topics)
171
+ - Topic-specific skill filters and system prompts
172
+ - Mention detection (@botname)
173
+ - Sender allowlists
174
+ - Group migration handling (when group ID changes)
175
+ - Group history tracking
176
+
177
+ ### 3.8 Multi-Account
178
+ - **accounts.ts** (139 LOC) - Multiple Telegram bot accounts
179
+ - Account binding per DM chat
180
+ - Account-specific config
181
+
182
+ ### 3.9 Security & Access Control
183
+ - **bot-access.ts** (94 LOC) - Allowlist checking
184
+ - **audit.ts** (162 LOC) - Audit logging for messages
185
+ - Sender verification (phone number, username, Telegram ID)
186
+ - Per-group allowlists
187
+
188
+ ### 3.10 Infrastructure
189
+ - **webhook.ts** (127 LOC) - Webhook mode (alternative to polling)
190
+ - **monitor.ts** (215 LOC) - Health monitoring, connection status
191
+ - **network-errors.ts** (150 LOC) - Recoverable error detection
192
+ - **proxy.ts** - SOCKS5/HTTP proxy support
193
+ - **token.ts** (102 LOC) - Token validation
194
+ - **format.ts** (98 LOC) - Markdown → Telegram HTML
195
+ - **download.ts** - Media file downloads
196
+ - **sent-message-cache.ts** - Deduplication of sent messages
197
+ - **update-offset-store.ts** - Persistent update offset tracking
198
+
199
+ ---
200
+
201
+ ## 4. Feature Mapping: OpenClaw Telegram → OpenCode SDK
202
+
203
+ | OpenClaw Telegram Feature | OpenCode SDK Equivalent | Implementation Notes |
204
+ |--------------------------|------------------------|---------------------|
205
+ | **Session/Thread Management** | `session.create/get/list/delete` | Map Telegram chat/thread → OpenCode session |
206
+ | **Send Message to AI** | `session.prompt()` | Main interaction point |
207
+ | **Shell Commands** | `session.shell()` | Could support `!command` syntax |
208
+ | **Slash Commands** | `session.command()` | Map Telegram /commands → OpenCode commands |
209
+ | **Abort Response** | `session.abort()` | /cancel command or inline button |
210
+ | **Stream Response** | `global.event()` SSE | Listen for `message.part.updated` events |
211
+ | **Tool Call Updates** | `message.part.updated` events | Show tool progress in chat |
212
+ | **Permission Requests** | `permission.list/respond` | Inline buttons: Allow/Deny/Always |
213
+ | **Question Dialogs** | `question.list/reply/reject` | Inline buttons or reply-based |
214
+ | **Model Selection** | `app.agents()` + config | Inline keyboard like OpenClaw |
215
+ | **Agent Selection** | `app.agents()` | /agent command with inline keyboard |
216
+ | **Session Rename** | `session.update()` | /rename command |
217
+ | **Session Fork** | `session.fork()` | /fork command |
218
+ | **Session Share** | `session.share()` | /share → returns URL |
219
+ | **View Diff** | `session.diff()` | /diff → formatted file changes |
220
+ | **View Todo** | `session.todo()` | /todo → task list |
221
+ | **Revert Changes** | `session.revert/unrevert` | /undo /redo commands |
222
+ | **File Attachment** | `session.prompt()` with FileParts | Photo/document → file part |
223
+ | **Media Download** | Direct from Telegram API | Download → base64 → file part |
224
+ | **Voice Messages** | Download → transcription/attach | Could use whisper or send as audio |
225
+ | **Message History** | `session.messages()` | /history or context loading |
226
+ | **Multi-Project** | `project.list()` + directory header | /project command to switch |
227
+ | **Config** | `config.get/update` | /config command |
228
+ | **Provider Auth** | `auth.set/remove` | /auth command |
229
+ | **Notifications** | `session.idle/error` events | Proactive messages on completion |
230
+ | **Draft Streaming** | `message.part.updated` + edit_message | Edit message as response streams in |
231
+ | **Markdown Formatting** | n/a (SDK returns markdown) | Convert markdown → Telegram HTML |
232
+ | **Message Chunking** | n/a | Split >4096 char messages |
233
+ | **Inline Buttons** | n/a | Permissions, questions, model selection |
234
+ | **Reactions** | n/a | Ack reaction on message receipt |
235
+ | **Group Support** | Works with directory header | Group chat → shared project session |
236
+ | **Forum Topics** | Works with directory header | Topic → separate session |
237
+ | **Error Handling** | `session.error` events | Show errors in chat |
238
+
239
+ ---
240
+
241
+ ## 5. Proposed Architecture
242
+
243
+ ```
244
+ opencode-telegram/
245
+ src/
246
+ index.ts # Entry point, bot startup
247
+ bot.ts # Grammy bot creation, middleware
248
+ session-map.ts # Telegram chat/thread → OpenCode session mapping
249
+ handlers/
250
+ message.ts # Text + media message handling
251
+ command.ts # /start, /new, /model, /agent, /cancel, etc.
252
+ callback.ts # Inline button callbacks
253
+ media.ts # Photo, document, voice, sticker handling
254
+ events/
255
+ listener.ts # SSE event listener (global.event)
256
+ dispatcher.ts # Route events → Telegram responses
257
+ send/
258
+ format.ts # Markdown → Telegram HTML
259
+ chunker.ts # Message splitting (4096 limit)
260
+ media.ts # Send photos, files, voice
261
+ draft-stream.ts # Live response streaming via edit_message
262
+ ui/
263
+ permissions.ts # Inline keyboard for permissions
264
+ questions.ts # Inline keyboard for questions
265
+ models.ts # Model selection inline keyboard
266
+ agents.ts # Agent selection inline keyboard
267
+ config.ts # Bot configuration
268
+ types.ts # Shared types
269
+ ```
270
+
271
+ ### 5.1 Priority Features (MVP)
272
+
273
+ 1. **Session Management** - chat → session mapping, /new, /list
274
+ 2. **Prompting** - text messages → `session.prompt()`
275
+ 3. **SSE Events** - stream responses back to Telegram
276
+ 4. **Response Delivery** - markdown formatting, chunking, streaming edits
277
+ 5. **Permission Handling** - inline buttons for allow/deny
278
+ 6. **Question Handling** - inline buttons for question replies
279
+ 7. **Abort** - /cancel to stop generation
280
+ 8. **Model/Agent Selection** - /model, /agent commands with inline keyboards
281
+ 9. **File Attachments** - photos/documents → file parts
282
+ 10. **Error Handling** - show errors, retry logic
283
+
284
+ ### 5.2 Advanced Features (Post-MVP)
285
+
286
+ 1. **Draft Streaming** - edit message as response arrives
287
+ 2. **Group Chat Support** - mention detection, allowlists
288
+ 3. **Forum Topics** - topic → session mapping
289
+ 4. **Voice Messages** - download + attach as audio
290
+ 5. **Slash Commands** - /commit, /review, etc. → `session.command()`
291
+ 6. **Shell Mode** - `!ls` → `session.shell()`
292
+ 7. **Session Sharing** - /share → public URL
293
+ 8. **Diff View** - /diff → formatted file changes
294
+ 9. **Multi-Project** - /project to switch directories
295
+ 10. **Notifications** - proactive messages on turn completion
296
+ 11. **Reactions** - ack reaction on message receipt, reactions on completion
297
+
298
+ ### 5.3 Key Differences from OpenClaw
299
+
300
+ | Aspect | OpenClaw Telegram | OpenCode Telegram (Proposed) |
301
+ |--------|------------------|------------------------------|
302
+ | Backend | Direct AI agent integration | SDK HTTP client (like web UI) |
303
+ | Session Store | Custom session store | OpenCode manages sessions |
304
+ | Model Catalog | Internal model catalog | `app.agents()` + provider list |
305
+ | Commands | Custom command registry | `command.list()` from OpenCode |
306
+ | Streaming | Token-by-token streaming | SSE `message.part.updated` events |
307
+ | Config | OpenClaw config system | `config.get/update` from SDK |
308
+ | Permissions | Not applicable | Full permission system via SDK |
309
+ | Questions | Not applicable | Full question system via SDK |
310
+ | Multi-Project | Not applicable | Directory header in SDK client |
311
+
312
+ ---
313
+
314
+ ## 6. SDK Methods NOT Used by Web App (Available for Telegram)
315
+
316
+ These SDK features are available but the web app doesn't use them:
317
+
318
+ - `pty.*` - Terminal sessions (could power interactive shell in Telegram)
319
+ - `tool.ids/list` - Enumerate available tools
320
+ - `find.text/files/symbols` - Code search
321
+ - `mcp.*` - MCP server management
322
+ - `session.promptAsync()` - Non-blocking prompt (useful for Telegram's async nature)
323
+ - `part.delete/update` - Edit/delete message parts
324
+ - `instance.dispose` - Cleanup
325
+
326
+ Of particular interest: **`session.promptAsync()`** could be ideal for Telegram since it doesn't block, allowing the bot to handle other messages while waiting for AI responses.