diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..352c183 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,204 @@ +# 协作与开发工作流 + +本仓库是 [`xai-org/grok-build`](https://github.com/xai-org/grok-build) 的 patch 化 fork。fork 多了哪些能力见 [README.md](README.md);这里只讲怎么改、怎么同步、怎么不把自己的活弄丢。 + +以 CraftBukkit / Spigot 的模型,在**不接受外部 PR 的上游**之上维护本地改动。 + +## 为什么是 patch,而不是 fork 后直接改 + +上游有两个决定性特征: + +1. 它是 xAI 内部 monorepo 的**周期性单向导出**。历史是一串线性的 `Synced from monorepo` + 压扁提交,根目录 `SOURCE_REV` 记录对应的内部 commit。上游随时会把整棵树重新投放一次。 +2. 上游 `CONTRIBUTING.md` 明确写着**不接受任何外部 PR 或补丁**。 + +也就是说:改动推不回去,而上游会不断整体覆盖。直接在 fork 上改,每次同步都要打一场 +merge 混战,而且没人说得清「我们到底改了什么」。 + +这正是 CraftBukkit 面对 Mojang 的处境。解法也一样——**把改动本身作为版本库里的唯一真相**: + +> `patches/` 是源码,`work/` 是编译产物。 + +## 目录结构 + +``` +newgrok/ ← 你提交的仓库 +├── patches/ ★ 唯一真相:0001-*.patch,一个补丁一件事 +├── scripts/ 工作流脚本 +├── upstream.rev 钉住的上游 commit +├── Makefile +├── upstream/ 派生:上游的纯净 clone (.gitignore) +└── work/ 派生:upstream@rev + patches(.gitignore) +``` + +对照 Spigot: + +| 这里 | Spigot / BuildTools | +|------|---------------------| +| `upstream/` | CraftBukkit 的 clone(纯净上游) | +| `work/` | 打完补丁的工作目录 | +| `patches/` | `CraftBukkit-Patches/` | +| `scripts/apply-patches.sh` | `applyPatches.sh` | +| `scripts/rebuild-patches.sh` | `rebuildPatches.sh` | +| `upstream.rev` | `versions/*.json` 里的版本钉 | + +`upstream/` 和 `work/` 都**不进 git**——它们任何时候都能从 `upstream.rev` + `patches/` +一字不差地重建。 + +两条规则,违反任意一条都会默默丢掉工作成果: + +1. **Rust 源码改动写在 `work/`,不要手改 `patches/`。** 补丁文件是生成物,手改之后下次就合不上。 +2. **`make rebuild` 是离开 `work/` 的唯一出口。** `make apply` 会 `git reset --hard` + `git clean -fdx`。没 commit 并且没 export 的改动,下次 apply 就没了。 + +## 日常循环 + +改代码永远在 `work/` 里,**一个提交 = 一个补丁**: + +```sh +make apply # 拿到干净的、打好补丁的树 +$EDITOR work/crates/codegen/.../foo.rs # 改 +cd work && git add -A && git commit -m '...' # 提交(提交信息就是补丁标题) +cd .. && make rebuild # 写回 patches/ +git add patches/ && git commit -m '...' # 提交到 fork 仓库 +``` + +**`make rebuild` 是唯一的出口。** 没跑过它的改动,下次 `make apply` 就没了。 + +修改已有的补丁,用普通的 git 手段就行——`work/` 是个正常的 git 仓库,`base` 标签是上游和我们的分界: + +```sh +cd work +git rebase -i base # 改写、合并、重排、删除任意补丁 +cd .. && make rebuild # patches/ 会整体重新生成 +``` + +随时看当前状态: + +```sh +make status +``` + +## 跟进上游同步 + +```sh +make update-dry # 只看会拉进来什么,不动任何东西 +make update # 重新钉 upstream.rev,并把补丁前移到新基线 +``` + +`make update` 会: + +1. `git fetch` 上游,列出 `upstream.rev..origin/main` 之间的新提交和 `SOURCE_REV` 变化 +2. 把 `upstream.rev` 更新为最新 +3. 在新基线上重放 `patches/` +4. 干净通过则自动 `make rebuild`,让补丁上下文对齐新代码 + +冲突时脚本会停下并给出解决步骤——和处理 rebase 冲突完全一样: + +```sh +cd work +git status # 看冲突文件 +$EDITOR <冲突文件> # 消掉冲突标记 +git add -A +git am --continue # 或 git am --skip / git am --abort +cd .. && make rebuild # 把前移后的结果写回去 +``` + +补丁数量多的时候,冲突是这个模型的固有成本,也是它的价值所在:冲突精确地指出上游改动 +撞上了你的哪一处改动,而不是让它悄悄消失。 + +## 全部命令 + +从仓库根目录跑 `make`(`make help` 列出全部目标)。脚本自己把 `~/.cargo/bin` 加进 `PATH`,交互 shell 里不必先有 `cargo`。 + +| 命令 | 作用 | +|------|------| +| `make setup` | 安装工具链(rustup / dotslash / protoc) | +| `make apply` | `upstream.rev + patches/ → work/`;加 `FORCE=1` 丢弃 `work/` 里的未提交改动 | +| `make rebuild` | `work/` 中 `base` 之上的提交 → `patches/` | +| `make update` | 跟进最新上游同步并前移补丁 | +| `make update-dry` | 预览上游差异,不做修改 | +| `make build` / `make release` | 编译 debug / release 二进制 | +| `make check` / `make clippy` / `make fmt` | 快速类型检查 / lint / 格式化 | +| `make test` | 跑测试(`PKG=` 指定 crate) | +| `make run` | 编译并启动 TUI | +| `make status` | 显示当前钉住的 rev、补丁数、`work/` 状态 | +| `make clean` | 删 `work/target` | +| `make distclean` | 删 `work/` 和 `upstream/`(不动 `patches/`) | + +`make build` 之后的额外参数会透传给 cargo,例如 `./scripts/build.sh --release --locked`。 + +**跑单测。** `make test` 是薄封装;要带 filter,直接走 `build.sh`,多出来的参数会原样转给 cargo: + +```sh +CARGO_CMD=test PKG=xai-grok-version ./scripts/build.sh test_fork_tag +CARGO_CMD=test PKG=xai-grok-pager-bin ./scripts/build.sh version_output_writer -- --nocapture +``` + +## 安全网 + +脚本刻意不会静默吞掉工作成果: + +- `work/` 有**未提交改动**时,`make apply` 拒绝执行(提示先 commit + rebuild,或 `FORCE=1`) +- `work/` 的提交数和 `patches/` 的文件数**对不上**时同样拒绝——这基本意味着你忘了 `make rebuild`。故意增删补丁文件时也会触发,检查分不清这两种情况 +- `git am` 进行到一半时,`make rebuild` / `make build` 会拒绝执行,而不是产出半成品 +- `FORCE=1`(即 `make apply FORCE=1`)是两条 apply 护栏的逃生口,意思是「丢掉 `work/`,按现在的 `patches/` 重放」 +- `make apply` 的 `git clean` 显式保留 `work/target`,否则每次打补丁都要付一次冷编译的代价 + +## 补丁格式 + +`rebuild` 用的是: + +``` +git format-patch --no-stat -N --zero-commit --full-index --no-signature +``` + +- `--zero-commit` —— 否则每次 rebase 后每个补丁的 `From ` 行都会变,`git diff` 里全是噪音 +- `--full-index` —— 给 `git am --3way` 留下按 blob 哈希回退的余地,上游漂移时更容易自动合上 + +## 构建依赖 + +`make setup` 会处理,这里说明它在做什么: + +- **Rust 1.94.0** —— 由上游 `rust-toolchain.toml` 钉住,rustup 自动装 +- **protoc** —— 只有 `xai-grok-tools-api` 需要(编译 `proto/grok-tools.proto`)。上游的解析顺序是 + `$PROTOC` → 向上查找 `bin/protoc` → `PATH`。`bin/protoc` 是个 + [DotSlash](https://dotslash-cli.com) wrapper,会按需下载 protoc 29.3;所以 `setup.sh` + 装 dotslash,失败则回退到系统 `protobuf-compiler` + +两个 git 依赖(`our-forks/async-openai`、`helix-editor/nucleo`)都是公开可访问的。 + +在这台机器上(4 核 / 7 GB),`xai-grok-pager-bin` 冷编译 debug 大约 11 分钟,`work/target` 会长到约 24 GB,二进制大约 612 MB。`cargo check` 和 `cargo build` 各有一份缓存,所以 `make build` 之后再 `make check` 并不快(冷跑大约 6 分钟)。不到真的缺盘,别跑 `make clean` / `make distclean`。迭代时尽量用单 crate(`build.sh` 总会带 `-p`)。 + +## 上游树里该改哪 + +`work/` 里大约 91 个 workspace member。常动的几个: + +| Crate | 角色 | +|-------|------| +| `xai-grok-pager-bin` | 组合根,产出 `xai-grok-pager` 二进制(发行名 `grok`) | +| `xai-grok-pager` | TUI:scrollback、prompt、modal、渲染;用户手册也在这里 | +| `xai-grok-shell` | Agent runtime + leader / stdio / headless 入口 | +| `xai-grok-tools` | 工具实现(terminal、文件编辑、搜索……) | +| `xai-grok-workspace` | 宿主文件系统、VCS、执行、checkpoint | + +从上游继承的约束: + +- **根目录 `Cargo.toml` 是生成的,当只读。** 改各 crate 自己的 `Cargo.toml`。它还带着 `[patch.crates-io]` 里对 `our-forks/async-openai` 的 git pin。 +- **永远指定 crate(`-p `)。** 全 workspace 编译慢到不现实;`build.sh` 会强制带 `-p`。 +- 动上游代码时,先看它已有的测试还成不成立。 + +## 示例补丁 + +`patches/0001` 和 `0002` 的提交信息标了 `[EXAMPLE PATCH]`,演示「改上游 Rust」和「新增文件」两种补丁,不需要就可以删。`0001` 同时也是 fork 品牌标记(`--version` 打出 `[newgrok]`),删之前想清楚。 + +删补丁文件要带 `FORCE=1`——`work/` 里还留着对应提交,删完之后 `patches/` 比 `work/` 少,安全检查会拦下来(它无法区分「你故意删的」和「你忘了 rebuild」): + +```sh +rm patches/0001-*.patch patches/0002-*.patch +make apply FORCE=1 +``` + +## 许可 + +上游代码为 Apache-2.0(见 `work/LICENSE`)。`patches/` 里的改动同样按 Apache-2.0 分发。 +本仓库是非官方 fork,与 xAI 无关。 diff --git a/README.md b/README.md index edb1979..0531ec5 100644 --- a/README.md +++ b/README.md @@ -1,189 +1,66 @@ -# newgrok — `xai-org/grok-build` 的 patch 化开发工作流 +# newgrok -以 CraftBukkit / Spigot 的模型,在**不接受外部 PR 的上游**之上维护本地改动。 +[`xai-org/grok-build`](https://github.com/xai-org/grok-build) 的非官方 fork。上游是 xAI 的终端 coding agent(全屏 TUI `grok`);本仓库在其上叠了一组可回放的补丁。 -## 为什么是 patch,而不是 fork 后直接改 +本仓库与 xAI 无关。编出来的二进制会标成 `grok [newgrok] `,不会和官方发行版搞混。 -上游 [`xai-org/grok-build`](https://github.com/xai-org/grok-build) 有两个决定性特征: +改代码、跟进上游、补丁怎么排,见 [CONTRIBUTING.md](CONTRIBUTING.md)。 -1. 它是 xAI 内部 monorepo 的**周期性单向导出**。历史是一串线性的 `Synced from monorepo` - 压扁提交,根目录 `SOURCE_REV` 记录对应的内部 commit。上游随时会把整棵树重新投放一次。 -2. `CONTRIBUTING.md` 明确写着**不接受任何外部 PR 或补丁**。 +## 这个 fork 多了什么 -也就是说:改动推不回去,而上游会不断整体覆盖。直接在 fork 上改,每次同步都要打一场 -merge 混战,而且没人说得清「我们到底改了什么」。 +### `/rc` — 当前会话镜像到 grok-glance -这正是 CraftBukkit 面对 Mojang 的处境。解法也一样——**把改动本身作为版本库里的唯一真相**: - -> `patches/` 是源码,`work/` 是编译产物。 - -## 目录结构 +`/rc` 把你正在用的会话经 ACP 镜像到 grok-glance 服务器。终端里的 TUI 完全不受影响:不重启、不切 headless、不把会话交出去。glance 变成同一会话的第二个视图,两端都能跟 turn 流、发 prompt、打断正在跑的 turn,以及回答权限请求、提问和 plan 批准。**两边都能答;谁先答谁赢**,另一边的对话框会自己关掉。 ``` -newgrok/ ← 你提交的仓库 -├── patches/ ★ 唯一真相:0001-*.patch,一个补丁一件事 -├── scripts/ 工作流脚本 -├── upstream.rev 钉住的上游 commit -├── Makefile -├── upstream/ 派生:上游的纯净 clone (.gitignore) -└── work/ 派生:upstream@rev + patches(.gitignore) +/rc 开关 +/rc on 连接(start / connect) +/rc off 断开(stop / disconnect) +/rc status 看链路状态 ``` -对照 Spigot: +在 `~/.grok/config.toml` 里配 glance: -| 这里 | Spigot / BuildTools | -|------|---------------------| -| `upstream/` | CraftBukkit 的 clone(纯净上游) | -| `work/` | 打完补丁的工作目录 | -| `patches/` | `CraftBukkit-Patches/` | -| `scripts/apply-patches.sh` | `applyPatches.sh` | -| `scripts/rebuild-patches.sh` | `rebuildPatches.sh` | -| `upstream.rev` | `versions/*.json` 里的版本钉 | +```toml +[remote_control] +url = "wss://glance.example.com/api/acp/agent" +api_key = "glance_sk_..." +auto_start = false # 启动就连,不用先敲 /rc +``` -`upstream/` 和 `work/` 都**不进 git**——它们任何时候都能从 `upstream.rev` + `patches/` -一字不差地重建。 +`GROK_RC_URL` / `GROK_RC_API_KEY` / `GROK_RC_AUTO_START` 会覆盖文件里的值。API key 更适合走环境变量,不要把 bearer token 写进明文配置。服务端用 `glance apikey add ` 签发。 -## 快速开始 +链路只活在当前会话里,退出即断,什么都不落盘。glance 连不上会退避重试,`/rc status` 会说明原因——对端挂了、慢了或中途被杀,都不会拖垮本地会话。远端 cancel 记成 `Client("glance")`(和按 Esc 同一类),远端 prompt 带 `clientIdentifier`,会话日志里分得清是谁做的。 + +### 专用工具优先 + +每个主会话用户 turn 会写入一份**只写一次、之后不再改**的 dispatch checklist,让模型在动手前先考虑 `explore` / search / read / plan / `deep-research`,而不是自己开一轮宽搜索,或拿 bash 去 `cat` / `grep` / `find` / `ls`。checklist 按当前工具集渲染进用户消息,前缀对 prompt cache 是稳定的。 + +如果 bash 还是做了有专用工具的文件操作,工具结果后面会跟一条 **nudge**(命令已经执行过了,只提醒下次换工具)。子 agent 同样会收到这条 nudge。 + +两段 reminder 在送给 compaction 摘要模型之前会被剥掉,避免把 agent 内部指令写进会话摘要。 + +### 会话级 `prompt_cache_key` + +主 turn 会显式钉上会话级的 `prompt_cache_key`(就是 conversation id)。Responses 路径以前靠 `x_grok_conv_id` 回退也能走到同一把钥匙;钉死之后,Chat Completions 的 `user` 字段、以及 recap / `/btw` 这类复用父会话前缀的旁路调用,都会共用同一个 key。 + +刻意不按 agent 加后缀:那样会把旁路调用设计上要蹭的前缀缓存拆开。 + +## 从源码构建 ```sh -make setup # 装 Rust 工具链(1.94.0,由 rust-toolchain.toml 钉住)+ dotslash/protoc +make setup # 装 Rust 1.94.0 + dotslash/protoc(幂等,可重跑) make apply # 用 upstream.rev + patches/ 生成 work/ -make build # 编译 work/target/debug/xai-grok-pager -make run # 直接启动 TUI +make build # 编出 work/target/debug/xai-grok-pager +make run # 编完直接开 TUI ``` -`make setup` 是幂等的,可以随时重跑。 +`make setup` 会把工具链装到 `~/.cargo`,脚本自己把它加进 `PATH`。若要在交互 shell 里直接用 `cargo`:fish 执行 `source ~/.cargo/env.fish`,bash 执行 `source ~/.cargo/env`。 -> Rust 装在 `~/.cargo`,脚本会自己把它加进 `PATH`。若要在交互 shell 里直接用 `cargo`: -> fish 执行 `source ~/.cargo/env.fish`,bash 执行 `source ~/.cargo/env`。 +第一次启动会打开浏览器做官方登录,见上游的 [authentication guide](https://github.com/xai-org/grok-build/blob/main/crates/codegen/xai-grok-pager/docs/user-guide/02-authentication.md)。打好补丁之后,更完整的用户手册在 `work/crates/codegen/xai-grok-pager/docs/user-guide/`(`/rc` 写在 slash commands 那一页)。 -## 日常循环 - -改代码永远在 `work/` 里,**一个提交 = 一个补丁**: - -```sh -make apply # 拿到干净的、打好补丁的树 -$EDITOR work/crates/codegen/.../foo.rs # 改 -cd work && git add -A && git commit -m '...' # 提交(提交信息就是补丁标题) -cd .. && make rebuild # 写回 patches/ -git add patches/ && git commit -m '...' # 提交到 fork 仓库 -``` - -**`make rebuild` 是唯一的出口。** 没跑过它的改动,下次 `make apply` 就没了。 - -修改已有的补丁,用普通的 git 手段就行——`work/` 是个正常的 git 仓库: - -```sh -cd work -git rebase -i base # 改写、合并、重排、删除任意补丁 -cd .. && make rebuild # patches/ 会整体重新生成 -``` - -随时看当前状态: - -```sh -make status -``` - -## 跟进上游同步 - -```sh -make update-dry # 只看会拉进来什么,不动任何东西 -make update # 重新钉 upstream.rev,并把补丁前移到新基线 -``` - -`make update` 会: - -1. `git fetch` 上游,列出 `upstream.rev..origin/main` 之间的新提交和 `SOURCE_REV` 变化 -2. 把 `upstream.rev` 更新为最新 -3. 在新基线上重放 `patches/` -4. 干净通过则自动 `make rebuild`,让补丁上下文对齐新代码 - -冲突时脚本会停下并给出解决步骤——和处理 rebase 冲突完全一样: - -```sh -cd work -git status # 看冲突文件 -$EDITOR <冲突文件> # 消掉冲突标记 -git add -A -git am --continue # 或 git am --skip / git am --abort -cd .. && make rebuild # 把前移后的结果写回去 -``` - -补丁数量多的时候,冲突是这个模型的固有成本,也是它的价值所在:冲突精确地指出上游改动 -撞上了你的哪一处改动,而不是让它悄悄消失。 - -## 全部命令 - -| 命令 | 作用 | -|------|------| -| `make setup` | 安装工具链(rustup / dotslash / protoc) | -| `make apply` | `upstream.rev + patches/ → work/`;加 `FORCE=1` 丢弃 `work/` 里的未提交改动 | -| `make rebuild` | `work/` 中 `base` 之上的提交 → `patches/` | -| `make update` | 跟进最新上游同步并前移补丁 | -| `make update-dry` | 预览上游差异,不做修改 | -| `make build` / `make release` | 编译 debug / release 二进制 | -| `make check` / `make clippy` / `make fmt` | 快速类型检查 / lint / 格式化 | -| `make test` | 跑测试(`PKG=` 指定 crate) | -| `make run` | 编译并启动 TUI | -| `make status` | 显示当前钉住的 rev、补丁数、`work/` 状态 | -| `make clean` | 删 `work/target` | -| `make distclean` | 删 `work/` 和 `upstream/`(不动 `patches/`) | - -`make build` 之后的额外参数会透传给 cargo,例如 `./scripts/build.sh --release --locked`。 - -## 安全网 - -脚本刻意不会静默吞掉工作成果: - -- `work/` 有**未提交改动**时,`make apply` 拒绝执行(提示先 commit + rebuild,或 `FORCE=1`) -- `work/` 的提交数和 `patches/` 的文件数**对不上**时同样拒绝——这基本意味着你忘了 `make rebuild` -- `git am` 进行到一半时,`make rebuild` / `make build` 会拒绝执行,而不是产出半成品 -- `make apply` 的 `git clean` 显式保留 `work/target`,否则每次打补丁都要付一次冷编译的代价 - -## 补丁格式 - -`rebuild` 用的是: - -``` -git format-patch --no-stat -N --zero-commit --full-index --no-signature -``` - -- `--zero-commit` —— 否则每次 rebase 后每个补丁的 `From ` 行都会变,`git diff` 里全是噪音 -- `--full-index` —— 给 `git am --3way` 留下按 blob 哈希回退的余地,上游漂移时更容易自动合上 - -## 构建依赖 - -`make setup` 会处理,这里说明它在做什么: - -- **Rust 1.94.0** —— 由上游 `rust-toolchain.toml` 钉住,rustup 自动装 -- **protoc** —— 只有 `xai-grok-tools-api` 需要(编译 `proto/grok-tools.proto`)。上游的解析顺序是 - `$PROTOC` → 向上查找 `bin/protoc` → `PATH`。`bin/protoc` 是个 - [DotSlash](https://dotslash-cli.com) wrapper,会按需下载 protoc 29.3;所以 `setup.sh` - 装 dotslash,失败则回退到系统 `protobuf-compiler` - -两个 git 依赖(`our-forks/async-openai`、`helix-editor/nucleo`)都是公开可访问的。 - -## 示例补丁 - -`patches/` 里预置了两个补丁,提交信息都标了 `[EXAMPLE PATCH]`: - -1. **`0001`** 改上游 Rust 源码——给 `xai-grok-version` 加 `FORK_NAME` / `fork_tag()`, - 并接进 pager 的版本输出,于是 `--version` 显示 `grok [newgrok] 1.0.3 (…)`。 - 标记插在 `grok ` 之后而不是追加到末尾,因为 `main.rs` 的 - `version_output_writer_preserves_channel_aware_contract` 断言该行仍以 channel label - (或 `)`)结尾——**改上游代码时顺手确认它的测试仍然成立**,这个补丁本身就是个例子。 -2. **`0002`** 新增文件——根目录 `FORK.md`。 - -不需要就直接删。注意要带 `FORCE=1`——`work/` 里还留着这两个补丁的提交,而删掉补丁文件后 -`patches/` 比 `work/` 少,安全检查会拦下来(它无法区分「你故意删的」和「你忘了 rebuild」): - -```sh -rm patches/0001-*.patch patches/0002-*.patch -make apply FORCE=1 -``` +日常开发循环、跟进上游、以及 `make` 全表见 [CONTRIBUTING.md](CONTRIBUTING.md)。 ## 许可 上游代码为 Apache-2.0(见 `work/LICENSE`)。`patches/` 里的改动同样按 Apache-2.0 分发。 -本仓库是非官方 fork,与 xAI 无关。