@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,426 @@
|
|
|
1
|
+
# Phase 6.5 — Production Hardening + Bot Control API
|
|
2
|
+
|
|
3
|
+
**Goal:** Fix 3 critical production gaps (AUDIT.md), improve /model UX, and add
|
|
4
|
+
a Bot Control API so the AI can manage its own model/agent/session via skills.
|
|
5
|
+
|
|
6
|
+
## What This Phase Delivers
|
|
7
|
+
|
|
8
|
+
1. **EventBus auto-reconnect (G1/G5)** — SSE stream break → auto-reconnect
|
|
9
|
+
with exponential backoff. Without this, the bot silently goes deaf.
|
|
10
|
+
|
|
11
|
+
2. **Grammy `apiThrottler()` middleware (G2)** — Automatic Telegram API 429
|
|
12
|
+
rate limit handling.
|
|
13
|
+
|
|
14
|
+
3. **Grammy `sequentialize()` middleware (G3)** — Per-chat sequential update
|
|
15
|
+
processing. Prevents race conditions with webhooks/runner.
|
|
16
|
+
|
|
17
|
+
4. **Smart /model filtering (G12)** — Only connected providers, most recent
|
|
18
|
+
model per family, active model marked with ✓. Reduces 86 providers / ~400
|
|
19
|
+
models → 4 providers / 16 models.
|
|
20
|
+
|
|
21
|
+
5. **Bot Control API** — Hono HTTP server (localhost:4097) that exposes bot
|
|
22
|
+
state to the AI running in OpenCode. Enables AI-driven model switching,
|
|
23
|
+
agent selection, and session management via skills.
|
|
24
|
+
|
|
25
|
+
6. **SKILL.md** — Teaches the AI how to use the Bot Control API to change
|
|
26
|
+
models, agents, and sessions autonomously.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Gap Details
|
|
31
|
+
|
|
32
|
+
### G1/G5 — EventBus auto-reconnect (CRITICAL)
|
|
33
|
+
|
|
34
|
+
**Current behavior:**
|
|
35
|
+
```ts
|
|
36
|
+
// event-bus.ts — stream ends → NOTHING HAPPENS. Bot goes deaf.
|
|
37
|
+
for await (const event of result.stream) { ... }
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**Fix:** Reconnect loop with exponential backoff (2s→30s, 1.8x, ±25% jitter).
|
|
41
|
+
|
|
42
|
+
### G2 — apiThrottler() middleware
|
|
43
|
+
|
|
44
|
+
**Fix:** `bot.api.config.use(apiThrottler())` — Grammy queues + retries on 429.
|
|
45
|
+
|
|
46
|
+
**Dependency:** `@grammyjs/transformer-throttler`
|
|
47
|
+
|
|
48
|
+
### G3 — sequentialize() middleware
|
|
49
|
+
|
|
50
|
+
**Fix:** `bot.use(sequentialize((ctx) => String(ctx.chat?.id ?? "")))` — first
|
|
51
|
+
middleware, before allowlist.
|
|
52
|
+
|
|
53
|
+
**Dependency:** `@grammyjs/runner`
|
|
54
|
+
|
|
55
|
+
### G12 — Smart /model filtering
|
|
56
|
+
|
|
57
|
+
**Fix — 3 filters:**
|
|
58
|
+
1. Only show providers in `connected[]` (have API keys)
|
|
59
|
+
2. Group by `family`, keep most recent `release_date` per family
|
|
60
|
+
3. Mark active model with `✓` (from override or server default)
|
|
61
|
+
|
|
62
|
+
### Bot Control API
|
|
63
|
+
|
|
64
|
+
**Problem:** The AI runs in the OpenCode server but can't control the Telegram
|
|
65
|
+
bot's state (model override, agent override, session management). These live in
|
|
66
|
+
the bot's in-memory SessionManager — a separate process.
|
|
67
|
+
|
|
68
|
+
**Solution:** Hono HTTP server on `127.0.0.1:TELEGRAM_API_PORT` (default 4097).
|
|
69
|
+
The AI calls it via `curl` from bash. Follows the OpenCode server pattern
|
|
70
|
+
(`new Hono()` → `Bun.serve({ fetch: app.fetch })`).
|
|
71
|
+
|
|
72
|
+
**How the AI identifies itself:**
|
|
73
|
+
1. AI calls `curl localhost:4096/session` (OpenCode API)
|
|
74
|
+
2. Finds session with title `"Telegram {chatId}"`
|
|
75
|
+
3. Uses `sessionId` in Bot Control API calls
|
|
76
|
+
4. Bot does reverse lookup via `sessionManager.getBySessionId()` → chatKey
|
|
77
|
+
|
|
78
|
+
**Endpoints:**
|
|
79
|
+
|
|
80
|
+
| Method | Path | Body | Returns |
|
|
81
|
+
|--------|------|------|---------|
|
|
82
|
+
| `GET` | `/api/sessions` | — | All active sessions (chatId, sessionId, model, agent) |
|
|
83
|
+
| `GET` | `/api/session/:sessionId` | — | Session info (model, agent, turn status) |
|
|
84
|
+
| `POST` | `/api/session/:sessionId/model` | `{ providerID, modelID }` | Updated session |
|
|
85
|
+
| `POST` | `/api/session/:sessionId/agent` | `{ agent }` | Updated session |
|
|
86
|
+
| `POST` | `/api/session/:sessionId/new` | — | New session created |
|
|
87
|
+
| `GET` | `/api/models` | — | Connected providers + filtered models (JSON) |
|
|
88
|
+
| `GET` | `/api/agents` | — | Available agents (JSON) |
|
|
89
|
+
|
|
90
|
+
**Security:** Binds to `127.0.0.1` only — accessible from same VPS, no auth.
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Architecture
|
|
95
|
+
|
|
96
|
+
### EventBus reconnect loop
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
start()
|
|
100
|
+
└→ reconnectLoop()
|
|
101
|
+
└→ listen()
|
|
102
|
+
└→ sdk.event.subscribe() → for await (stream)
|
|
103
|
+
├→ event → onEvent(...)
|
|
104
|
+
└→ stream ends
|
|
105
|
+
└→ if stopped: return
|
|
106
|
+
└→ wait(backoff with jitter)
|
|
107
|
+
└→ reconnectLoop() (retry)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**Backoff:** `delay = min(maxDelay, initialDelay * factor^attempt) * (1 + jitter * random(-1, 1))`
|
|
111
|
+
|
|
112
|
+
### Grammy middleware stack (updated)
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
1. apiThrottler() ← NEW (API transformer)
|
|
116
|
+
2. sequentialize(chatId) ← NEW (before all handlers)
|
|
117
|
+
3. allowlistMiddleware
|
|
118
|
+
4. bot.command("start", ...)
|
|
119
|
+
5. ... (all other handlers unchanged)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Bot Control API (Hono pattern)
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
index.ts
|
|
126
|
+
├→ createBot(config, deps) ← Grammy bot (existing)
|
|
127
|
+
├→ createApiServer(deps, port) ← Hono API (NEW)
|
|
128
|
+
└→ eventBus.start() ← SSE listener (existing)
|
|
129
|
+
|
|
130
|
+
createApiServer(deps)
|
|
131
|
+
└→ new Hono()
|
|
132
|
+
.get("/api/sessions", ...)
|
|
133
|
+
.get("/api/session/:sessionId", ...)
|
|
134
|
+
.post("/api/session/:sessionId/model", ...)
|
|
135
|
+
.post("/api/session/:sessionId/agent", ...)
|
|
136
|
+
.post("/api/session/:sessionId/new", ...)
|
|
137
|
+
.get("/api/models", ...)
|
|
138
|
+
.get("/api/agents", ...)
|
|
139
|
+
└→ Bun.serve({ hostname: "127.0.0.1", port, fetch: app.fetch })
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Dependencies shared: `sessionManager`, `turnManager`, `sdk`.
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## New Files
|
|
147
|
+
|
|
148
|
+
```
|
|
149
|
+
src/
|
|
150
|
+
api-server.ts ← Hono app + Bun.serve (Bot Control API)
|
|
151
|
+
api-server.test.ts ← Tests for all endpoints
|
|
152
|
+
|
|
153
|
+
deploy/
|
|
154
|
+
skills/
|
|
155
|
+
telegram-control/
|
|
156
|
+
SKILL.md ← AI skill doc (copy to .opencode/skills/ on deploy)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Modified Files
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
src/
|
|
163
|
+
event-bus.ts ← Add reconnect loop with exponential backoff
|
|
164
|
+
event-bus.test.ts ← Tests for reconnect behavior
|
|
165
|
+
bot.ts ← Add sequentialize() + pass connected/default to model handlers
|
|
166
|
+
bot.test.ts ← Test middleware ordering
|
|
167
|
+
config.ts ← Add apiPort config (TELEGRAM_API_PORT)
|
|
168
|
+
config.test.ts ← Test apiPort parsing
|
|
169
|
+
index.ts ← Add apiThrottler() + start API server + shutdown
|
|
170
|
+
handlers/models.ts ← Add filterModels(), update format* signatures
|
|
171
|
+
handlers/models.test.ts ← Tests for filtering, connected, ✓ indicator
|
|
172
|
+
package.json ← Add hono, @grammyjs/transformer-throttler, @grammyjs/runner
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## TDD Execution Order
|
|
178
|
+
|
|
179
|
+
### A1. EventBus reconnect (8-10 tests)
|
|
180
|
+
|
|
181
|
+
**File:** `src/event-bus.ts` + `src/event-bus.test.ts`
|
|
182
|
+
|
|
183
|
+
Changes to EventBus:
|
|
184
|
+
- Add `reconnectLoop()` method that wraps `listen()` with retry logic
|
|
185
|
+
- Add backoff config: `initialDelayMs`, `maxDelayMs`, `backoffFactor`, `jitter`
|
|
186
|
+
- `start()` calls `reconnectLoop()` instead of `listen()` directly
|
|
187
|
+
- `stop()` sets `stopped = true` (already exists) — breaks reconnect loop
|
|
188
|
+
- Log reconnection attempts with attempt number and delay
|
|
189
|
+
|
|
190
|
+
**Tests:**
|
|
191
|
+
|
|
192
|
+
*Reconnect behavior:*
|
|
193
|
+
1. Reconnects after stream ends (mock stream that yields 2 events then closes)
|
|
194
|
+
2. Does NOT reconnect after stop() is called
|
|
195
|
+
3. Backoff delay increases on consecutive failures
|
|
196
|
+
4. Backoff delay resets to initial after successful connection
|
|
197
|
+
5. Jitter adds randomness to delay (delay varies between calls)
|
|
198
|
+
6. Max delay is capped at maxDelayMs
|
|
199
|
+
|
|
200
|
+
*Error handling:*
|
|
201
|
+
7. Reconnects after subscribe() throws an error
|
|
202
|
+
8. Reconnects after stream throws mid-iteration
|
|
203
|
+
9. Continues processing events after reconnecting (events from new stream arrive)
|
|
204
|
+
|
|
205
|
+
*Integration:*
|
|
206
|
+
10. stop() during reconnect delay cancels the reconnect
|
|
207
|
+
|
|
208
|
+
### B2. Grammy middleware — sequentialize + throttler (3-4 tests)
|
|
209
|
+
|
|
210
|
+
**File:** `src/bot.ts` + `src/bot.test.ts` + `src/index.ts`
|
|
211
|
+
|
|
212
|
+
Changes:
|
|
213
|
+
- `createBot()`: add `sequentialize()` as first middleware (before allowlist)
|
|
214
|
+
- `index.ts`: add `apiThrottler()` to `bot.api.config`
|
|
215
|
+
|
|
216
|
+
**Tests:**
|
|
217
|
+
1. Bot has sequentialize middleware (verify middleware count or behavior)
|
|
218
|
+
2. apiThrottler is configured on bot API (verify transformer count)
|
|
219
|
+
3. sequentialize key uses chat ID
|
|
220
|
+
|
|
221
|
+
### C3. Smart /model filtering (8-10 tests)
|
|
222
|
+
|
|
223
|
+
**File:** `src/handlers/models.ts` + `src/handlers/models.test.ts` + `src/bot.ts`
|
|
224
|
+
|
|
225
|
+
New function:
|
|
226
|
+
- `filterModels(models)` — groups by `family`, picks most recent `release_date`
|
|
227
|
+
per family, excludes `status: "deprecated"`
|
|
228
|
+
|
|
229
|
+
Changes to existing functions:
|
|
230
|
+
- `formatProviderList(providers, connected?)` — filter by connected, show count
|
|
231
|
+
- `formatModelList(providerID, providerName, models, activeModelID?)` — ✓ marker
|
|
232
|
+
- `handleModel(params)` — pass `connected` and `default` from SDK response
|
|
233
|
+
- `bot.ts` callback — apply `filterModels`, pass `activeModelID`
|
|
234
|
+
|
|
235
|
+
**Tests:**
|
|
236
|
+
|
|
237
|
+
*filterModels:*
|
|
238
|
+
1. Groups by family, returns most recent per family
|
|
239
|
+
2. Excludes deprecated models
|
|
240
|
+
3. Keeps models without family field (uses id as key)
|
|
241
|
+
4. Handles single model per family (no-op)
|
|
242
|
+
|
|
243
|
+
*formatProviderList:*
|
|
244
|
+
5. Filters to only connected providers when connected array provided
|
|
245
|
+
6. Shows model count in button text
|
|
246
|
+
7. Shows all providers when connected not provided (backward compat)
|
|
247
|
+
|
|
248
|
+
*formatModelList:*
|
|
249
|
+
8. Marks active model with ✓ prefix
|
|
250
|
+
9. No ✓ when activeModelID not provided
|
|
251
|
+
|
|
252
|
+
*handleModel:*
|
|
253
|
+
10. Passes connected and default from SDK response
|
|
254
|
+
|
|
255
|
+
### D4. Bot Control API server (10-12 tests)
|
|
256
|
+
|
|
257
|
+
**File:** `src/api-server.ts` + `src/api-server.test.ts` + `src/config.ts`
|
|
258
|
+
|
|
259
|
+
New files:
|
|
260
|
+
- `api-server.ts` — Hono app with all endpoints + `createApiServer()` function
|
|
261
|
+
- `api-server.test.ts` — tests against the Hono app (no real Bun.serve needed,
|
|
262
|
+
test via `app.request()` — Hono's built-in test utility)
|
|
263
|
+
|
|
264
|
+
Config change:
|
|
265
|
+
- `config.ts` — add `apiPort: number` (from `TELEGRAM_API_PORT`, default 4097)
|
|
266
|
+
|
|
267
|
+
Index change:
|
|
268
|
+
- `index.ts` — call `createApiServer()`, stop on shutdown
|
|
269
|
+
|
|
270
|
+
**Hono app design (following OpenCode pattern):**
|
|
271
|
+
```ts
|
|
272
|
+
import { Hono } from "hono"
|
|
273
|
+
|
|
274
|
+
export type ApiDeps = {
|
|
275
|
+
sessionManager: SessionManager
|
|
276
|
+
turnManager: TurnManager
|
|
277
|
+
sdk: OpencodeClient
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export function createApiApp(deps: ApiDeps) {
|
|
281
|
+
return new Hono()
|
|
282
|
+
.get("/api/sessions", (c) => { /* list active sessions */ })
|
|
283
|
+
.get("/api/session/:sessionId", (c) => { /* session info */ })
|
|
284
|
+
.post("/api/session/:sessionId/model", (c) => { /* set model */ })
|
|
285
|
+
.post("/api/session/:sessionId/agent", (c) => { /* set agent */ })
|
|
286
|
+
.post("/api/session/:sessionId/new", (c) => { /* new session */ })
|
|
287
|
+
.get("/api/models", (c) => { /* connected + filtered */ })
|
|
288
|
+
.get("/api/agents", (c) => { /* available agents */ })
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export function createApiServer(deps: ApiDeps, port: number) {
|
|
292
|
+
const app = createApiApp(deps)
|
|
293
|
+
return Bun.serve({
|
|
294
|
+
hostname: "127.0.0.1",
|
|
295
|
+
port,
|
|
296
|
+
fetch: app.fetch,
|
|
297
|
+
})
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
**Tests (using `app.request()`):**
|
|
302
|
+
|
|
303
|
+
*Session endpoints:*
|
|
304
|
+
1. GET /api/sessions returns all active sessions with chatId, model, agent
|
|
305
|
+
2. GET /api/sessions returns empty array when no sessions
|
|
306
|
+
3. GET /api/session/:sessionId returns session info
|
|
307
|
+
4. GET /api/session/:sessionId returns 404 for unknown session
|
|
308
|
+
|
|
309
|
+
*Model/Agent override:*
|
|
310
|
+
5. POST /api/session/:sessionId/model sets modelOverride in SessionManager
|
|
311
|
+
6. POST /api/session/:sessionId/model returns 404 for unknown session
|
|
312
|
+
7. POST /api/session/:sessionId/agent sets agentOverride in SessionManager
|
|
313
|
+
8. POST /api/session/:sessionId/agent returns 404 for unknown session
|
|
314
|
+
|
|
315
|
+
*Session management:*
|
|
316
|
+
9. POST /api/session/:sessionId/new creates new session for the chat
|
|
317
|
+
|
|
318
|
+
*Provider/agent listing:*
|
|
319
|
+
10. GET /api/models returns connected providers with filtered models
|
|
320
|
+
11. GET /api/agents returns available agents
|
|
321
|
+
12. GET /api/models uses filterModels (reuses C3 logic)
|
|
322
|
+
|
|
323
|
+
### E5. SKILL.md — AI model/agent control skill
|
|
324
|
+
|
|
325
|
+
**File:** `deploy/skills/telegram-control/SKILL.md`
|
|
326
|
+
|
|
327
|
+
No tests — documentation only. Teaches the AI:
|
|
328
|
+
1. How to discover its own sessionId (curl OpenCode session list, match title)
|
|
329
|
+
2. Bot Control API base URL and endpoints
|
|
330
|
+
3. Workflow examples: "switch to Opus", "use a different agent", "start new chat"
|
|
331
|
+
4. Available models/agents listing
|
|
332
|
+
|
|
333
|
+
Deployment: copy to `$OPENCODE_DIRECTORY/.opencode/skills/telegram-control/SKILL.md`
|
|
334
|
+
on the VPS. Document in VPS.md.
|
|
335
|
+
|
|
336
|
+
### F6. E2E tests — phase-6.5 (6-8 tests)
|
|
337
|
+
|
|
338
|
+
**File:** `e2e/phase-6.5.test.ts`
|
|
339
|
+
|
|
340
|
+
New E2E tests validating the real Telegram + OpenCode + Bot API integration.
|
|
341
|
+
The E2E runner already has the bot process running (with API server on :4097).
|
|
342
|
+
|
|
343
|
+
**/model filtering (via Telegram userbot):**
|
|
344
|
+
1. /model shows provider buttons (not 86 — verify count is small, e.g. < 10)
|
|
345
|
+
2. Clicking a provider shows models (verify count is small per provider)
|
|
346
|
+
|
|
347
|
+
**Bot Control API (via curl from E2E test):**
|
|
348
|
+
3. GET /api/sessions returns at least 1 session after sending a message
|
|
349
|
+
4. POST /api/session/:id/model changes the model (GET confirms new value)
|
|
350
|
+
5. POST /api/session/:id/agent changes the agent (GET confirms new value)
|
|
351
|
+
6. POST /api/session/:id/new creates a new session (old sessionId differs)
|
|
352
|
+
7. GET /api/models returns providers with models (non-empty)
|
|
353
|
+
8. GET /api/agents returns agents (non-empty)
|
|
354
|
+
|
|
355
|
+
**How E2E tests call the Bot API:**
|
|
356
|
+
The E2E test process runs on the same machine as the bot. It can call
|
|
357
|
+
`fetch("http://127.0.0.1:4097/api/sessions")` directly — no Telegram
|
|
358
|
+
intermediary needed. This tests the real Hono server with real SessionManager.
|
|
359
|
+
|
|
360
|
+
**Flow for test 4 (model change):**
|
|
361
|
+
```
|
|
362
|
+
1. Userbot sends "hello" → bot creates session
|
|
363
|
+
2. E2E calls GET /api/sessions → find sessionId
|
|
364
|
+
3. E2E calls POST /api/session/:id/model { providerID, modelID }
|
|
365
|
+
4. E2E calls GET /api/session/:id → verify modelOverride changed
|
|
366
|
+
5. Userbot sends "ping" → bot responds (model override is used)
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
### G7. Full E2E regression
|
|
370
|
+
|
|
371
|
+
Run `bun test ./e2e/` — all previous 32 tests + new 6-8 tests must pass.
|
|
372
|
+
|
|
373
|
+
---
|
|
374
|
+
|
|
375
|
+
## Dependencies to Add
|
|
376
|
+
|
|
377
|
+
```json
|
|
378
|
+
{
|
|
379
|
+
"dependencies": {
|
|
380
|
+
"hono": "^4.x",
|
|
381
|
+
"@grammyjs/transformer-throttler": "^1.x",
|
|
382
|
+
"@grammyjs/runner": "^2.x"
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
## Acceptance Criteria
|
|
388
|
+
|
|
389
|
+
- [ ] EventBus reconnects automatically after SSE stream breaks
|
|
390
|
+
- [ ] Reconnection uses exponential backoff (2s→30s)
|
|
391
|
+
- [ ] Bot continues working after OpenCode server restart
|
|
392
|
+
- [ ] Grammy apiThrottler handles 429 rate limits
|
|
393
|
+
- [ ] Grammy sequentialize prevents per-chat race conditions
|
|
394
|
+
- [ ] /model shows only connected providers (not all 86)
|
|
395
|
+
- [ ] /model shows only most recent model per family
|
|
396
|
+
- [ ] /model marks active model with ✓
|
|
397
|
+
- [ ] Bot Control API starts on configured port (default 4097)
|
|
398
|
+
- [ ] AI can list sessions via GET /api/sessions
|
|
399
|
+
- [ ] AI can change model via POST /api/session/:id/model
|
|
400
|
+
- [ ] AI can change agent via POST /api/session/:id/agent
|
|
401
|
+
- [ ] AI can create new session via POST /api/session/:id/new
|
|
402
|
+
- [ ] SKILL.md documents full workflow for AI
|
|
403
|
+
- [ ] E2E: /model shows filtered providers (small count, not 86)
|
|
404
|
+
- [ ] E2E: Bot Control API sessions, model, agent, new all work end-to-end
|
|
405
|
+
- [ ] `bun test src/` passes (all unit tests)
|
|
406
|
+
- [ ] `bun test ./e2e/` passes (all E2E tests, previous 32 + new ~7)
|
|
407
|
+
|
|
408
|
+
## Estimated Scope
|
|
409
|
+
|
|
410
|
+
- ~250-300 LOC new src (api-server.ts ~120, model filtering ~50, reconnect ~80)
|
|
411
|
+
- ~350-400 LOC new tests (unit + E2E)
|
|
412
|
+
- ~80 LOC SKILL.md
|
|
413
|
+
- 4 new files (api-server.ts, api-server.test.ts, e2e/phase-6.5.test.ts, SKILL.md)
|
|
414
|
+
- 3 new npm dependencies (hono, @grammyjs/transformer-throttler, @grammyjs/runner)
|
|
415
|
+
|
|
416
|
+
### Test Count Estimate
|
|
417
|
+
|
|
418
|
+
| File | New Tests |
|
|
419
|
+
|------|-----------|
|
|
420
|
+
| event-bus.test.ts | ~10 |
|
|
421
|
+
| bot.test.ts | ~3 |
|
|
422
|
+
| models.test.ts | ~10 |
|
|
423
|
+
| api-server.test.ts | ~12 |
|
|
424
|
+
| **Unit total** | **~35 new → ~287 total** |
|
|
425
|
+
| e2e/phase-6.5.test.ts | ~7 |
|
|
426
|
+
| **E2E total** | **~7 new → ~39 total** |
|