@pedrohnas/opencode-telegram 1.2.0 → 1.3.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.
- package/.claude/skills/playwright-cli/data/page-2026-02-09T01-41-55-194Z.yml +36 -0
- package/.claude/skills/playwright-cli/data/page-2026-02-09T01-42-17-115Z.yml +36 -0
- package/.claude/skills/playwright-cli/data/page-2026-02-09T01-43-15-988Z.yml +26 -0
- package/.claude/skills/playwright-cli/data/page-2026-02-09T01-43-26-107Z.yml +26 -0
- package/.claude/skills/playwright-cli/data/page-2026-02-09T01-45-03-139Z.yml +29 -0
- package/.claude/skills/playwright-cli/data/page-2026-02-09T01-45-21-579Z.yml +29 -0
- package/.claude/skills/playwright-cli/data/page-2026-02-09T01-45-48-051Z.yml +30 -0
- package/.claude/skills/playwright-cli/data/page-2026-02-09T01-46-27-632Z.yml +33 -0
- package/.claude/skills/playwright-cli/data/page-2026-02-09T01-46-46-519Z.yml +33 -0
- package/.claude/skills/playwright-cli/data/page-2026-02-09T01-47-28-491Z.yml +349 -0
- package/.claude/skills/playwright-cli/data/page-2026-02-09T01-47-34-834Z.yml +349 -0
- package/.claude/skills/playwright-cli/data/page-2026-02-09T01-47-54-066Z.yml +168 -0
- package/.claude/skills/playwright-cli/data/page-2026-02-09T01-48-19-667Z.yml +219 -0
- package/.claude/skills/playwright-cli/data/page-2026-02-09T01-49-32-311Z.yml +221 -0
- package/.claude/skills/playwright-cli/data/page-2026-02-09T01-49-57-109Z.yml +230 -0
- package/.claude/skills/playwright-cli/data/page-2026-02-09T01-50-24-052Z.yml +235 -0
- package/.claude/skills/playwright-cli/data/page-2026-02-09T01-50-41-148Z.yml +248 -0
- package/.claude/skills/playwright-cli/data/page-2026-02-09T01-51-10-916Z.yml +234 -0
- package/.claude/skills/playwright-cli/data/page-2026-02-09T01-51-28-271Z.yml +234 -0
- package/.claude/skills/playwright-cli/data/page-2026-02-09T01-52-32-324Z.yml +234 -0
- package/.claude/skills/playwright-cli/data/page-2026-02-09T01-52-47-801Z.yml +196 -0
- package/.claude/skills/playwright-cli/data/page-2026-02-09T01-56-07-361Z.yml +203 -0
- package/.claude/skills/playwright-cli/data/page-2026-02-09T01-56-35-534Z.yml +49 -0
- package/.claude/skills/playwright-cli/data/page-2026-02-09T01-57-04-658Z.yml +52 -0
- package/docs/AUDIT.md +193 -0
- package/docs/PROGRESS.md +188 -0
- package/docs/plans/phase-5.md +410 -0
- package/docs/plans/phase-6.5.md +426 -0
- package/docs/plans/phase-6.md +349 -0
- package/e2e/helpers.ts +34 -0
- package/e2e/phase-5.test.ts +295 -0
- package/e2e/phase-6.5.test.ts +239 -0
- package/e2e/phase-6.test.ts +302 -0
- package/package.json +6 -3
- package/src/api-server.test.ts +309 -0
- package/src/api-server.ts +201 -0
- package/src/bot.test.ts +354 -0
- package/src/bot.ts +200 -2
- package/src/config.test.ts +16 -0
- package/src/config.ts +4 -0
- package/src/event-bus.test.ts +337 -1
- package/src/event-bus.ts +83 -3
- package/src/handlers/agents.test.ts +122 -0
- package/src/handlers/agents.ts +93 -0
- package/src/handlers/media.test.ts +264 -0
- package/src/handlers/media.ts +168 -0
- package/src/handlers/models.test.ts +319 -0
- package/src/handlers/models.ts +191 -0
- package/src/index.ts +15 -0
- package/src/send/draft-stream.test.ts +76 -0
- package/src/send/draft-stream.ts +13 -1
- package/src/session-manager.test.ts +46 -0
- package/src/session-manager.ts +10 -1
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
- generic [ref=e3]:
|
|
2
|
+
- region "Site notifications" [ref=e4]:
|
|
3
|
+
- generic [ref=e8]:
|
|
4
|
+
- generic [ref=e9]: ⚠️
|
|
5
|
+
- alert [ref=e11]:
|
|
6
|
+
- text: "Security Update: Classic tokens have been revoked. Granular tokens are now limited to 90 days and require 2FA by default. Update your CI/CD workflows to avoid disruption."
|
|
7
|
+
- link "Learn more about npm authentication changes" [ref=e12] [cursor=pointer]:
|
|
8
|
+
- /url: https://gh.io/all-npm-classic-tokens-revoked
|
|
9
|
+
- text: Learn more
|
|
10
|
+
- text: .
|
|
11
|
+
- button "Close notification" [ref=e13] [cursor=pointer]: ×
|
|
12
|
+
- generic [ref=e14]:
|
|
13
|
+
- banner [ref=e15]:
|
|
14
|
+
- link "Npm" [ref=e18] [cursor=pointer]:
|
|
15
|
+
- /url: /
|
|
16
|
+
- img [ref=e19]
|
|
17
|
+
- main [ref=e21]:
|
|
18
|
+
- generic [ref=e22]:
|
|
19
|
+
- heading "Manage Recovery Codes" [level=1] [ref=e23]
|
|
20
|
+
- generic [ref=e25]:
|
|
21
|
+
- heading "Manage Recovery Codes" [level=2] [ref=e26]
|
|
22
|
+
- generic [ref=e27]:
|
|
23
|
+
- generic [ref=e28]:
|
|
24
|
+
- img [ref=e30]
|
|
25
|
+
- heading "Recovery Codes" [level=3] [ref=e36]
|
|
26
|
+
- paragraph [ref=e37]: Please make sure you have saved your codes in a secure place.
|
|
27
|
+
- generic [ref=e38]:
|
|
28
|
+
- button "65f3c8c151b712df1aafdbd003dd13eadb9c6ee9d687c2e3013d8f6add1fc36a 05a76c8c99cb696bd35b7caac484098cb19c653ea310483d6c695de277461398 4c70ca25505284883c2e225357d90fd9fb55ad56eae75a81bd1bb5486f7cb315 a6278db03b5e5f380f865550d119f6235517eced0e5bee47b25c603b4c23ad1c e614086e2300d1679d3e7dea55ab11d4bbe8d1a2843a1a0eefd28a62bf4dbd1e" [ref=e39]:
|
|
29
|
+
- paragraph [ref=e40]: 65f3c8c151b712df1aafdbd003dd13eadb9c6ee9d687c2e3013d8f6add1fc36a
|
|
30
|
+
- paragraph [ref=e41]: 05a76c8c99cb696bd35b7caac484098cb19c653ea310483d6c695de277461398
|
|
31
|
+
- paragraph [ref=e42]: 4c70ca25505284883c2e225357d90fd9fb55ad56eae75a81bd1bb5486f7cb315
|
|
32
|
+
- paragraph [ref=e43]: a6278db03b5e5f380f865550d119f6235517eced0e5bee47b25c603b4c23ad1c
|
|
33
|
+
- paragraph [ref=e44]: e614086e2300d1679d3e7dea55ab11d4bbe8d1a2843a1a0eefd28a62bf4dbd1e
|
|
34
|
+
- generic [ref=e46]:
|
|
35
|
+
- button "Copy" [ref=e47]:
|
|
36
|
+
- img [ref=e48]
|
|
37
|
+
- text: Copy
|
|
38
|
+
- button "Download" [ref=e50] [cursor=pointer]:
|
|
39
|
+
- img [ref=e51]
|
|
40
|
+
- text: Download
|
|
41
|
+
- button "Print" [ref=e55]:
|
|
42
|
+
- img [ref=e56]
|
|
43
|
+
- text: Print
|
|
44
|
+
- generic [ref=e58]:
|
|
45
|
+
- heading "Generate New Recovery Codes" [level=3] [ref=e59]
|
|
46
|
+
- paragraph [ref=e60]: When you generate new recovery codes, you must download or print the new codes. Your old codes won’t work anymore.
|
|
47
|
+
- button "Generate new recovery codes" [ref=e62]
|
|
48
|
+
- link "Go back to settings" [ref=e64] [cursor=pointer]:
|
|
49
|
+
- /url: /settings/pedrohnas/tfa/list
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
- generic [ref=e3]:
|
|
2
|
+
- region "Site notifications" [ref=e4]:
|
|
3
|
+
- generic [ref=e8]:
|
|
4
|
+
- generic [ref=e9]: ⚠️
|
|
5
|
+
- alert [ref=e11]:
|
|
6
|
+
- text: "Security Update: Classic tokens have been revoked. Granular tokens are now limited to 90 days and require 2FA by default. Update your CI/CD workflows to avoid disruption."
|
|
7
|
+
- link "Learn more about npm authentication changes" [ref=e12] [cursor=pointer]:
|
|
8
|
+
- /url: https://gh.io/all-npm-classic-tokens-revoked
|
|
9
|
+
- text: Learn more
|
|
10
|
+
- text: .
|
|
11
|
+
- button "Close notification" [ref=e13] [cursor=pointer]: ×
|
|
12
|
+
- generic [ref=e16]:
|
|
13
|
+
- alert [ref=e18]: Successfully generated new recovery codes.
|
|
14
|
+
- button "Close notification" [active] [ref=e19] [cursor=pointer]: ×
|
|
15
|
+
- generic [ref=e20]:
|
|
16
|
+
- banner [ref=e21]:
|
|
17
|
+
- link "Npm" [ref=e24] [cursor=pointer]:
|
|
18
|
+
- /url: /
|
|
19
|
+
- img [ref=e25]
|
|
20
|
+
- main [ref=e27]:
|
|
21
|
+
- generic [ref=e28]:
|
|
22
|
+
- heading "Manage Recovery Codes" [level=1] [ref=e29]
|
|
23
|
+
- generic [ref=e31]:
|
|
24
|
+
- heading "Manage Recovery Codes" [level=2] [ref=e32]
|
|
25
|
+
- generic [ref=e33]:
|
|
26
|
+
- generic [ref=e34]:
|
|
27
|
+
- img [ref=e36]
|
|
28
|
+
- heading "Recovery Codes" [level=3] [ref=e42]
|
|
29
|
+
- paragraph [ref=e43]: Please make sure you have saved your codes in a secure place.
|
|
30
|
+
- generic [ref=e44]:
|
|
31
|
+
- button "578abdc2cb48fb0b41b1412a2b02024955524ec30cb2d04e810da940dd264567 fcaae8cf53d18eb3d551e9b1c94a59eb9d0ff92375aacbc12f9761d34b7e74c1 996ce0478fa870cf6eec988e5a5e53b432f10fd1c2b6c3f430edc9f1a6b96905 3685bbc53649f71a076068bab844c9d9702d17d00858ccefdbd5bfefcc35aa3c 35c5f6a1fd6cefa84d2e6c4f399a5a821c4764ae46fd3b4f88a1523b463d2966" [ref=e45]:
|
|
32
|
+
- paragraph [ref=e46]: 578abdc2cb48fb0b41b1412a2b02024955524ec30cb2d04e810da940dd264567
|
|
33
|
+
- paragraph [ref=e47]: fcaae8cf53d18eb3d551e9b1c94a59eb9d0ff92375aacbc12f9761d34b7e74c1
|
|
34
|
+
- paragraph [ref=e48]: 996ce0478fa870cf6eec988e5a5e53b432f10fd1c2b6c3f430edc9f1a6b96905
|
|
35
|
+
- paragraph [ref=e49]: 3685bbc53649f71a076068bab844c9d9702d17d00858ccefdbd5bfefcc35aa3c
|
|
36
|
+
- paragraph [ref=e50]: 35c5f6a1fd6cefa84d2e6c4f399a5a821c4764ae46fd3b4f88a1523b463d2966
|
|
37
|
+
- generic [ref=e52]:
|
|
38
|
+
- button "Copy" [ref=e53]:
|
|
39
|
+
- img [ref=e54]
|
|
40
|
+
- text: Copy
|
|
41
|
+
- button "Download" [ref=e56] [cursor=pointer]:
|
|
42
|
+
- img [ref=e57]
|
|
43
|
+
- text: Download
|
|
44
|
+
- button "Print" [ref=e61]:
|
|
45
|
+
- img [ref=e62]
|
|
46
|
+
- text: Print
|
|
47
|
+
- generic [ref=e64]:
|
|
48
|
+
- heading "Generate New Recovery Codes" [level=3] [ref=e65]
|
|
49
|
+
- paragraph [ref=e66]: When you generate new recovery codes, you must download or print the new codes. Your old codes won’t work anymore.
|
|
50
|
+
- button "Generate new recovery codes" [ref=e68]
|
|
51
|
+
- link "Go back to settings" [ref=e70] [cursor=pointer]:
|
|
52
|
+
- /url: /settings/pedrohnas/tfa/list
|
package/docs/AUDIT.md
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# Audit Report — OpenCode Telegram Bot vs OpenClaw Reference
|
|
2
|
+
|
|
3
|
+
**Date:** 2026-02-08
|
|
4
|
+
**Scope:** Phases 0-6 complete, comparison against OpenClaw Telegram integration (~3,500 LOC, 41 files)
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 1. Project Stats
|
|
9
|
+
|
|
10
|
+
| Metric | Our Bot | OpenClaw |
|
|
11
|
+
|--------|---------|----------|
|
|
12
|
+
| Source LOC | 2,580 (21 files) | ~3,500 (41 files) |
|
|
13
|
+
| Unit tests | 252 | ~200+ (estimated) |
|
|
14
|
+
| E2E tests | 32 | Manual |
|
|
15
|
+
| Total LOC (incl tests + E2E) | ~7,000 | ~10,000+ |
|
|
16
|
+
| Architecture | SDK HTTP bridge | Direct agent integration |
|
|
17
|
+
| Framework | Grammy | Grammy |
|
|
18
|
+
| Runtime | Bun | Node.js |
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 2. Quality Assessment by Phase
|
|
23
|
+
|
|
24
|
+
### Phases 0-4: Excellent
|
|
25
|
+
|
|
26
|
+
The core foundation is solid:
|
|
27
|
+
- **Anti-leak architecture** consistently applied (LRU, AbortController, bounded Maps)
|
|
28
|
+
- **TDD discipline** maintained — every file has colocated tests
|
|
29
|
+
- **EventBus** single SSE design is clean and memory-efficient
|
|
30
|
+
- **DraftStream** throttled editing works well
|
|
31
|
+
- **Permission/Question** handling with PendingRequests + inline keyboards is correct
|
|
32
|
+
- **Session management** with restore-on-restart is production-ready
|
|
33
|
+
|
|
34
|
+
### Phase 5 (Model & Agent Selection): Good
|
|
35
|
+
|
|
36
|
+
- Clean separation: `models.ts` (139 LOC) and `agents.ts` (93 LOC)
|
|
37
|
+
- Callback data fits 64-byte limit using direct IDs
|
|
38
|
+
- Override persistence across `/new` and session switches — properly fixed
|
|
39
|
+
- 28 unit tests + 9 E2E tests
|
|
40
|
+
- **Minor issue:** No pagination for providers with many models (deferred, acceptable)
|
|
41
|
+
|
|
42
|
+
### Phase 6 (Media & Files): Good
|
|
43
|
+
|
|
44
|
+
- Download/conversion pipeline is correct and well-tested
|
|
45
|
+
- MIME map is comprehensive (43 extensions)
|
|
46
|
+
- File size limit enforcement works
|
|
47
|
+
- DraftStream race condition properly fixed with `sending` flag
|
|
48
|
+
- 21 unit tests + 6 E2E tests (including fragmentation regression)
|
|
49
|
+
- **Minor issue:** `handleMedia` in bot.ts is an inline anonymous function, not exported for direct unit testing. It works via E2E tests, but differs from the pattern of other exported handlers.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## 3. Production Gaps (vs OpenClaw)
|
|
54
|
+
|
|
55
|
+
### Critical — Should fix before production
|
|
56
|
+
|
|
57
|
+
| # | Gap | Impact | Effort |
|
|
58
|
+
|---|-----|--------|--------|
|
|
59
|
+
| **G1** | **EventBus has no auto-reconnect** | SSE stream break = bot stops receiving events silently. No exponential backoff. | Medium |
|
|
60
|
+
| **G2** | **No Grammy `apiThrottler()` middleware** | Telegram API 429 rate limits not handled. Under high load, API calls fail. | Low |
|
|
61
|
+
| **G3** | **No `sequentialize()` middleware** | Race conditions possible if Grammy processes concurrent updates for same chat (unlikely in polling mode, but risky with webhooks). | Low |
|
|
62
|
+
|
|
63
|
+
### Important — Should fix for robustness
|
|
64
|
+
|
|
65
|
+
| # | Gap | Impact | Effort |
|
|
66
|
+
|---|-----|--------|--------|
|
|
67
|
+
| **G4** | **No error classification** | Network errors (ECONNRESET, ETIMEDOUT) not distinguished from fatal errors. All treated the same. | Medium |
|
|
68
|
+
| **G5** | **EventBus `listen()` doesn't retry** | If the SSE stream ends (server restart, network issue), the bot goes deaf. No automatic reconnection. | Medium |
|
|
69
|
+
| **G6** | **No update deduplication** | Telegram can deliver duplicate updates. We process them again. | Low |
|
|
70
|
+
|
|
71
|
+
### Nice to have — For future phases
|
|
72
|
+
|
|
73
|
+
| # | Gap | Impact | Effort |
|
|
74
|
+
|---|-----|--------|--------|
|
|
75
|
+
| G7 | No text fragment assembly | Users who paste >4096 chars get each fragment processed separately | Medium |
|
|
76
|
+
| G8 | No media group buffering | Multi-photo sends = N separate prompts instead of one combined | Medium |
|
|
77
|
+
| G9 | No inbound debouncing | Rapid messages processed individually (can flood the AI) | Low |
|
|
78
|
+
| G10 | No sent-message cache | Can't deduplicate outbound messages | Low |
|
|
79
|
+
| G11 | No update offset persistence | Bot restart may re-process recent updates | Low |
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## 4. Code Structure Analysis
|
|
84
|
+
|
|
85
|
+
### Strengths
|
|
86
|
+
|
|
87
|
+
1. **Clean module boundaries** — Each handler file is self-contained with pure functions + async handlers
|
|
88
|
+
2. **Dependency injection** — `DraftStreamDeps`, `BotDeps`, SDK as params (not globals)
|
|
89
|
+
3. **Testability** — Every handler can be tested without Grammy context
|
|
90
|
+
4. **Anti-leak guarantees** — AbortController per turn, LRU bounded SessionManager, TTL on PendingRequests
|
|
91
|
+
5. **Session restore** — Bot survives restarts by matching "Telegram {chatId}" title pattern
|
|
92
|
+
|
|
93
|
+
### Areas for Improvement
|
|
94
|
+
|
|
95
|
+
1. **`bot.ts` is growing** (479 LOC) — Approaching the split threshold. Consider extracting:
|
|
96
|
+
- Callback routing → `handlers/callbacks.ts`
|
|
97
|
+
- `handleMedia` → export from `handlers/media.ts` for testability
|
|
98
|
+
- `handleMessage` is already well-structured
|
|
99
|
+
|
|
100
|
+
2. **`index.ts` does too much** (283 LOC) — Event routing and `finalizeResponse` live in the entry point. Should be in a dedicated `event-router.ts` for unit testability.
|
|
101
|
+
|
|
102
|
+
3. **Event types are `any`** — EventBus uses `any` for event types. Type-safe event handling would catch bugs at compile time.
|
|
103
|
+
|
|
104
|
+
4. **No structured logging** — All errors go to `console.error`. No log levels, no structured format, no error context.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## 5. What We Do Better Than OpenClaw
|
|
109
|
+
|
|
110
|
+
| Aspect | Our Advantage |
|
|
111
|
+
|--------|--------------|
|
|
112
|
+
| **Architecture** | SDK bridge is thinner (~2,580 LOC vs ~3,500). Delegates AI complexity to server. |
|
|
113
|
+
| **Testing** | 252 unit + 32 E2E with TDD rigor. Phase-gated with full regression at each step. |
|
|
114
|
+
| **Memory safety** | Explicit anti-leak design: LRU, AbortController, bounded everything. |
|
|
115
|
+
| **Deployment** | Simple env vars (4-5 vars). No YAML config, no multi-account, no pairing system. |
|
|
116
|
+
| **Session management** | Server-side persistence with local LRU cache. Clean restore on restart. |
|
|
117
|
+
| **Phase gating** | Clear boundaries, docs per phase, regression testing. Sustainable development. |
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## 6. Feature Parity Matrix
|
|
122
|
+
|
|
123
|
+
| Feature | Our Bot | OpenClaw | Notes |
|
|
124
|
+
|---------|---------|----------|-------|
|
|
125
|
+
| Text messages | ✅ | ✅ | |
|
|
126
|
+
| Photo/Document/Audio/Video | ✅ | ✅ | Phase 6 |
|
|
127
|
+
| Voice messages | ✅ | ✅ | |
|
|
128
|
+
| Streaming (edit draft) | ✅ | ✅ | With `sending` fix |
|
|
129
|
+
| Permissions (inline buttons) | ✅ | N/A | OpenClaw has no permission system |
|
|
130
|
+
| Questions (inline buttons) | ✅ | N/A | OpenClaw has no question system |
|
|
131
|
+
| /cancel (abort) | ✅ | ✅ | |
|
|
132
|
+
| Session management | ✅ | ✅ | /list, /rename, /delete, /info, /history |
|
|
133
|
+
| Session restore on restart | ✅ | ✅ | Title pattern matching |
|
|
134
|
+
| Model selection | ✅ | ✅ | Phase 5 |
|
|
135
|
+
| Agent selection | ✅ | N/A | OpenClaw has different agent system |
|
|
136
|
+
| Allowlist | ✅ | ✅ | |
|
|
137
|
+
| Markdown → HTML | ✅ | ✅ | |
|
|
138
|
+
| Message chunking | ✅ | ✅ | |
|
|
139
|
+
| HTML fallback | ✅ | ✅ | |
|
|
140
|
+
| Typing indicator | ✅ | ✅ | |
|
|
141
|
+
| Tool progress | ✅ | N/A | |
|
|
142
|
+
| Auto-reconnect SSE | ❌ | ✅ | **Gap G1/G5** |
|
|
143
|
+
| Rate limit middleware | ❌ | ✅ | **Gap G2** |
|
|
144
|
+
| Sequentialize middleware | ❌ | ✅ | **Gap G3** |
|
|
145
|
+
| Text fragment assembly | ❌ | ✅ | Gap G7 |
|
|
146
|
+
| Media group buffering | ❌ | ✅ | Gap G8 |
|
|
147
|
+
| Inbound debouncing | ❌ | ✅ | Gap G9 |
|
|
148
|
+
| Group chat support | ❌ | ✅ | Phase 8 planned |
|
|
149
|
+
| Forum topics | ❌ | ✅ | Phase 8 planned |
|
|
150
|
+
| Webhook mode | ❌ | ✅ | Phase 9 planned |
|
|
151
|
+
| Sticker vision cache | ❌ | ✅ | Nice to have |
|
|
152
|
+
| Voice → voice bubble | ❌ | ✅ | Nice to have |
|
|
153
|
+
| Multi-account | ❌ | ✅ | Not needed |
|
|
154
|
+
| Audit logging | ❌ | ✅ | Not needed |
|
|
155
|
+
| Update offset persistence | ❌ | ✅ | Gap G11 |
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## 7. Recommendations
|
|
160
|
+
|
|
161
|
+
### Before Production (Priority)
|
|
162
|
+
|
|
163
|
+
1. **Fix EventBus reconnection (G1+G5)** — Add exponential backoff loop in `listen()`. When SSE stream ends or errors, wait 2s→30s (1.8x factor) and reconnect. This is the most critical gap.
|
|
164
|
+
|
|
165
|
+
2. **Add `apiThrottler()` (G2)** — One line of Grammy middleware. Prevents 429 errors.
|
|
166
|
+
|
|
167
|
+
3. **Add `sequentialize()` (G3)** — One line of Grammy middleware. Prevents per-chat race conditions (important if switching to webhooks later).
|
|
168
|
+
|
|
169
|
+
### Before Phase 7 (Recommended)
|
|
170
|
+
|
|
171
|
+
4. **Extract event routing from index.ts** — Move `onEvent` callback and `finalizeResponse` to `event-router.ts`. Enables unit testing of event logic.
|
|
172
|
+
|
|
173
|
+
5. **Type SSE events** — Replace `any` with typed event union in EventBus.
|
|
174
|
+
|
|
175
|
+
### For Future Phases
|
|
176
|
+
|
|
177
|
+
6. **Text fragment assembly (G7)** — Buffer near-limit messages for 1500ms, merge.
|
|
178
|
+
7. **Media group buffering (G8)** — Buffer same `media_group_id` for 200ms.
|
|
179
|
+
8. **Inbound debouncing (G9)** — 300-500ms buffer for rapid messages.
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## 8. Overall Assessment
|
|
184
|
+
|
|
185
|
+
**The codebase quality is good.** Phases 5 and 6 maintained the same standards as earlier phases:
|
|
186
|
+
- Clean module boundaries
|
|
187
|
+
- Comprehensive test coverage
|
|
188
|
+
- Anti-leak design throughout
|
|
189
|
+
- Bugs found via manual testing were properly diagnosed and fixed with tests
|
|
190
|
+
|
|
191
|
+
**The main gaps are infrastructure-level** (reconnection, rate limiting, middleware), not code quality issues. These are expected at the Phase 6 stage and should be addressed in Phase 9 (Infrastructure & Security) or as a hardening pass before production deployment.
|
|
192
|
+
|
|
193
|
+
**LOC target from spec was ~2,000 for Phase 1.** At Phase 6 we're at 2,580 for all source — well within the expected trajectory toward the spec's full target.
|
package/docs/PROGRESS.md
CHANGED
|
@@ -325,3 +325,191 @@ e2e/
|
|
|
325
325
|
- `sdk.session.abort()` is fire-and-forget — errors logged but don't block new turn
|
|
326
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
327
|
- `sdk.session.summarize()` requires providerID + modelID — simpler to guide users to ask the AI directly
|
|
328
|
+
|
|
329
|
+
---
|
|
330
|
+
|
|
331
|
+
## Phase 5 — Model & Agent Selection ✅
|
|
332
|
+
|
|
333
|
+
**Status:** Complete
|
|
334
|
+
**Date:** 2026-02-08
|
|
335
|
+
**Plan:** See `docs/plans/phase-5.md`
|
|
336
|
+
|
|
337
|
+
### Delivered
|
|
338
|
+
- **`/model` command** — Shows providers as inline keyboard. Clicking a provider shows its models. Selecting a model stores a `modelOverride` in SessionEntry, passed to every `session.prompt()`.
|
|
339
|
+
- **`/agent` command** — Shows available agents (filtered: non-hidden) as inline keyboard. Selecting stores `agentOverride` in SessionEntry.
|
|
340
|
+
- **Model reset** — "Reset to default" button clears override.
|
|
341
|
+
- **Agent reset** — Same pattern.
|
|
342
|
+
- **Back navigation** — Model selection supports back→provider list.
|
|
343
|
+
- **Override persistence** — Model/agent overrides survive `/new` and session switches (`/list`).
|
|
344
|
+
- **Callback data encoding** — Uses `mdl:{providerID}:{modelID}` directly (fits 64-byte limit for most providers). Falls back to truncation.
|
|
345
|
+
|
|
346
|
+
### Tests
|
|
347
|
+
| Type | Count | Status |
|
|
348
|
+
|------|-------|--------|
|
|
349
|
+
| Unit (bun test src/) | 222 | ✅ all pass |
|
|
350
|
+
| E2E Phase 0-4 (regression) | 17 | ✅ all pass |
|
|
351
|
+
| E2E Phase 5 | 3 | ✅ all pass |
|
|
352
|
+
|
|
353
|
+
### Files Created
|
|
354
|
+
```
|
|
355
|
+
src/
|
|
356
|
+
handlers/
|
|
357
|
+
models.ts ← formatProviderList, formatModelList, parseModelCallback, handleModel, handleModelSelect
|
|
358
|
+
models.test.ts ← 18 tests
|
|
359
|
+
agents.ts ← formatAgentList, parseAgentCallback, handleAgent, handleAgentSelect
|
|
360
|
+
agents.test.ts ← 10 tests
|
|
361
|
+
e2e/
|
|
362
|
+
phase-5.test.ts ← 3 E2E tests
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### Files Modified
|
|
366
|
+
```
|
|
367
|
+
src/
|
|
368
|
+
session-manager.ts ← Added modelOverride? and agentOverride? to SessionEntry
|
|
369
|
+
session-manager.test.ts ← 3 new tests for override fields
|
|
370
|
+
bot.ts ← /model, /agent commands; mdl: + agt: callback routing;
|
|
371
|
+
pass overrides in handleMessage prompt call
|
|
372
|
+
bot.test.ts ← 6 new tests (override passing, callbacks)
|
|
373
|
+
index.ts ← setMyCommands includes /model and /agent
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
### Key Design Decisions
|
|
377
|
+
- **Direct ID encoding in callback data** — `mdl:anthropic:claude-sonnet-4-5-20250929` fits 64 bytes. No need for index-based lookup maps.
|
|
378
|
+
- **Override persistence across `/new`** — overrides are per-user preferences, not per-session. Preserved by saving before remove and restoring after create.
|
|
379
|
+
- **Hidden agents filtered** — `sdk.app.agents()` may return hidden agents; we filter them out.
|
|
380
|
+
- **No pagination** — Most providers have <10 models. Deferred for later.
|
|
381
|
+
|
|
382
|
+
### Lessons Learned
|
|
383
|
+
- SDK v2 uses flat params: `sdk.session.prompt({ sessionID, parts, model: { providerID, modelID } })`, not nested path/body.
|
|
384
|
+
- `sdk.provider.list()` returns `{ data: { all: Provider[] } }` where each Provider has `models: { [modelID]: Model }`.
|
|
385
|
+
|
|
386
|
+
---
|
|
387
|
+
|
|
388
|
+
## Phase 6 — Media & Files ✅
|
|
389
|
+
|
|
390
|
+
**Status:** Complete
|
|
391
|
+
**Date:** 2026-02-08
|
|
392
|
+
**Plan:** See `docs/plans/phase-6.md`
|
|
393
|
+
|
|
394
|
+
### Delivered
|
|
395
|
+
- **Photo handling** — Highest resolution photo downloaded, converted to base64 data URL, sent as `FilePartInput`.
|
|
396
|
+
- **Document handling** — Any document type (PDF, code, text) downloaded and sent as file part. Original filename preserved.
|
|
397
|
+
- **Voice/Audio handling** — Voice messages (OGG) and audio files downloaded and sent as file parts.
|
|
398
|
+
- **Video handling** — Video files downloaded and sent as file parts.
|
|
399
|
+
- **Caption support** — Caption becomes `TextPartInput` alongside `FilePartInput`.
|
|
400
|
+
- **File size limit** — 20MB check before download (Telegram Bot API limit).
|
|
401
|
+
- **MIME detection** — Extension-based fallback when Telegram doesn't provide MIME type (43 extensions mapped).
|
|
402
|
+
- **DraftStream race condition fix** — Added `sending` flag to prevent concurrent `sendMessage` calls when multiple SSE events arrive before first message is created. Fixes fragmentation with fast models (Gemini Flash).
|
|
403
|
+
- **Override persistence fix** — `handleNew` and `handleSessionCallback` now preserve model/agent overrides across session changes.
|
|
404
|
+
|
|
405
|
+
### Tests
|
|
406
|
+
| Type | Count | Status |
|
|
407
|
+
|------|-------|--------|
|
|
408
|
+
| Unit (bun test src/) | 252 | ✅ all pass |
|
|
409
|
+
| E2E Phase 0-5 (regression) | 26 | ✅ all pass |
|
|
410
|
+
| E2E Phase 6 | 6 | ✅ all pass |
|
|
411
|
+
|
|
412
|
+
### Files Created
|
|
413
|
+
```
|
|
414
|
+
src/
|
|
415
|
+
handlers/
|
|
416
|
+
media.ts ← extractFileRef, downloadTelegramFile, bufferToDataUrl,
|
|
417
|
+
buildFilePart, buildMediaParts, getMimeFromFileName
|
|
418
|
+
media.test.ts ← 21 tests
|
|
419
|
+
e2e/
|
|
420
|
+
phase-6.test.ts ← 6 E2E tests (photo, document, caption, regression text,
|
|
421
|
+
fragmentation, interruption)
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
### Files Modified
|
|
425
|
+
```
|
|
426
|
+
src/
|
|
427
|
+
bot.ts ← handleMedia function, 5 Grammy media handlers (photo,
|
|
428
|
+
document, voice, audio, video) BEFORE message:text;
|
|
429
|
+
handleMessage accepts optional parts param;
|
|
430
|
+
handleNew/handleSessionCallback preserve overrides
|
|
431
|
+
bot.test.ts ← 6 new tests (media parts, override persistence)
|
|
432
|
+
send/draft-stream.ts ← Added `sending` flag to prevent concurrent sendMessage race
|
|
433
|
+
send/draft-stream.test.ts ← 3 new tests for race condition scenarios
|
|
434
|
+
e2e/
|
|
435
|
+
phase-5.test.ts ← 6 new tests (model override + persistence + E2E)
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
### Key Design Decisions
|
|
439
|
+
- **Grammy handler ordering** — Media handlers (`message:photo`, etc.) must come BEFORE `message:text` because photos with captions also match `message:text`.
|
|
440
|
+
- **Shared `handleMedia` function** — All 5 media types share the same handler logic.
|
|
441
|
+
- **Data URL approach** — Download → Buffer → base64 data URL. Simple, no temp files, works within HTTP body limits even for 20MB files (~27MB base64).
|
|
442
|
+
- **`sending` flag pattern** — Based on OpenClaw's `inFlight` pattern: first `update()` sets flag, concurrent calls just store `pending`, flag cleared after `sendMessage` completes.
|
|
443
|
+
|
|
444
|
+
### Bugs Fixed This Phase
|
|
445
|
+
1. **Streaming fragmentation** — With fast models (Gemini Flash), multiple SSE events called `DraftStream.update()` concurrently. All saw `messageId === null` and each called `sendMessage`, creating N separate messages. Fixed with `sending` guard flag.
|
|
446
|
+
2. **Model/agent override lost on `/new`** — `sessionManager.remove()` deleted the entry with overrides, then `getOrCreate()` created a clean one. Fixed by saving overrides before remove and restoring after.
|
|
447
|
+
|
|
448
|
+
### Lessons Learned
|
|
449
|
+
- Telegram requires valid PNG encoding — hand-crafted zlib fails, must use `zlib.deflateSync()`.
|
|
450
|
+
- gramjs `CustomFile(name, size, path, buffer)` for sending files in E2E tests.
|
|
451
|
+
- E2E fragmentation test: count bot messages between events, assert ≤ expected (not exact count).
|
|
452
|
+
- `DraftStream` race condition only manifests with fast models (high SSE event rate).
|
|
453
|
+
|
|
454
|
+
---
|
|
455
|
+
|
|
456
|
+
## Phase 6.5 — Production Hardening + Bot Control API ✅
|
|
457
|
+
|
|
458
|
+
**Status:** Complete (unit tests pass, E2E written — awaiting E2E run)
|
|
459
|
+
**Date:** 2026-02-08
|
|
460
|
+
**Plan:** See `docs/plans/phase-6.5.md`
|
|
461
|
+
**Audit:** See `docs/AUDIT.md`
|
|
462
|
+
|
|
463
|
+
### Delivered
|
|
464
|
+
- **EventBus auto-reconnect (G1/G5)** — SSE stream break → auto-reconnect with exponential backoff (2s→30s, 1.8x factor, ±25% jitter). Cancellable via `stop()`.
|
|
465
|
+
- **Grammy `apiThrottler()` (G2)** — API transformer that queues and retries on Telegram 429 rate limits.
|
|
466
|
+
- **Grammy `sequentialize()` (G3)** — First middleware, ensures per-chat serial update processing. Prevents race conditions with webhooks.
|
|
467
|
+
- **Smart /model filtering (G12)** — Only connected providers, models grouped by family (most recent per family), active model marked with ✓. Reduces ~400 models → ~16.
|
|
468
|
+
- **Bot Control API** — Hono HTTP server on `127.0.0.1:4097` exposing 7 endpoints for AI-driven model/agent/session control.
|
|
469
|
+
- **SKILL.md** — Teaches AI how to discover its sessionId and use the Bot Control API.
|
|
470
|
+
|
|
471
|
+
### Tests
|
|
472
|
+
| Type | Count | Status |
|
|
473
|
+
|------|-------|--------|
|
|
474
|
+
| Unit (bun test src/) | 295 | ✅ all pass |
|
|
475
|
+
| E2E Phase 6.5 | 8 | Written, pending run |
|
|
476
|
+
|
|
477
|
+
### Files Created
|
|
478
|
+
```
|
|
479
|
+
src/
|
|
480
|
+
api-server.ts ← Hono app + createApiServer (Bot Control API)
|
|
481
|
+
api-server.test.ts ← 15 tests (all endpoints via app.request())
|
|
482
|
+
deploy/
|
|
483
|
+
skills/
|
|
484
|
+
telegram-control/
|
|
485
|
+
SKILL.md ← AI skill doc for Bot Control API
|
|
486
|
+
e2e/
|
|
487
|
+
phase-6.5.test.ts ← 8 E2E tests (/model filtering + Bot Control API)
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
### Files Modified
|
|
491
|
+
```
|
|
492
|
+
src/
|
|
493
|
+
event-bus.ts ← reconnectLoop + exponential backoff + cancellable sleep
|
|
494
|
+
event-bus.test.ts ← 13 new tests (reconnect, backoff, jitter, error recovery)
|
|
495
|
+
bot.ts ← sequentialize() middleware + filterModels in mdl: callback
|
|
496
|
+
bot.test.ts ← 3 new tests (sequentialize, throttler import)
|
|
497
|
+
config.ts ← Added apiPort config (TELEGRAM_API_PORT, default 4097)
|
|
498
|
+
config.test.ts ← 2 new tests for apiPort
|
|
499
|
+
index.ts ← apiThrottler + createApiServer + apiServer.stop in shutdown
|
|
500
|
+
handlers/models.ts ← filterModels(), formatProviderList(connected), formatModelList(activeModelID)
|
|
501
|
+
handlers/models.test.ts ← 8 new tests (filtering, connected, ✓ marker)
|
|
502
|
+
package.json ← Added hono, @grammyjs/transformer-throttler, @grammyjs/runner
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
### New Dependencies
|
|
506
|
+
- `hono` ^4.x — HTTP framework for Bot Control API
|
|
507
|
+
- `@grammyjs/transformer-throttler` ^1.x — Telegram API 429 rate limit handling
|
|
508
|
+
- `@grammyjs/runner` ^2.x — sequentialize middleware for per-chat serial processing
|
|
509
|
+
|
|
510
|
+
### Key Design Decisions
|
|
511
|
+
- **Bot Control API on localhost** — AI calls it via `curl` from bash. No auth needed (bound to 127.0.0.1 only).
|
|
512
|
+
- **AI self-identification** — AI discovers its sessionId by calling OpenCode session list (`localhost:4096/session`), matches title pattern `"Telegram {chatId}"`.
|
|
513
|
+
- **filterModels pattern** — Group by family, sort by release_date descending, pick first. Handles missing family (uses model id as key).
|
|
514
|
+
- **Hono test pattern** — `app.request()` — no real Bun.serve needed in tests.
|
|
515
|
+
- **EventBus backoff** — `delay = min(maxDelay, initialDelay × factor^attempt) × (1 ± jitter)`. Cancellable sleep via sleepReject.
|