探索主題

Agent: 探索者 (explorer)
Confidence: 68%
Cost: $0.3543
Duration: 1m 22s
Task ID: 34f1a317-0037-433a-899c-f5d5d13c5e20


現在我有完整的資訊了。讓我彙整探索報告。

探索主題

grammY Bot 框架的中介軟體設計模式

發現

  1. 我們的中介軟體是扁平的線性堆疊index.ts:9-14):4 個 bot.use() 呼叫依序串接。grammY 實際上支援樹狀結構——Composer 可以建立分支、巢狀模組、error boundary,但我們完全沒用到。

  2. message-handler.ts 是 250 行的巨型 handler:plugin routing、intent matching、user tracking、model routing、Claude 呼叫、streaming progress 全部擠在同一個 bot.on('message:text') 裡。這正是 Composer 模組化要解決的問題——每個關注點應該是獨立的 Composer。

  3. grammY 的 Composer.filter() + errorBoundary() 組合可以取代我們手動的 if/else 分支。例如:admin 判斷可以用 bot.filter(ctx => ctx.from?.id === ADMIN_ID) 建立專屬分支,而不是在 handler 內部 if/else。

  4. Error boundary 可以做域隔離:目前 errorHandlerMiddleware 是全域 try/catch,但 grammY 支援 per-module error boundary。plugin routing 失敗不應該影響到 Claude Code 呼叫——可以各自包在不同的 errorBoundary 裡。

  5. 可測試性的關鍵突破:獨立的 Composer 模組可以單獨 unit test——不需要啟動整個 bot,直接 new Composer() → 注入 mock context → 驗證行為。目前 message-handler.ts 幾乎無法單元測試。

有趣的連結

  • grammY 的 bot.route() 可以取代我們的 model routing if/else 分支——根據 route tier 把 update 分流到不同 Composer
  • Composer.lazy() 支援動態載入中介軟體——可以讓 plugin hot-reload 更優雅

延伸問題

  • 具體重構方案:如何把 message-handler.ts 拆成 userTrackingComposerpluginRouterComposerintentMatchComposerclaudeComposer(admin) 的 Composer 樹?每個 Composer 獨立測試。
  • Composer.lazy()是否能改善 plugin hot-reload?目前用 file-copy cache busting,lazy middleware 可能更乾淨。

重要性:4/5

能直接改善專案品質:message-handler.ts 的可維護性和可測試性是當前瓶頸。拆分成 Composer 樹是低風險、高回報的重構。

★ Insight ─────────────────────────────────────
grammY 的中介軟體不是堆疊,是樹。 大多數教程只教 bot.use(a); bot.use(b) 的線性模式,但 grammY 內部維護的是完整的樹狀結構。Composer 是這棵樹的節點,filter()/on() 建立分支,errorBoundary() 建立隔離域。理解這點後,「一個 250 行的 handler」就可以變成「5 個各 50 行的可測試模組」。

實作路線圖

1
2
3
4
5
6
bot.use(errorBoundary)           // 全域錯誤兜底
└─ bot.use(authComposer) // 身份驗證
└─ bot.use(trackingComposer) // 用戶追蹤(fire-and-forget)
├─ pluginComposer.errorBoundary(...) // plugin routing(隔離)
├─ intentComposer // NL intent matching
└─ claudeComposer.filter(isAdmin) // Claude Code(僅 admin)

─────────────────────────────────────────────────