Skip to content

Latest commit

 

History

13 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

opencode-autoretry

OpenCode 插件:智能检测静默中断并自动重试。

当 OpenCode 会话因网络抖动、中转网关超时(60s nginx 超时)或静默中断失败时,插件能识别不同类型的错误类型并重试,让工作持续推进不被打断。

功能特性

自动检测三大类中断

  • 连接断开ECONNRESETServer disconnected、网络抖动
  • 静默中断:0 输出、finish=unknown,OpenCode 自己都检测不到的本质错误
  • 超时断开Connection failed: Server disconnected without sending a response.

空白完成保护

  • 识别中转网关 60s 超时的伪造 finish=length + 0 output + 空 parts
  • 避免下游组件(如大纲页时间轴)误判为"正常截断"
  • 只对真正空白(无文本无工具调用)触发续写

双层子代理防护

  • 子会话身份门禁:会话存在 parentID 时,插件不会发送任何“继续”消息;不依赖 agent 名称或消息内容猜测
  • 根编排等待保护:根会话调用工具等待子代理返回时,保留消息模式保护,避免自然 idle 被误判为中断
  • idle、轮询、session.error 和延迟重试均走同一门禁;真正发送前会再次读取会话身份,避免定时器竞态误伤
  • 无法读取会话身份时采用 fail-closed:宁可少重试一次,也不向未知会话注入消息

可靠的重试机制

  • 回合预算:每个用户回合最多重试 2 次;预算只在真正的用户新消息时重置,session.idle 清理不会抹掉计数
  • 硬上限终态:达到上限后写入 exhausted 终态并停止本回合所有自动续写,后续 session.error 无法重建预算(修复了「继续」曾无限循环的 bug)
  • 退避:5s → 10s(避免和网关 HTTP keepalive 冲突)
  • 轮询兜底:60 秒无事件才轮询(极端情况)

三大防奔溃机制

  1. session.error 触发时直接用事件 error 对象,不依赖 DB 时序
  2. session.idle 只结束本轮运行态,不删除回合预算(之前会整条删除,把重试计数清零)
  3. chat.message 识别出自己注入的「继续」时不重置预算(之前注入标记会被 idle 清掉,误判成真用户消息)

状态分层

回合预算与临时运行态分开存放(src/session-state.ts),这是修复无限续写的关键:

  • 回合预算retryCount / blankCount / exhausted):只在真正的用户新消息时重置,idle 清理不得触碰
  • 临时运行态roundActive / lastActivity / 注入标记):可随回合结束复位,但同样不做整条删除

清洁的单一真相源

  • 核心编排逻辑统一在 src/index.ts,会话状态分层在 src/session-state.ts
  • npm run build && npm run install-plugin 部署
  • 与本地 autoretry.js 分离,永远保持同步

安装

从源码安装

git clone https://github.com/rayn1314/opencode-autoretry.git
cd opencode-autoretry
npm install
npm run build
npm run install-plugin

安装脚本会将 dist/index.js 拷到 ~/.config/opencode/plugins/autoretry.js

预编译版本

(待发布)

配置

启用/禁用开关

插件默认启用。如需临时关闭,在 opencode.json 中用元组格式注册插件并传入 enabled: false

{
  "plugin": [
    ["~/.config/opencode/plugins/autoretry.js", { "autoretry": { "enabled": false } }]
  ]
}

关闭后插件不注册任何事件处理器,不会检测中断、不会发送"继续"、不会安排重试。

如果你用普通字符串格式注册插件("plugin": ["~/.config/opencode/plugins/autoretry.js"]), 也可以在 opencode.json 顶级放 "autoretry": { "enabled": false },插件会在加载时通过 client.config.get() 读取。

运行时命令开关

插件还支持通过 /autoretry 命令在运行时启用/禁用,无需修改配置文件或重启 OpenCode:

/autoretry off     → 关闭插件,清理所有 pending 定时器
/autoretry on      → 重新开启插件
/autoretry status  → 查看当前开关状态

插件在初始化时自动注册 /autoretry 命令到全局目录 ~/.config/opencode/command/autoretry.md,无需手动创建命令定义文件,且不会污染任何项目仓库。 如果该文件已存在则不会覆盖,避免覆盖你的定制。 旧版本曾把文件写到各项目的 .opencode/command/ 下,新版本会在加载时自动清理这些遗留文件(仅删除内容为 {{arguments}} 的自动生成文件,用户定制的同名文件保留)。

关闭后插件会立即清理所有 pending 的重制定时器和会话状态,不再检测中断或发送续写。 重新开启后恢复全部功能。

其它配置

检测策略和退避参数目前使用硬编码默认值(单一真相源):

{
  "autoretry": {
    "enabled": true,
    "maxRetries": 2,
    "backoffMs": [5000, 10000],
    "pollIntervalMs": 60000,
    "retryOn": ["输出截断", "静默中断", "数据流截断", "连接断开", "请求超时", "请求参数错"]
  }
}

暂不可配的原因:

  • 检测逻辑依赖 FFT(可视化反馈)
  • 子代理等待防护当前对所有 agent 一致
  • 空白检测已收敛为唯一出口,无需额外配置

错误分类

分类 触发条件 奔溃行为
用户中止 MessageAbortedError 跳过,不打扰
输出截断 output>0 重试发送"继续"
静默中断 0 output + 无 error + finish=unknown 重试发送上一条消息
数据流截断 JSON parsing / Unterminated 重试发送"继续"
连接断开 Connection failed / Server disconnected / ECONNRESET 重试发送上一条消息
请求超时 timeout / timed out 重试发送上一条消息
请求参数错 HTTP 400 / bad request 重试发送上一条消息
限流 HTTP 429 / rate limit toast 提醒,不重试
服务商异常 HTTP 5xx / provider toast 提醒,不重试

工作原理

session.error 路径(第一防线)

session.error 事件 ← OpenCode 服务端
    ↓
直接提取 error.name + error.message(事件自带)
    ↓
分类错误类型
    ↓
可重试? → 否 → toast 提醒
    ↓
是 → 检查 retryCount < maxRetries
    ↓
是 → scheduleRetry(5s/10s)

特点:错误消息还没入库也能分类,不依赖 DB 时序。

session.idle 路径(第二防线)

用户发消息 → session idle
    ↓
已有 pending retryTimer → 只结束本轮运行态,保留定时器
    ↓
否则 → checkAndRetry 查消息列表
    ↓
预算已 exhausted → 跳过(本回合不再自动续写)
    ↓
finish=stop/length + output=0 → 空白完成检测
    ↓
finish=unknown → 分类错误,scheduleRetry
    ↓
结束本轮运行态 + 清 pollTimer(绝不动回合预算)

特点session.idle 只复位临时运行态,不删除回合预算,因此重试计数能跨 idle 累积到上限。

轮询路径(极端兜底)

60s 内无任何事件 → poll check 触发
    ↓
检查 lastActivity 是否 ≥ 60s
    ↓
是 → checkAndRetry(同 session.idle 路径)
    ↓
清空 pollTimer

chat.message 防循环机制

用户发消息 → consumeOwnInjection(sessionID)?
    ├─ 是(autoretry 发的,标记有效)→ 只更新时间戳,不重置预算
    └─ 否(真正用户 或 标记已过期)→ resetTurn:retryCount=0, blankCount=0, exhausted=false

特点autoSendPending 标记现在存放在不会被 idle 清理的临时运行态里, 并带 60s 兜底有效期,既能识别自己注入的续写,也不会因标记卡死而误吞真正的用户消息。

与 watchdog 的关系

  • opencode-autoretry(本插件):运行在 OpenCode 内部,负责自愈
  • opencode-watchdog(外部工具):独立进程,负责外部监控备份

两者互补,不冲突:

  • 插件在内部自动重试,推进工作
  • watchdog 在外部兜底,当插件重试失败或禁用时仍能提醒

