farming-code 2.2.28 → 2.2.29
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 +9 -31
- package/README.zh_cn.md +8 -30
- package/THIRD_PARTY_NOTICES.md +25 -6
- package/backend/acp/packaged-claude-acp.js +13 -0
- package/backend/acp/packaged-codex-acp.js +2 -2
- package/backend/acp-runtime.js +37 -2
- package/backend/acp-transcript.js +174 -9
- package/backend/agent-manager.js +27 -2
- package/backend/config-manager.js +44 -0
- package/backend/data/LICENSE.agent-browser +201 -0
- package/backend/data/runtime-dependency-manifest.json +199 -0
- package/backend/data/runtime-dependency-sources.json +5 -0
- package/backend/executable-discovery.js +7 -0
- package/backend/farming-agent-bootstrap.zh_cn.md +4 -2
- package/backend/farming-app-cli.js +160 -64
- package/backend/farming-cli.js +5 -3
- package/backend/index-html.js +13 -0
- package/backend/main-agent-skills.js +6 -10
- package/backend/npm-update-helper.js +43 -6
- package/backend/provider-adapters.js +10 -0
- package/backend/runtime-dependency-manager.js +759 -0
- package/backend/runtime-executable-invocation.js +20 -0
- package/backend/server-process-identity.js +47 -0
- package/backend/server.js +140 -157
- package/backend/slash-command-discovery.js +138 -1
- package/backend/storage-layout.js +15 -0
- package/backend/update-service.js +27 -1
- package/config/farming.install.env.example +4 -0
- package/dist/acp/LICENSE.claude-agent-acp +191 -0
- package/dist/acp/LICENSE.claude-agent-sdk +1 -0
- package/dist/acp/claude-agent-acp-0.59.0.mjs +41606 -0
- package/dist/assets/{App-CM7Cy-l4.css → App-BKNUILuL.css} +1 -1
- package/dist/assets/App-Ex2oVVXO.js +693 -0
- package/dist/assets/{FileEditorMarkdownPreview-DYOMm-b1.js → FileEditorMarkdownPreview-Cs9cLv8a.js} +1 -1
- package/dist/assets/FileEditorPane-CNtuGjQ3.js +2 -0
- package/dist/assets/IconGlyphs-BClgag6E.js +1 -0
- package/dist/assets/ProjectFilesSection-Cf2BEcAN.js +12 -0
- package/dist/assets/ReviewPage-BRuxFaH5.js +3 -0
- package/dist/assets/code-dark-BEZTQwYZ.css +1 -0
- package/dist/assets/{index-BzQO3pUr.js → index-CQNaZc3l.js} +2 -2
- package/dist/assets/main-DK9Sy7-c.css +1 -0
- package/dist/assets/{workspace-editor-monaco-tXA-udGx.js → workspace-editor-monaco-CJNwbpCy.js} +1 -1
- package/dist/assets/workspace-editor-monaco-CO9w8YV0.js +1 -0
- package/dist/assets/workspace-view-state-BjKPYp0E.js +7 -0
- package/dist/index.html +32 -3
- package/extensions/browser/backend/agent-browser-runtime.js +1041 -0
- package/extensions/browser/backend/browser-mcp-server.js +352 -0
- package/extensions/browser/backend/browser-resource-manager.js +899 -114
- package/extensions/browser/backend/browser-resource-store.js +47 -6
- package/extensions/browser/backend/browser-router.js +7 -2
- package/extensions/browser/backend/executable-discovery.js +146 -13
- package/extensions/browser/bin/farming-browser +384 -30
- package/extensions/browser/frontend/BrowserSidebarPortals.tsx +17 -19
- package/extensions/browser/frontend/BrowserViewer.tsx +217 -46
- package/extensions/browser/frontend/browser.css +68 -17
- package/extensions/browser/frontend/types.ts +6 -0
- package/extensions/browser/frontend/useBrowserResources.ts +10 -3
- package/index.html +31 -2
- package/package.json +10 -6
- package/shared/backend-connection-status.d.ts +12 -0
- package/shared/backend-connection-status.js +35 -4
- package/backend/agent-activity.js +0 -35
- package/dist/assets/App-CPG4WcjO.js +0 -658
- package/dist/assets/FileEditorPane-DXXq82aM.js +0 -2
- package/dist/assets/IconGlyphs-DrdPjYXr.js +0 -1
- package/dist/assets/ProjectFilesSection-CP66XVPl.js +0 -12
- package/dist/assets/ReviewPage-w21MEPD6.js +0 -3
- package/dist/assets/code-dark-BDjwj7Um.css +0 -1
- package/dist/assets/main-BlVUXfxa.css +0 -1
- package/dist/assets/workspace-editor-monaco-CFEfFNcA.js +0 -1
- package/dist/assets/workspace-view-state-BS_V8JVs.js +0 -7
- package/extensions/browser/backend/cdp-browser-runtime.js +0 -648
- package/extensions/browser/backend/cdp-client.js +0 -167
- /package/dist/acp/{codex-acp-1.1.4.js → codex-acp-1.1.4.mjs} +0 -0
package/README.md
CHANGED
|
@@ -55,17 +55,17 @@ Start or resume Codex, Claude Code, OpenCode, Qoder, and other detected coding C
|
|
|
55
55
|
|
|
56
56
|
Browse, search, and lightly edit Project Files without leaving the current task. Inspect Git Changes, History, Diff, and Blame, then open a commit or working-copy change in Review.
|
|
57
57
|
|
|
58
|
-

|
|
59
|
-
|
|
60
58
|
### Browser Resources
|
|
61
59
|
|
|
62
|
-
Each Project can own multiple named Browser Resources.
|
|
60
|
+
Each Project can own multiple named Browser Resources. In **Plugins → Browser**, users choose a discovered Chromium-based system browser or an external loopback CDP endpoint. Farming prepares a pinned `agent-browser` runtime during installation or update, verifies the cache again before startup, and sends both human input and Agent actions to the same session. Farming does not download Chromium or depend on Playwright. Agents discover the live capability with `farming capabilities`, then use the progressively disclosed `farming browser` CLI for navigation, interaction, inspection, debugging, page state, frames/dialogs, and Project-scoped file transfer; npm installations also expose the `farming-browser` alias. See [Farming Browser for Agents](docs/products/code/browser-agent-cli.md).
|
|
61
|
+
|
|
62
|
+
Farming can also connect to an externally managed Chromium CDP endpoint, including one exposed by Docker; see [external CDP browser setup](./docs/products/code/external-cdp-browser.md).
|
|
63
63
|
|
|
64
64
|
This first Browser Viewer targets web pages and simple Agent interaction. It is not a replacement for the browser's full window chrome, DevTools, downloads UI, extensions, or Computer Use.
|
|
65
65
|
|
|
66
66
|
## Supported Agents
|
|
67
67
|
|
|
68
|
-
Farming
|
|
68
|
+
Farming prepares the pinned Codex and Claude executables needed by its structured runtimes during installation or update and verifies them again before startup, while continuing to discover other installed CLIs on the host. Codex, Claude Code, OpenCode, and Qoder support both structured Chat and a native Terminal; other detected coding agents use the Terminal path.
|
|
69
69
|
|
|
70
70
|
| Agent | Structured Chat | Terminal | History / resume |
|
|
71
71
|
| --- | --- | --- | --- |
|
|
@@ -73,10 +73,6 @@ Farming discovers installed CLIs on the host. Codex, Claude Code, OpenCode, and
|
|
|
73
73
|
| Claude Code | Yes | Yes | Yes |
|
|
74
74
|
| OpenCode | Yes | Yes | Yes |
|
|
75
75
|
| Qoder | Yes | Yes | Yes |
|
|
76
|
-
| Qwen Code | — | Yes | CLI-dependent |
|
|
77
|
-
| Aider | — | Yes | CLI-dependent |
|
|
78
|
-
| GitHub Copilot CLI | — | Yes | CLI-dependent |
|
|
79
|
-
| Amazon Q | — | Yes | CLI-dependent |
|
|
80
76
|
| bash / zsh | — | Yes | No |
|
|
81
77
|
|
|
82
78
|
Farming hosts CLIs that already work on the same machine. It does not replace provider installation, login, or account configuration.
|
|
@@ -94,20 +90,16 @@ Development machine
|
|
|
94
90
|
├── coding Agent processes
|
|
95
91
|
├── real terminals
|
|
96
92
|
├── repositories and project files
|
|
97
|
-
└── optional
|
|
93
|
+
└── optional agent-browser sessions
|
|
98
94
|
```
|
|
99
95
|
|
|
100
96
|
The browser can disconnect and reconnect without stopping an Agent. A normal Farming server restart can also reconnect supported live terminal sessions. The desktop layout keeps projects, conversations, files, and review together; the mobile layout focuses one conversation, terminal, or file at a time.
|
|
101
97
|
|
|
102
|
-
<p align="center">
|
|
103
|
-
<img src="./docs/products/code/assets/05-mobile-agent-chat.png" alt="Farming Code reconnecting to a running Agent from a phone" width="390">
|
|
104
|
-
</p>
|
|
105
|
-
|
|
106
98
|
## Farming CRT
|
|
107
99
|
|
|
108
100
|
Farming CRT is an optional keyboard-first, retro control-room interface for scanning many Agents, opening their Chat or Terminal sessions, searching history, and viewing usage telemetry.
|
|
109
101
|
|
|
110
|
-

|
|
111
103
|
|
|
112
104
|
Code and CRT use the same backend Agents and sessions. Switching interfaces does not create a second Agent. Farming Code remains the default interface and the supported phone interface. See the [Farming CRT guide](./docs/products/crt/README.md) for controls and workflows.
|
|
113
105
|
|
|
@@ -117,25 +109,11 @@ Farming Net is a separate, token-protected directory for Farming deployments. It
|
|
|
117
109
|
|
|
118
110
|
## Installation And Updates
|
|
119
111
|
|
|
120
|
-
|
|
112
|
+
Install with the Quick Start command above. For npm installations, **Settings → Updates** prepares a selected release while the current Server keeps running, then applies it after confirmation. The existing startup token is retained.
|
|
121
113
|
|
|
122
|
-
|
|
123
|
-
farming status
|
|
124
|
-
farming url
|
|
125
|
-
farming logs
|
|
126
|
-
farming stop
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
The startup token is stored in `~/.farming/.session-token` and reused across restarts and upgrades. **Settings → Updates** uses a two-step flow for npm installations: **Prepare** installs the selected version into a separate staging directory while the current server keeps running, then **Restart to update** switches the package directory and restarts Farming only after the user clicks it. If the new server does not start, Farming restores the previous package directory. npm preparation first uses the machine's configured registry and retries the registry shown in Settings only when that registry lacks the selected version. Before preparing or applying an update, Farming verifies that npm targets the same package root that launched the server. GitHub Releases also provide standalone CLI and directory bundles; see [GitHub Releases](https://github.com/zhuwenzhuang/farming/releases) for current artifacts.
|
|
130
|
-
|
|
131
|
-
To run from source with the same port and base path:
|
|
132
|
-
|
|
133
|
-
```bash
|
|
134
|
-
npm install
|
|
135
|
-
PORT=6694 FARMING_BASE_PATH=/farming npm start
|
|
136
|
-
```
|
|
114
|
+

|
|
137
115
|
|
|
138
|
-
|
|
116
|
+
Standalone CLI and directory bundles remain available from [GitHub Releases](https://github.com/zhuwenzhuang/farming/releases).
|
|
139
117
|
|
|
140
118
|
## Security
|
|
141
119
|
|
package/README.zh_cn.md
CHANGED
|
@@ -55,17 +55,17 @@ Farming Code 是默认的桌面与手机界面。它按项目组织工作,把
|
|
|
55
55
|
|
|
56
56
|
浏览、搜索并轻量编辑 Project Files,不需要离开当前任务。检查 Git Changes、History、Diff 和 Blame,再把 Commit 或 Working Copy 修改打开到 Review。
|
|
57
57
|
|
|
58
|
-

|
|
59
|
-
|
|
60
58
|
### Browser Resource
|
|
61
59
|
|
|
62
|
-
每个 Project 可以拥有多个可命名的 Browser Resource
|
|
60
|
+
每个 Project 可以拥有多个可命名的 Browser Resource。用户在**插件 → 浏览器**中选择已发现的 Chromium 系统浏览器,或填写外部回环 CDP Endpoint。Farming 在安装或更新阶段准备锁定版本的 `agent-browser` Runtime,并在启动前再次校验 Cache,然后把人的输入和 Agent 操作发送给同一个 Session。Farming 不下载 Chromium,也不依赖 Playwright。Agent 先用 `farming capabilities` 发现实时能力,再通过渐进式披露的 `farming browser` CLI 使用导航、交互、检查、调试、页面状态、Frame/Dialog 与 Project 级文件传输;npm 安装还会提供 `farming-browser` 别名。详见 [Farming Browser Agent CLI](docs/products/code/browser-agent-cli.zh_cn.md)。
|
|
61
|
+
|
|
62
|
+
Farming 也可以连接由管理员独立管理的 Chromium CDP Endpoint,包括 Docker 中暴露的 Chromium;配置方式见[外部 CDP 浏览器指南](./docs/products/code/external-cdp-browser.zh_cn.md)。
|
|
63
63
|
|
|
64
64
|
第一版 Browser Viewer 面向网页展示与简单 Agent 操作,不替代浏览器完整窗口、DevTools、下载界面、浏览器扩展或 Computer Use。
|
|
65
65
|
|
|
66
66
|
## 支持的 Agent
|
|
67
67
|
|
|
68
|
-
Farming
|
|
68
|
+
Farming 在安装或更新阶段准备结构化 Runtime 所需的锁定版本 Codex 与 Claude Executable,并在启动前再次校验,同时继续发现开发机上安装的其他 CLI。Codex、Claude Code、OpenCode 和 Qoder 同时支持结构化 Chat 与原生 Terminal;其他检测到的 Coding Agent 使用 Terminal 路径。
|
|
69
69
|
|
|
70
70
|
| Agent | 结构化 Chat | Terminal | History / Resume |
|
|
71
71
|
| --- | --- | --- | --- |
|
|
@@ -73,10 +73,6 @@ Farming 会发现开发机上已经安装的 CLI。Codex、Claude Code、OpenCod
|
|
|
73
73
|
| Claude Code | 是 | 是 | 是 |
|
|
74
74
|
| OpenCode | 是 | 是 | 是 |
|
|
75
75
|
| Qoder | 是 | 是 | 是 |
|
|
76
|
-
| Qwen Code | — | 是 | 取决于 CLI |
|
|
77
|
-
| Aider | — | 是 | 取决于 CLI |
|
|
78
|
-
| GitHub Copilot CLI | — | 是 | 取决于 CLI |
|
|
79
|
-
| Amazon Q | — | 是 | 取决于 CLI |
|
|
80
76
|
| bash / zsh | — | 是 | 否 |
|
|
81
77
|
|
|
82
78
|
Farming 承载的是已经能在同一台机器正常工作的 CLI,不替代 Provider 的安装、登录和账户配置。
|
|
@@ -99,15 +95,11 @@ Farming 承载的是已经能在同一台机器正常工作的 CLI,不替代 P
|
|
|
99
95
|
|
|
100
96
|
浏览器断开或重新连接不会停止 Agent。Farming Server 正常重启后也可以重新连接受支持的实时 Terminal Session。桌面布局把项目、对话、文件和 Review 放在一起;手机布局一次聚焦一段对话、一个 Terminal 或一个文件。
|
|
101
97
|
|
|
102
|
-
<p align="center">
|
|
103
|
-
<img src="./docs/products/code/assets/05-mobile-agent-chat.png" alt="使用手机重新连接仍在运行的 Farming Code Agent" width="390">
|
|
104
|
-
</p>
|
|
105
|
-
|
|
106
98
|
## Farming CRT
|
|
107
99
|
|
|
108
100
|
Farming CRT 是可选的键盘优先复古控制室,用来扫视多个 Agent、打开它们的 Chat 或 Terminal、搜索 History,以及查看用量遥测。
|
|
109
101
|
|
|
110
|
-

|
|
111
103
|
|
|
112
104
|
Code 与 CRT 使用相同的后端 Agent 和 Session,切换界面不会创建第二个 Agent。Farming Code 仍是默认界面,也是受支持的手机界面。控制方式和完整流程见 [Farming CRT 指南](./docs/products/crt/README.zh_cn.md)。
|
|
113
105
|
|
|
@@ -117,25 +109,11 @@ Farming Net 是独立、带 Token 鉴权的 Farming 部署目录。它提供一
|
|
|
117
109
|
|
|
118
110
|
## 安装与更新
|
|
119
111
|
|
|
120
|
-
|
|
112
|
+
按上面的快速开始命令安装即可。对于 npm 安装,**Settings → Updates** 会在当前 Server 继续运行时准备所选版本,确认后再应用更新;原有启动 Token 保持不变。
|
|
121
113
|
|
|
122
|
-
|
|
123
|
-
farming status
|
|
124
|
-
farming url
|
|
125
|
-
farming logs
|
|
126
|
-
farming stop
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
启动 Token 存放在 `~/.farming/.session-token`,重启和升级会继续复用。**Settings → Updates** 为 npm 安装提供两阶段更新:点击**准备**后,所选版本会安装到独立 staging 目录,当前服务继续运行;准备完成后,只有用户点击**重启并应用**,Farming 才会切换包目录并重启。如果新服务启动失败,Farming 会恢复旧包目录。npm 准备阶段会先使用机器配置的 registry;只有该 registry 缺少所选版本时,才会回退到 Settings 展示版本所用的 registry。准备和应用更新前,Farming 都会校验 npm 目标包根目录与启动当前服务的包根目录一致。GitHub Releases 也提供独立 CLI 和目录 Bundle;当前产物见 [GitHub Releases](https://github.com/zhuwenzhuang/farming/releases)。
|
|
130
|
-
|
|
131
|
-
从源码运行,并使用相同的端口与 Base Path:
|
|
132
|
-
|
|
133
|
-
```bash
|
|
134
|
-
npm install
|
|
135
|
-
PORT=6694 FARMING_BASE_PATH=/farming npm start
|
|
136
|
-
```
|
|
114
|
+

|
|
137
115
|
|
|
138
|
-
|
|
116
|
+
独立 CLI 和目录 Bundle 仍可从 [GitHub Releases](https://github.com/zhuwenzhuang/farming/releases) 下载。
|
|
139
117
|
|
|
140
118
|
## 安全
|
|
141
119
|
|
package/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -15,8 +15,6 @@ preserved in bundled `node_modules` when bundled dependencies are enabled.
|
|
|
15
15
|
| Package | Version | License | Purpose |
|
|
16
16
|
| --- | --- | --- | --- |
|
|
17
17
|
| `@agentclientprotocol/sdk` | 1.2.1 | Apache-2.0 | ACP JSON-RPC client and protocol types |
|
|
18
|
-
| `@agentclientprotocol/codex-acp` | 1.1.4 | Apache-2.0 | Codex ACP adapter |
|
|
19
|
-
| `@agentclientprotocol/claude-agent-acp` | 0.59.0 | Apache-2.0 | Claude Code ACP adapter |
|
|
20
18
|
| `@modelcontextprotocol/sdk` | 1.29.0 | MIT | Browser Extension MCP server and stdio transport |
|
|
21
19
|
| `@xterm/addon-clipboard` | 0.2.0 | MIT | Browser terminal clipboard integration |
|
|
22
20
|
| `@xterm/addon-fit` | 0.11.0 | MIT | Browser terminal sizing |
|
|
@@ -47,12 +45,32 @@ preserved in bundled `node_modules` when bundled dependencies are enabled.
|
|
|
47
45
|
| `remark-gfm` | 4.0.1 | MIT | GitHub Flavored Markdown support |
|
|
48
46
|
| `remark-math` | 6.0.0 | MIT | Markdown math syntax support |
|
|
49
47
|
| `ripgrep` | 0.3.1 | MIT | Node wrapper for file search support |
|
|
48
|
+
| `tar` | 7.5.19 | BlueOak-1.0.0 | Safe extraction of version-locked startup dependency archives |
|
|
50
49
|
| `ws` | 8.21.0 | MIT | WebSocket server/client support |
|
|
51
50
|
| `yaml` | 2.9.0 | ISC | YAML parsing |
|
|
52
51
|
| `zod` | 3.25.76 | MIT | MCP tool input schema validation |
|
|
53
52
|
|
|
54
53
|
## Vendored Assets
|
|
55
54
|
|
|
55
|
+
Farming builds version- and SHA-256-locked ACP runtime files from these exact
|
|
56
|
+
development dependencies and ships their license texts beside the runtime
|
|
57
|
+
files under `dist/acp/`:
|
|
58
|
+
|
|
59
|
+
| Package | Version | License | Purpose |
|
|
60
|
+
| --- | --- | --- | --- |
|
|
61
|
+
| `@agentclientprotocol/codex-acp` | 1.1.4 | Apache-2.0 | Reviewed and patched Codex ACP adapter |
|
|
62
|
+
| `@openai/codex` | 0.144.6 | Apache-2.0 | Codex executable discovery and launch bridge; platform CLI binaries are excluded |
|
|
63
|
+
| `@agentclientprotocol/claude-agent-acp` | 0.59.0 | Apache-2.0 | Claude Code ACP adapter |
|
|
64
|
+
| `@anthropic-ai/claude-agent-sdk` | 0.3.207 | Anthropic commercial terms | Claude Agent SDK bridge; platform CLI binaries are excluded |
|
|
65
|
+
|
|
66
|
+
Before starting a fresh Server, Farming may prepare the exact Codex CLI
|
|
67
|
+
0.144.6, Claude Agent SDK CLI package 0.3.207, and `agent-browser` 0.32.3
|
|
68
|
+
platform artifacts in its private runtime cache. They are not part of the npm
|
|
69
|
+
or application package and remain independent works under their own terms.
|
|
70
|
+
Farming accepts only the version-locked sources and integrity values declared
|
|
71
|
+
by its release manifest. The `agent-browser` license text is included at
|
|
72
|
+
`backend/data/LICENSE.agent-browser`.
|
|
73
|
+
|
|
56
74
|
Farming vendors `ghostty-web` distribution files under
|
|
57
75
|
`frontend/vendor/ghostty-web` for the optional Ghostty debug renderer. The
|
|
58
76
|
source package is `ghostty-web` 0.4.0, licensed under MIT. Its license is kept
|
|
@@ -114,10 +132,11 @@ their code.
|
|
|
114
132
|
|
|
115
133
|
Farming can launch or observe coding-agent CLIs installed by the user, including
|
|
116
134
|
OpenCode and the terminal forms of OpenAI Codex CLI and Anthropic Claude Code.
|
|
117
|
-
The
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
135
|
+
The structured ACP runtime includes reviewed Codex and Claude adapter code, but
|
|
136
|
+
Farming does not bundle a platform Codex or Claude Code CLI inside its package.
|
|
137
|
+
The adapters use the exact system or verified startup-cache executable prepared
|
|
138
|
+
by the Farming launcher. Those tools remain separate projects governed by their
|
|
139
|
+
own terms and licenses.
|
|
121
140
|
|
|
122
141
|
Farming is not affiliated with, sponsored by, or endorsed by OpenAI, Anthropic,
|
|
123
142
|
Microsoft, or the maintainers of the third-party projects listed above.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const PACKAGED_CLAUDE_ACP_ARG = '--farming-claude-acp';
|
|
2
|
+
|
|
3
|
+
async function runPackagedClaudeAcp() {
|
|
4
|
+
if (!process.pkg) {
|
|
5
|
+
throw new Error('The packaged Claude ACP entry is available only in a standalone Farming CLI');
|
|
6
|
+
}
|
|
7
|
+
throw new Error('The standalone Farming CLI omitted its embedded Claude ACP runtime');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
module.exports = {
|
|
11
|
+
PACKAGED_CLAUDE_ACP_ARG,
|
|
12
|
+
runPackagedClaudeAcp,
|
|
13
|
+
};
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
const PACKAGED_CODEX_ACP_ARG = '--farming-codex-acp';
|
|
2
2
|
|
|
3
|
-
function runPackagedCodexAcp() {
|
|
3
|
+
async function runPackagedCodexAcp() {
|
|
4
4
|
if (!process.pkg) {
|
|
5
5
|
throw new Error('The packaged Codex ACP entry is available only in a standalone Farming CLI');
|
|
6
6
|
}
|
|
7
|
-
|
|
7
|
+
throw new Error('The standalone Farming CLI omitted its embedded Codex ACP runtime');
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
module.exports = {
|
package/backend/acp-runtime.js
CHANGED
|
@@ -12,6 +12,7 @@ const { AcpSessionState } = require('./acp-session-state');
|
|
|
12
12
|
const { readCodexHistoryImageData } = require('./codex-transcript');
|
|
13
13
|
const { AcpClientFileSystem, AcpClientTerminalManager } = require('./acp/client-services');
|
|
14
14
|
const { PACKAGED_CODEX_ACP_ARG } = require('./acp/packaged-codex-acp');
|
|
15
|
+
const { PACKAGED_CLAUDE_ACP_ARG } = require('./acp/packaged-claude-acp');
|
|
15
16
|
const { permissionSecurityWarnings } = require('./acp/permission-security');
|
|
16
17
|
const { patchBlock, rejectPatch } = require('./acp/patch-decisions');
|
|
17
18
|
const { getProviderAdapter, listProviderAdapters } = require('./provider-adapters');
|
|
@@ -36,12 +37,22 @@ const CODEX_STEER_METHOD = '_codex/session/steer';
|
|
|
36
37
|
const CODEX_ACP_PACKAGE = '@agentclientprotocol/codex-acp';
|
|
37
38
|
const CODEX_ACP_VERSION = '1.1.4';
|
|
38
39
|
const CODEX_ACP_SHA256 = 'e36876cc2250737c719644e6c69ab054a3f8b58071fad45c44407013082942ff';
|
|
40
|
+
const CLAUDE_ACP_PACKAGE = '@agentclientprotocol/claude-agent-acp';
|
|
41
|
+
const CLAUDE_ACP_VERSION = '0.59.0';
|
|
42
|
+
const CLAUDE_ACP_SHA256 = 'a6aa515dd02382617bf46d9eac47b8a1022c6835bcf7a8d61e2c63939be2e49c';
|
|
39
43
|
const CODEX_ACP_VENDOR_ENTRY = path.join(
|
|
40
44
|
__dirname,
|
|
41
45
|
'..',
|
|
42
46
|
'dist',
|
|
43
47
|
'acp',
|
|
44
|
-
`codex-acp-${CODEX_ACP_VERSION}.
|
|
48
|
+
`codex-acp-${CODEX_ACP_VERSION}.mjs`,
|
|
49
|
+
);
|
|
50
|
+
const CLAUDE_ACP_VENDOR_ENTRY = path.join(
|
|
51
|
+
__dirname,
|
|
52
|
+
'..',
|
|
53
|
+
'dist',
|
|
54
|
+
'acp',
|
|
55
|
+
`claude-agent-acp-${CLAUDE_ACP_VERSION}.mjs`,
|
|
45
56
|
);
|
|
46
57
|
const execFileAsync = promisify(execFile);
|
|
47
58
|
|
|
@@ -70,6 +81,15 @@ function adapterEntry(packageName) {
|
|
|
70
81
|
if (packageName === CODEX_ACP_PACKAGE && fs.existsSync(CODEX_ACP_VENDOR_ENTRY)) {
|
|
71
82
|
return verifiedCodexAcpEntry(CODEX_ACP_VENDOR_ENTRY);
|
|
72
83
|
}
|
|
84
|
+
if (packageName === CLAUDE_ACP_PACKAGE && fs.existsSync(CLAUDE_ACP_VENDOR_ENTRY)) {
|
|
85
|
+
const actualSha256 = fileSha256(CLAUDE_ACP_VENDOR_ENTRY);
|
|
86
|
+
if (actualSha256 !== CLAUDE_ACP_SHA256) {
|
|
87
|
+
throw new Error(
|
|
88
|
+
`Claude ACP runtime failed integrity verification: expected ${CLAUDE_ACP_SHA256}, found ${actualSha256}`,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
return CLAUDE_ACP_VENDOR_ENTRY;
|
|
92
|
+
}
|
|
73
93
|
let sdkDirectory;
|
|
74
94
|
try {
|
|
75
95
|
sdkDirectory = path.dirname(runtimeRequire.resolve('@agentclientprotocol/sdk'));
|
|
@@ -109,7 +129,11 @@ function resolveAcpLaunch(provider, options = {}) {
|
|
|
109
129
|
const launch = nodeAdapterLaunch(
|
|
110
130
|
adapterEntry(adapter.acp.packageName),
|
|
111
131
|
options.runtimeEnv || process.env,
|
|
112
|
-
adapter.acp.packageName === CODEX_ACP_PACKAGE
|
|
132
|
+
adapter.acp.packageName === CODEX_ACP_PACKAGE
|
|
133
|
+
? PACKAGED_CODEX_ACP_ARG
|
|
134
|
+
: adapter.acp.packageName === CLAUDE_ACP_PACKAGE
|
|
135
|
+
? PACKAGED_CLAUDE_ACP_ARG
|
|
136
|
+
: '',
|
|
113
137
|
);
|
|
114
138
|
return { ...launch, version: adapter.acp.version };
|
|
115
139
|
}
|
|
@@ -2508,6 +2532,17 @@ class AcpRuntime extends EventEmitter {
|
|
|
2508
2532
|
return visible;
|
|
2509
2533
|
}
|
|
2510
2534
|
|
|
2535
|
+
getTranscriptEntry(agentId, entryId) {
|
|
2536
|
+
const binding = this.requireBinding(agentId);
|
|
2537
|
+
const entries = (binding.sessionState?.entries || []).filter(candidate => (
|
|
2538
|
+
String(candidate?.id || '') === String(entryId || '')
|
|
2539
|
+
));
|
|
2540
|
+
if (entries.length !== 1) return null;
|
|
2541
|
+
const [entry] = entries;
|
|
2542
|
+
if (!entry || binding.sessionState.isInternalEntry(entry)) return null;
|
|
2543
|
+
return entry;
|
|
2544
|
+
}
|
|
2545
|
+
|
|
2511
2546
|
getPatchDecision(agentId, toolCallId, requestedPath) {
|
|
2512
2547
|
const binding = this.requireBinding(agentId);
|
|
2513
2548
|
return binding.patchDecisions.get(`${String(toolCallId || '')}\n${String(requestedPath || '')}`) || '';
|
|
@@ -1,12 +1,31 @@
|
|
|
1
|
+
const crypto = require('crypto');
|
|
1
2
|
const { createTwoFilesPatch, diffLines } = require('diff');
|
|
2
3
|
|
|
3
4
|
const MAX_RENDERED_DIFF_CHARS = 64 * 1024;
|
|
4
5
|
const MAX_INLINE_TOOL_DETAIL_CHARS = 4 * 1024;
|
|
6
|
+
const MAX_TRANSCRIPT_INLINE_TOOL_DETAIL_CHARS = 64 * 1024;
|
|
7
|
+
const MAX_TRANSCRIPT_MEDIA_BYTES = 25 * 1024 * 1024;
|
|
8
|
+
const MAX_TRANSCRIPT_MEDIA_BASE64_CHARS = Math.ceil(MAX_TRANSCRIPT_MEDIA_BYTES / 3) * 4;
|
|
5
9
|
const MAX_EMBEDDED_RESOURCE_TEXT_CHARS = 4 * 1024;
|
|
6
10
|
const MAX_COLLABORATION_AGENTS = 16;
|
|
7
11
|
const MAX_COLLABORATION_ID_CHARS = 160;
|
|
8
12
|
const MAX_COLLABORATION_PATH_CHARS = 512;
|
|
9
13
|
const MAX_COLLABORATION_MESSAGE_CHARS = 160;
|
|
14
|
+
const TRANSCRIPT_MEDIA_MIME_TYPES = new Set([
|
|
15
|
+
'audio/aac',
|
|
16
|
+
'audio/flac',
|
|
17
|
+
'audio/mp4',
|
|
18
|
+
'audio/mpeg',
|
|
19
|
+
'audio/ogg',
|
|
20
|
+
'audio/wav',
|
|
21
|
+
'audio/wave',
|
|
22
|
+
'audio/webm',
|
|
23
|
+
'audio/x-wav',
|
|
24
|
+
'image/gif',
|
|
25
|
+
'image/jpeg',
|
|
26
|
+
'image/png',
|
|
27
|
+
'image/webp',
|
|
28
|
+
]);
|
|
10
29
|
|
|
11
30
|
function diffBlocks(content) {
|
|
12
31
|
return (Array.isArray(content) ? content : [])
|
|
@@ -184,7 +203,92 @@ function transcriptResource(resourceValue) {
|
|
|
184
203
|
};
|
|
185
204
|
}
|
|
186
205
|
|
|
187
|
-
function
|
|
206
|
+
function transcriptMediaValue(block) {
|
|
207
|
+
if (!block || typeof block !== 'object') return null;
|
|
208
|
+
if (['image', 'audio'].includes(block.type)) return block;
|
|
209
|
+
if (
|
|
210
|
+
block.type === 'content'
|
|
211
|
+
&& block.content
|
|
212
|
+
&& typeof block.content === 'object'
|
|
213
|
+
&& ['image', 'audio'].includes(block.content.type)
|
|
214
|
+
) {
|
|
215
|
+
return block.content;
|
|
216
|
+
}
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function validatedTranscriptMedia(block) {
|
|
221
|
+
const type = String(block?.type || '');
|
|
222
|
+
const data = typeof block?.data === 'string' ? block.data : '';
|
|
223
|
+
const mimeType = String(block?.mimeType || '').trim().toLowerCase();
|
|
224
|
+
if (
|
|
225
|
+
!['image', 'audio'].includes(type)
|
|
226
|
+
|| !data
|
|
227
|
+
|| data.length > MAX_TRANSCRIPT_MEDIA_BASE64_CHARS
|
|
228
|
+
|| data.length % 4 !== 0
|
|
229
|
+
|| !TRANSCRIPT_MEDIA_MIME_TYPES.has(mimeType)
|
|
230
|
+
|| !/^[a-z0-9+/]*={0,2}$/i.test(data)
|
|
231
|
+
) return null;
|
|
232
|
+
const paddingChars = data.endsWith('==') ? 2 : (data.endsWith('=') ? 1 : 0);
|
|
233
|
+
const decodedBytes = (data.length / 4) * 3 - paddingChars;
|
|
234
|
+
if (decodedBytes <= 0 || decodedBytes > MAX_TRANSCRIPT_MEDIA_BYTES) return null;
|
|
235
|
+
return { data, decodedBytes, mimeType, type };
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function transcriptMediaId(block) {
|
|
239
|
+
const media = validatedTranscriptMedia(block);
|
|
240
|
+
if (!media) return '';
|
|
241
|
+
return crypto.createHash('sha256')
|
|
242
|
+
.update(media.type)
|
|
243
|
+
.update('\0')
|
|
244
|
+
.update(media.mimeType)
|
|
245
|
+
.update('\0')
|
|
246
|
+
.update(media.data)
|
|
247
|
+
.digest('hex');
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function decodeAcpTranscriptMedia(block) {
|
|
251
|
+
const media = validatedTranscriptMedia(block);
|
|
252
|
+
if (!media) return null;
|
|
253
|
+
const content = Buffer.from(media.data, 'base64');
|
|
254
|
+
if (content.length !== media.decodedBytes) return null;
|
|
255
|
+
return {
|
|
256
|
+
content,
|
|
257
|
+
mimeType: media.mimeType,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function compactTranscriptMedia(block, entry, options = {}) {
|
|
262
|
+
const mediaPathPrefix = String(options.mediaPathPrefix || '').replace(/\/+$/, '');
|
|
263
|
+
if (!mediaPathPrefix) return JSON.parse(JSON.stringify(block));
|
|
264
|
+
const hasInlineData = typeof block?.data === 'string' && block.data.length > 0;
|
|
265
|
+
if (!hasInlineData) return JSON.parse(JSON.stringify(block));
|
|
266
|
+
const mediaId = transcriptMediaId(block);
|
|
267
|
+
const projected = { ...block };
|
|
268
|
+
delete projected.data;
|
|
269
|
+
if (!mediaId) return projected;
|
|
270
|
+
projected.url = `${mediaPathPrefix}/${encodeURIComponent(String(entry?.id || ''))}/${mediaId}`;
|
|
271
|
+
return projected;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function acpTranscriptMedia(entry, requestedMediaId) {
|
|
275
|
+
const mediaId = String(requestedMediaId || '').trim().toLowerCase();
|
|
276
|
+
if (!/^[a-f0-9]{64}$/.test(mediaId)) return null;
|
|
277
|
+
const direct = Array.isArray(entry?.content) ? entry.content : [];
|
|
278
|
+
const output = entry?.rawOutput && typeof entry.rawOutput === 'object' ? entry.rawOutput : {};
|
|
279
|
+
const result = output?.result && typeof output.result === 'object' ? output.result : {};
|
|
280
|
+
const raw = Array.isArray(result.content)
|
|
281
|
+
? result.content
|
|
282
|
+
: (Array.isArray(output.content) ? output.content : []);
|
|
283
|
+
for (const block of [...direct, ...raw]) {
|
|
284
|
+
const media = transcriptMediaValue(block);
|
|
285
|
+
if (!media) continue;
|
|
286
|
+
if (transcriptMediaId(media) === mediaId) return media;
|
|
287
|
+
}
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function transcriptMediaBlocks(entry, options = {}) {
|
|
188
292
|
const direct = Array.isArray(entry?.content) ? entry.content : [];
|
|
189
293
|
const output = entry?.rawOutput && typeof entry.rawOutput === 'object' ? entry.rawOutput : {};
|
|
190
294
|
const result = output?.result && typeof output.result === 'object' ? output.result : {};
|
|
@@ -194,7 +298,10 @@ function transcriptMediaBlocks(entry) {
|
|
|
194
298
|
const blocks = [...direct, ...raw].flatMap(block => {
|
|
195
299
|
if (!block || typeof block !== 'object') return [];
|
|
196
300
|
if (block.type === 'terminal') return [{ type: 'terminal', terminalId: String(block.terminalId || '') }];
|
|
197
|
-
if (['image', 'audio'
|
|
301
|
+
if (['image', 'audio'].includes(block.type)) {
|
|
302
|
+
return [compactTranscriptMedia(block, entry, options)];
|
|
303
|
+
}
|
|
304
|
+
if (block.type === 'resource_link') return [JSON.parse(JSON.stringify(block))];
|
|
198
305
|
if (block.type === 'resource') {
|
|
199
306
|
return [{
|
|
200
307
|
type: 'resource',
|
|
@@ -203,7 +310,11 @@ function transcriptMediaBlocks(entry) {
|
|
|
203
310
|
}
|
|
204
311
|
if (block.type !== 'content' || !block.content || typeof block.content !== 'object') return [];
|
|
205
312
|
const content = block.content;
|
|
206
|
-
if (['image', 'audio'
|
|
313
|
+
if (['image', 'audio'].includes(content.type)) {
|
|
314
|
+
const projected = compactTranscriptMedia(content, entry, options);
|
|
315
|
+
return [{ type: 'content', content: projected }];
|
|
316
|
+
}
|
|
317
|
+
if (content.type === 'resource_link') {
|
|
207
318
|
return [{ type: 'content', content: JSON.parse(JSON.stringify(content)) }];
|
|
208
319
|
}
|
|
209
320
|
if (content.type === 'resource') {
|
|
@@ -226,6 +337,20 @@ function transcriptMediaBlocks(entry) {
|
|
|
226
337
|
});
|
|
227
338
|
}
|
|
228
339
|
|
|
340
|
+
function transcriptEntryWithCompactMedia(entry, options = {}) {
|
|
341
|
+
const mediaPathPrefix = String(options.mediaPathPrefix || '').trim();
|
|
342
|
+
if (!mediaPathPrefix || !Array.isArray(entry?.content)) return entry;
|
|
343
|
+
const content = entry.content.map(block => {
|
|
344
|
+
const media = transcriptMediaValue(block);
|
|
345
|
+
if (!media) return block;
|
|
346
|
+
const projected = compactTranscriptMedia(media, entry, options);
|
|
347
|
+
return block?.type === 'content'
|
|
348
|
+
? { ...block, content: projected }
|
|
349
|
+
: projected;
|
|
350
|
+
});
|
|
351
|
+
return { ...entry, content };
|
|
352
|
+
}
|
|
353
|
+
|
|
229
354
|
function generatedMediaTool(entry) {
|
|
230
355
|
const title = String(entry?.title || '').trim().toLowerCase();
|
|
231
356
|
const id = String(entry?.id || '').trim().toLowerCase();
|
|
@@ -296,9 +421,13 @@ function transcriptCodexToolMeta(entry) {
|
|
|
296
421
|
return Object.keys(compact).length > 0 ? compact : null;
|
|
297
422
|
}
|
|
298
423
|
|
|
299
|
-
function acpTranscriptToolEntry(entry) {
|
|
424
|
+
function acpTranscriptToolEntry(entry, options = {}) {
|
|
300
425
|
if (!entry || entry.type !== 'tool') return entry;
|
|
301
426
|
const detail = detailForTool(entry);
|
|
427
|
+
const requestedInlineDetailChars = Number(options.maxInlineDetailChars);
|
|
428
|
+
const inlineDetailChars = Number.isFinite(requestedInlineDetailChars)
|
|
429
|
+
? Math.max(0, Math.min(MAX_INLINE_TOOL_DETAIL_CHARS, Math.floor(requestedInlineDetailChars)))
|
|
430
|
+
: MAX_INLINE_TOOL_DETAIL_CHARS;
|
|
302
431
|
const changes = patchChanges(entry.content).map(change => ({
|
|
303
432
|
path: change.path,
|
|
304
433
|
kind: change.kind,
|
|
@@ -313,7 +442,11 @@ function acpTranscriptToolEntry(entry) {
|
|
|
313
442
|
.join('\n');
|
|
314
443
|
const meta = {};
|
|
315
444
|
if (entry?._meta?.subagent_session_info) {
|
|
316
|
-
|
|
445
|
+
const sessionId = boundedCollaborationString(
|
|
446
|
+
entry._meta.subagent_session_info.session_id,
|
|
447
|
+
MAX_COLLABORATION_ID_CHARS
|
|
448
|
+
);
|
|
449
|
+
if (sessionId) meta.subagent_session_info = { session_id: sessionId };
|
|
317
450
|
}
|
|
318
451
|
if (entry?._meta?.farming_patch_decisions) {
|
|
319
452
|
meta.farming_patch_decisions = JSON.parse(JSON.stringify(entry._meta.farming_patch_decisions));
|
|
@@ -326,12 +459,12 @@ function acpTranscriptToolEntry(entry) {
|
|
|
326
459
|
title: String(entry.title || ''),
|
|
327
460
|
kind: String(entry.kind || 'other'),
|
|
328
461
|
status: String(entry.status || ''),
|
|
329
|
-
content: transcriptMediaBlocks(entry),
|
|
462
|
+
content: transcriptMediaBlocks(entry, options),
|
|
330
463
|
...(Object.keys(meta).length > 0 ? { _meta: meta } : {}),
|
|
331
|
-
transcriptDetail: detail.length <=
|
|
464
|
+
transcriptDetail: detail.length <= inlineDetailChars
|
|
332
465
|
? detail
|
|
333
|
-
: `${detail.slice(0,
|
|
334
|
-
transcriptDetailTruncated: detail.length >
|
|
466
|
+
: `${detail.slice(0, inlineDetailChars)}${inlineDetailChars > 0 ? '\n\n' : ''}[Open to load full detail]`,
|
|
467
|
+
transcriptDetailTruncated: detail.length > inlineDetailChars,
|
|
335
468
|
transcriptPatchSummary: patchSummary,
|
|
336
469
|
transcriptChanges: changes,
|
|
337
470
|
generatedMedia: generatedMediaTool(entry),
|
|
@@ -339,9 +472,41 @@ function acpTranscriptToolEntry(entry) {
|
|
|
339
472
|
};
|
|
340
473
|
}
|
|
341
474
|
|
|
475
|
+
function acpTranscriptEntries(entries, options = {}) {
|
|
476
|
+
const requestedBudget = Number(options.maxInlineToolDetailChars);
|
|
477
|
+
let remainingInlineDetailChars = Number.isFinite(requestedBudget)
|
|
478
|
+
? Math.max(0, Math.floor(requestedBudget))
|
|
479
|
+
: MAX_TRANSCRIPT_INLINE_TOOL_DETAIL_CHARS;
|
|
480
|
+
const source = Array.isArray(entries) ? entries : [];
|
|
481
|
+
const projected = new Array(source.length);
|
|
482
|
+
|
|
483
|
+
// The transcript page owns only a bounded summary. Exact tool and subagent
|
|
484
|
+
// detail remains backend-owned and is loaded on explicit expansion.
|
|
485
|
+
for (let index = source.length - 1; index >= 0; index -= 1) {
|
|
486
|
+
const entry = source[index];
|
|
487
|
+
if (entry?.type !== 'tool') {
|
|
488
|
+
projected[index] = transcriptEntryWithCompactMedia(entry, options);
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
491
|
+
const projectedEntry = acpTranscriptToolEntry(entry, {
|
|
492
|
+
maxInlineDetailChars: remainingInlineDetailChars,
|
|
493
|
+
mediaPathPrefix: options.mediaPathPrefix,
|
|
494
|
+
});
|
|
495
|
+
remainingInlineDetailChars = Math.max(
|
|
496
|
+
0,
|
|
497
|
+
remainingInlineDetailChars - String(projectedEntry.transcriptDetail || '').length
|
|
498
|
+
);
|
|
499
|
+
projected[index] = projectedEntry;
|
|
500
|
+
}
|
|
501
|
+
return projected;
|
|
502
|
+
}
|
|
503
|
+
|
|
342
504
|
module.exports = {
|
|
505
|
+
acpTranscriptEntries,
|
|
506
|
+
acpTranscriptMedia,
|
|
343
507
|
acpToolChanges: entry => patchChanges(entry?.content, { includeDiff: true }),
|
|
344
508
|
acpToolDetail: detailForTool,
|
|
345
509
|
acpToolReviewChanges: entry => patchReviewChanges(entry?.content),
|
|
346
510
|
acpTranscriptToolEntry,
|
|
511
|
+
decodeAcpTranscriptMedia,
|
|
347
512
|
};
|
package/backend/agent-manager.js
CHANGED
|
@@ -50,7 +50,13 @@ const {
|
|
|
50
50
|
stopPersistedAcpProcessGroup,
|
|
51
51
|
} = require('./acp-runtime');
|
|
52
52
|
const { chatRuntimeForProvider, isChatMode } = require('./chat-runtime');
|
|
53
|
-
const {
|
|
53
|
+
const {
|
|
54
|
+
acpTranscriptEntries,
|
|
55
|
+
acpTranscriptMedia,
|
|
56
|
+
acpToolChanges,
|
|
57
|
+
acpToolDetail,
|
|
58
|
+
acpToolReviewChanges,
|
|
59
|
+
} = require('./acp-transcript');
|
|
54
60
|
const {
|
|
55
61
|
applyCodexTerminalProfile,
|
|
56
62
|
codexTerminalProfileFromOutput,
|
|
@@ -5085,10 +5091,21 @@ class AgentManager extends EventEmitter {
|
|
|
5085
5091
|
const transcript = this.acpRuntime.getTranscriptSession(agentId, options);
|
|
5086
5092
|
return {
|
|
5087
5093
|
...transcript,
|
|
5088
|
-
entries: transcript.entries
|
|
5094
|
+
entries: acpTranscriptEntries(transcript.entries, {
|
|
5095
|
+
mediaPathPrefix: options.mediaPathPrefix,
|
|
5096
|
+
}),
|
|
5089
5097
|
};
|
|
5090
5098
|
}
|
|
5091
5099
|
|
|
5100
|
+
getAcpTranscriptMedia(agentId, entryId, mediaId) {
|
|
5101
|
+
this.requireLiveAcpAgent(agentId);
|
|
5102
|
+
const entry = this.acpRuntime.getTranscriptEntry(agentId, entryId);
|
|
5103
|
+
if (!entry) throw new Error('ACP transcript entry not found');
|
|
5104
|
+
const media = acpTranscriptMedia(entry, mediaId);
|
|
5105
|
+
if (!media) throw new Error('ACP transcript media not found');
|
|
5106
|
+
return media;
|
|
5107
|
+
}
|
|
5108
|
+
|
|
5092
5109
|
getAcpToolDetail(agentId, toolCallId) {
|
|
5093
5110
|
this.requireLiveAcpAgent(agentId);
|
|
5094
5111
|
const entry = this.acpRuntime.getToolEntry(agentId, toolCallId);
|
|
@@ -5661,6 +5678,14 @@ class AgentManager extends EventEmitter {
|
|
|
5661
5678
|
if (admission) {
|
|
5662
5679
|
transitionLifecycleOperation(staged, admission.operation.id, 'succeeded');
|
|
5663
5680
|
}
|
|
5681
|
+
if (!admission && !structuralUpdateChanged && !readUpdateChanged) {
|
|
5682
|
+
return {
|
|
5683
|
+
agentId,
|
|
5684
|
+
...updates,
|
|
5685
|
+
changed: false,
|
|
5686
|
+
requiresState: false,
|
|
5687
|
+
};
|
|
5688
|
+
}
|
|
5664
5689
|
try {
|
|
5665
5690
|
this.ensurePersistentAgentSession(staged);
|
|
5666
5691
|
} catch (error) {
|