欢迎使用并协同开发 CoBot 项目。本文档面向参与本项目代码阅读、架构设计、排错诊断及代码评审的 AI Agent 及人类开发者,旨在明确项目的技术栈架构、智能体角色分工、核心设计约束与开发规约。
CoBot 是一个使用 TypeScript (tsx + ESM) 编写的 Telegram Bot,通过 @anthropic-ai/claude-agent-sdk 驱动本地 Claude Code 实例,实现与本地工作区会话的交互、任务调度、多模态支持及自动化管理。
本项目内置了 3 个专有 Agent 配置文件(位于 .claude/agents/),针对不同任务场景提供了定制化的 Prompt 与上下文约束:
| Agent 角色 | 配置文件 | 职责范围 |
|---|---|---|
| Architect | cobot-architect.md | 架构设计与新功能规划:负责新命令设计、队列/注册表模型演进、多模态支持与 SDK/Bot 边界重构。 |
| Debugger | cobot-debug.md | 运行时故障诊断:排查 Telegram ↔ CoBot ↔ Claude SDK 链路异常、代理超时与数据库状态。 |
| Reviewer | cobot-review.md | 代码评审与质量把控:审查 PR/Diff,重点关注流式正确性、队列安全、代理/网络边角案、Telegram API 限频及测试覆盖率。 |
| 视角/分层 | 技术选型 | 说明 |
|---|---|---|
| Bot 框架 | grammY (1.30+) |
使用 Long-polling 机制。注意:底层为 node-fetch,需直接通过 baseFetchConfig 注入代理。 |
| Agent SDK | @anthropic-ai/claude-agent-sdk |
封装为 runClaude 异步生成器,通过 query() 进行流式推理与工具调用。 |
| 持久化存储 | better-sqlite3 |
使用 WAL 模式,数据库文件位于 data/cobot.db,管理任务状态与 Cron 任务。 |
| 定时调度器 | node-cron |
由 src/scheduler/cron.ts 的 CronManager 统一调度与管理。 |
| 运行时 | Node.js (>=20) + tsx |
标准 ES Module (type: "module")。 |
| 测试框架 | Node.js 内置 Test Runner | 命令 node --import tsx --test <files...>,禁止引入 vitest/jest 等外部框架。 |
| 日志组件 | pino + pino-pretty |
结构化 JSON/Pretty 日志输出。 |
| 网络代理 | scutil / ENV 自动检测 |
自动检测 macOS代理并生成 HttpsProxyAgent / SocksProxyAgent(端口 127.0.0.1:10808)。 |
src/
├── bot/
│ ├── bot.ts — grammY 初始化、中间件配置、权限拦截与命令路由
│ ├── commands.ts — /command 响应逻辑、纯文本消息处理与键盘别名
│ ├── runs.ts — runTurn / runOne / submitInteractive 任务处理与队列消耗循环
│ ├── streaming.ts — TelegramStreamer(Rich Message 与 HTML 降级流式渲染)
│ ├── indicator.ts — SilenceIndicator 心跳与状态提示("Still working…")
│ ├── media.ts — handlePhoto / handleDocument 多模态附件解析
│ └── *.test.ts — bot 模块单元测试
├── claude/
│ ├── driver.ts — runClaude 异步生成器,对 SDK query() 的隔离封装
│ ├── sessions.ts — SDK listSessions 接口封装
│ ├── types.ts — DriverEvent、PromptInput、MediaAttachment 等核心类型定义
│ └── driver.test.ts — SDK 驱动层测试
├── registry/
│ └── registry.ts — 单 Chat FIFO 任务队列与活跃任务(active-run)追踪
├── store/
│ └── db.ts — SQLite 数据库绑定、running_tasks 与 cron_jobs 表管理
├── scheduler/
│ └── cron.ts — CronManager 定时任务注册、分发与中止管理
├── util/
│ ├── proxy.ts — 代理环境检测与 Agent 实例化
│ ├── http.ts — 带代理的 Buffer 下载工具
│ ├── send.ts — Telegram 发送封装(带重试/代理的 sendMessage 等)
│ ├── tgfmt.ts — Telegram HTML 转义与 Markdown 转换引擎
│ └── logger.ts — Pino 日志封装
├── config.ts — YAML 与环境变量配置加载器
└── index.ts — 应用入口:初始化 Bot、装载 Cron 并启动长轮询
所有 AI Agent 或人类开发者在修改代码时,必须遵守以下核心约束:
src/claude/driver.ts是 SDK 的唯一边界。src/bot/或src/registry/禁止直接import任何@anthropic-ai/claude-agent-sdk的类型或函数。- Bot 层仅通过
DriverEvent联合类型与 Driver 进行通信。若需增加交互状态,应在src/claude/types.ts中扩充DriverEvent变体。
- grammY 的 Long-polling 更新循环必须保持流畅,绝对不能在 Update Handler 中直接
await长时间运行的任务(否则/stop等指令将被阻塞无法响应)。 - 提交任务必须使用
void runOne(...)或submitInteractive非阻塞触发。
- 每个 Chat 拥有独立的 FIFO 队列(由
registry管理)。 submitInteractive在任务发起前检查registry.isActive(),若当前已有活跃任务则入队。registry.start()必须在await runTurn()之前同步调用,确保无并发隙缝。任务完成后由drainQueued触发下一任务。
TelegramStreamer有两级长度控制,注意区分:- 单条消息追加阈值
maxEditChars(默认3500,由config.telegram.maxEditChars配置):buffer 累积超过该值即 flush 成新消息并重置编辑目标。 - Telegram 硬上限分片
TG_HARD_LIMIT = 32000(streaming.ts内常量):单条消息超过 32768 字符上限时按 32000 分片发送。
- 单条消息追加阈值
- 节流限制:默认
flushMs = 900ms(由config.telegram.flushMs配置)串行节流,所有发送经 Promise 链串行化避免并发 edit 限频。 - 采用 Rich Message 优先 + HTML 降级 策略。中间态文本发送给 Telegram 解析前,必须通过
sanitizeStreamMarkdown修复未闭合的代码块与行内标点。 - 详细渲染机制请参考文档:docs/markdown_rendering.md。
- 由于无前端交互界面,Driver 默认设置
canUseTool = () => ({ behavior: "allow" })自动批准工具调用,防止任务因等待交互权限而超时挂起。
- 本机对
api.telegram.org的请求需通过代理(127.0.0.1:10808)。 - 任何新增的 HTTP/HTTPS 请求客户端(包括 grammY
baseFetchConfig),均须传入src/util/proxy.ts生成的 Proxy Agent。
- 添加新命令:
- 在
src/bot/commands.ts中编写命令处理逻辑。 - 在
src/bot/bot.ts中注册路由。 - 如为用户侧核心功能,同步提供中文按键别名。
- 在
- 编写与运行单元测试:
- 测试文件命名遵循
src/<module>/<file>.test.ts。 - 在
package.json的"test"脚本中注册新测试文件。 - 运行测试指令:
npm test
- 测试文件命名遵循
- 类型检查:
- 提交前须运行:
npm run typecheck
- 提交前须运行:
本项目提供了统一的服务控制脚本 scripts/cobot.sh 及全局 cobot CLI 指令:
-
环境依赖与 CLI 注册:
./scripts/cobot.sh install
该指令会自动复制配置文件模版、安装 npm 依赖、运行交互式配置(Telegram Token / 用户 ID / 开发根目录,默认取项目目录的上级)、类型检查,注册全局
cobotCLI 软链接,并在配置完成后自动启动 Bot 服务。 -
全局服务管理命令(可在系统任何目录下运行):
cobot start或./scripts/cobot.sh start:以 Aqua(GUI)会话的 launchd agent 启动 Bot 服务,自动检测并注入代理(如127.0.0.1:10808)。cobot stop或./scripts/cobot.sh stop:卸载 agent(并launchctl disable,避免下次登录被重新拉起)、平滑关闭进程,并清理 SQLite 数据库中因中断残留的任务锁。cobot restart或./scripts/cobot.sh restart:安全重启 Bot 服务。cobot status或./scripts/cobot.sh status:查看服务 PID 状态、launchd agent/session、代理连通及bot.log运行日志。cobot uninstall或./scripts/cobot.sh uninstall:停止服务并移除全局cobotCLI、launchd agent 与node_modules/dist/data/日志/config.yaml/.env(源码目录保留)。加--yes跳过确认提示。
CoBot 进程树必须位于 macOS 的 Aqua(GUI)launchd 会话内。 securityd 只向该会话内的进程释放登录钥匙串密文;处于 per-user Background 会话的进程(SSH 登录、或 LimitLoadToSessionType 声明为 Background 的任务)读取时会被拒绝(errSecInteractionNotAllowed)。此时 ~/.gitconfig 里的 credential.helper = !gh auth git-credential 会静默失效,因为 gh 的 OAuth token 存在登录钥匙串中(~/.config/gh/hosts.yml 无明文字段):
security find-generic-password -s gh:github.com -w → 拒绝(密文不可读,条目元数据仍可列出)
gh auth git-credential get → 返回空
git push → fatal: could not read Username for
'https://github.com': Device not configured
注意写钥匙串是允许的、读才被拒,所以这种会话里 gh auth login 会「成功」却毫无作用——这是该故障最迷惑人的地方。因此修复方向是让 Bot 跑进 Aqua,而不是把 token 挪到保护更弱的地方。
因此 start 不再 nohup 拉起进程,而是 bootstrap 一个 LimitLoadToSessionType=[Aqua] 的 LaunchAgent(默认 label com.cobot.agent,plist 位于 ~/Library/LaunchAgents/)。job 位于 gui/<uid>,无论调用者处于哪个会话都会被拉起——所以从 Telegram 触发的 /restart(由 Bot 自己 spawn cobot.sh)落地的新实例同样在 Aqua,可自愈一个曾被 SSH 启动的实例。
cobot status 会打印 [Session] 行(由 cobot __run 启动时写入 data/session):非 Aqua 即意味着钥匙串不可用、Bot 的 git/gh 推送必然失败。
可用环境变量:COBOT_AGENT_LABEL(自定义 label)、COBOT_KEEPALIVE=1(崩溃后由 launchd 自动重启,默认关闭)。
遇到运行异常时,请按顺序检查:
- 服务控制:运行
cobot status或cobot restart排查或重置服务状态。 - 代理可用性:
scutil --proxy确认127.0.0.1:10808代理正常开启。 - 残留任务清理:
sqlite3 data/cobot.db "SELECT * FROM running_tasks WHERE status='running'"(确认是否有未清理的死锁任务)。 - 日志分析:查阅
bot.log与 CoBot 控制台 pino 输出。