日志查看

插件使用 OpenCode 内置日志系统,可在 OpenCode 日志中查看:

service: autoretry
level: info
message: autoretry plugin loaded (idle-check)
message: detected interrupt: 连接断开
message: retry sent (1)
message: subagent wait detected, skipping all retry/continuation

什么时候触发分类

  • session.error:最后一条 assistant 消息有 error
  • session.idle:最后一条 assistant 消息无 error + finish=undefined
  • poll check:最后一条 assistant 消息无 error + finish=undefined + 超过 60s

什么时候跳过

  • session.idle 检测到 finish=stop/finish=length → 不走分类
  • 子代理等待(前条消息有 tool calls + 当前无 error + output=0) → 不发续写
  • finish=stop 且有文本/工具 → 不触发空白完成续写

开发

# 安装依赖
npm install

# 运行回归测试(会先编译 TypeScript)
npm test

# 编译 TypeScript
npm run build

# 类型检查
npm run typecheck

# 部署到全局插件目录
npm run install-plugin

诊断脚本

仓库提供 4 个只读诊断脚本,直接查询 OpenCode 的 SQLite 数据库,帮助定位异常消息和验证 autoretry 行为。

diagnose — 综合诊断

扫描数据库中的异常消息模式,快速掌握整体状况。

npm run diagnose          # 扫描最近 7 天
npm run diagnose -- 30    # 扫描最近 30 天
npm run diagnose -- 0     # 扫描全部记录

检测项:

  1. 空白完成(finish=stop/length + output=0)
  2. 中转网关伪造 length(finish=length + output=0 + input>0)
  3. 连接断开(Connection failed / disconnected / ECONNRESET)
  4. 静默中断(finish=undefined + output=0 + 无 error)
  5. 零 token 消息(input=0 + output=0)
  6. autoretry 触发记录("继续"消息)

timeline — Session 消息时间线

查看特定 session 的完整消息流,包括角色、finish、token、错误、工具调用。

npm run timeline -- <session-id>
# 示例:
npm run timeline -- ses_0c82b5cf5ffeDHr2ayODq7mlX8

输出包含每条消息的 finish 状态、token 用量、error 详情、工具调用摘要。如果发现错误消息,会自动检查错误后是否有 autoretry 介入。

errors — 错误消息查找

按关键词搜索消息,查看完整 JSON 结构和上下文。

npm run errors -- "Connection failed"
npm run errors -- "disconnected"
npm run errors -- "UnknownError"
npm run errors                          # 不带参数:列出最近 10 条有 error 的消息

每条匹配消息会显示:role、finish、tokens、error 完整结构、provider/model,以及前一条 assistant 消息的状态(用于判断 isSubagentWait 是否应该拦截)。

trace — autoretry 触发追踪

查找 autoretry 发送的"继续"消息,显示每次触发的上下文和结果。

npm run trace             # 最近所有触发
npm run trace -- 7        # 最近 7 天
npm run trace -- <session-id>  # 特定 session

每次触发显示:

  • 触发原因(前一条 assistant 的 finish、output、error、hasTool)
  • 触发类型(空白完成续写 / 中断重试 / 输出截断续写)
  • 重试结果(✓ 成功 / ✗ 失败 / ? 仍空白)

数据库路径

脚本自动定位 ~/.local/share/opencode/opencode.db。可通过环境变量覆盖:

OPENCODE_DB=/path/to/opencode.db npm run diagnose

已知限制

  1. 依赖 OpenCode 事件结构session.error 的 payload 结构未在文档中明确,当前做防御性处理
  2. 配置读取:当前配置硬编码在 src/index.ts,未来可扩展为全局配置
  3. 重试内容:当前只重发用户消息的 parts,不处理 attachments

License

MIT

About

OpenCode 插件:自动重试中断的 AI 对话 - 检测静默中断、网络错误并在 3s→6s→12s 退避后重试

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages