@forget2save/opencode-goal-mode
v0.2.0
Published
A persistent, budgeted Goal mode plugin for OpenCode.
Downloads
107
Maintainers
Readme
OpenCode Goal Mode
这是一个可以加载到 OpenCode 1.16.x–1.18.x 的 Goal 模式插件。普通 turn 返回最终文本后,如果当前 session 仍有 active Goal,插件会监听 session.idle,注入 synthetic continuation,并让同一 session 继续处理剩余缺口。Goal 完成、暂停、阻塞或触及预算后,循环停止。
当前版本采用“显式终态工具”路线:工作 agent 必须调用 goal_complete,提交总体证据,并逐项覆盖验收条件;普通 final answer 本身不会结束 Goal。它不是独立 judge,因此证据仍属于 agent 自报。
已实现能力
- Goal 按 session 持久化到项目的
.opencode/goal-mode/。 active → paused | blocked | complete | budget_limited状态机。paused | blocked → active恢复路径。- continuation 次数与 wall-clock deadline 双预算。
session.idle自动续跑,消息标记为 synthetic,不伪装成真实用户输入。- session 级并发门禁,避免一次 idle 被重复调度。
- 替换、暂停、完成、清理、删除 session 或卸载插件时取消正在执行的续跑。
- 派发前再次检查 deadline;取消或派发失败会释放预留次数,失败时转为
paused,避免重试风暴。 - 每次模型调用前只注入静态 Goal 安全合同,目标正文仍按用户数据处理。
- 状态文件使用严格校验、抗碰撞文件名和可恢复替换,并自动迁移旧版状态。
- 五个用户命令和七个模型工具。
兼容性
当前声明的兼容范围为 @opencode-ai/plugin >=1.16.0 <1.19.0,并使用 OpenCode 的稳定插件接口:
- 配置字段是单数
plugin; - 插件入口是
Plugin函数; - 自动续跑调用
client.session.promptAsync(); - 状态注入使用
experimental.chat.system.transform。
官方另有仍处于 beta 的 V2/opencode2 插件接口,它使用复数 plugins。两套接口不能混用。
以下版本已经分别完成兼容性验证:
| OpenCode | 验证结果 | | --- | --- | | 1.16.0 | 通过 | | 1.16.1 | 通过 | | 1.16.2 | 通过 | | 1.18.15 | 通过 |
这里的宿主验证覆盖插件加载和适配路径,不等同于真实模型提供商下的长时间稳定性证明。由于自动续跑依赖 session.idle 和 promptAsync(),长驻的 TUI/server 是主要使用场景;opencode run 可能在 idle 后先行退出,不建议把它作为无人值守 Goal 循环的可靠宿主。
插件使用 TypeScript,因为 OpenCode 会在自身进程中加载 JavaScript/TypeScript 模块。教程中的 Python 示例不改变宿主插件 ABI。
安装
推荐直接在目标项目的 opencode.jsonc 中引用 npm 包:
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["@forget2save/opencode-goal-mode"]
}OpenCode 启动时会自动下载并缓存插件。用下面的命令确认配置和插件可以正常加载:
opencode debug startup如果要在本地开发插件,先在插件目录安装依赖:
cd opencode-goal-mode
bun install然后使用相对于配置文件的本地路径:
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["./opencode-goal-mode"]
}也可以把整个包复制到目标项目,再调整相对路径。本地包依赖不会由 OpenCode 自动替你安装,所以复制后仍需在插件目录运行一次 bun install。
使用
创建 Goal:
/goal 把 HTTP 客户端迁移到 httpx.AsyncClient;保持 API 行为不变;pytest 和 mypy 通过;不要部署命令会要求模型调用 goal_set,然后立即开始工作。普通 turn 结束而 Goal 仍为 active 时,插件会自动开始下一次 continuation。
控制命令会先读取当前 Goal ID,再调用对应工具:
/goal-status
/goal-pause 我需要先检查当前改动
/goal-resume
/goal-clear模型可用工具:
goal_set:创建或替换 Goal,可单独指定验收条件、最大续跑次数和最大分钟数。goal_status:读取状态和剩余预算。goal_pause:使用当前 Goal ID 暂停。goal_resume:使用当前 Goal ID 从paused或blocked恢复。goal_complete:使用当前 Goal ID,携带总体证据和逐项验收证据进入终态。goal_block:使用当前 Goal ID,记录真正的外部阻塞及其证据。goal_clear:使用当前 Goal ID 删除该 session 的 Goal 状态。
除 goal_set 和 goal_status 外,所有状态变更工具都要求 goal_id。如果 ID 已过期,工具会拒绝修改并提示重新调用 goal_status,防止旧请求覆盖新 Goal。
默认预算为 12 次 continuation 和 60 分钟。单个 Goal 可以通过 goal_set 的 max_continuations、max_minutes 覆盖。
状态与数据
每个 session 使用一个 JSON 文件,包含:
- 目标与验收条件;
- 状态、创建时间和 deadline;
- continuation 已用量和上限;
- 总体完成证据、逐项验收证据、阻塞证据或最后一次调度错误;
- Goal ID 与 revision。
状态格式当前为 schema v2;插件会读取 schema v1 状态,并在下一次写入时迁移。每个 session 使用 SHA-256 文件名,读取时限制状态文件大小并核对文件内的 session ID。新的 goal_set 会用新 Goal ID 替换该 session 的旧 Goal。收到 session.deleted 后,插件会同步删除对应状态。
安全边界
- Goal 模式不会扩大 OpenCode 原有工具权限。
- continuation 有次数和时间上限。
complete必须带非空总体证据,并为每条验收条件提供对应证据;当前版本不会独立验证证据真实性。blocked必须同时提供阻塞原因和阻塞证据。blocked和paused不会自动继续;恢复需要显式操作。- 调度失败会释放本次预留并进入
paused,不会消耗次数后无限重试。 - 状态会跨插件重载保留;但重启后已经处于 idle 的 session 不一定重新发出 idle 事件,需要用户执行
/goal-resume或发送一条消息重新唤醒。 - 目标文本作为用户数据被明确分隔,不得覆盖 system policy 或权限边界。
开发与验证
bun run check
bun test test
bun run build
bun run smoke或者一次完成:
bun run verify实现分层:
src/domain.ts:Goal 数据合同、状态机和预算判定。src/store.ts:JSON 持久化与同 session 串行更新。src/controller.ts:idle gate、continuation 预留和错误降级。src/system-hook.ts:隔离 experimental system hook 与静态安全合同。src/index.ts:OpenCode hooks、commands 与 tools 适配。test/:状态机、持久化、预算、并发和失败路径测试。scripts/plugin-smoke.mjs:从编译产物实例化插件并验证完整适配路径。
当前限制
插件不会独立判定证据真实性。OpenCode 的 experimental.chat.system.transform 仍带有 experimental 前缀;OpenCode 升级后应重新运行完整验证,并检查该 hook 是否更名。
