|
1 | | -# zexi/dev Fork — What's Different from Upstream |
| 1 | +# zexi/dev Fork — 与上游的差异说明 |
2 | 2 |
|
3 | | -This document records every intentional divergence from `anomalyco/opencode` so |
4 | | -that after each weekly rebase the goals are clear and regressions can be caught |
5 | | -quickly. |
| 3 | +本文档记录了相对于 `anomalyco/opencode` 的每一处有意改动,以便每次 rebase 后能快速明确目标、发现回归。 |
6 | 4 |
|
7 | 5 | --- |
8 | 6 |
|
9 | | -## How to rebase |
| 7 | +## 如何 rebase |
10 | 8 |
|
11 | | -The workflow `sync-upstream.yml` runs every Friday and opens a PR rebasing |
12 | | -`zexi/dev` onto upstream automatically. |
| 9 | +`sync-upstream.yml` 工作流每周五自动运行,将 `zexi/dev` rebase 到上游并开 PR。 |
13 | 10 |
|
14 | | -Manual rebase onto a clean branch: |
| 11 | +手动整理到新分支的方法: |
15 | 12 |
|
16 | 13 | ```sh |
17 | 14 | git fetch upstream |
18 | 15 | git checkout -b zexi/dev-clean upstream/dev |
19 | 16 | git merge --squash zexi/dev |
20 | | -# resolve conflicts, then commit in logical groups (see commits below) |
| 17 | +# 解决冲突,然后按功能分组提交(参考下方提交说明) |
21 | 18 | ``` |
22 | 19 |
|
23 | 20 | --- |
24 | 21 |
|
25 | | -## Fork commits (relative to upstream/dev) |
| 22 | +## Fork 提交列表(相对于 upstream/dev) |
26 | 23 |
|
27 | | -### 1 · Release workflows — `dfa2df240` |
| 24 | +### 1 · 自定义发布工作流 — `dfa2df240` |
28 | 25 |
|
29 | | -**Files:** `.github/workflows/zexi-electron.yml`, `.github/workflows/sync-upstream.yml` |
| 26 | +**涉及文件:** `.github/workflows/zexi-electron.yml`、`.github/workflows/sync-upstream.yml` |
30 | 27 |
|
31 | | -Custom CI that builds and releases signed macOS (arm64 + notarization) and |
32 | | -Windows (x64 + Azure trusted signing) Electron packages on every push to |
33 | | -`zexi/dev`. Releases are tagged `zexi-electron-<stamp>-<sha>`; old releases are |
34 | | -pruned to keep the 3 most recent. |
| 28 | +每次推送到 `zexi/dev` 时,自动构建并发布签名的 macOS(arm64 + 公证)和 Windows(x64 + Azure 可信签名)Electron 安装包。发布标签格式为 `zexi-electron-<时间戳>-<sha>`,只保留最近 3 个版本。 |
35 | 29 |
|
36 | | -The sync workflow disables all upstream workflows except these two. |
| 30 | +同步工作流会禁用上游所有其他工作流,只保留这两个。 |
37 | 31 |
|
38 | | -**Rebase risk:** Low — touches only `.github/`. Conflicts only if upstream |
39 | | -renames its own workflow files. |
| 32 | +**Rebase 风险:** 低 — 只涉及 `.github/`,仅当上游重命名自己的工作流文件时才会冲突。 |
40 | 33 |
|
41 | 34 | --- |
42 | 35 |
|
43 | | -### 2 · Configure desktop local server access and sync opened projects — `bd776bfa3` |
| 36 | +### 2 · 桌面本地服务器配置 + 已开启项目同步 — `bd776bfa3` |
44 | 37 |
|
45 | | -**Files:** `packages/app/src/components/dialog-select-server.tsx`, `packages/app/src/components/server/server-row.tsx`, `packages/app/src/components/status-popover*.tsx`, `packages/desktop/src/main/{index,ipc,server,sidecar,constants}.ts`, `packages/desktop/src/preload/`, `packages/opencode/src/config/{server,projects}.ts`, `packages/opencode/src/server/shared/opened-projects{,.sql}.ts`, `packages/opencode/src/server/routes/instance/httpapi/{groups,handlers}/global.ts`, `packages/opencode/migration/20260511170500_opened_projects_db/migration.sql`, `packages/sdk/js/src/v2/gen/`, i18n files |
| 38 | +**涉及文件:** `packages/app/src/components/dialog-select-server.tsx`、`server-row.tsx`、`status-popover*.tsx`、`packages/desktop/src/main/{index,ipc,server,sidecar,constants}.ts`、`packages/desktop/src/preload/`、`packages/opencode/src/config/{server,projects}.ts`、`packages/opencode/src/server/shared/opened-projects{,.sql}.ts`、`handlers/global.ts`、DB 迁移文件、SDK 生成文件、i18n 文件 |
46 | 39 |
|
47 | | -**What it does:** |
| 40 | +**功能说明:** |
48 | 41 |
|
49 | | -**a) Local server config UI** |
50 | | -Adds UI for users to configure the local Electron-embedded server's hostname, |
51 | | -port, username, and password from within the app. Config is persisted in |
52 | | -Electron's store under `localServerConfig`. Key IPC channels: |
53 | | -`get-local-server-config`, `set-local-server-config`. |
| 42 | +**a) 本地服务器配置 UI** |
| 43 | +在应用内新增 UI,允许用户配置 Electron 内嵌服务器的主机名、端口、用户名和密码。配置持久化到 Electron store 的 `localServerConfig` 字段下。新增 IPC 通道:`get-local-server-config`、`set-local-server-config`。 |
54 | 44 |
|
55 | | -**b) Opened projects sync** |
56 | | -Moves opened-project state into a single `OpenedProjectsContext` (Solid.js) |
57 | | -backed by a server-side SQLite table (`OpenedProjectTable`). All windows/tabs |
58 | | -subscribe to the `project.opened.updated` SSE event and stay in sync without |
59 | | -polling. Server-side API routes under `/global` handle list, open, close, and |
60 | | -reorder. |
| 45 | +**b) 已开启项目同步** |
| 46 | +将已开启项目的状态统一到 `OpenedProjectsContext`(Solid.js),由服务端 SQLite 表(`OpenedProjectTable`)持久化。所有窗口/标签页订阅 `project.opened.updated` SSE 事件,无需轮询即可实时同步。服务端在 `/global` 下提供列表、打开、关闭、重排序接口。 |
61 | 47 |
|
62 | | -**Rebase risk:** High — touches many app-layer files. Most likely conflict |
63 | | -points: `status-popover.tsx`, `dialog-select-server.tsx`, `handlers/global.ts`, |
64 | | -and the SDK generated files. |
| 48 | +**Rebase 风险:** 高 — 涉及大量 app 层文件。最常见冲突点:`status-popover.tsx`、`dialog-select-server.tsx`、`handlers/global.ts` 以及 SDK 生成文件。 |
65 | 49 |
|
66 | 50 | --- |
67 | 51 |
|
68 | | -### 3 · Embed and serve web UI from sidecar — `54660edfa` |
| 52 | +### 3 · 将 Web UI 内嵌到 sidecar 并提供服务 — `54660edfa` |
69 | 53 |
|
70 | | -**Files:** `packages/core/src/flag/flag.ts`, `packages/desktop/electron-builder.config.ts`, `packages/desktop/electron.vite.config.ts`, `packages/desktop/scripts/prebuild.ts`, `packages/opencode/src/config/server.ts`, `packages/opencode/src/server/shared/{public-ui,ui}.ts` |
| 54 | +**涉及文件:** `packages/core/src/flag/flag.ts`、`packages/desktop/electron-builder.config.ts`、`electron.vite.config.ts`、`prebuild.ts`、`packages/opencode/src/config/server.ts`、`packages/opencode/src/server/shared/{public-ui,ui}.ts` |
71 | 55 |
|
72 | | -**What it does:** |
73 | | -Bundles the web SPA into the sidecar binary at build time via a generated |
74 | | -module (`opencode-web-ui.gen.ts`). The sidecar serves it directly, with a |
75 | | -priority chain: |
| 56 | +**功能说明:** |
| 57 | +构建时通过生成模块(`opencode-web-ui.gen.ts`)将 Web SPA 打包进 sidecar 二进制。sidecar 直接提供服务,优先级如下: |
76 | 58 |
|
77 | | -1. Embedded bundle (`opencode-web-ui.gen.ts`) — production |
78 | | -2. Local directory (`OPENCODE_DEV_UI_DIR`) — dev mode, set automatically in |
79 | | - Electron dev to `packages/app/dist` |
80 | | -3. Proxy to `https://app.opencode.ai` (override with `OPENCODE_DEV_UI_URL`) — |
81 | | - fallback |
| 59 | +1. 内嵌 bundle(`opencode-web-ui.gen.ts`)— 生产环境 |
| 60 | +2. 本地目录(`OPENCODE_DEV_UI_DIR`)— 开发模式,Electron dev 时自动指向 `packages/app/dist` |
| 61 | +3. 代理到 `https://app.opencode.ai`(可用 `OPENCODE_DEV_UI_URL` 覆盖)— 兜底 |
82 | 62 |
|
83 | | -Static assets (HTML shell, JS/CSS bundles, icons, favicons) under |
84 | | -`isPublicUIPath()` are served without authentication so a remote browser can |
85 | | -load the UI shell before entering credentials. |
| 63 | +`isPublicUIPath()` 匹配的静态资源(HTML shell、JS/CSS bundle、图标、favicon)无需认证即可访问,让远程浏览器在输入密码前就能加载页面骨架。 |
86 | 64 |
|
87 | | -**Prerequisite for dev:** build `packages/app` first: |
| 65 | +**开发模式前提:** 需先构建 `packages/app`: |
88 | 66 | ```sh |
89 | 67 | cd packages/app && bun run build |
90 | 68 | ``` |
91 | 69 |
|
92 | | -**Rebase risk:** Medium — `ui.ts` conflicts if upstream restructures |
93 | | -`serveUIEffect`. `flag.ts` conflicts if upstream adds flags at the same |
94 | | -location. |
| 70 | +**Rebase 风险:** 中 — `ui.ts` 在上游重构 `serveUIEffect` 时会冲突;`flag.ts` 在上游同位置新增 flag 时会冲突。 |
95 | 71 |
|
96 | 72 | --- |
97 | 73 |
|
98 | | -### 4 · Serve SPA at any subpath without auth, display auth-required page on 401 — `7bac50fd8` |
| 74 | +### 4 · SPA 任意子路径免认证访问 + 401 显示认证提示页 — `7bac50fd8` |
99 | 75 |
|
100 | | -**Files:** `packages/opencode/src/server/routes/instance/httpapi/middleware/authorization.ts`, `packages/opencode/src/server/routes/instance/httpapi/server.ts`, `packages/app/src/pages/error.tsx`, `packages/app/src/pages/session.tsx`, `packages/app/src/i18n/{en,zh,zht}.ts`, `packages/opencode/test/server/httpapi-ui.test.ts` |
| 76 | +**涉及文件:** `packages/opencode/src/server/routes/instance/httpapi/middleware/authorization.ts`、`server.ts`、`packages/app/src/pages/error.tsx`、`session.tsx`、`packages/app/src/i18n/{en,zh,zht}.ts`、`packages/opencode/test/server/httpapi-ui.test.ts` |
101 | 77 |
|
102 | | -**What it does:** |
| 78 | +**功能说明:** |
103 | 79 |
|
104 | | -**a) SPA catch-all serves without auth** |
105 | | -The `/*` route loads unconditionally (no credentials check) so the browser can |
106 | | -bootstrap the SPA shell at any subpath. API routes under `/global/*` and |
107 | | -instance routes remain fully protected. |
| 80 | +**a) SPA catch-all 免认证加载** |
| 81 | +`/*` 路由无条件响应,浏览器可在任意子路径下引导加载 SPA shell,无需服务端 session cookie。`/global/*` 和 instance 路由仍受完整保护。 |
108 | 82 |
|
109 | | -**b) Bearer auth scheme** |
110 | | -`WWW-Authenticate` is `Bearer realm="Secure Area"` instead of `Basic`. This |
111 | | -suppresses the browser's native credential popup on 401 while keeping the server |
112 | | -fully functional — it still reads and validates `Authorization: Basic` headers |
113 | | -sent by the desktop app. |
| 83 | +**b) Bearer 认证方案** |
| 84 | +`WWW-Authenticate` 响应头改为 `Bearer realm="Secure Area"`(原为 `Basic`),避免浏览器在 401 时弹出原生凭证对话框,同时保持服务端正常读取桌面端发来的 `Authorization: Basic` 头。 |
114 | 85 |
|
115 | | -**c) Auth-required error page** |
116 | | -On 401 the SPA shows a localized "Authentication required" page with a manual |
117 | | -"Go to home" button, breaking the infinite redirect loop that previously occurred |
118 | | -when the app auto-navigated from `/` to the last opened project. |
| 86 | +**c) 认证提示错误页** |
| 87 | +401 时 SPA 展示本地化的"需要身份验证"页面,附带手动"返回首页"按钮,彻底消除了之前因 app 自动从 `/` 导航到最近打开项目而引发的无限重定向循环。 |
119 | 88 |
|
120 | | -**d) /global/health probe** |
121 | | -Allows unauthenticated health probes so the SPA can discover the server before |
122 | | -the user enters credentials. If credentials are supplied but wrong, returns 401. |
| 89 | +**d) /global/health 探针** |
| 90 | +允许未认证的健康检查,让 SPA 在用户输入密码前就能发现服务器。若凭证已提供但有误,仍返回 401。 |
123 | 91 |
|
124 | | -**Rebase risk:** Low for `authorization.ts` (small, self-contained). Medium for |
125 | | -`error.tsx` if upstream changes the error page structure. |
| 92 | +**Rebase 风险:** `authorization.ts` 低(小而独立);`error.tsx` 中(上游若改动错误页结构会冲突)。 |
126 | 93 |
|
127 | 94 | --- |
128 | 95 |
|
129 | | -### 5 · Cap diff size to prevent SQLite/V8 crash on large files — `c37061538` |
| 96 | +### 5 · 限制 diff 大小,防止大文件导致 SQLite/V8 崩溃 — `c37061538` |
130 | 97 |
|
131 | | -**Files:** `packages/opencode/src/tool/apply_patch.ts`, `packages/opencode/src/tool/edit.ts` |
| 98 | +**涉及文件:** `packages/opencode/src/tool/apply_patch.ts`、`packages/opencode/src/tool/edit.ts` |
132 | 99 |
|
133 | | -Truncates patch/diff strings before storing them to prevent SQLite blob limits |
134 | | -and V8 string size limits from crashing the process on very large file edits. |
| 100 | +存储前截断 patch/diff 字符串,防止超大 diff(如删除 50MB+ 二进制文件时产生的 ~380MB diff 字符串)触发 V8 内存上限,导致 sidecar 进程 SIGTRAP 崩溃。 |
135 | 101 |
|
136 | | -**Rebase risk:** Low — isolated tool change. |
| 102 | +对应上游 issue:https://github.com/anomalyco/opencode/issues/27657 |
| 103 | + |
| 104 | +**Rebase 风险:** 低 — 改动独立,不影响其他模块。 |
137 | 105 |
|
138 | 106 | --- |
139 | 107 |
|
140 | | -## Behavioral invariants |
| 108 | +## 行为不变量 |
141 | 109 |
|
142 | | -After every rebase, verify these before merging/releasing: |
| 110 | +每次 rebase 后,发布前请验证以下场景: |
143 | 111 |
|
144 | | -| # | Scenario | Expected | |
145 | | -|---|----------|----------| |
146 | | -| 1 | Remote browser opens `http://<host>:4096/` (no credentials) | 200, loads UI shell (no browser auth popup) | |
147 | | -| 2 | Remote browser fetches `/favicon-96x96-v3.png` | 200 (no auth required) | |
148 | | -| 3 | SPA fetches `/global/config` without credentials | 401 with `WWW-Authenticate: Bearer …` (not `Basic`) | |
149 | | -| 4 | Navigate to `http://<host>:4096/<project>/session/<id>` without credentials | Loads SPA shell, shows auth-required page, no infinite redirect | |
150 | | -| 5 | Desktop app bottom-left shows server icon with current server URL | Visible, clickable | |
151 | | -| 6 | Clicking server icon opens server config dialog | Dialog appears, shows local-server config panel in desktop mode | |
152 | | -| 7 | Setting local server credentials and restarting → credentials persist | Config survives restart | |
153 | | -| 8 | Opening a project in one browser tab → other tabs update | `project.opened.updated` event triggers sync | |
154 | | -| 9 | Dev mode: remote browser sees fork UI (server icon in bottom left) | Not the upstream `app.opencode.ai` version | |
| 112 | +| # | 场景 | 预期结果 | |
| 113 | +|---|------|----------| |
| 114 | +| 1 | 远程浏览器访问 `http://<host>:4096/`(无凭证) | 200,加载 UI shell,不弹出认证对话框 | |
| 115 | +| 2 | 远程浏览器访问 `/favicon-96x96-v3.png` | 200,不需要认证 | |
| 116 | +| 3 | SPA 无凭证访问 `/global/config` | 401,`WWW-Authenticate: Bearer …`(不是 `Basic`) | |
| 117 | +| 4 | 无凭证访问 `http://<host>:4096/<project>/session/<id>` | 加载 SPA shell,显示认证提示页,不出现无限重定向 | |
| 118 | +| 5 | 桌面应用左下角显示服务器图标及当前服务器地址 | 可见、可点击 | |
| 119 | +| 6 | 点击服务器图标打开配置对话框 | 桌面模式下显示本地服务器配置面板 | |
| 120 | +| 7 | 设置本地服务器凭证后重启,凭证保持 | 配置在重启后仍存在 | |
| 121 | +| 8 | 在一个浏览器标签页中打开项目,其他标签页同步更新 | `project.opened.updated` 事件触发同步 | |
| 122 | +| 9 | 开发模式下,远程浏览器看到 fork 版 UI(左下角有服务器图标) | 不是上游 `app.opencode.ai` 的版本 | |
155 | 123 |
|
156 | | -Quick automated check (run against a live local server on port 4096): |
| 124 | +快速自动化检查(在本地 4096 端口服务运行时执行): |
157 | 125 |
|
158 | 126 | ```sh |
159 | | -# Root → 200 |
| 127 | +# 根路径 → 200 |
160 | 128 | curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:4096/ |
161 | 129 | # Favicon → 200 |
162 | 130 | curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:4096/favicon-96x96-v3.png |
163 | | -# API → 401 Bearer (not Basic) |
| 131 | +# API → 401 Bearer(不是 Basic) |
164 | 132 | curl -sI http://127.0.0.1:4096/global/config | grep -i www-authenticate |
165 | 133 | ``` |
166 | 134 |
|
167 | | -Expected output: `200`, `200`, `www-authenticate: Bearer realm="Secure Area"`. |
| 135 | +预期输出:`200`、`200`、`www-authenticate: Bearer realm="Secure Area"`。 |
0 commit comments