farming-code 2.2.6 → 2.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +102 -204
- package/README.zh_cn.md +102 -246
- package/backend/acp-runtime.js +93 -1
- package/backend/agent-manager.js +81 -20
- package/backend/cli-agents.js +1 -1
- package/backend/farming-app-cli.js +52 -1
- package/backend/farming-session-store.js +1 -0
- package/backend/npm-update-helper.js +4 -0
- package/backend/server.js +5 -1
- package/backend/update-service.js +14 -2
- package/dist/assets/App-8dYAM6ql.js +124 -0
- package/dist/assets/{FileEditorPane-BQLocXOp.js → FileEditorPane-RWiFD2cq.js} +1 -1
- package/dist/assets/{IconGlyphs-RkMAdBXF.js → IconGlyphs-DfL0EBnj.js} +1 -1
- package/dist/assets/{ProjectFilesSection-lS1azbgj.js → ProjectFilesSection-Q4PDsWmM.js} +1 -1
- package/dist/assets/{ReviewPage-DgnmgU2T.js → ReviewPage-BaXu1ZdX.js} +1 -1
- package/dist/assets/{code-dark-Cjb1QGhu.css → code-dark-CDkOQAtK.css} +1 -1
- package/dist/assets/{index-BpHPeJf0.js → index-BrbljRqn.js} +2 -2
- package/dist/assets/main-D073SnW4.css +1 -0
- package/dist/index.html +1 -1
- package/frontend/skins/crt/app.js +58 -2
- package/frontend/skins/crt/index.html +1 -1
- package/frontend/skins/crt/styles/billing.css +6 -0
- package/frontend/skins/crt/styles/effects.css +11 -3
- package/package.json +1 -1
- package/dist/assets/App-Dza5yEDe.js +0 -124
- package/dist/assets/main-DTJwH51M.css +0 -1
package/README.zh_cn.md
CHANGED
|
@@ -9,321 +9,177 @@
|
|
|
9
9
|

|
|
10
10
|

|
|
11
11
|
|
|
12
|
-
Farming
|
|
12
|
+
Farming 是一个运行在开发机上的浏览器 AI Coding Agent 工作台。它把多个实时 Agent、结构化对话、真实终端、项目文件、Review、历史记录和运行时控制放在一起,同时代码仓库和 Agent 进程仍然留在开发机上。
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
把 Farming 运行在你平时使用 Coding CLI 的机器旁边,就可以从电脑或手机回到同一批工作。关闭浏览器不会停止 Agent;Farming Server 重启时,独立的原生 PTY Host 也可以保留正在运行的终端会话。
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
## 两套界面,同一套运行时
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
Farming 2 在同一批 Agent 和 Session 上提供两套完整的浏览器界面。
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
### Farming Code
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
默认工作台,适合阅读对话、介入任务、编辑文件,以及持续 Review 同一个演进中的修改。
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+

|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
### Farming CRT
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
键盘优先的控制室,适合同时观察多个 Agent、打开结构化 Chat 或原生 Terminal、搜索历史和查看实时用量遥测。
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+

|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
Farming 2 是本仓库最近最大的一次产品变化。它把 Farming 从一个较轻的复古 agent monitor,推进成了更实用的远程 coding workbench,也为长期的 Main Agent 注意力管理目标补齐底座:
|
|
41
|
-
|
|
42
|
-
- 在浏览器里启动和管理 Codex、Claude、OpenCode、Qoder、bash 和 zsh session;
|
|
43
|
-
- 发现并恢复 Codex、Claude、OpenCode 和 Qoder 的本地历史 session,再重新接入 Farming 托管的实时 terminal;
|
|
44
|
-
- Codex / Claude 启动选项按 provider 收敛,不展示当前 agent 无法表达的控件;
|
|
45
|
-
- 在一个工作区里组织 project-scoped agents、pinned/unread sessions、Search、History 和 active terminals;
|
|
46
|
-
- 提供 Project Files:Open Editors、文件树、文件搜索、Monaco 编辑、Markdown/图片预览、git changes、git diff、行级 git blame;
|
|
47
|
-
- terminal 输出里的 `path:line` 可直接打开文件,`http(s)` URL 可直接在新标签页打开;
|
|
48
|
-
- Codex / Claude Composer 暴露底层运行时能表达的启动权限、模型和速度 profile;App Server Codex 会直接更新当前 thread 的权限,terminal-owned session 切换权限时才重启 CLI:已有 provider Session ID 时 resume,还没有可 resume 的 ID 时启动新会话;
|
|
49
|
-
- Composer 支持文本附件;粘贴或选择图片时会保存到 Farming 服务侧 `~/.farming/attachments`,并把图片路径插入消息,便于远端 Codex / Claude 读取;Farming 自动生成的图片附件默认保留 7 天后清理;
|
|
50
|
-
- 显示轻量 usage、context、token rate、quota 与机器状态,例如 CPU、MEM;
|
|
51
|
-
- 复用 Farming 的远程托管和多端浏览器接入能力,让桌面和手机访问同一套远程 Linux 服务。
|
|
52
|
-
|
|
53
|
-
具体截图、安装方式和产品说明见 [Farming 2 产品介绍](./docs/products/code/README.zh_cn.md)。
|
|
54
|
-
|
|
55
|
-
## 快速开始
|
|
56
|
-
|
|
57
|
-
默认推荐通过 npm 安装,把 Farming 运行在已经能正常执行 `codex` 或 `claude` 的开发机上。
|
|
58
|
-
|
|
59
|
-
```bash
|
|
60
|
-
npm install --global farming-code
|
|
61
|
-
farming daemon
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
默认端口是 `6694`,浏览器路径是 `/farming`,配置目录是 `~/.farming`,并默认启用 token auth。首次鉴权启动会生成一个随机但可读的口令,并保存到 `~/.farming/.session-token`;之后重启和升级都会复用这个 token,除非显式设置 `FARMING_TOKEN`。在中文时区默认是中文俳句式口令,日本时区默认是日文俳句式口令,其它时区默认是英文短语。启动日志会打印类似下面的 URL:
|
|
65
|
-
|
|
66
|
-
```text
|
|
67
|
-
http://linux-host:6694/farming?token=<startup-token>
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
把完整 URL 复制到桌面或手机浏览器中打开,点击 `New Agent`,选择 `Codex`、`Claude Code`、`bash` 或 `zsh`,填入 workspace,就可以进入远程工作台。
|
|
32
|
+
| | Farming Code | Farming CRT |
|
|
33
|
+
| --- | --- | --- |
|
|
34
|
+
| 更适合 | 长对话、文件、编辑、Diff、Review | 总览监控、键盘控制、终端操作、遥测 |
|
|
35
|
+
| 实时 Session | 结构化 Chat 与真实 PTY Terminal | 磷光风格 Chat 与真实 xterm Terminal |
|
|
36
|
+
| 导航方式 | 项目侧栏、Search、History、Files | 稳定 Agent 机位和键盘控制台 |
|
|
37
|
+
| 外观 | 浅色与深色 | CRT 效果、终端字号、可选 Dynamic Heat |
|
|
38
|
+
| 入口 | `/farming/code/` 或 `/farming/` | `/farming/crt/` |
|
|
71
39
|
|
|
72
|
-
|
|
40
|
+
切换界面不会重启或复制 Agent。如果 Farming Code 启动或渲染失败,有限范围的诊断层仍会保留后面的实时 CRT 界面,而不是把正在运行的 Session 一起遮掉。
|
|
73
41
|
|
|
74
|
-
|
|
42
|
+
完整能力矩阵和截图导览见 [Farming 2 产品总览](./docs/products/README.zh_cn.md)。两套界面的完整流程分别见 [Farming Code 指南](./docs/products/code/README.zh_cn.md) 和 [Farming CRT 指南](./docs/products/crt/README.zh_cn.md)。
|
|
75
43
|
|
|
76
|
-
|
|
44
|
+
## 现在可以做什么
|
|
77
45
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
46
|
+
- 按项目组织实时 Agent,置顶重点工作、重命名、查看未读状态,并归档或恢复任务。
|
|
47
|
+
- Codex、Claude Code、OpenCode 和 Qoder 使用结构化 ACP Chat。计划、推理、工具调用、权限请求、内嵌终端、子 Session、附件、排队追问和精确修改摘要都可以保留,但不会淹没最终答案。
|
|
48
|
+
- 需要原汁原味 CLI 行为时打开真实 PTY Terminal。Chat / Terminal 切换会改变实际运行时,并在身份可用时安全地恢复同一个 Provider Session。
|
|
49
|
+
- 修改运行中 Codex 所支持的模型、思考强度、Fast、Ultra 和权限设置。Terminal 的修改会在下一条消息前作用到当前工作流,而不是只改未来的启动 Profile。
|
|
50
|
+
- 浏览 Project Files 和 Open Editors,用 ripgrep 搜索、Monaco 轻量编辑、预览 Markdown/图片、跟随 `path:line` 链接,并检查 Git Changes、Diff 和 Blame。
|
|
51
|
+
- 围绕同一个演进中的 Change 跨 Revision Review,让 Finding 与对比版本绑定,标记文件已审阅,并关注多轮之间真正有意义的变化。
|
|
52
|
+
- 搜索实时 Agent 和受支持 Provider 的完整 Session 归档,然后打开、继续、恢复或 Resume 对应工作。
|
|
53
|
+
- 在 Provider 提供所需数据时查看 CPU/MEM、Token Rate、Context、Quota、Provider 用量,以及 CRT 的按日/实时 Token 遥测。
|
|
54
|
+
- 在电脑和手机浏览器中使用同一个服务;不同布局围绕不同设备上可用的注意力设计。
|
|
85
55
|
|
|
86
|
-
|
|
56
|
+

|
|
87
57
|
|
|
88
|
-
|
|
58
|
+

|
|
89
59
|
|
|
90
|
-
|
|
91
|
-
浏览器皮肤
|
|
92
|
-
React + Vite + Monaco + terminal renderer
|
|
93
|
-
|
|
|
94
|
-
| HTTP / WebSocket
|
|
95
|
-
v
|
|
96
|
-
Farming 内核
|
|
97
|
-
Express server + token auth + agent manager + session providers
|
|
98
|
-
|
|
|
99
|
-
| native pty host + session engine
|
|
100
|
-
v
|
|
101
|
-
执行环境
|
|
102
|
-
bash / zsh / Codex / Claude Code
|
|
103
|
-
```
|
|
60
|
+
## 支持的 Agent 路径
|
|
104
61
|
|
|
105
|
-
|
|
62
|
+
Farming 会发现开发机上已经安装的 CLI。有 ACP 支持的 Provider 使用更完整的结构化运行时,其他检测到的 Coding Agent 仍然可以作为一等 Terminal Session 使用。
|
|
106
63
|
|
|
107
|
-
|
|
64
|
+
| Agent | 结构化 Chat | 原生 Terminal | History / Resume |
|
|
65
|
+
| --- | --- | --- | --- |
|
|
66
|
+
| Codex | ACP | 是 | 是 |
|
|
67
|
+
| Claude Code | ACP | 是 | 是 |
|
|
68
|
+
| OpenCode | ACP | 是 | 是 |
|
|
69
|
+
| Qoder | ACP | 是 | 是 |
|
|
70
|
+
| Qwen Code | — | 是 | 取决于 CLI |
|
|
71
|
+
| Aider | — | 是 | 取决于 CLI |
|
|
72
|
+
| GitHub Copilot CLI | — | 是 | 取决于 CLI |
|
|
73
|
+
| Amazon Q | — | 是 | 取决于 CLI |
|
|
74
|
+
| bash / zsh | — | 是 | 没有 Provider Session Resume |
|
|
108
75
|
|
|
109
|
-
|
|
76
|
+
Farming 承载的是已经能在同一台机器正常工作的 CLI,不替代 Provider 的安装、登录和账户配置。
|
|
110
77
|
|
|
111
|
-
|
|
78
|
+
## 快速开始
|
|
112
79
|
|
|
113
|
-
|
|
80
|
+
安装 Node.js 22 或更新版本,并先安装、登录至少一个受支持的 Coding CLI:
|
|
114
81
|
|
|
115
82
|
```bash
|
|
116
83
|
npm install --global farming-code
|
|
117
84
|
farming daemon
|
|
118
85
|
```
|
|
119
86
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
兼容发布形态主要有两种:
|
|
123
|
-
|
|
124
|
-
- 单文件 CLI:适合现代 Linux 和 macOS,拿到一个 `farming` 可执行文件后直接运行。
|
|
125
|
-
- App bundle:适合目录式部署;包是一个 tarball,解压后通过根目录 `./farming` 脚本启动,脚本负责 Node heap 和运行环境。
|
|
126
|
-
|
|
127
|
-
源码远程部署脚本是开发团队把当前 checkout 部署到固定 Linux 机器的便捷路径。
|
|
128
|
-
|
|
129
|
-
如果要启动 Codex 或 Claude Code,目标机器上还需要提前安装并登录对应 CLI,确保在普通 SSH shell 中直接运行 `codex` 或 `claude` 能进入交互。
|
|
130
|
-
|
|
131
|
-
### 从源码打包
|
|
132
|
-
|
|
133
|
-
构建单文件 CLI:
|
|
134
|
-
|
|
135
|
-
```bash
|
|
136
|
-
npm install
|
|
137
|
-
npm run release:cli
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
构建可解压运行的 app bundle:
|
|
141
|
-
|
|
142
|
-
```bash
|
|
143
|
-
npm install
|
|
144
|
-
npm run release:app
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
`release:app` 会生成 `releases/<version>/farming-<version>-<platform>-<arch>.tar.gz`,包内包含已经构建好的前端、production dependencies 和根目录启动脚本,使用目标系统 runtime。
|
|
148
|
-
|
|
149
|
-
如果目标是 glibc 低于 2.28 的 Linux x64,构建带固定 glibc 2.28 runtime 的发布资产:
|
|
150
|
-
|
|
151
|
-
```bash
|
|
152
|
-
npm run release:app:legacy-linux
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
它会生成 `farming-<version>-linux-x64-legacy-glibc228.tar.gz`。这是首次安装引导包:安装器把 runtime 解压到 `~/.farming/glibc228`,把包内 Farming 版本放入私有 prefix `~/.farming/npm`,并生成稳定入口 `~/.farming/bin/farming`。后续普通版本更新直接通过 npm 安装到同一 prefix,再用兼容 launcher 重启;不需要反复下载兼容 tar。
|
|
87
|
+
默认端口是 `6694`,Base Path 是 `/farming`,配置目录是 `~/.farming`,Token 鉴权默认开启。启动日志会打印类似下面的 URL:
|
|
156
88
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
```bash
|
|
160
|
-
npm run release:app:linux-compat
|
|
89
|
+
```text
|
|
90
|
+
http://development-host:6694/farming?token=<startup-token>
|
|
161
91
|
```
|
|
162
92
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
如果已经在配置好的 Linux x64 构建机上准备了干净源码,`scripts/build-linux-compat-release-on-builder.sh` 可以继续自动完成容器构建、ABI 校验、打包后 bash Agent 冒烟和产物输出。通过 `FARMING_COMPAT_IMAGE` 指定构建机上已经存在的镜像。脚本使用 `--pull=never`,并默认关闭容器网络,以复用已有镜像和缓存,避免发布时隐式下载构建环境;只有确实需要主动刷新缓存时才设置 `FARMING_COMPAT_ALLOW_NETWORK=1`。
|
|
166
|
-
|
|
167
|
-
### 启动单文件 CLI
|
|
168
|
-
|
|
169
|
-
拿到对应平台的单文件 `farming` 后:
|
|
93
|
+
打开 URL,选择 **New Agent**、Agent 类型和 Workspace,然后从 Chat 或 Terminal 开始工作。常用守护进程命令:
|
|
170
94
|
|
|
171
95
|
```bash
|
|
172
|
-
|
|
173
|
-
|
|
96
|
+
farming status
|
|
97
|
+
farming url
|
|
98
|
+
farming logs
|
|
99
|
+
farming stop
|
|
174
100
|
```
|
|
175
101
|
|
|
176
|
-
|
|
102
|
+
第一次带鉴权启动会把随机、可读的 Token 写入 `~/.farming/.session-token`;后续重启和升级都会复用,除非显式设置 `FARMING_TOKEN`。Token 默认根据时区使用中文、日文或英文。
|
|
177
103
|
|
|
178
|
-
|
|
104
|
+

|
|
179
105
|
|
|
180
|
-
|
|
106
|
+
## 桌面与手机
|
|
181
107
|
|
|
182
|
-
|
|
183
|
-
tar -xzf farming-<version>-linux-x64.tar.gz
|
|
184
|
-
cd farming-<version>-linux-x64
|
|
185
|
-
./farming
|
|
186
|
-
```
|
|
108
|
+
桌面端把项目、对话、文件和 Review 放在彼此靠近的位置。移动端一次聚焦一段对话、一个终端或一个文件,并把导航移入抽屉,更适合查看进度和发送短介入。
|
|
187
109
|
|
|
188
|
-
|
|
110
|
+
<p align="center">
|
|
111
|
+
<img src="./docs/products/code/assets/05-mobile-agent-chat.jpg" alt="Farming Code 手机界面" width="320">
|
|
112
|
+
|
|
113
|
+
<img src="./docs/products/crt/assets/09-crt-mobile-dashboard.jpg" alt="Farming CRT 手机界面" width="320">
|
|
114
|
+
</p>
|
|
189
115
|
|
|
190
|
-
|
|
116
|
+
## 安装与更新
|
|
191
117
|
|
|
192
|
-
|
|
193
|
-
./farming status
|
|
194
|
-
./farming logs
|
|
195
|
-
./farming stop
|
|
196
|
-
./farming start
|
|
197
|
-
./farming url
|
|
198
|
-
```
|
|
118
|
+
npm 包是默认分发方式。**Settings → Updates** 可以原地升级 npm 安装:Farming 会在当前 Server 仍运行时安装新包,只在安装成功后重启;新 Server 无法启动时会尝试回退。
|
|
199
119
|
|
|
200
|
-
|
|
120
|
+
GitHub Releases 也提供独立 CLI 和目录 Bundle。旧版 Linux x64 可以用 `linux-x64-legacy-glibc228` 完成第一次安装,后续应用更新继续使用同一个私有 npm Prefix。受控环境还可以单独构建 glibc 2.17 ABI Bundle。当前产物和版本说明见 [GitHub Releases](https://github.com/zhuwenzhuang/farming/releases)。
|
|
201
121
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
```bash
|
|
205
|
-
npm run release:remote
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
这条路径会从当前源码 checkout 构建 app bundle、上传到远端、安装并启动服务,适合团队开发和 dogfood。
|
|
209
|
-
|
|
210
|
-
### 开发调试
|
|
211
|
-
|
|
212
|
-
只在本地开发 Farming 本身时,才需要直接启动源码服务:
|
|
122
|
+
从源码开发:
|
|
213
123
|
|
|
214
124
|
```bash
|
|
215
125
|
npm install
|
|
216
126
|
npm start
|
|
217
127
|
```
|
|
218
128
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
```bash
|
|
222
|
-
npm run start:no-auth
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
远程安装和 release 细节见 [`docs/products/code/README.zh_cn.md`](./docs/products/code/README.zh_cn.md)。
|
|
226
|
-
|
|
227
|
-
## 配置
|
|
228
|
-
|
|
229
|
-
运行时配置存储在 `~/.farming/settings.json`。
|
|
230
|
-
Agent session 元数据单独存储在 `~/.farming/sessions/`。Farming 使用稳定的
|
|
231
|
-
`fsess_*` 文件作为自己的 Agent 记录;live `agent-...` id 以及 Codex / Claude
|
|
232
|
-
provider session id 都作为这些记录上的元数据保存。主页面 Projects membership
|
|
233
|
-
存放在 `sessions/index.json`,`mainPageSessionKeys` 只是为兼容 API 暴露出来的投影。
|
|
234
|
-
归档 run/history 存储在 `~/.farming/history/runs.json`,不属于 `settings.json`。
|
|
235
|
-
主题覆盖配置、启动 token、server pid/state/log 文件和 native pty host 日志也都在同一个 config 目录下。
|
|
236
|
-
|
|
237
|
-
主要用户配置项:
|
|
129
|
+
只有在可信本地开发环境中,才可以用 `npm run start:no-auth` 关闭 Token 鉴权。
|
|
238
130
|
|
|
239
|
-
|
|
240
|
-
- `agentLaunchProfiles.codex`:Codex 的启动权限、模型、reasoning、service tier profile;
|
|
241
|
-
- `agentLaunchProfiles.claude`:Claude 的启动 permission、model、effort profile;
|
|
242
|
-
- `agentHomes`:管理 Codex、Claude、OpenCode、Qoder 的 agent home 元数据,每项只包含 `id` 和 `path`;每个 provider 都保留不可删除的 `default` home;
|
|
243
|
-
- `workspaceHistory`:New Agent 启动时使用的最近 workspace;
|
|
244
|
-
- `dangerouslySkipAgentPermissionsByDefault`:是否默认让支持的 coding agent(如 Codex、Claude、OpenCode、Qoder、Qwen、Aider、GitHub Copilot CLI、Amazon Q)使用各自最激进的权限绕过启动 flag。
|
|
131
|
+
## 工作原理
|
|
245
132
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
133
|
+
```text
|
|
134
|
+
Farming Code / Farming CRT
|
|
135
|
+
React, Monaco, xterm.js, CRT browser skin
|
|
136
|
+
│ HTTP + WebSocket
|
|
137
|
+
▼
|
|
138
|
+
Farming core
|
|
139
|
+
auth, Agent manager, ACP, history, files, review, usage
|
|
140
|
+
│ native PTY host + session providers
|
|
141
|
+
▼
|
|
142
|
+
Development host
|
|
143
|
+
repositories, shells, Codex, Claude Code, OpenCode, Qoder, ...
|
|
144
|
+
```
|
|
253
145
|
|
|
254
|
-
|
|
255
|
-
- `config/farming.install.env.example`
|
|
146
|
+
后端负责生命周期、鉴权、Session 路由、Workspace 边界、History 和配置。交互式 Terminal 默认由独立的原生 PTY Host 持有,因此浏览器和 Server 可以重新连接,而不需要替换实际进程。xterm.js 是产品默认终端渲染器;Ghostty Web Adapter 只保留为显式调试路径。
|
|
256
147
|
|
|
257
|
-
|
|
148
|
+
运行时设置存放在 `~/.farming/settings.json`。Farming Session 元数据、项目成员索引、归档运行、主题设置、更新状态、日志和启动 Token 使用 `~/.farming/` 下彼此独立的文件。外部 Provider History 仍然只读。
|
|
258
149
|
|
|
259
150
|
## 安全
|
|
260
151
|
|
|
261
|
-
Farming
|
|
262
|
-
|
|
263
|
-
启动 token 会同时保护 HTTP 页面和 WebSocket 连接。它在首次鉴权启动时随机生成,持久保存到 `~/.farming/.session-token`,并在后续重启和升级中复用。生成的新 token 刻意做成比长十六进制串更容易复制的可读口令:中文时区默认生成中文俳句式口令,日本时区默认生成日文俳句式口令,其它时区默认生成英文短语;也可以用 `FARMING_TOKEN_LOCALE=zh|ja|en|auto` 显式指定新 token 的生成语言。
|
|
264
|
-
|
|
265
|
-
`FARMING_DISABLE_AUTH=1` 只应该用于可信本地开发环境。terminal-owned 的 Codex / Claude session 切换权限时会用所选 flag 重启 CLI:已有 provider Session ID 时会 resume,还没有可 resume 的 ID 时启动新会话。App Server Codex 则直接在当前 thread 上更新审批和 sandbox 策略,不启动 CLI。
|
|
266
|
-
|
|
267
|
-
安全上报和部署注意事项见 [SECURITY.md](./SECURITY.md)。
|
|
268
|
-
|
|
269
|
-
## 排错
|
|
270
|
-
|
|
271
|
-
- **Codex 或 Claude Code 无法启动**:先确认同一台机器上对应 CLI 已安装、已登录,并能在普通 shell 中直接运行。
|
|
272
|
-
- **Native PTY 无法启动**:检查目标系统的 Node.js 与打包的 `node-pty` runtime 是否兼容;glibc 低于 2.28 的 Linux x64 请使用 `-legacy-glibc228` 包。
|
|
273
|
-
- **端口占用**:可以传 `--port <port>`;如果没有显式指定端口,默认 daemon 启动会自动上探选择可用端口。
|
|
274
|
-
- **手机访问不了**:使用启动日志里打印的 Network URL,并确认手机能访问目标开发机。
|
|
275
|
-
- **找不到 token URL**:运行 `./farming url`,或查看 `./farming logs`。
|
|
276
|
-
|
|
277
|
-
## 目录结构
|
|
152
|
+
Farming 会控制目标机器上的真实终端和文件。请只运行在可信开发机和可信网络中,不要在没有 VPN、SSH Tunnel、HTTPS Reverse Proxy 或等价访问控制时直接暴露到公网。
|
|
278
153
|
|
|
279
|
-
|
|
280
|
-
farming/
|
|
281
|
-
├── .gitattributes # 源码归档 export-ignore 规则
|
|
282
|
-
├── backend/ # Node.js server、session engine、agent manager、session/history/usage/file/slash-command APIs
|
|
283
|
-
├── src/ # React + Vite 前端;Farming Code 皮肤与交互 helper 位于 src/components/code/
|
|
284
|
-
├── frontend/skins/crt/ # 独立的实时 CRT 入口、应用逻辑与视觉效果
|
|
285
|
-
├── frontend/*.js # 多皮肤共享的 terminal/session 浏览器 bridge
|
|
286
|
-
├── docs/
|
|
287
|
-
│ ├── products/code/ # Farming 2 产品介绍、Farming Code 皮肤截图、安装说明和验收 dogfood 方案
|
|
288
|
-
│ └── products/crt/ # CRT 皮肤 README 和布局说明
|
|
289
|
-
├── config/ # 部署 / 安装配置模板
|
|
290
|
-
├── scripts/ # 部署、release、测试、产品截图和辅助脚本
|
|
291
|
-
├── tests/e2e/ # Playwright 展示与浏览器流程测试
|
|
292
|
-
├── reference/ # 外部项目源码、工具链和调研 walkthrough;不作为 Farming 运行时依赖
|
|
293
|
-
├── pkg.config.cjs # 平台 CLI 应用打包配置(@yao-pkg/pkg + legacy pkg)
|
|
294
|
-
└── bin/farming # 开发态产品 CLI;发布后二进制也叫 farming
|
|
295
|
-
```
|
|
296
|
-
|
|
297
|
-
公开产品文档现在从根 README 和 `docs/products/` 下的皮肤 README 进入。`releases/` 是本地打包输出目录,不提交到源码仓库。
|
|
154
|
+
Token 鉴权同时保护 HTTP 和 WebSocket。`FARMING_DISABLE_AUTH=1` 只适合可信本地开发;Workspace 文件 API 会校验所有路径都位于所选项目根目录内。报告和部署说明见 [SECURITY.md](./SECURITY.md)。
|
|
298
155
|
|
|
299
|
-
##
|
|
156
|
+
## 文档
|
|
300
157
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
158
|
+
- [Farming 2 产品总览与能力矩阵](./docs/products/README.zh_cn.md)
|
|
159
|
+
- [Farming Code 指南](./docs/products/code/README.zh_cn.md)
|
|
160
|
+
- [Farming CRT 指南](./docs/products/crt/README.zh_cn.md)
|
|
161
|
+
- [移动端指南](./docs/products/code/mobile-guide.zh_cn.md)
|
|
162
|
+
- [ACP 运行时](./docs/products/code/acp-runtime.zh_cn.md)
|
|
163
|
+
- [Review 基础](./docs/products/code/review-foundation.zh_cn.md)
|
|
164
|
+
- [版本历史](https://github.com/zhuwenzhuang/farming/releases)
|
|
165
|
+
- [贡献者说明](./AGENTS.zh_cn.md)
|
|
306
166
|
|
|
307
|
-
|
|
167
|
+
## 开发检查
|
|
308
168
|
|
|
309
169
|
```bash
|
|
310
170
|
npm test
|
|
311
171
|
npm run typecheck
|
|
312
172
|
npm run lint
|
|
173
|
+
FARMING_BASE_PATH=/farming npm run build
|
|
313
174
|
npm run test:e2e:playwright
|
|
314
175
|
```
|
|
315
176
|
|
|
316
|
-
|
|
177
|
+
产品截图由匿名 Demo Workspace 和真实浏览器流程生成:
|
|
317
178
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
- [l4wei](https://github.com/l4wei)
|
|
322
|
-
|
|
323
|
-
## 贡献
|
|
324
|
-
|
|
325
|
-
欢迎提交 issue 和 pull request。开始前请阅读 [CONTRIBUTING.md](./CONTRIBUTING.md);如果改动影响用户可见行为、打包方式或部署方式,请同步更新 README、产品文档和相关验收说明。
|
|
179
|
+
```bash
|
|
180
|
+
npm run docs:product:screenshots
|
|
181
|
+
```
|
|
326
182
|
|
|
327
183
|
## License
|
|
328
184
|
|
|
329
|
-
MIT
|
|
185
|
+
Farming 使用 [MIT License](./LICENSE)。第三方组件声明见 [THIRD_PARTY_NOTICES.md](./THIRD_PARTY_NOTICES.md)。
|
package/backend/acp-runtime.js
CHANGED
|
@@ -23,6 +23,7 @@ const DEFAULT_CANCEL_TIMEOUT_MS = 15_000;
|
|
|
23
23
|
const DEFAULT_HISTORY_REPLAY_MIN_WAIT_MS = 350;
|
|
24
24
|
const DEFAULT_HISTORY_REPLAY_QUIET_MS = 150;
|
|
25
25
|
const DEFAULT_HISTORY_REPLAY_MAX_WAIT_MS = 5_000;
|
|
26
|
+
const CODEX_SET_SESSION_MODEL_METHOD = 'session/set_model';
|
|
26
27
|
|
|
27
28
|
let sdkPromise;
|
|
28
29
|
const runtimeRequire = createRequire(__filename);
|
|
@@ -386,6 +387,16 @@ class AcpRuntime extends EventEmitter {
|
|
|
386
387
|
binding.configOptions = sessionResponse?.configOptions || [];
|
|
387
388
|
binding.sessionState.currentModeId = String(binding.modes?.currentModeId || '');
|
|
388
389
|
binding.sessionState.configOptions = JSON.parse(JSON.stringify(binding.configOptions));
|
|
390
|
+
if (provider === 'codex' && options.serviceTier && options.serviceTier !== 'config') {
|
|
391
|
+
const fastOption = binding.configOptions.find(option => (
|
|
392
|
+
option.type === 'boolean'
|
|
393
|
+
&& /fast/i.test(`${option.id || ''} ${option.name || ''} ${option.category || ''}`)
|
|
394
|
+
));
|
|
395
|
+
const fastEnabled = ['fast', 'priority'].includes(options.serviceTier);
|
|
396
|
+
if (fastOption && fastOption.currentValue !== fastEnabled) {
|
|
397
|
+
await this.applySessionConfigOption(binding, fastOption.id, fastEnabled, { emit: false });
|
|
398
|
+
}
|
|
399
|
+
}
|
|
389
400
|
binding.state = 'idle';
|
|
390
401
|
binding.updatedAt = new Date().toISOString();
|
|
391
402
|
this.emitRuntime(binding);
|
|
@@ -857,6 +868,87 @@ class AcpRuntime extends EventEmitter {
|
|
|
857
868
|
|
|
858
869
|
async setSessionConfigOption(agentId, configId, value) {
|
|
859
870
|
const binding = this.requireBinding(agentId);
|
|
871
|
+
const option = binding.configOptions?.find(candidate => candidate.id === String(configId || ''));
|
|
872
|
+
if (
|
|
873
|
+
binding.provider === 'codex'
|
|
874
|
+
&& option?.type === 'select'
|
|
875
|
+
&& /(^|[\s_-])model([\s_-]|$)/i.test(`${option.id} ${option.name || ''} ${option.category || ''}`)
|
|
876
|
+
) {
|
|
877
|
+
// Let the adapter choose a supported fallback effort from its current
|
|
878
|
+
// snapshot first. The refresh extension requires an explicit effort and
|
|
879
|
+
// would otherwise reject a valid model change (for example ultra -> a
|
|
880
|
+
// model that tops out at max).
|
|
881
|
+
await this.applySessionConfigOption(binding, configId, value, { emit: false });
|
|
882
|
+
const reasoning = binding.configOptions?.find(candidate => (
|
|
883
|
+
candidate.type === 'select'
|
|
884
|
+
&& /(reasoning|thought)/i.test(`${candidate.id} ${candidate.name || ''} ${candidate.category || ''}`)
|
|
885
|
+
));
|
|
886
|
+
if (typeof reasoning?.currentValue === 'string' && reasoning.currentValue) {
|
|
887
|
+
await this.refreshCodexSessionModel(binding, String(value ?? ''), reasoning.currentValue);
|
|
888
|
+
return this.applySessionConfigOption(binding, configId, value);
|
|
889
|
+
}
|
|
890
|
+
this.emitSession(binding);
|
|
891
|
+
return { sessionId: binding.sessionId, configOptions: binding.configOptions };
|
|
892
|
+
}
|
|
893
|
+
return this.applySessionConfigOption(binding, configId, value);
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
async setSessionConfigOptions(agentId, changes) {
|
|
897
|
+
const binding = this.requireBinding(agentId);
|
|
898
|
+
const normalized = Array.isArray(changes)
|
|
899
|
+
? changes.filter(change => change && typeof change.configId === 'string' && Object.prototype.hasOwnProperty.call(change, 'value'))
|
|
900
|
+
: [];
|
|
901
|
+
if (normalized.length === 0) throw new Error('ACP config options are required');
|
|
902
|
+
|
|
903
|
+
const configById = new Map((binding.configOptions || []).map(option => [option.id, option]));
|
|
904
|
+
const modelChange = normalized.find(change => {
|
|
905
|
+
const option = configById.get(change.configId);
|
|
906
|
+
return option?.type === 'select'
|
|
907
|
+
&& /(^|[\s_-])model([\s_-]|$)/i.test(`${option.id} ${option.name || ''} ${option.category || ''}`);
|
|
908
|
+
});
|
|
909
|
+
const reasoningChange = normalized.find(change => {
|
|
910
|
+
const option = configById.get(change.configId);
|
|
911
|
+
return option?.type === 'select'
|
|
912
|
+
&& /(reasoning|thought)/i.test(`${option.id} ${option.name || ''} ${option.category || ''}`);
|
|
913
|
+
});
|
|
914
|
+
|
|
915
|
+
let response;
|
|
916
|
+
const handled = new Set();
|
|
917
|
+
if (binding.provider === 'codex' && modelChange && reasoningChange) {
|
|
918
|
+
await this.applySessionConfigOption(binding, modelChange.configId, modelChange.value, { emit: false });
|
|
919
|
+
await this.applySessionConfigOption(binding, reasoningChange.configId, reasoningChange.value, { emit: false });
|
|
920
|
+
const currentReasoning = binding.configOptions?.find(candidate => (
|
|
921
|
+
candidate.type === 'select'
|
|
922
|
+
&& /(reasoning|thought)/i.test(`${candidate.id} ${candidate.name || ''} ${candidate.category || ''}`)
|
|
923
|
+
));
|
|
924
|
+
await this.refreshCodexSessionModel(
|
|
925
|
+
binding,
|
|
926
|
+
String(modelChange.value ?? ''),
|
|
927
|
+
String(currentReasoning?.currentValue || reasoningChange.value || '')
|
|
928
|
+
);
|
|
929
|
+
response = await this.applySessionConfigOption(binding, modelChange.configId, modelChange.value);
|
|
930
|
+
handled.add(modelChange);
|
|
931
|
+
handled.add(reasoningChange);
|
|
932
|
+
}
|
|
933
|
+
for (const change of normalized) {
|
|
934
|
+
if (handled.has(change)) continue;
|
|
935
|
+
response = await this.setSessionConfigOption(agentId, change.configId, change.value);
|
|
936
|
+
}
|
|
937
|
+
return response;
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
async refreshCodexSessionModel(binding, model, effort) {
|
|
941
|
+
await withTimeout(
|
|
942
|
+
binding.connection.request(CODEX_SET_SESSION_MODEL_METHOD, {
|
|
943
|
+
sessionId: binding.sessionId,
|
|
944
|
+
modelId: `${model}[${effort}]`,
|
|
945
|
+
}),
|
|
946
|
+
this.requestTimeoutMs,
|
|
947
|
+
'Codex ACP session/set_model capability refresh'
|
|
948
|
+
);
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
async applySessionConfigOption(binding, configId, value, options = {}) {
|
|
860
952
|
const request = typeof value === 'boolean'
|
|
861
953
|
? { sessionId: binding.sessionId, configId: String(configId || ''), type: 'boolean', value }
|
|
862
954
|
: { sessionId: binding.sessionId, configId: String(configId || ''), value: String(value ?? '') };
|
|
@@ -867,7 +959,7 @@ class AcpRuntime extends EventEmitter {
|
|
|
867
959
|
);
|
|
868
960
|
binding.configOptions = response?.configOptions || binding.configOptions;
|
|
869
961
|
binding.sessionState.configOptions = JSON.parse(JSON.stringify(binding.configOptions));
|
|
870
|
-
this.emitSession(binding);
|
|
962
|
+
if (options.emit !== false) this.emitSession(binding);
|
|
871
963
|
return { sessionId: binding.sessionId, configOptions: binding.configOptions };
|
|
872
964
|
}
|
|
873
965
|
|