Skip to content

Latest commit

 

History

6 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

tinyotb

tinyotb = Tiny Opencode-telegram Bridge——opencode server 的 Telegram 远程控制桥。常驻 daemon 把 opencode 会话事件推到 Telegram,并把你的回复 / 审批转回 opencode。

项目原始来源:codex-telegram-bridge (该项目的模块划分、渲染、daemon 结构可对照参考;它是 WebSocket/codex 后端,本项目的 opencode HTTP+SSE 部分为全新实现)。 Rust 实现,纯同步(ureq),无 async runtime,单二进制。

本项目由 AI 模型 Kimi K3DeepSeek V4 Flash 协作实现。

功能

  • /away 开启远程模式(先确认 opencode server 可达,只推之后的事件);/back 关闭;
  • 会话完成 / 出错时推送通知(附回答原文摘要);
  • opencode 请求权限时推送通知,附「允许一次 / 总是允许 / 拒绝」内联按钮,点按钮即完成审批;
  • 用 Telegram 的「回复(Reply)」回复某条通知,内容转发回对应会话;
  • /sessions [n] 浏览最近会话、/new [提示词] 新建会话、/status 查看状态、/help;
  • /project [id] 查看 / 切换已注册项目(仅限 config 里预置的目录,远程不接受任意路径);
  • systemd 用户服务安装(tinyotb daemon install)。

安装

cargo build --release
install -m755 target/release/tinyotb ~/.local/bin/tinyotb

快速开始

# 1. 创建 Telegram bot(找 @BotFather),拿到 token;
#    把 bot 加进你的聊天,拿到 chat_id(可用 @userinfobot 之类查询)。
# 2. 配置:
tinyotb setup --bot-token 123456:ABC... --chat-id 123456789 \
  --allowed-user-id 123456789 --directory /path/to/project
# 3. 检查:
tinyotb doctor
# 4. 启动 daemon(或安装为 systemd 用户服务):
tinyotb daemon install        # 推荐:开机自启 + 崩溃重启
# 手动方式:
tinyotb daemon run

然后到 Telegram 里发 /away,之后的事件就会推过来。

命令

命令 说明
/away 开启远程模式(先检查 opencode server 可达)
/back 关闭远程模式
/status away 状态 + server 健康 + 待审批数 + 会话数
/sessions [n] 最近 n 个会话(默认 5,每条一条消息,回复即切换)
/new [提示词] 新建会话,可选附带首条提示词
/project 列出已注册项目(● = 当前)
/project <id> 切换到已注册项目(daemon 热重载,无需重启)
/help 帮助

本地 CLI:

命令 说明
tinyotb setup ~/.tinyotb/config.json(0600);--add-project <路径> 可重复,注册可切换项目
tinyotb doctor 检查 opencode 二进制 / server / config / Telegram token / daemon 状态
tinyotb status away 状态 + server 健康 + state 概况
tinyotb daemon run [--once] 前台运行 daemon;--once 只跑一轮(调试)
tinyotb daemon start 启动 daemon:已装 systemd 服务走 systemctl;未装则在后台拉起(日志写 ~/.tinyotb/daemon.log)
tinyotb daemon stop 停止 daemon(systemd 服务或裸跑进程,按 lock 文件里的 pid)
tinyotb daemon install / uninstall systemd 用户服务(~/.config/systemd/user/tinyotb.service)

所有 CLI 输出为 JSON envelope:{ok: true, data: ...}{ok: false, error: {code, message}}。daemon 的 stdout 为 JSONL 日志。

systemd 服务管理

tinyotb daemon install 会在 ~/.config/systemd/user/tinyotb.service 写入 unit(开机自启 + 崩溃自动重启,Restart=on-failure),日常控制直接用 systemctl --user:

systemctl --user start tinyotb      # 启动(等价:tinyotb daemon start)
systemctl --user stop tinyotb       # 停止(等价:tinyotb daemon stop)
systemctl --user restart tinyotb    # 重启(改完 config 后必须重启才生效)
systemctl --user status tinyotb     # 查看运行状态
systemctl --user enable tinyotb     # 开机自启(install 已自动做)
systemctl --user disable tinyotb    # 取消开机自启
journalctl --user -u tinyotb -f     # 实时查看日志

注意:

  • 开机自启的前提是用户会话在登录前就存在:执行一次 loginctl enable-linger $USER,让未登录时服务也能跑(远程模式正是要人不在时工作,建议开启);
  • ~/.tinyotb/config.json 后需 systemctl --user restart tinyotb 才生效(唯一例外:/project 切项目是热重载,不用重启);
  • opencode server 由 daemon 自动拉起(autostart: true 且不可达时),平时无需手动维护;
  • 卸载:tinyotb daemon uninstall(停止 + 禁用 + 删除 unit)。

配置

~/.tinyotb/config.json(0600;TINYOTB_STATE_DIR 可覆盖目录):

{
  "telegram": { "bot_token": "...", "chat_id": 123, "allowed_user_id": 456 },
  "opencode": {
    "base_url": "http://127.0.0.1:4096",
    "username": null,
    "password": null,
    "autostart": true,
    "command": "opencode",
    "directory": "/path/to/project"
  },
  "projects": [
    { "id": "tinyotb", "label": "tinyotb", "cwd": "/path/to/project" },
    { "id": "dex", "label": "dex", "cwd": "/path/to/other" }
  ],
  "poll_interval_ms": 1000
}
  • opencode.username/password:opencode server 的 HTTP Basic 认证(OPENCODE_SERVER_USERNAME/PASSWORD),不设则不需要;
  • autostart:daemon 发现 server 不可达时自动 opencode serve --port <base_url 端口>;
  • base_url 强制 loopback(127.0.0.1 / localhost / ::1),远程 URL 拒绝;
  • allowed_user_id 校验所有入站消息 / callback;不设则不做用户校验(不推荐);
  • projects:/project 的可切换注册表(轻量版多项目)。opencode.directory 是当前项目;切换 = 把某个注册项的 cwd 设为当前目录,daemon 热重载(重建 REST 客户端与 SSE 流,opencode server 无需重启)。切换后旧的回复路由 / 待审批 / 最近会话清空。

架构

Telegram Bot API ←→ telegram.rs ←→ daemon.rs 主循环 ←→ opencode.rs (REST)
                                    ↑   |
                                    |   ↓
                              sse.rs reader 线程 ←→ opencode /event (SSE)
  • 纯同步:ureq 3 做 HTTP 与 SSE;SSE 按行解析,跑在专用 reader 线程,经 mpsc 送回主循环;断线退避重连(1s/2s/5s/10s);
  • 纯逻辑与 IO 分离:sse.rs 行解析、events.rs 事件映射、render.rs 渲染全部无 IO,可单测;
  • 幂等:事件经 seen_event_ids 去重;会话完成用「busy/retry → idle 状态迁移」判定,session.statussession.idle 双事件不会重复通知;
  • 重连对账:SSE 重连成功后 GET /session/status 与内存状态比对,补出断线期间的完成事件;
  • 状态持久化:state.json 原子写(tmp + rename),读时容错;路由表 / 已见事件各保留最近 500 条;
  • 单实例:daemon.lock(fs2 排他锁),防止双 daemon 造成 getUpdates 冲突。

已知限制(与 opencode 1.18.x 实测):

  • 断线期间的 permission.asked 不会在重连后重放(服务端不缓存),只能等用户手动处理;session.idle 会重放,配合对账可补完成事件;
  • 事件帧无 id 字段,去重不依赖服务端 id;
  • 权限回复 body 用 {response: "once"|"always"|"reject"},remember 字段服务端未实现。

测试

cargo test          # 单元测试 + 二进制冒烟 + 端到端(mock opencode + mock Telegram)
cargo clippy --all-targets

tests/e2e.rsTcpListener::bind("127.0.0.1:0") 起最小 HTTP/SSE mock,spawn 真实 daemon 二进制,验证 /away → 权限按钮 → 完成通知 → 回复路由 → 回调审批 全链路。

手动验收清单

  1. opencode serve(或 autostart: true 让 daemon 拉起);
  2. 测试 bot 发 /away → 确认「远程模式已开启」;
  3. 在 opencode 里发起一个会话 → 收到完成通知(含回答摘要);
  4. 回复该通知 → 收到「已发送」确认;
  5. 触发权限请求(如 "*": "ask")→ 收到带按钮通知 → 点「允许一次」→ 按钮变为「✅ 已允许一次」;
  6. /status/sessions/new/back 各过一遍。

许可

MIT

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages