- Python 66.5%
- JavaScript 11%
- Nix 9.6%
- Shell 8.8%
- TypeScript 2.7%
- Other 1.4%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
Add the gateway IDs gpt-6-luna-codex and gpt-6-sol-codex, mapped to the new models.dev entries openai/gpt-6-luna and openai/gpt-6-sol. Both override only the display name, the OpenAI SDK, and the same truncating limit as Astra Codex (context 272000 / input 258400 / output 32767), so family, capabilities, and pricing stay upstream. Insert the two projected entries into the committed offline snapshot without a full refresh, drop the temporary reserved-ID exemption now that the IDs are collected, add a generation-based behavior test, and update the model counts and reservation notes in the docs. Verified with python3 tests/models.py, just check, just build, just check-flake, and git diff --check. |
||
| agents | ||
| config | ||
| docs | ||
| nix | ||
| plugins | ||
| prompts | ||
| scripts | ||
| skills | ||
| tests | ||
| .envrc | ||
| .gitignore | ||
| .muxrun.toml | ||
| AGENTS.md | ||
| flake.lock | ||
| flake.nix | ||
| justfile | ||
| README.md | ||
gjz010/harness
gjz010 的 opinionated OpenCode 个人工具:通过 Nix 打包启动器、签名提交工具、 muxrun、skills、direnv 和 goal plugins,并保留 bubblewrap 沙箱。它不是需要复制到每个 项目的 starter,也不再根据 Git 身份选择 developer profile。
本仓库使用 flake-parts,支持 x86_64-linux。开发本仓库不需要 devenv CLI;
使用方既可以直接 nix run,也可以把包加入已有 flake 或 devenv 项目的 packages。
在已有项目中安装(agent 入口)
把一个已有项目接入 gjz010/harness,请让该项目的 agent 读取
skills/install-gjz010-harness/SKILL.md。该 skill 是
canonical 安装/迁移流程:先读用户 Git URL 指向的 revision 的 README 与 skill,
盘点目标项目,再按目标现有的 flake、devenv 或无 Nix 环境加包,最后按证据去重
(goal/TPS、Matt skills、muxrun、签名、agent/routing 兼容、启动沙箱)。
请安装 gjz010/harness(git+https://git.gjz010.com/gjz010/harness.git)
安装不改动目标项目的工具体系:已有 flake/devenv 只合并 input 和 package;无 Nix
项目保留自己的 npm/uv/cmake 等,宿主具备 Nix 与 x86_64-linux 时可用
nix run 'git+https://git.gjz010.com/gjz010/harness.git' -- --workspace 项目路径
直接运行,不为项目新建 flake、devenv 或 Justfile。默认不安装到全局 profile。
安装/更新快捷入口
gjz010-harness-install(app 为 install)在当前终端开启一个交互式 harness
会话,并让其中的 agent 盘点所选项目:没有持久 harness 依赖时走
install-gjz010-harness,已有依赖时走 update-gjz010-harness 把该项目的 harness
pin 更新到上游。默认 workspace 是 cwd,用 --workspace DIR 切换:
# 已发布版本;包含新接口的 revision push 前请改用本地 checkout
nix run 'git+https://git.gjz010.com/gjz010/harness.git#install'
nix run 'git+https://git.gjz010.com/gjz010/harness.git#install' -- --workspace /path/to/project
nix run .#install
nix run .#install -- --workspace /path/to/project
这是 agent 驱动的入口,不是确定性的系统安装器:真正的安装/更新由会话内的
skill 按目标项目现有工具执行。它需要宿主启用 flakes 的 Nix、x86_64-linux、
可用的 bubblewrap namespace,以及目标 provider 的认证;默认不写入全局 profile。
命令在当前终端前台运行 TUI;退出会话即返回 shell。更新 skill 的独立性、目标输入
边界和报告要求见 skills/update-gjz010-harness/SKILL.md。
本地配置说明:gjz010/harness 不假设目标存在 OpenCode,也不要求固定的配置布局。
目标已有的 opencode.json/opencode.jsonc、tui.json/tui.jsonc 会被原生合并,
project MCP、instructions、普通 plugins 和标量保留。agent 也按原生 deep merge
处理:同名 agent、default_agent、disable 与 permission 只覆盖显式字段,未覆盖
字段继承打包定义;没有强制还原 agent 拓扑的运行时插件。
机器相关的 token、代理和本机参数放在启动时 source 的私有 env 文件中(见下文),
不进入 Git、Nix 表达式或 store。完整的调研证据见
docs/harness-installation-research.md。
直接运行
宿主需要启用 flakes 的 Nix,以及允许 bubblewrap 使用的 Linux namespace。 在本地 checkout 中运行(新接口尚未 push 时请使用本地版本):
nix run .
nix run . -- --workspace /path/to/project
nix run . -- web --workspace /path/to/project -- --port 43118
nix run .#install
nix run .#install -- --workspace /path/to/project
新接口发布后,可在目标项目目录运行:
nix run 'git+https://git.gjz010.com/gjz010/harness.git'
安装或将包加入开发环境后,命令接口为:
harness [web] [--workspace DIR] [-- OpenCode args...]
workspace 默认是 cwd,不是位置参数。切换项目使用 --workspace,OpenCode
参数放在 -- 后。harness web 默认监听 127.0.0.1:43117,端口优先级为显式
-- --port N/-- --port=N > GJZ010_HARNESS_WEB_PORT > 默认 43117,例如
harness web -- --port=43118。无需 bootstrap;已有 provider/auth 可继续使用。
Flake 接口
inputs 为 nixpkgs、flake-parts、muxrun、mcp-harbor
(https://git.gjz010.com/gjz010/mcp-harbor.git),以及 non-flake inputs
matt-skills(GitHub mattpocock/skills)、no-negative-echo
(GitHub LB623/no-negative-echo)和 goal-plugin
(https://git.gjz010.com/gjz010/opencode-goal-plugin.git fork),由 flake.lock 固定。
mcp-harbor 保留上游自己的 nixpkgs pin(不设置 follows)。
| 输出 | 用途 |
|---|---|
packages.x86_64-linux.default / harness |
同一个默认包,包含 harness、gjz010-harness-install、gjz010-harness-commit、muxrun、gjz010-harness-models 二进制 |
packages.x86_64-linux.resources |
只读 OpenCode config、instructions、plugin 和合并后的 skills |
packages.x86_64-linux.agent-tools |
签名提交等 agent 工具,目前提供 gjz010-harness-commit |
packages.x86_64-linux.muxrun |
固定版本的 muxrun |
packages.x86_64-linux.mcp-harbor |
固定版本的 mcp-harbor stdio MCP proxy,同时随默认包进入沙箱兜底 PATH |
packages.x86_64-linux.goal-plugin |
离线打包的 goal server plugin,不含 TUI |
packages.x86_64-linux.model-snippet |
独立的 gjz010-harness-models 模型 snippet 生成工具 |
apps.x86_64-linux.default |
运行 gjz010/harness |
apps.x86_64-linux.install |
gjz010-harness-install,开启安装/更新 gjz010/harness 的交互式会话 |
devShells.x86_64-linux.default |
开发本仓库所需的工具,不依赖 devenv CLI |
devenvModules.default |
可选薄模块,仅提供 gjz010.harness.enable、gjz010.harness.package;只加包,不启动服务 |
加入外部 flake
以下为外部项目的最小 flake.nix 示例;已有项目只需合并 input 和 package:
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
harness.url = "git+https://git.gjz010.com/gjz010/harness.git";
};
outputs = { nixpkgs, harness, ... }:
let
system = "x86_64-linux";
pkgs = import nixpkgs { inherit system; };
in {
devShells.${system}.default = pkgs.mkShell {
packages = [ harness.packages.${system}.default ];
};
};
}
加入外部 devenv
在目标项目已有的 devenv.yaml 中添加 input(保留原有 inputs):
inputs:
nixpkgs:
url: github:cachix/devenv-nixpkgs/rolling
harness:
url: git+https://git.gjz010.com/gjz010/harness.git
然后在该项目的 devenv.nix 中加包:
{ inputs, pkgs, ... }: {
packages = [ inputs.harness.packages.${pkgs.stdenv.hostPlatform.system}.default ];
}
也可用可选模块替代上述直接加包方式:
{ inputs, ... }: {
imports = [ inputs.harness.devenvModules.default ];
gjz010.harness.enable = true;
# gjz010.harness.package = ...; # 可选:覆盖默认包
}
这些远端示例需要包含新接口的版本已经发布;未 push 的本地改动不会被远端 URL 使用。
配置与重载
包内配置位于只读 Nix store,由启动器设置的 GJZ010_HARNESS_RESOURCES 指向。
resources 按 config/matt-skills.json 从 Nix input 复制 25 个完整 Matt Pocock
skill 目录,并在构建时运行包内实际 muxrun 二进制的 muxrun skill 生成 skill。
选择的 skill 名称与原 vendored 集合相同,但上游 revision 可能包含更新内容,
不保证与旧副本逐字节一致。skills/configure-environment-in-gjz010-harness 仍由本仓库维护。
第三方 Skill no-negative-echo 也作为 non-flake Nix input 固定:flake.nix
以 github:LB623/no-negative-echo/<rev> 锁定上游整仓库,构建时只把其
no-negative-echo/ 子目录复制到只读 resources 的 skills/no-negative-echo/,
并对 7 个关键文件做 sha256 断言,源树或副本被替换都会使构建失败。
resources 还包含 plugins、instructions 和 OpenCode config。
修改源码后需要重新构建/运行新包,不能直接修改 store。
公共配置常量
以下三个文件是受信任的仓库构建输入,随包编译或复制进只读 store,不是本机设置, 也不应包含 token、私钥或宿主环境值:
config/commit-identity.sh:gjz010-harness-commit的公开 agent Git 姓名、邮箱和签名密钥指纹 (只有公钥指纹,没有私钥),以readonly方式拼接进脚本,不能被子进程环境覆盖。config/runtime-defaults.sh:gjz010/harness 默认 web 端口43117;127.0.0.1绑定仍写死在 启动器代码中。端口以显式 CLI-- --port N优先,其次GJZ010_HARNESS_WEB_PORT(运行时 env 文件之后生效),最后默认43117。config/muxrun-defaults.toml:项目没有.muxrun.toml时 fallback 配置的静态默认值; 启动器仍先计算并校验安全的 root 路径,再追加该文件。项目自己的配置优先。
修改这些文件后需要重新构建/运行新包(Nix store 不会原地更新);本机差异继续通过
运行时 env 文件或 GJZ010_HARNESS_WEB_PORT 等既有覆盖方式提供,不能写进 Nix 表达式或 store。
宿主 OpenCode 的 config、data、cache、state 目录仍被绑定,以保留 provider/auth
和会话状态;这不是完全 hermetic 的 OpenCode 配置环境。用户已有的
OPENCODE_CONFIG_CONTENT 等配置仍可保留,排查配置行为时也需检查这些来源。
启动时可选 source 以下 shell 文件:
${GJZ010_HARNESS_ENV_FILE:-${XDG_CONFIG_HOME:-$HOME/.config}/gjz010/harness/env}
默认文件不存在不影响启动。config/env.example 只提供公开占位示例;真实 token、
代理和本机参数放在权限为 600 的私有 env 文件中,不能进入 Git、Nix 表达式或
store。该文件是会执行的 shell 代码,只使用可信内容。显式设置 GJZ010_HARNESS_ENV_FILE
时应指向已存在的文件。旧默认路径 $XDG_CONFIG_HOME/harness/env 不再隐式读取;
旧 ignored profile .env 不自动搬迁或覆盖,手工迁移见
迁移说明。
GitHub MCP 与 gh 命令
打包的 resources 在沙箱 PATH 上提供 gh CLI,并在 config/opencode.json 中默认注册
官方 GitHub MCP server 为 mcp.github:类型为 local,命令为 github-mcp-server stdio,
可执行文件的绝对 Nix store 路径在构建时写入 command[0],不在 PATH 中查找。打包的是
GitHub 官方 release 二进制,内置 github.com OAuth 公共客户端凭据,因此首次使用无需
token 即可通过浏览器 OAuth 登录。GITHUB_PERSONAL_ACCESS_TOKEN(或 GitHub App 环境变量)
仍然可用且优先级更高,可写进上面的私有 env 文件;config/env.example 中有一行注释示例。
OAuth token 只保存在内存中,每次重启 OpenCode 后都需要重新登录。gh 使用自己的认证,
可独立使用:宿主 $XDG_CONFIG_HOME/gh(默认 ~/.config/gh)以只读方式回绑,宿主上已
gh auth login 的账号在沙箱内直接可用;沙箱内无法改写该目录,需要重新登录请在宿主完成。
修改 config 或 MCP 后需要重新构建/运行新包并重启 OpenCode;新建 chat session 不等于重启。
mcp-harbor
包内提供固定的 mcp-harbor stdio MCP proxy 二进制,随默认包和沙箱兜底 PATH 可用;
它把本地 stdio client 转发到远端 Streamable HTTP MCP backend,并可把 catalog 快照缓存到
项目内。默认包不注册任何 mcp-harbor MCP 服务,也不改动现有 MCP 条目:需要时由目标项目
通过 configure-mcp-harbor skill 配置。该 skill 是只读 resources 中指向同一个
mcp-harbor input 源码 .opencode/skills/configure-mcp-harbor/ 的 symlink
($GJZ010_HARNESS_RESOURCES/skills/configure-mcp-harbor/),构建时断言 SKILL.md 非空。
更新用 just update-mcp-harbor;修改后需要重新构建/运行新包并重启 OpenCode。
沙箱 hook
可选地设置运行环境变量 GJZ010_HARNESS_SANDBOX_HOOK(也可写进上面的 env 文件)。
非空时,启动器在追加完全部默认参数和 web 参数后、exec bwrap 之前,把该路径解析为
可读普通文件(realpath -e,相对路径相对启动 cwd 而非 --workspace),并作为普通
命令 source 恰好一次。set -euo pipefail 仍然生效:hook 中失败的普通命令或 return
会阻止 bwrap 启动;文件缺失、目录或不可读会以明确的 harness 错误退出。未设置或为空
表示完全禁用。hook 不会自动从项目中加载。
hook 以启动用户的权限执行受信任的宿主 Bash,可以读取规范化的 workspace 变量并
向 Bash 数组 args 追加 bwrap 参数,因此能改变甚至覆盖默认沙箱限制;除这两项外的
启动器内部结构不是稳定接口。追加的路径必须加引号,例如把一个 workspace 内的目录
再追加为绑定:
args+=(--bind "$workspace/data" "$workspace/data")
这里只展示未经沙箱验证的 bwrap 参数;是否可行取决于具体参数,不要据此假设 OverlayFS 等更复杂的能力。hook 每次启动只执行一次,修改 hook 后需要重新启动 harness。
项目依赖属于目标项目:继续使用它已有的 flake、devenv 或其他工具,而不是
把依赖写进 gjz010/harness 仓库。需要更改依赖、代理或凭据时使用
configure-environment-in-gjz010-harness skill,先区分项目依赖与本机环境。
direnv plugin 在每次 OpenCode shell/PTY 调用前通过 shell.env 刷新目标项目的
direnv diff,并处理变量删除。目标项目使用 direnv 时,修改环境后执行:
direnv reload
direnv export json >/dev/null
launcher 保留完整 DIRENV_* 元数据。plugin 首次刷新先正常执行 direnv export json,
使切换项目或进入无 .envrc 目录时仍能撤销旧环境;若没有导出变化且仍有活动的
DIRENV_FILE,仅在额外一次 export 子进程中移除 DIRENV_WATCHES,重新应用项目环境。
之后仍按正常 direnv 增量机制刷新,不每次重新加载,也不清除 DIRENV_DIFF、
DIRENV_FILE 或 DIRENV_DIR。若 .envrc 被阻止,先审查可信内容,
再在宿主终端执行 direnv allow .。沙箱只读使用宿主 direnv 授权记录,不允许
agent 修改宿主的授权库。若授权目录在 gjz010/harness 启动后才首次创建,需要重启 gjz010/harness
才会挂载它。本仓库 .envrc 使用 use flake;外部项目保留自己的激活方式。
plugin 不替项目选择或迁移环境工具。
PATH 优先级:打包的 harness 工具列表通过 GJZ010_HARNESS_TOOL_PATH 暴露,启动器
把它从 wrapper 生成的前置段移出并追加到 PATH 末尾,因此项目/宿主 PATH 优先,打包工具
兜底。launcher 自身从打包工具列表固定 bwrap/OpenCode 的绝对路径,不受项目同名程序
影响。direnv plugin 初始化只移除 PATH 末尾完整匹配的一个 launcher 兜底块,保留项目/宿主
原有的同目录项;若末尾已被其他环境刷新改变,则不猜测删除。direnv 自身以
process.env.PATH 为刷新基线;每次 shell.env 交给 OpenCode 执行环境的 output.env.PATH =
刷新后的项目/宿主 PATH + 唯一追加的打包兜底,不重复累积,也不回流到下一次 direnv
export 的 env。即使没有 PATH delta、export 为空、目标目录没有 .envrc 或刷新报错,
执行环境仍然保留兜底。plugin 在初始化时从该工具列表解析打包 direnv 的绝对路径,
所以项目把 PATH 完全替换掉也能刷新。修改本段涉及的 launcher/plugin 后需要重新
构建/运行新包并重启 OpenCode;新建 chat session 不等于重启。
goal fork 通过 nix/goal-plugin.nix 和 nix/goal-plugin-package-lock.json
离线构建为自包含 server bundle,配置加载 resources 中的
plugins/opencode-goal.mjs;不打包或启用 TUI 扩展,也无需启动时安装 npm 依赖。
启动器在运行时设置 OPENCODE_GOAL_STATE_PATH,默认值为所选 workspace 下的
.opencode/goals.json:cwd 启动时是 $PWD/.opencode/goals.json,
--workspace DIR 时是 DIR/.opencode/goals.json。workspace 已作为可写目录挂载,
因此该状态文件可写;启动器不做 Git 根发现,路径只取决于 workspace 选择,外部项目
无需是 Git 仓库。goal 插件在写入时递归创建缺失的父目录,所以 workspace 不需要预先
存在 .opencode,启动器也不会创建或改写它。
可通过运行环境显式设置 OPENCODE_GOAL_STATE_PATH 覆盖,非空值原样保留,空值回退到
上述默认路径;覆盖不会增加沙箱挂载:路径必须位于已有可写挂载内。旧版全局状态
${XDG_DATA_HOME:-$HOME/.local/share}/opencode/goal/goals.json 保留在原处,
不自动迁移或删除。当前固定的上游版本仅在进程内串行化状态写入;多个 gjz010/harness 进程
同时修改同一 goals.json 可能丢失更新。并行使用时请为各进程设置不同的状态文件,或
避免同时写入;原子文件替换并不能代替跨进程锁。状态文件路径在启动时确定,
修改后需要重启 OpenCode。
外部项目应自行忽略 /.opencode/goals.json,不要忽略整个 .opencode 目录,
以免掩盖其他需要跟踪的配置。
TUI 配置由仓库的 config/tui.json 管理:构建时复制进只读 resources,启动器设置
OPENCODE_TUI_CONFIG 指向该文件。它只启用一个 TUI 插件,路径为
./plugins/opencode-tps-meter/tui.mjs,相对该配置文件解析;不给 goal 开 TUI。
TPS Meter 与 matt-skills 一样是固定的 Nix input,不依赖运行时 npm 下载:flake.nix
中的 non-flake input tps-meter 指向上游 npm 正式发布的 0.4.0 tarball
(https://registry.npmjs.org/opencode-tps-meter/-/opencode-tps-meter-0.4.0.tgz),
flake.lock 以 narHash 锁定并按内容校验。Nix build 时把该包的 dist/tui.mjs
复制到只读 resources 的 plugins/opencode-tps-meter/,OpenCode 启动时直接本地加载,
不需要网络。插件只依赖 Node builtin 和宿主提供的 optional peers(solid-js、
@opentui/solid),因此不复制第二套 reactive runtime,也没有非 builtin 的额外依赖。
升级版本需要修改 flake.nix 中的 tarball URL(版本号写在 URL 里)并运行
just update-tps-meter 刷新 flake.lock;只运行该 recipe 不会改变版本。修改后需
重新构建/运行新包,并重启 OpenCode 才会生效。
update-gjz010-harness 命令
每个采用 gjz010/harness 的项目都可以使用 /update-gjz010-harness。它由包内
plugins/opencode-harness-commands.mjs 通过 OpenCode 的 config hook 注册到解析后的
配置对象上,因此不会向目标项目写入任何文件;若项目自己已定义同名命令则保留项目版本,
不会被覆盖。命令展开为一段提示词,指示 agent 使用 update-gjz010-harness skill 更新本项目
锁定的 gjz010/harness 依赖,并透传用户输入;因此仍受该 skill 的 propose-and-confirm
门控约束。
命令随只读 resources 提供:修改后需要重新构建/运行新包并重启 OpenCode。新建 chat session 不等于重启。
OpenCode 配置变化后重启 OpenCode:包括 config、skills、instructions 路径、
MCP、agents、commands、plugin 和包版本。启动时 source 的 gjz010/harness env,以及
provider/config substitution/MCP 使用的进程环境变化,也需要重启。仅用于 shell
的目标项目环境在成功 reload 后可由 plugin 注入下一次调用;新建 chat session
不等于重启。历史源码调研保留在 docs/research/,不作为跨版本重载保证。
模型元数据
打包的 resources/opencode.json 自动包含 provider.newapi-selfhost.models
的完整 15 个模型,含 mimo-v2.6-pro / mimo-v2.6-flash。宿主只需在项目或
global OpenCode 配置里提供 provider.newapi-selfhost 的连接设置与凭据;
OpenCode 原生 deep merge 按字段合并,会保留宿主的 options.baseURL、
apiKey、provider 级 npm 和自定义模型,再把它们与打包的模型列表并集。
同名字段按 global($XDG_CONFIG_HOME/opencode/opencode.json)→ 打包
OPENCODE_CONFIG → 项目 opencode.json 的优先级递增,项目只覆盖它显式写出的
字段,其余字段保留低优先层的值。打包产物只增加 provider.newapi-selfhost.models,
不含连接地址、认证或 provider 级 npm。模型来源和 overrides 以
config/newapi-models.json 为准。
构建使用提交在仓库里的离线快照 config/models-dev-snapshot.json:它取自
https://models.dev/api.json(旧条目获取日期 2026-09-22;gpt-6-luna 与
gpt-6-sol 于 2026-09-23 单独获取并插入,未整份刷新),只保存清单引用的来源,
且每个模型都经过 project() 白名单,不含 id、api、baseURL、headers、
options、env、npm 等传输字段。构建期 nix/package.nix 运行
scripts/model-snippet.py generate --manifest config/newapi-models.json --catalog config/models-dev-snapshot.json,再用 jq deep merge 进编译后的
opencode.json。构建和启动都不访问 models.dev。
更新快照(先确认 models.dev 仍有清单中的全部来源,审查 diff 后提交):
curl -fsSL https://models.dev/api.json -o /tmp/models-dev.json
python3 - <<'PY' > config/models-dev-snapshot.json
import importlib.util, json
from pathlib import Path
spec = importlib.util.spec_from_file_location("models", "scripts/model-snippet.py")
models = importlib.util.module_from_spec(spec)
spec.loader.exec_module(models)
catalog = json.loads(Path("/tmp/models-dev.json").read_text())
manifest = json.loads(Path("config/newapi-models.json").read_text())
snapshot = {}
for entry in manifest["models"].values():
provider, model_id = entry["source"].split("/", 1)
snapshot.setdefault(provider, {}).setdefault("models", {})[model_id] = models.project(
catalog[provider]["models"][model_id])
print(json.dumps(
{p: {"models": {m: snapshot[p]["models"][m] for m in sorted(snapshot[p]["models"])}}
for p in sorted(snapshot)}, indent=2, ensure_ascii=False))
PY
just check 校验该 JSON;just test 实际从快照生成并核对 15 个模型且快照无传输
字段。打包 agent 的 newapi-selfhost/... 模型必须全部存在于生成结果中,没有例外;
错拼或其他未收录 ID 仍会失败。修改快照、清单或 nix/package.nix 后必须重新构建并
退出、完全重启 OpenCode;新建 chat session 不等于重启。
gjz010-harness-models 仍可从 models.dev 读取元数据,或用显式快照离线生成 snippet:
# 使用包内的清单;也可用 nix run .#model-snippet -- generate
gjz010-harness-models generate > /tmp/newapi-models.json
# 仓库开发:使用当前源码清单;--catalog 可指定 models.dev API JSON 快照以离线生成
just model-snippet --catalog /path/to/models-dev.json
# 使用自己的来源映射,从 OpenCode JSON/JSONC 中提取模型列表和当前差异
gjz010-harness-models extract --config ~/.config/opencode/opencode.jsonc \
--sources /path/to/sources.json > /tmp/model-manifest.json
sources.json 是网关模型名到 models.dev provider/model-id 的映射,例如
{"qwen-3.8-27b":"cerebras/qwen-3.8-27b"}。它必须覆盖输入配置中的所有模型。
生成时可用 --manifest 或 GJZ010_HARNESS_MODEL_MANIFEST 指定其他清单。网络只在
工具运行时使用,Nix 构建不读取宿主配置,也不抓取实时模型目录。
当前清单包含 15 个模型:11 个从本机 newapi-selfhost.models 提取,另有 2 个
MiMo 模型和 2 个 GPT 6 Luna/Sol 模型映射到 models.dev。提取的条目仅记录本地已有字段相对 models.dev
的差异。相同字段跟随目录更新;差异无法自动判断是有意修正还是
旧数据,因此全部保留供人工审核。覆盖对象递归合并,数组整体替换,null 删除字段。
支持能力、价格、限制、模态、状态及显式 provider.npm;不复制 catalog 的 id
或连接配置。未知模型来源、无映射或不支持的 override 字段会报错,不猜测替代模型。
- Qwen 使用 Cerebras
qwen-3.8-27b,保留 context131072/ output16384。 - GPT 的网关
-codex名称映射到对应 OpenAI 元数据,保留本机名称、限制、价格和 SDK 修正。 - GPT 5.6 的 Sol/Luna/Terra 额外用
limit.input: null去除上游922000输入限制,避免超过本地262144上下文;重新 extract 后应复核这一项。 - GPT 6 Luna/Sol 的
gpt-6-luna-codex/gpt-6-sol-codex覆盖显示名、provider.npm和与 Astra Codex 相同的截断限制context 272000 / input 258400 / output 32767;family、能力和价格采用上游openai/gpt-6-luna/openai/gpt-6-sol,不复制 GPT 5.6 的本地价格覆盖。 - DeepSeek V4.1 Flash 在当前 catalog 的 DeepSeek provider 中没有精确条目,显式选用
openrouter/deepseek/deepseek-v4.1-flash;可在清单中更换来源。 - MiMo 直接采用 models.dev
opencode-go的mimo-v2.6-flash与mimo-v2.6-pro,无本地覆盖;生成器会去除上游 transport 字段。
Runtime modes
默认模式为 normal(普通);Tab 在 expert(专家)、pro(专业)、
normal(普通)、fast(极速)、mimo(小米)五种 primary 间切换。也可用
harness -- --agent expert 显式选择。Build/Plan 和旧的通用 agents 已禁用;
模式不是自动执行的 worker 流水线。
| 模式 | Primary 模型 | 可用 workers |
|---|---|---|
专家 expert |
Astra | explore-deepseek, explore-expert, code-deepseek, code-astra, review-deepseek, review-astra, computer-use-astra |
专业 pro |
Astra | explore-luna, explore-sol, code-luna, code-sol, review-fix-astra, computer-use-astra |
普通 normal(默认) |
Astra | explore-deepseek, code-deepseek, review-fix-astra, computer-use-astra |
极速 fast |
DeepSeek | explore-deepseek, code-deepseek, review-deepseek |
小米 mimo |
MiMo V2.6 Pro | explore-mimo, code-mimo, review-mimo, review-fix-mimo |
agents/ 维护 5 个 primary 和 19 个隐藏 worker。Nix 将 Markdown frontmatter
与正文编译进 OpenCode 配置;primary 只组合公共编排职责和本模式路由规则。
统一 subagent-routing skill 已移除。原生 task 权限同时过滤工具描述中的 worker
列表并拒绝不允许的目标;所有 worker 禁止递归 task。
pro 由 Astra 掌握需求、架构与最终验收:常规探索和边界清晰的实现交给
explore-luna/code-luna;跨模块约束或复杂故障调查交给 explore-sol;困难调试或
需要独立推理的复杂实现交给 code-sol,且当难度已经明显时可以跳过 Luna 直接选择
Sol。Luna 的概念性阻塞返回主控,由主控决定是否升级 Sol,而不是重复同一条失败路径。
该模式复用既有按需审查修复的 review-fix-astra,不新增独立 review worker,也不是
每次 patch 的必经流程。
computer-use-astra 是交互式应用闭环的专职 worker,只在 normal/expert/pro 可用,
fast 对其精确 deny。仅当任务契约明确目标应用、需要现场观察的状态或交互反馈,
以及无法靠源码、编译或确定性测试确认的验收项时才调用;任务难、其他 worker 失败、
想要更强推理或仅存在 MCP 都不构成理由,也不能把它当作代码执行的兜底或绕过当前模式
的代码执行层级。
Primary 默认委派 worker,只有判断某次分派明显不必要(例如无设计歧义的一行或单文件
改动,委派成本明显高于收益)时才直接编辑;非平凡、有风险、多文件、涉及架构或需要
独立验证的工作仍交给 worker。bash: allow 不是文件系统只读保证,也没有新增
沙箱。打包 agents 只带 agent-from-gjz010-harness 来源标记,它不是所有权或不可覆盖
保证;agent 配置按原生 deep merge,同名项目 agent、default_agent、disable 与
permission 可覆盖打包默认,未知项目 agent 也不会被这段配置拦截,仍受项目/global/
native 权限约束。用户控制的其他插件、指令、显式 API/CLI 调用不是此路由策略的安全
边界。
若项目复用某个受限 worker 的名字,只改该 agent 定义不会自动改掉调用方 primary 上
编译出的 task: deny;需要在调用方项目的 permission.task 里显式 allow,例如
{"agent":{"normal":{"permission":{"task":{"my-worker":"allow"}}}}}。请使用精确规则,
不要用全局 "*": "allow" 取代它。
模型使用 newapi-selfhost/...;包的离线快照自动加载全部 15 个模型元数据,
provider 连接和凭据仍由本机 OpenCode 配置提供。包加载成功不代表远端模型可用。
pro 的 Luna/Sol worker 引用 newapi-selfhost/gpt-6-luna-codex 与
newapi-selfhost/gpt-6-sol-codex;两者现已收录进离线清单与快照,元数据来自
models.dev 的 openai/gpt-6-luna 与 openai/gpt-6-sol,覆盖显示名、SDK 和与 Astra Codex 相同的截断限制。
tests/models.py 要求每个被引用的打包模型都有离线元数据,没有例外;错拼或其他
未收录 ID 仍会失败。定义或插件更改后重新构建并退出、重启 OpenCode;新建聊天
不会重载配置。实现证据、输入修正和限制见 runtime modes。
沙箱边界
Astra 模型提示词
plugins/astra-scope.ts 使用 v1 plugin 的 experimental.chat.system.transform,
当请求模型的 ID、API model ID 或显示名包含 gpt-6-astra(忽略大小写)时,
将 prompts/astra-scope.md 追加到已有 system 内容,不替换原指令。它不限定
provider 或 agent,因此 Astra subagent 也适用;其他模型不注入。
提示词在插件初始化时读取一次,修改后需重新构建并重启 OpenCode。
这是提示词约束而不是权限隔离;该 hook 是 experimental,升级 OpenCode 后应复验。
gjz010/harness 公共 OpenCode 配置使用 {"permission":{"*":"allow","external_directory":"allow"}},
默认不弹出工具权限确认。subagent 显式的 task: deny / edit: deny 仍保留;
这不改变下述 bubblewrap 挂载限制,也不改写宿主 OpenCode 配置。
- 保留网络,根文件系统只读,HOME 被遮蔽,显式 workspace 可读写。
XDG_RUNTIME_DIR指向沙箱内私有临时目录,供 devenv 等工具创建运行时文件, 不把宿主/run/user改为可写。- 当调用者进程可见的
XDG_RUNTIME_DIR与WAYLAND_DISPLAY能解析出宿主上真实的 Unix socket 时,启动器只把这个单个 socket 绑定到沙箱私有/tmp/harness-runtime/<basename>,并把沙箱内WAYLAND_DISPLAY指向该 basename, 以便 TUI 的剪贴板工具(wl-paste/wl-copy)连接;XDG_RUNTIME_DIR仍为沙箱私有, 不绑定整个/run/user,也不暴露宿主 runtime 目录下的 lock、xauth 等其它文件。 仅当该宿主 socket 可解析时才会追加这些参数,否则保持旧行为。检测依赖调用者进程 可见的环境变量,因此在 harness 沙箱内部再次启动 harness(嵌套启动)时,调用者看到的是 已被改写的/tmp/harness-runtime,转发不会生效;这是不报错也不接入的安全 fail-safe, 嵌套场景下图片粘贴仍不可用。 - 宿主 OpenCode config/data/cache/state 可写;没有项目 muxrun 配置时,按 workspace 隔离的 muxrun fallback state 也可写,以保留持久任务。
- 启动器无条件固定
OPENCODE_DB=opencode-stable.db(相对 OpenCode 数据目录的裸文件名, 不是绝对路径):上游构建 channel 从 stable 改为 prod 后默认数据库会变成opencode.db,既有会话会看似丢失;全新安装也会创建opencode-stable.db而非opencode.db。要改用规范名,在私有 runtime env 文件($XDG_CONFIG_HOME/gjz010/harness/env) 或进程环境中设置非空OPENCODE_DB;启动器先 source 该 env 文件再固定,env 值优先。 - 已有 muxrun 配置的
root必须位于 workspace 内(相对路径以配置文件目录为基准); 外部状态目录会明确拒绝。Unix socket 路径过长时需选择更短的状态或项目路径。 - GPG 使用 extra socket 和公钥材料;SSH 使用 agent socket 以及只读 config、
known_hosts。宿主的
ghCLI 状态目录$XDG_CONFIG_HOME/gh(默认~/.config/gh, 其中的hosts.yml可能含 OAuth token)以只读方式回绑,因此沙箱内gh可直接复用 宿主已登录的 GitHub 账号,但无法改写或覆盖该凭据。启动器不绑定 GPG/SSH 私钥文件。 - 系统 SSH 配置保持启用(不禁用
/etc/ssh,也不把GIT_SSH_COMMAND改成-F /dev/null)。根只读绑定会让Include指向的 Nix store 文件在沙箱内保持 商店的合成属主(nobody:nogroup),OpenSSH 会因此报Bad owner or permissions并直接退出。启动器读取/etc/ssh/ssh_config,枚举其递归Include引用的现有文件(含嵌套、glob、相对/etc/ssh路径、条件块下的 Include, 不执行任何Match exec),把每个 Include 的真实目标重新绑定为调用用户属主的只读副本 (--perms 0444 --ro-bind-data)。路径和内容不变,不写宿主、不改变 UID 映射, 遇到 group/world-writable Include 或读取失败则终止启动。系统入口本身不复制: OpenSSH 只对 Include 目标启用该权限检查。默认 Git 与普通 SSH 都按同一套 正常配置加载;显式传入的GIT_SSH_COMMAND原样保留;~/.ssh/config与known_hosts的只读挂载不变。 - 该 SSH 属主副本只在启动器构建 bwrap 参数时生成,需重启启动器(新会话)才生效。
真实沙箱验证入口:
just smoke-ssh-config [PACKAGE](离线、无网络、不发起 OpenCode 模型请求)。 - 拒绝
/、HOME、会暴露 HOME 的上层目录以及敏感/运行时目录作为 workspace。 选择项目目录,而不是扩大到整个 HOME 来绕过限制。 - Git worktree 的 common dir 若位于 workspace 外会明确失败。使用同时包含
worktree 与 common dir 的安全共同上层目录作为
--workspace;启动器不会默默 增加额外挂载。若共同上层是 HOME 等被拒绝目录,请先调整项目布局。
这不是针对恶意代码的密闭沙箱。网络、认证 sockets、Nix daemon、可读的宿主根目录 和持久 OpenCode 配置仍提供宿主能力;socket 可代为认证/签名,即使没有暴露私钥。 只读根也不等于隐藏所有宿主数据。只对可信项目和工具使用这些权限。
开发与维护
nix develop
# 或在审查 .envrc 后执行 direnv allow .
just check
| Recipe | 用途 |
|---|---|
just check |
静态检查和 tests |
just test |
少量离线合同测试,不启动 OpenCode、bubblewrap 或认证服务 |
just build |
nix build . |
just check-flake |
nix flake check |
just update-matt-skills |
nix flake update matt-skills,仅更新指定 input |
just update-no-negative-echo |
nix flake update no-negative-echo,仅更新指定 input |
just update-muxrun |
nix flake update muxrun,仅更新指定 input |
just update-mcp-harbor |
nix flake update mcp-harbor,仅更新指定 input |
just update-goal-plugin |
nix flake update goal-plugin,仅更新指定 input |
just gjz010-harness-commit "message" |
显式请求提交时使用的签名提交 wrapper |
just opencode / just opencode-web |
本仓库启动入口 |
Matt skills 初始迁移 pin 为 74ca5fe077456a0b3b2f5310cf9430999fd0b5fd,
goal fork 为 1f52d99d0984882d3be4c3997b29ceb85271a13d;no-negative-echo
固定为 eba9f1d2b4c19e699786a49427189988ad6d8d65;mcp-harbor 固定为
1fdb3840ba97be4d60639326d1c5039b951d3e37;muxrun pin 保持不变。
后续以 flake.lock 为准。旧 sync/restore recipes 和 skills-lock.json 已移除;
普通构建直接使用 Nix inputs,无需恢复 checkout 内副本。
更新后审查 lock 和上游内容,运行 just check、just build、just check-flake。
goal 上游变更可能需要手工更新 nix/goal-plugin-package-lock.json 和
nix/goal-plugin.nix 中的 npmDepsHash;update recipe 不会自动更新它们。
重新构建并重启 OpenCode 才会加载新资源。
架构与 agent 检查规则见 AGENTS.md;skills 维护见
skills README。迁移期间 scratch 仍使用
.devenv/tmp/<task>/,这是 legacy 目录名,不表示依赖 devenv。
自动化检查只覆盖关键逻辑,不代表真实沙箱、认证或 OpenCode 集成已经通过。 修改启动器或资源时,应按改动范围在临时 workspace/HOME 中手工做 smoke test。
English
An opinionated, sandboxed OpenCode tool for gjz010, packaged with flake-parts for
x86_64-linux. Run locally with nix run ., or add its package to an existing
flake/devenv environment. Host authentication and persistent OpenCode state are
retained; this is not a hermetic security boundary against malicious code.