Telegram Bot UI 统一管理本机 Codex CLI 和 Claude Code CLI 终端会话。
采用服务解耦 + 插件化架构,为后续多 Agent 编排预留扩展点。
当前 MVP 已实现仓库选择、任务排队、历史日志持久化、git worktree 隔离和 Telegram 命令菜单注册。
本地运行已切换到受管单实例模式,默认使用 .runtime/telegram-ai-manager/local/ 存放 PID、日志和状态。
- Runtime: Node.js 20+ / TypeScript 5.5+ (strict mode)
- Telegram: grammY
- 终端桥接: node-pty(失败时自动回退
child_process.spawn) - 任务队列: BullMQ + Redis(失败时降级为 InMemory)
- 存储: SQLite (better-sqlite3)
- 日志: pino
- 测试: vitest
- 包管理: pnpm
- 所有模块通过
core/service-registry.ts做依赖注入,禁止跨模块直接 import 实现 - 插件通过
PluginContext+commandRegistry注册命令,不直接耦合 TelegramBot - Agent 适配器通过
AgentAdapter接口编程,新增 Agent 只需实现接口 - 事件驱动:所有跨模块通信走
EventBus,类型约束EventMap - 每个任务分配隔离 workspace;Git 仓库优先用
git worktree,非 Git 目录才复制 /repos只列出DEFAULT_WORKSPACE_SOURCE_PATH直接子目录下可识别的 Git 仓库- 用户通常先通过
/repos选择仓库,再创建/task、/codex、/claude任务;若未选择仓库,则回退到DEFAULT_WORKSPACE_SOURCE_PATH /task和自由文本会走默认 Agent;当前默认 Agent 是codex/task、/codex、/claude支持workspace::prompt显式覆盖目标路径- 同一 task id 重试前必须先清理残留 worktree 注册;任务失败、取消和重启恢复后必须回收 workspace;成功任务要保留 worktree 供后续提交
MessageHistoryStore会把机器人消息 id 持久化到data/message-history.json;RepositorySelectionStore与PendingTaskInputStore仅保存在进程内存中,重启后视为已清空- 任务发布采用分步流:
/submit只提交任务分支,/merge只合并到本地main,/push只推送origin/main;不做自动 rebase 或强制 merge GIT_BRANCH_ISOLATION只影响失败/取消/恢复阶段的临时 Git workspace 清理是否顺带删除task/<task_id>分支;/push成功后的 retained worktree 清理默认保留任务分支/push成功后必须自动清理对应任务的本地 worktree,但默认保留任务分支- 非 Git 任务只保留复制出的 workspace 路径,不进入
/submit、/merge、/push发布流 task_logs持久化任务输出,/logs读取历史日志而不是进程内缓存WORKSPACE_BASE_DIR必须位于源仓库目录外部,避免自我复制和路径污染- 本地 runtime 默认通过
RUNTIME_HEALTH_HOST/RUNTIME_HEALTH_PORT提供 readiness 检查 pnpm dev必须清理旧实例,避免getUpdates 409和僵尸任务污染plugin-mcp当前仍为预留插件,不暴露 Telegram 用户命令
- 内置命令:
/start、/repos、/task、/status、/logs、/cancel、/submit、/merge、/push、/clear、/reset - 插件命令:
/codex、/claude - 命令菜单由
src/bot/bot.ts在启动时通过setMyCommands()注册 /task、/codex、/claude的命令格式均支持[workspace::]prompt;/task和自由文本默认走codex/status需要展示当前已选仓库、活跃任务、worktree 路径、最近错误/logs省略task_id时读取当前用户最近一条任务;/cancel省略task_id时取消最近一条queued/running任务- Codex 任务默认不向 Telegram 流式推送中间过程,只在完成时返回最终结果;成功的 Git 任务还要附带
task_id、分支名、worktree 路径和/submit、/merge、/push提示,提取失败时明确引导使用/logs /submit、/merge、/push的默认目标是当前用户最近一条可执行的 Git 任务;/submit只会选择仍保留 Git worktree 的已完成任务,省略task_id时默认使用chore(task): submit <task_id>;若要自定义 commit message,当前实现必须显式传入task_id/submit、/merge、/push校验失败时必须明确返回阻断原因;/push成功后只清理本地 worktree,不自动删除任务分支- 任务完成后的 Telegram 发布按钮顺序必须是
submit -> merge -> push;merge、push除文本命令外,还应提供 Telegram 可点击按钮;文本命令直接执行,按钮路径在真正执行前增加确认步骤 /clear、/clear all、/reset会清理当前 chat 中已跟踪的机器人消息、仓库选择和任务上下文;消息跟踪结果持久化在data/message-history.json,修改这些行为时必须补测试- 运行脚本:
pnpm dev(受管后台启动)、pnpm dev:watch(裸 watch 调试)、pnpm stop、pnpm status
src/
├── core/ # 抽象层:EventBus, PluginManager, ServiceRegistry, types
├── services/ # 业务服务:terminal/, agent/, workspace/, task/
├── bot/ # Telegram Bot:commands/, handlers/, middleware/
├── plugins/ # 自包含插件:plugin-codex/, plugin-claude-code/, plugin-mcp/
└── shared/ # 通用工具:logger, constants, utils
- 函数优先于类,除非需要状态管理
- 所有公开 API 使用 JSDoc 注释
- 错误处理使用自定义 AppError 类型,不抛裸 Error
- 异步操作必须有超时(默认 30s)和取消机制
- 所有 CLI 子进程输出必须清理 ANSI 转义符
- 文件路径一律使用 path.join(),不硬编码分隔符
- ESM import 必须显式带
.js扩展 - 修改 Bot 命令、提示文案、仓库选择或任务生命周期时,同步更新文档和命令菜单描述
- 修改启动/停止方式、runtime 端口、PID 或日志路径时,同步更新文档和脚本说明
- 单元测试:
pnpm test - 测试框架:vitest
- 覆盖率目标:核心模块 > 80%
- 测试文件命名:
*.test.ts,与源文件同目录或tests/镜像目录 - 新增逻辑文件必须补对应测试;文档变更不要求新增测试,但若行为描述变化,相关行为测试必须同步存在
- 用户先选仓库:
RepositoryCatalog扫描DEFAULT_WORKSPACE_SOURCE_PATH的直接子目录;如果该路径本身就是仓库根目录,它不会出现在/repos菜单里 RepositorySelectionStore记录userId -> repoPath,但仅保存在进程内存中PendingTaskInputStore仅保存“下一条文本使用哪个 Agent”的两步输入状态;它与仓库选择一样不会跨重启持久化MessageHistoryStore会把机器人消息 id 持久化到data/message-history.json,供/clear、/reset清理历史机器人回复/repos仅返回 Git 仓库;未选择仓库时,任务默认落到DEFAULT_WORKSPACE_SOURCE_PATH- 创建任务时,
TaskRunner调用WorkspaceManager.prepareWorkspace() - Git 仓库走
git worktree add,worktree 根位于WORKSPACE_BASE_DIR - 非 Git 目标路径回退为目录复制
- Agent 在隔离目录运行,输出写入
task_logs /logs不带 task id 时读取当前用户最近一条任务;/cancel不带 task id 时取消当前用户最近一条活跃任务- 成功的 Git 任务保留 worktree,并向用户返回
task_id、分支名、worktree 路径以及/submit <task_id>、/merge <task_id>、/push <task_id>下一步提示;成功的非 Git 任务只保留 workspace 路径 - Codex 任务只在完成时回传最终结果;若未提取到最终结果,必须引导用户使用
/logs <task_id>查看原始日志 /submit省略task_id时默认作用于最近一条可提交 Git 任务,并自动使用默认提交信息;若要自定义 commit message,必须显式传入task_id/merge固定执行git merge --ff-only task/<task_id>;主仓库不在main、有未提交改动、任务 worktree 有脏改动或无法 fast-forward 时必须阻断/push固定执行git push origin main;只有任务分支已进入本地main且仓库存在origin时才允许执行,push 成功后清理任务 worktree 并清空持久化的workspacePath,失败时不得提前清理- 启动恢复时,历史
queued任务会重新入队,历史running任务会被标记为失败并清理 workspace;普通取消/失败同样会清理对应 worktree / workspace,状态和错误信息持久化到 SQLite
- 默认本地入口是
pnpm dev,不是裸tsx watch - 运行时状态统一写入
.runtime/telegram-ai-manager/local/ - SQLite 数据库存放在
data/tasks.db,机器人消息历史存放在data/message-history.json - 启动前必须清理旧 PID 和旧 bot 进程,避免 Telegram
getUpdates 409 - 健康检查默认只监听
127.0.0.1:43117 pnpm stop需要优先优雅停止,再做安全兜底清理
以下内容发生变化时,必须在同一组改动中同步更新:
README.md:用户可见功能、启动方式、命令清单、运行流程CLAUDE.md:架构约束、交互面、开发规则AGENTS.md:Codex / Agent 执行规则docs/mvp-implementation-plan.md:当前实现状态、运行约束和文档闭环范围.claude/agents/*.md:架构审查、代码审查、测试审查标准.claude/commands/*.md:计划、预检、同步和插件开发命令说明,需覆盖发布流与 Codex 最终回包约束src/plugins/CLAUDE.md:插件开发约束(命令注册、测试、文档同步)
- 分支:优先使用
codex/<name>、claude/<name>或语义化前缀分支,不直接在main上开发 - 提交格式:Conventional Commits (
feat:,fix:,refactor:,docs:,test:,chore:) - 每个 Agent 工作在独立分支,通过 PR 合并到 main
- 禁止直接推送 main 分支
pnpm install # 安装依赖
pnpm dev # 受管后台启动本地实例
pnpm dev:watch # 原始 tsx watch 调试模式
pnpm build # TypeScript 编译
pnpm start # 受管方式启动 dist(先执行 pnpm build)
pnpm stop # 停止当前本地实例
pnpm status # 查看 PID / readiness / 日志
pnpm test # 运行测试
pnpm lint # ESLint 检查
pnpm typecheck # tsc --noEmit- 不要修改
core/types.ts中的接口签名,除非同步更新所有实现 - 不要在插件中直接操作 Bot 实例,通过 PluginContext / commandRegistry 注册
- 不要在 service 层引入 Telegram 相关类型,保持传输层无关
- Redis 连接失败时队列应降级为内存模式
REDIS_URL仍属于必填配置项;自动降级仅覆盖 Redis 服务不可用场景node-pty失败时必须保持child_process.spawn回退链路可用- 不要把
WORKSPACE_BASE_DIR配到源仓库内部 - 不要在本地同时运行多个 bot 实例