警告:本项目主要由 AI 生成,代码质量、安全性和适用性需要使用者自行审查和判断。项目会连接 QQ Bot、运行 Codex CLI、访问网络、读取环境变量,并在 Docker 容器内执行命令。部署前请认真检查源码、Docker 配置、挂载目录、密钥注入方式和运行权限,风险自负。
通过 QQ 单聊与 Codex CLI 交互的机器人。
推荐使用 Docker 部署:QQBot 和 Codex 运行在同一个容器里,Codex 使用容器专用的 CODEX_HOME,持久化在当前项目的 ./codex-home。这样容器里的 Codex 配置、会话和认证状态都和宿主机的 ~/.codex 分开。
- 只支持 QQ 单聊。
- 不做 openid 白名单过滤,任何单聊发送者都可以使用。
/new之前共享同一个 Codex 会话。- 支持给 Codex 会话命名、创建、恢复和删除命名;删除命名不会删除 Codex 实际 session 记录。
- 如果 Codex 正在执行任务,新的普通消息会加入队列;需要立即纠偏时使用
/interrupt <消息>。 - 支持接收 QQ 单聊图片,下载到
workspace/qq-images后传给 Codex。 - 支持发送 Codex 在
workspace中新生成或修改的图片文件。 - 支持接收 QQ 单聊文件,下载到
workspace/qq-files/<日期>后把路径传给 Codex 读取。 - 支持通过
/file send <path>或容器内qq-file send <path>发送workspace内文件。 - 支持用 QQ Markdown 消息发送 Codex 的回答;发送失败会自动回退为普通文本。
- 支持 Codex 官方 memories,通过
/memory管理容器内 Codex 记忆;/new后的下一次任务会把当前记忆作为一次性系统提示传入。 - 提供容器内
qq-memoryCLI 和 Codex skill,Codex 可在用户要求“记住/忘记/查看记忆”时自行调用同一套记忆工具。 - 提供容器内
qq-notifyCLI 和 Codex skill,用于按需发送孤立 QQ 通知;普通回复不应使用。 - 提供容器内
qq-fileCLI 和 Codex skill,用于按需发送文件;Codex 主动发送前应先询问确认。 - Docker 模式下 Codex 在容器内拥有完整权限,宿主机边界由 Docker 挂载目录控制。
- 容器内 HTTP/HTTPS 流量可以走宿主机代理。
支持的 QQ 命令:
/new 重置 Codex 会话,不清空 workspace 文件
/stop 中止当前 Codex 任务
/stop <pid> 结束指定后台进程
/ps 查看容器内后台进程
/status 查看当前是否空闲或正在执行
/run 让 Codex 运行一条 bash 命令
/memory 查看和管理容器内 Codex 记忆
/session 查看和管理命名 Codex 会话
/model 查看或切换 Codex 模型
/queue 查看和管理待处理消息队列
/file 查看和发送文件
/interrupt 中断当前 Codex 任务并立即处理纠偏消息
/run 示例:
/run ./gradlew assembleDebug
/run npm test
/run 不由 QQBot 直接执行命令,而是交给容器内 Codex CLI 执行并汇报结果,因此仍会走现有的中断、状态、超时和日志机制。
/ps 会列出容器内可管理的后台进程,包含 PID、父 PID、状态、运行时长和命令。/stop <pid> 会向指定进程发送 SIGTERM;不带 PID 的 /stop 仍然只中止当前 Codex 任务。
/queue 支持的命令:
/queue list 查看消息队列
/queue add <message> 把消息加入队列
/queue jump <message> 把消息插入队首,优先处理
/queue popback 删除最后加入的消息
/queue clear 清空队列
/interrupt <message> 中断当前任务并立即处理纠偏消息
当 Codex 正在运行时,普通文本消息会自动加入队列,不会打断当前任务。当前任务完成且队列非空时,bot 会发送任务完成提示,展示当前消息队列和下一条即将处理的消息,然后按先进先出的顺序依次发送队列消息给 Codex。/queue jump <message> 会把消息插入队首,优先于现有队列处理。/interrupt <message> 会打断当前任务并立即执行该消息,已有队列保持不变;纠偏消息完成后再继续处理队列。队列为空时,bot 只会发送没有后续任务的提示。
/session 支持的命令:
/session 查看当前和已命名 session
/session list 查看当前和已命名 session
/session name <name> 给当前 Codex 会话命名
/session new <name> 创建新的命名 session
/session resume <name> 恢复命名 session
/session rm <name> 删除 session 名称,不删除实际 Codex session 记录
如果当前 session 已经建立 Codex thread 但还没有命名,执行 /session new <name> 或 /session resume <name> 会先提醒你命名,避免误丢上下文。确认不要保留当前未命名 session 时,可以追加 --discard:
/session new <name> --discard
/session resume <name> --discard
命名 session 只保存在 data/state.json 中,用来记录名称到 Codex threadId 的映射。/session rm <name> 只删除这个映射,不会删除 codex-home 中的 Codex 实际 session 文件。/new 仍然可用,会清空当前 threadId 和当前 session 名称,但不会删除已命名 session 列表。
/model 支持的命令:
/model 查看当前模型
/model <model> 切换后续 Codex 任务使用的模型
/model reset 恢复 `.env` 中的 CODEX_MODEL 或 Codex CLI 默认模型
/model <model> 会把选择保存到 data/state.json,重启容器后仍会生效。它不会修改 .env 或 codex-home/config.toml。
/file 支持的命令:
/file list 查看最近接收的文件
/file recent 查看最近接收的文件
/file send <path> 发送 workspace 内文件
/file send 可以使用 /workspace 绝对路径,也可以使用相对 workspace 的路径。为避免误发密钥和运行数据,bot 只允许发送 workspace 内的普通文件。
.
├── codex-config/config.toml # 构建进镜像的默认 Codex 配置
├── codex-home/ # 容器 Codex 的持久化 HOME,git 忽略
├── cron.d/ # 容器 /etc/cron.d,git 忽略
├── data/ # bot 状态和日志,git 忽略
├── workspace/ # Codex 工作目录,git 忽略
├── Dockerfile
├── docker-compose.yml
└── src/
Docker 挂载关系:
./workspace -> /workspace
./data -> /data
./codex-home -> /codex-home
./cron.d -> /etc/cron.d
不要挂载宿主机敏感路径,例如 /、~/.codex、~/.ssh、/var/run/docker.sock。
创建 .env:
cp .env.example .env填写 QQ 机器人信息:
QQ_APP_ID=你的 AppID
QQ_APP_SECRET=你的 AppSecret模型 API key 和 endpoint 可以从宿主机 shell 环境继承:
export V_API_KEY=你的 API key
export V_API_BASE_URL=你的BASE URL也可以写进 .env:
V_API_KEY=你的 API key
V_API_BASE_URL=你的BASE URL如果宿主机代理不是 127.0.0.1:18899,修改 .env 中的代理配置:
HOST_PROXY_PORT=18899
HOST_HTTP_PROXY=
HOST_HTTPS_PROXY=
HOST_ALL_PROXY=
HOST_NO_PROXY=localhost,127.0.0.1,::1默认会用 HOST_PROXY_PORT 生成 http://host.docker.internal:<port>。如果你的代理需要完整 URL,可以填写 HOST_HTTP_PROXY、HOST_HTTPS_PROXY、HOST_ALL_PROXY 覆盖默认值。
注意:容器里访问宿主机代理要用 host.docker.internal,不要用 127.0.0.1。容器内的 127.0.0.1 指向容器自己。
构建镜像和运行容器时都以当前目录 .env 为准,不让宿主机 shell 中同名环境变量覆盖 .env。构建阶段通过 Docker BuildKit secret 读取 .env,只用于当次 RUN 命令设置代理,不会把 .env 复制进最终镜像。
图片相关限制可以通过 .env 调整:
MAX_INPUT_IMAGES=4
MAX_OUTPUT_IMAGES=4
MAX_IMAGE_BYTES=10485760Markdown 发送默认开启:
QQ_ENABLE_MARKDOWN=true如果 QQ Bot 后台没有 Markdown 权限,发送失败会自动回退为普通文本。想完全关闭 Markdown 可以设置:
QQ_ENABLE_MARKDOWN=false开启 Codex 内置 web search:
CODEX_ENABLE_SEARCH=trueCodex 单次任务超时时间,单位是毫秒:
CODEX_TIMEOUT_MS=180000超过该时间后,QQBot 会中止当前 Codex 进程并返回超时状态。长时间构建、联网搜索或生成任务可以适当调大这个值。
收到普通消息后、启动 Codex 前发送的提示文案:
RECEIVED_MESSAGE=已收到,Codex 正在处理。如果不想发送这条提示,可以设为空:
RECEIVED_MESSAGE=Codex 正常完成当前任务后的提示文案,以及队列为空时的提示文案:
TASK_COMPLETE_MESSAGE=当前任务已完成。
QUEUE_EMPTY_MESSAGE=队列为空,暂无后续任务。任意一项设为空时,对应提示不会发送:
TASK_COMPLETE_MESSAGE=
QUEUE_EMPTY_MESSAGE=单个 QQBot 记忆文件长度提醒阈值:
MEMORY_MAX_CHARS=4000构建镜像:
docker compose build镜像会安装 Node.js 依赖和 Codex CLI,并把 codex-config/config.toml 作为默认 Codex 配置打包进去。
容器第一次启动时,docker-entrypoint.sh 会把镜像内置配置:
/opt/codex-config/config.toml
复制到:
./codex-home/config.toml
前提是 ./codex-home/config.toml 还不存在。如果已经存在,不会覆盖。生成配置时会把 V_API_BASE_URL 写入模型供应商 endpoint。
启动 bot:
docker compose up -d查看日志:
docker compose logs -f qqbot看到类似输出表示启动成功:
HTTP(S) proxy enabled: http://host.docker.internal:8899/
codex-qqbot starting
workspace: /workspace
data: /data
QQ gateway connected
之后就可以给 QQ 机器人发送单聊消息。
接收图片时,bot 会把 QQ 单聊事件中的图片附件下载到:
/workspace/qq-images/<message-id>/
然后通过 Codex CLI 的 --image 参数传给 Codex。只有图片附件会被传入,默认每条消息最多 4 张,每张最多 10 MiB。
发送图片时,bot 会在 Codex 任务结束后扫描 /workspace 中新生成或新修改的图片文件,并发送给 QQ。输入目录 /workspace/qq-images 会被排除,避免把用户刚发来的图片原样回传。
支持的图片扩展名:
.png .jpg .jpeg .gif .webp
当前图片发送依赖 QQ 单聊富媒体接口:先上传图片文件,再发送 msg_type: 7 的富媒体消息。
接收非图片附件时,bot 会把 QQ 单聊事件中的附件流式下载到:
/workspace/qq-files/<日期>/<文件名>
例如:
/workspace/qq-files/2026-05-08/report.pdf
如果同一天出现重名文件,会自动重命名为 report-1.pdf、report-2.pdf。文件保存后,bot 会把本地路径写入本轮 Codex prompt,由 Codex 自行读取文件内容。当前不主动限制接收文件大小,实际限制取决于 QQ 附件链接、容器磁盘和网络。
查看和发送文件:
/file list
/file recent
/file send /workspace/report.html
/file send report.html
发送文件只允许 workspace 内路径。普通文件发送依赖 QQ API v2 单聊富媒体接口:先用 file_type: 4 上传文件,再发送 msg_type: 7 的富媒体消息。上传接口使用 file_data base64,因此发送大文件时会占用进程内存;如果 QQ 返回类型、大小或权限错误,bot 会把错误返回给用户。
容器内也提供独立 CLI:
qq-file list
qq-file recent
qq-file send /workspace/report.html默认目标是 bot 最近收到的单聊 openid,也可以用环境变量覆盖:
QQ_FILE_OPENID=<openid> qq-file send /workspace/report.html镜像会安装内置 skill 到:
/codex-home/skills/qq-file
当你明确要求 Codex “发送这个文件”时,Codex 可以按 skill 指令调用 qq-file send。如果 Codex 自己判断某个生成文件应该发给你,skill 要求它先询问确认。
记忆保存在:
./codex-home/memories/qqbot.md
这是容器内 Codex 的官方 memories 目录,对应容器路径:
/codex-home/memories/qqbot.md
Docker 入口脚本会确保容器内 Codex 配置启用:
[features]
memories = true由于容器使用独立的 CODEX_HOME=/codex-home,这些记忆不会影响宿主机的 ~/.codex。
执行 /new 时,bot 会重置当前 Codex thread,并标记下一次任务需要读取 qqbot.md。下一条普通消息启动新会话时,bot 会把当时的完整记忆作为一次性系统提示传入 Codex;之后同一会话继续 resume,不再重复传入。再次执行 /new 会重新触发这个流程。
每次通过 /memory set、/memory del、/memory clear 修改记忆时,bot 会在 data/state.json 中累计一段待注入的记忆变更 diff。下一次 /new 后的新会话会同时收到完整记忆和这段 diff;成功建立 Codex thread 后,待注入 diff 会自动清空。
支持的命令:
/memory 查看当前记忆和用法
/memory show 查看当前记忆
/memory get <key> 查看某个 key
/memory set <key> <value>
/memory set <key>=<value>
/memory add <key> <value> set 的兼容别名
/memory del <key> 删除某个 key
/memory clear 清空记忆
容器内也提供独立 CLI,QQBot 和 Codex skill 使用同一套实现:
qq-memory show
qq-memory get <key>
qq-memory set <key> <value>
qq-memory set <key>=<value>
qq-memory del <key>
qq-memory clear镜像会安装内置 skill 到:
/codex-home/skills/qq-memory
当你通过 QQ 对 Codex 说“记住……”“忘记……”“你的记忆里有什么”这类需求时,Codex 可以按 skill 指令调用 qq-memory,而不是依赖 QQBot 对消息做关键词命中。
容器内提供独立 CLI:
qq-notify "消息内容"
qq-notify --text "消息内容"
qq-notify --markdown "**提醒:** 该起床了"
qq-notify --format markdown "**提醒:** 该起床了"它会发送一条孤立的 QQ 单聊消息,不携带上下文。默认使用纯文本;可以通过 --markdown 或 --format markdown 发送 QQ Markdown,发送失败时会按 bot 配置回退为普通文本。也可以设置默认格式:
QQ_NOTIFY_FORMAT=markdown qq-notify "**提醒:** 该起床了"默认目标是 bot 最近收到的单聊 openid,该值保存在:
./data/state.json
也可以用环境变量覆盖目标:
QQ_NOTIFY_OPENID=<openid> qq-notify "消息内容"镜像会安装内置 skill 到:
/codex-home/skills/qq-notify
这个 skill 只用于创建通知或定时提醒,例如“完成后通知我一声”“每天早上 8 点提醒我该起床了”。普通问答、任务结果、状态更新和一般性回复不应调用 qq-notify。
容器会启动 cron。项目的 ./cron.d 会挂载到容器内 /etc/cron.d,因此定时任务会在容器重建后保留。Codex 可以创建 /etc/cron.d/* 文件来定时调用 qq-notify,例如:
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
CODEX_HOME=/codex-home
DATA_DIR=/data
WORKSPACE_DIR=/workspace
QQ_API_BASE=https://api.sgroup.qq.com
0 8 * * * root qq-notify --text "该起床了"不要把 QQ 密钥写进 cron 命令或 cron 文件。bot 启动时会生成权限为 0600 的运行时环境文件:
./data/qqbot.env
容器内路径:
/data/qqbot.env
qq-notify 会自动加载该文件中的 QQ 密钥、代理和目录配置,所以 cron 中只需要调用 qq-notify。如果你把运行时环境文件移动到其他路径,只需要在 cron 中设置非敏感的路径变量:
QQBOT_RUNTIME_ENV_FILE=/path/to/qqbot.envdocker-entrypoint.sh 会在启动 cron 前把 /etc/cron.d/* 中的普通文件权限规整为 root:root 和 0644。cron 文件名建议只使用字母、数字、下划线和短横线,不要包含点号。
记忆文件使用 key-value Markdown 格式:
- language: 中文
- style: 简洁直接
- project: codex-qqbot
如果 qqbot.md 超过 MEMORY_MAX_CHARS,bot 会在更新记忆后提醒你手动精简;不会自动删除任何 key-value 项。
容器启动时,docker-entrypoint.sh 会在启动 QQBot 前依次加载:
/etc/profile
/root/.profile
启动脚本会确保 /root/.profile 和 /root/.bash_profile 自动加载 /codex-home/.profile。因此 QQBot 主进程以及 Codex 后续执行的 bash -lc 命令都会继承同一份持久化 profile。
其中 /codex-home 挂载到当前项目的 ./codex-home,因此可以创建:
mkdir -p codex-home
touch codex-home/.profile在这个文件里持久化容器内 QQBot 和 Codex 都需要继承的环境变量,例如:
export PATH="/workspace/bin:$PATH"
export MY_TOOL_HOME="/workspace/tools"只有 export 出来的环境变量会被 QQBot 主进程继承,并继续传给它启动的 Codex CLI。alias、shell function、未 export 的变量通常不会对 QQBot 调用 Codex 生效。cron 任务也不应依赖 profile,建议在 cron 文件或 wrapper 脚本里显式设置 PATH 和必要环境变量。
打开容器内 bash:
docker compose run --rm -it qqbot bash常用检查命令:
echo "$V_API_KEY"
echo "$V_API_BASE_URL"
cat /codex-home/config.toml
codex --version手动测试容器内 Codex,无 Codex 沙箱:
codex -C /workspace --dangerously-bypass-approvals-and-sandbox exec --skip-git-repo-check "只回复 OK"修改 .env 或宿主机环境变量后,重启容器:
docker compose up -d --force-recreate修改源码、Dockerfile、codex-config/config.toml 或 docker-entrypoint.sh 后,重新构建并重启:
docker compose build
docker compose up -d --force-recreate停止:
docker compose down查看日志:
docker compose logs -f qqbot仍然可以在宿主机本地运行:
npm install
npm run build
npm run dev本地模式默认使用 Codex 的 workspace-write 沙箱。实际部署推荐使用 Docker 模式。
- QQBot 使用 QQ 官方 OpenAPI 和 WebSocket Gateway。
- QQ 机器人后台需要开启单聊消息事件权限。
.env、data/、workspace/、codex-home/、cron.d/、dist/、node_modules/已被 git 忽略。
本项目使用 MIT License,见 LICENSE。