Skip to content

Repository files navigation

Codex REDLINE

English | 简体中文

将 Grandstream HT802 和一台模拟电话变成当前本机 Codex 任务的语音终端:摘机说话、挂机提交;Codex 完成后电话最多响两声,接听后播报回复,并继续等待下一轮语音。

本仓库只包含 HT802 电话方案。它不包含 ESP32 固件、串口状态灯、第二个 Codex 会话或非公开 Codex App Server/MCP 接口。

Setup:从接线到第一次通话

Setup 必须按下面的顺序完成。网络和 ATA 是后续步骤的基础;Codex Hook 在安装或改路径后需要重新加载。每一步末尾的“完成标志”用于判断是否可以继续。

第 1 步:接线并准备 Mac 网络

  1. 将模拟电话接到 HT802 PHONE 1
  2. 将 HT802 的 Ethernet 口接到 Mac 的独立 USB Ethernet 接口并通电。
  3. 在“系统设置 → 网络”中把该接口设为手动 IPv4:
    • 地址:192.168.82.1
    • 子网掩码:255.255.255.0
    • 路由器:留空或 0.0.0.0
  4. 不要在该接口启用 Internet Sharing;继续使用 Wi-Fi 上网。

完成标志: Mac 的专用接口显示已连接,地址为 192.168.82.1/24

第 2 步:设置 HT802

如果不知道 HT802 地址,摘机拨 ***,听到菜单后按 02。用浏览器进入设备管理页,先把设备设为静态地址 192.168.82.100/24,然后按 HT802 配置填写 PHONE 1 的 SIP、自动拨号、DTMF 和响铃参数。

保存并重启 HT802,然后检查网络:

ping 192.168.82.100

完成标志: ping 可以访问 192.168.82.100,管理页显示 PHONE 1 已保存预期参数。

第 3 步:安装并构建 Companion

git clone https://github.com/prikevs/codex-redline.git
cd codex-redline
export HT802_PHONE_ROOT="$PWD"
export HT802_PASSWORD='设备管理员密码'
python3 ht802-configure.py configure
python3 ht802-configure.py inspect
npm ci
ffmpeg -version
sh native/build-bridge.sh
sh native/build-audio.sh

如果 ffmpeg -version 失败,请先使用本机包管理器安装 ffmpeg。首次使用时,在“系统设置 → 隐私与安全性 → 辅助功能”中允许 REDLINE Bridge.app

完成标志: inspect 返回预期 P 值,并且以下检查都成功:

'.runtime/REDLINE Bridge.app/Contents/MacOS/RedlineBridge' --check
'.runtime/phone-audio' --devices

第 4 步:配置 OpenAI、Codex Hook 和 Skill

export OPENAI_API_KEY='...'
python3 install-phone-hooks.py
ln -sfn "$PWD/skills/redline-phone-bind" "$HOME/.codex/skills/redline-phone-bind"

安装或移动 Hook 后,完全退出并重新打开 Codex,再在 Codex /hooks 中审查并信任新命令。只关闭当前任务页面可能不会重新加载已经缓存的 Hook 路径。

完成标志: ~/.codex/hooks.json 中的 UserPromptSubmitStop 指向当前 clone 的 phone_hook.py,绑定 Skill 指向当前 clone。

第 5 步:启动、绑定并验收

从保存了 OPENAI_API_KEY 的环境启动服务:

node src/phone-control.mjs start
node src/phone-control.mjs status

打开要绑定的 Codex 任务,并让 Codex 执行“将电话绑定到当前任务”。Skill 会在该任务环境中运行 bind,使用真实的 CODEX_THREAD_ID。不要从普通 Terminal 手工填写任务 UUID。

绑定成功后,PHONE 1 最多响两声。接听后应听到提示音;说一句测试内容并挂机,Codex 输入框应只提交一次。Codex 完成后电话再次最多响两声,接听一秒后开始播放回复。

完成标志: status 显示正确的 binding.projectPathvoiceSubmission.enabled: true,并完成一次“电话提交 → Codex 回复 → 电话播报”的闭环。

功能规格

核心用户流程

  1. 在目标 Codex 任务中执行 bind
  2. Companion 将任务 UUID 和执行 bind 时的 project 根目录保存在本机。
  3. 绑定成功后 PHONE 1 最多响两声。
  4. 如果接听绑定回铃,电话播放 660 Hz 就绪音并开始监听;说话后挂机即转写并提交。
  5. 如果没有接听,绑定仍然有效;以后直接摘机也会在就绪音后开始监听。
  6. Codex 完成后,官方 Hook 把最终回复交给 Companion。
  7. Companion 清理不适合朗读的 Markdown,使用 OpenAI TTS 生成语音,转换成电话音频并先归档到对应 project。
  8. PHONE 1 最多响两声。接听后先保留 1 秒静音,再播放回复。
  9. 回复播完后播放 660 Hz 提示音并直接进入下一轮监听;说话并挂机再次提交。
  10. 播报尚未结束时挂机会停止并跳过该回复;下一次摘机开始新一轮输入。

行为边界

项目 规格
模拟口 HT802 PHONE 1;PHONE 2 不参与
SIP Companion 监听 UDP 192.168.82.1:5090
RTP Companion 监听 UDP 192.168.82.1:15004
编解码 G.711 μ-law / PCMU,8 kHz,20 ms RTP 帧
DTMF RFC 2833 / RFC 4733,动态 payload type,配置值 101
摘机目标 内部用户 263,HT802 自动补齐 SIP host
回铃上限 c=2000/4000; 下 0–2 秒、6–8 秒两个响铃段;8.2 秒取消
接听延迟 回复开头前 1 秒 PCMU 静音
就绪提示 660 Hz,300 ms;提示音结束后才接收用户语音
错误提示 220 Hz,600 ms;快速失败时延迟 350 ms,避免接通瞬间被截断
输入提交 仅正常物理挂机提交;空白、异常断线、焦点变化均不提交
回复队列 最多 8 条;服务重启时未读队列丢失
单条回复 最多 20,000 字符;TTS 每 1,000 字符按句末分段
播报时长 最多 10 分钟

架构

flowchart LR
    P[模拟电话<br/>PHONE 1] <-->|RJ11| H[Grandstream HT802]
    H <-->|SIP + RTP/PCMU<br/>直连以太网| D[本机 Companion]
    D -->|24 kHz PCM<br/>WebSocket| STT[OpenAI Realtime STT]
    D -->|macOS Accessibility| C[当前 Codex 输入框]
    C -->|UserPromptSubmit / Stop Hook| D
    D -->|回复文字| TTS[OpenAI Speech API]
    TTS -->|WAV| D
    D -->|8 kHz WAV| A[project/.redline/phone-replies]
Loading

Companion 不创建或恢复另一个 Codex 会话。任务选择由 CODEX_THREAD_ID 完成;提交依赖当前前台 Codex 窗口中唯一的空白 Do anything 输入框。辅助功能桥会自动聚焦该输入框,但无法独立证明屏幕上展示的任务 UUID,因此绑定任务必须保持打开。

已验证环境

  • macOS,Apple Silicon。
  • Grandstream HT802 V1.5A,固件 1.0.3.2
  • 老式模拟电话接 PHONE 1。
  • HT802 通过 USB Ethernet 直接连接 Mac。
  • Node.js 20 或更高版本。
  • ffmpegPATH 中。
  • Xcode Command Line Tools,用于编译 Swift 辅助程序。
  • Codex 桌面应用和可用的 OPENAI_API_KEY

HT802 V2 和新固件的页面布局、字段编号或默认值可能不同。Grandstream 的官方 HT80x 管理手册说明了静态地址、FXS/SIP、Off Hook Auto Dial、DTMF 和 System Ring Cadence;官方用户手册说明了 IVR、网页配置和 PHONE 端口操作。本仓库脚本的 P 值以已验证的 V1 固件为准。

网络拓扑

默认使用一个不经过路由器的隔离子网:

设备 地址 用途
Mac USB Ethernet 192.168.82.1/24 SIP 与 RTP 服务端
HT802 192.168.82.100/24 ATA
HT802 SIP 端口 5060/UDP Companion 发起回铃
Companion SIP 端口 5090/UDP HT802 摘机自动呼叫
Companion RTP 端口 15004/UDP 双向 PCMU

在“系统设置 → 网络”中将连接 HT802 的 USB Ethernet 服务设为手动 IPv4:

  • IP 地址:192.168.82.1
  • 子网掩码:255.255.255.0
  • 路由器:留空或 0.0.0.0

不要在这个接口上启用 Internet Sharing。Wi-Fi 可以继续承担互联网访问。

如需自定义地址和端口,在启动服务及执行配置脚本前设置:

export HT802_LOCAL_ADDRESS=192.168.82.1
export HT802_ADDRESS=192.168.82.100
export HT802_SIP_PORT=5090
export HT802_DEVICE_SIP_PORT=5060
export HT802_RTP_PORT=15004

自动配置脚本固定使用 /24 掩码;如果需要其他网段长度,应先修改并测试脚本。

HT802 配置

网页界面方案

  1. 把模拟电话接到 PHONE 1,把 HT802 Ethernet 接到 Mac 的专用网口并通电。
  2. 如不知道地址,摘机拨 ***,听到菜单后按 02;设备会播报当前 IPv4 地址。
  3. 浏览器打开 HT802 地址,以管理员身份登录。
  4. 在 Basic Settings 中选择 Static IP,设置 192.168.82.100/24
  5. 在 FXS Port 1 中配置下表,保存后重启。
网页字段 作用
Primary SIP Server 192.168.82.1:5090 将 PHONE 1 的 SIP 请求发给 Companion
SIP User ID redline 本地 SIP 标识,不需要公网账号
SIP Registration No 直连方案不使用 SIP 注册服务器
Off Hook Auto Dial 263 摘机立即呼叫 Companion
Off Hook Auto Dial Delay 0 不等待拨号
DTMF Payload Type 101 与 Companion 的 RFC 4733 协商一致
DTMF 方式 RFC 2833 发送带外 DTMF 事件
System Ring Cadence c=2000/4000; 每次响 2 秒、停 4 秒

已验证脚本实际提交的 V1 P 值如下。不同固件应以 Grandstream 对应版本的 configuration template 为准,不要只凭 P 编号跨版本复制。

页面 P 值
config_a1 P47 192.168.82.1:5090
config_a1 P35 redline
config_a1 P31 0
config_a1 P71 263
config_a1 P4045 0
config_a1 P850 101
config_a1 P870 0
config_a1 P4010 c=2000/4000;
config2 P8 1,静态 IPv4
config2 P9P12 192.168.82.100
config2 P13P16 255.255.255.0

自动配置与回滚

脚本直连设备网页接口,禁用代理,保存第一次修改前的原值,回读验证后才要求重启。默认管理员密码为 admin;生产设备应通过环境变量提供已经修改过的密码。

export HT802_PASSWORD='设备管理员密码'
export HT802_EXPECTED_MAC='设备标签上的 MAC 地址'

python3 ht802-configure.py inspect
python3 ht802-configure.py configure

HT802_EXPECTED_MAC 可选;设置后脚本会在修改前核对目标设备。第一次 configure 的原值保存在 .runtime/ht802-original.json,后续配置不会覆盖基线。

回滚 ATA:

python3 ht802-configure.py restore

恢复前必须仍能访问 HT802 当前地址。恢复 ATA 后,再把 Mac USB Ethernet 改回 DHCP 或原配置。

安装

克隆后在仓库根目录保存其绝对路径,后续从其他 project 调用绑定命令时会用到:

git clone https://github.com/prikevs/codex-redline.git
cd codex-redline
export HT802_PHONE_ROOT="$PWD"

1. Node 与音频依赖

npm ci
ffmpeg -version

2. 编译 macOS 辅助程序

sh native/build-bridge.sh
sh native/build-audio.sh

REDLINE Bridge.app 负责识别、聚焦和提交 Codex 输入框。首次使用时,在“系统设置 → 隐私与安全性 → 辅助功能”中允许它。其 bundle identifier 为 local.redline.phone-bridge,为兼容已有授权而保留。

phone-audio 是可选的 BlackHole/Dictate 诊断工具;正常 OpenAI STT + Accessibility 路径不需要 BlackHole。

验证辅助功能:

'.runtime/REDLINE Bridge.app/Contents/MacOS/RedlineBridge' --check
'.runtime/REDLINE Bridge.app/Contents/MacOS/RedlineBridge' --validate

--validate 只验证并聚焦唯一空白输入框,不输入内容。

3. 配置 OpenAI

export OPENAI_API_KEY='...'

仓库提供不含凭据的 .env.example 作为变量清单。程序不会自动读取 .env;可由 shell、密码管理器或服务管理器注入这些变量。

  • 用户语音:OpenAI Realtime gpt-live-transcribe,电话 PCMU 在内存中转换为 24 kHz PCM。
  • 回复语音:OpenAI gpt-4o-mini-ttscedar,速度 1.15
  • 可覆盖:HT802_TTS_MODELHT802_TTS_VOICEHT802_TTS_SPEEDHT802_TTS_INSTRUCTIONS
  • 旧的 REDLINE_TTS_* 变量仍作为兼容别名接受,HT802_TTS_* 优先。

实现使用 OpenAI 的实时转写指南语音生成接口

4. 安装 Codex Hooks

python3 install-phone-hooks.py

脚本只合并 UserPromptSubmitStop command hooks,保留其他 hook,并在 .runtime/hooks-before-phone.json 保存一次原配置备份。安装后在 Codex /hooks 中审查并信任。Hook 只转发任务 ID、turn ID、事件类型和最终回复,不读取 transcript 文件、不批准权限、不提交提示词。

安装器只去重当前仓库生成的精确命令。若从其他目录迁移,请在 ~/.codex/hooks.json 中删除旧仓库的 phone_hook.py command,避免同一事件被转发两次;其他 command hook 不受影响。

Codex Hooks 的行为以官方 Hooks 文档为准。

5. 安装绑定 Skill

将仓库中的 skill 链接到个人 skills 目录:

ln -sfn "$PWD/skills/redline-phone-bind" "$HOME/.codex/skills/redline-phone-bind"

Skill 的职责只有 bind、rebind 和 unbind。它从调用任务的 CODEX_THREAD_ID 取得真实任务 UUID,不允许手工传入另一个 UUID。

运行

启动服务时必须保留 OPENAI_API_KEY 和网络环境变量:

node src/phone-control.mjs start
node src/phone-control.mjs status

start 是幂等操作。它把守护进程输出写到 .runtime/phone.log,控制 socket 为 .runtime/phone.sock

在需要绑定的 Codex project 根目录执行:

node "$HT802_PHONE_ROOT/src/phone-control.mjs" bind

bind 保存当前 CODEX_THREAD_ID、本机 host 标识、标题和当前绝对工作目录。它同时创建 <project>/.redline/phone-replies/、启用语音输入并触发最多两声确认回铃。

其他命令:

node src/phone-control.mjs enable-voice
node src/phone-control.mjs disable-voice
node src/phone-control.mjs test-transcription
node src/phone-control.mjs test-dictate-audio
node src/phone-control.mjs restore-microphone
node src/phone-control.mjs unbind
node src/phone-control.mjs stop
  • disable-voice 只关闭用户语音输入,回复播报仍工作。
  • 服务重启后语音输入默认关闭;执行 enable-voice 或重新 bind
  • unbind 清除绑定、停止通话、清空待播回复并关闭语音输入,不响铃。
  • stop 只停止服务并保留绑定。

回复归档与数据处理

每条通过 Hook 收到且成功合成的回复,必须先归档才允许响铃:

<bound-project>/.redline/phone-replies/
  .gitignore
  2026-09-08T12-34-56-789Z__<turn-id>__<uuid>.wav

WAV 是 8 kHz、单声道、16-bit PCM,内容与电话实际播报信号一致,不包含接听前 1 秒静音。文件以 owner-only 权限原子写入;目录内 .gitignore 使用 *,防止私人语音进入 Git。文件不会自动清理。

.redline/phone-replies、SIP 用户 redlineredline-phone-bind skill 和 local.redline.phone-bridge bundle identifier 是从原项目迁移时保留的兼容名称;它们不表示仍依赖原 REDLINE 仓库。

数据边界:

  • 用户电话原始音频不落盘,实时发送给 OpenAI 转写。
  • 用户转写文字不落盘;成功状态只保留字符数。
  • Codex 最终回复文字发送给 OpenAI TTS。
  • 生成的回复电话音频按用户要求长期保存在绑定 project。
  • 绑定记录、设备备份、hook 备份、日志和 Unix socket 位于本仓库 .runtime/,不进入 Git。

状态与故障排查

status 的关键字段:

字段 含义
binding 当前任务、project 路径和绑定时间
handset on_hook / off_hook
lineBusy 当前是否有 SIP 通话
queuedReplies 待播报回复数量
replyReady 队首回复是否已完成归档并可播放
replyError TTS、归档或队列错误
lastReplyArchive 最近一条归档 WAV 的绝对路径
voiceSubmission 语音输入是否启用、进行中及最近提交结果
confirmation 绑定回铃状态
lastEvent 最近运行事件

常见问题:

现象 检查
摘机没有提示音 Codex 是否在前台、绑定任务是否打开、输入框是否为空;查看 voiceSubmission.result
低音错误提示 Accessibility、API key、输入框草稿或 Realtime 初始化失败;查看 .runtime/phone.log
HT802 没有呼叫 检查 Off Hook Auto Dial=263、Primary SIP Server、Mac 192.168.82.1 和 UDP 5090
能接通但无声 检查 RTP 15004、防火墙、PCMU、HT802 SDP 地址和 PHONE 1
Codex 完成不回铃 检查 hooks 信任、任务 UUID、服务 socket、replyError 与 project 写权限
回复没有归档 重新从目标 project 根目录执行 bind,检查 binding.projectPath
开头漏听 确认代码仍使用 1 秒静音前缀;过早挂机会跳过回复
只响一次或不满两次 两声是上限;接听、线路忙或 SIP 取消都会提前终止

连通性检查:

ping 192.168.82.100
nc -vz -u 192.168.82.100 5060
python3 ht802-configure.py inspect
node src/phone-control.mjs status
tail -f .runtime/phone.log

测试

npm test

自动测试覆盖 μ-law、RTP、SIP、双响铃截止、绑定持久化、转写生命周期、Accessibility 提交边界、TTS 分段、回复归档、播报中挂机、回复后继续监听,以及接听绑定回铃后开始语音。

硬件验收顺序:

  1. inspect 能读到预期 HT802。
  2. status 显示线路空闲且绑定含正确 projectPath
  3. 直接摘机听到高音,说固定测试句并挂机。
  4. Codex 输入框只提交一次,内容与语音一致。
  5. Codex 完成后电话最多响两声。
  6. 接听后 1 秒再开始播放,开头完整。
  7. WAV 出现在绑定 project,能够正常播放。
  8. 回复结束后听到高音,再说一句并挂机,进入下一 round。
  9. 播报中挂机立即停止,下一次摘机开始新 round。
  10. unbind 后摘机不再提交,旧任务完成也不回铃。

安全与限制

  • SIP/RTP 没有 TLS、认证或媒体加密,只应运行在直连或可信隔离网络。
  • Unix socket 权限为 0600,运行目录与归档目录为 owner-only。
  • Companion 不处理 Codex 权限批准,不读取完整对话历史。
  • Accessibility 提交不重试不确定结果,避免重复消息。
  • 当前只支持一个 HT802、PHONE 1 和一个绑定任务。
  • 服务没有自动启动项;Mac 重启后需重新 startenable-voice
  • HT802 管理密码和 OpenAI key 只通过环境变量提供,不写入仓库。

发布公开仓库前,确认 .runtime/.env 和各 project 的 .redline/phone-replies/ 没有被强制加入 Git。提交历史不应包含 API key、设备管理密码、绑定任务 UUID、归档音频或个人绝对路径。

许可证

MIT

About

Use a Grandstream HT802 analog telephone as a voice interface for a local Codex task

Resources

Stars

17 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages