@qwen-code/arena-agent 0.0.10-dev.1 → 0.0.10-dev.11

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.
Files changed (162) hide show
  1. package/.env.example +68 -14
  2. package/CHANGELOG.md +245 -0
  3. package/README.md +307 -57
  4. package/dist/agent.d.ts +28 -2
  5. package/dist/agent.js +389 -109
  6. package/dist/agent.js.map +1 -1
  7. package/dist/anthropic.js +32 -35
  8. package/dist/anthropic.js.map +1 -1
  9. package/dist/ask-user.js +2 -0
  10. package/dist/ask-user.js.map +1 -1
  11. package/dist/attachments.js +4 -3
  12. package/dist/attachments.js.map +1 -1
  13. package/dist/brave-search.js +1 -3
  14. package/dist/brave-search.js.map +1 -1
  15. package/dist/canonical-data.d.ts +2 -0
  16. package/dist/canonical-data.js +4 -2
  17. package/dist/canonical-data.js.map +1 -1
  18. package/dist/cli.js +184 -36
  19. package/dist/cli.js.map +1 -1
  20. package/dist/code-tools.d.ts +8 -2
  21. package/dist/code-tools.js +709 -117
  22. package/dist/code-tools.js.map +1 -1
  23. package/dist/compaction.d.ts +50 -3
  24. package/dist/compaction.js +390 -185
  25. package/dist/compaction.js.map +1 -1
  26. package/dist/config.d.ts +23 -2
  27. package/dist/config.js +52 -5
  28. package/dist/config.js.map +1 -1
  29. package/dist/connector-mcp.d.ts +47 -0
  30. package/dist/connector-mcp.js +314 -0
  31. package/dist/connector-mcp.js.map +1 -0
  32. package/dist/connectors.d.ts +8 -0
  33. package/dist/connectors.js +57 -0
  34. package/dist/connectors.js.map +1 -0
  35. package/dist/fetch-page.js +9 -33
  36. package/dist/fetch-page.js.map +1 -1
  37. package/dist/file-edit.d.ts +8 -0
  38. package/dist/file-edit.js +63 -0
  39. package/dist/file-edit.js.map +1 -0
  40. package/dist/history.d.ts +16 -3
  41. package/dist/history.js +117 -19
  42. package/dist/history.js.map +1 -1
  43. package/dist/midturn-checkpoint-hook.d.ts +44 -0
  44. package/dist/midturn-checkpoint-hook.js +38 -0
  45. package/dist/midturn-checkpoint-hook.js.map +1 -0
  46. package/dist/mock.js +5 -0
  47. package/dist/mock.js.map +1 -1
  48. package/dist/mode.js +4 -2
  49. package/dist/mode.js.map +1 -1
  50. package/dist/model-router-media.d.ts +1 -0
  51. package/dist/model-router-media.js +121 -1
  52. package/dist/model-router-media.js.map +1 -1
  53. package/dist/openai-responses.js +2 -0
  54. package/dist/openai-responses.js.map +1 -1
  55. package/dist/process-manager.js +2 -1
  56. package/dist/process-manager.js.map +1 -1
  57. package/dist/prompts.d.ts +2 -1
  58. package/dist/prompts.js +15 -37
  59. package/dist/prompts.js.map +1 -1
  60. package/dist/protocol.d.ts +5 -1
  61. package/dist/protocol.js +24 -2
  62. package/dist/protocol.js.map +1 -1
  63. package/dist/provider.d.ts +12 -5
  64. package/dist/provider.js +63 -52
  65. package/dist/provider.js.map +1 -1
  66. package/dist/repetitive-output.d.ts +12 -0
  67. package/dist/repetitive-output.js +80 -0
  68. package/dist/repetitive-output.js.map +1 -0
  69. package/dist/replay-responses.d.ts +17 -0
  70. package/dist/replay-responses.js +100 -0
  71. package/dist/replay-responses.js.map +1 -0
  72. package/dist/routify-web-fetch.js +20 -7
  73. package/dist/routify-web-fetch.js.map +1 -1
  74. package/dist/sandbox-file-write.d.ts +10 -0
  75. package/dist/sandbox-file-write.js +58 -0
  76. package/dist/sandbox-file-write.js.map +1 -0
  77. package/dist/server.js +17 -5
  78. package/dist/server.js.map +1 -1
  79. package/dist/session-memory-prompts.d.ts +5 -0
  80. package/dist/session-memory-prompts.js +7 -0
  81. package/dist/session-memory-prompts.js.map +1 -0
  82. package/dist/snapshot-client.d.ts +10 -0
  83. package/dist/snapshot-client.js +39 -0
  84. package/dist/snapshot-client.js.map +1 -0
  85. package/dist/spec.d.ts +5 -4
  86. package/dist/spec.js +31 -17
  87. package/dist/spec.js.map +1 -1
  88. package/dist/speech-audio.d.ts +9 -0
  89. package/dist/speech-audio.js +130 -0
  90. package/dist/speech-audio.js.map +1 -0
  91. package/dist/speech-model.d.ts +3 -0
  92. package/dist/speech-model.js +8 -0
  93. package/dist/speech-model.js.map +1 -0
  94. package/dist/summary-compaction.d.ts +21 -0
  95. package/dist/summary-compaction.js +211 -0
  96. package/dist/summary-compaction.js.map +1 -0
  97. package/dist/tools.d.ts +8 -1
  98. package/dist/tools.js +407 -135
  99. package/dist/tools.js.map +1 -1
  100. package/dist/tui.js +13 -10
  101. package/dist/tui.js.map +1 -1
  102. package/dist/types.d.ts +30 -3
  103. package/dist/voice-registry.d.ts +2 -0
  104. package/dist/voice-registry.js +10 -2
  105. package/dist/voice-registry.js.map +1 -1
  106. package/dist/web-fetch-output.d.ts +9 -0
  107. package/dist/web-fetch-output.js +38 -0
  108. package/dist/web-fetch-output.js.map +1 -0
  109. package/dist/workspace.d.ts +2 -0
  110. package/dist/workspace.js +11 -6
  111. package/dist/workspace.js.map +1 -1
  112. package/docs/BINARY_DISTRIBUTION.md +27 -16
  113. package/docs/MODES_AND_RELEASES.md +60 -6
  114. package/docs/SDK.md +5 -0
  115. package/modes/code-arena/capture-provenance.json +57 -0
  116. package/modes/code-arena/full-stack/system-prompt.txt +1 -1
  117. package/modes/code-arena/full-stack/template/eslint.config.mjs +3 -4
  118. package/modes/code-arena/full-stack/template/postcss.config.mjs +2 -2
  119. package/modes/code-arena/full-stack/tools.json +186 -186
  120. package/modes/code-arena/html/system-prompt.txt +1 -1
  121. package/modes/code-arena/html/tools.json +123 -123
  122. package/modes/code-arena/react/system-prompt.txt +4 -3
  123. package/modes/code-arena/react/template/package.json +4 -4
  124. package/modes/code-arena/react/template/src/App.tsx +1 -1
  125. package/modes/code-arena/react/template/src/main.tsx +1 -1
  126. package/modes/code-arena/react/template/tsconfig.json +6 -0
  127. package/modes/code-arena/react/template/vite.config.ts +1 -0
  128. package/modes/code-arena/react/tools.json +178 -184
  129. package/package.json +15 -4
  130. package/scripts/README.md +80 -0
  131. package/scripts/check-changelog.mjs +66 -0
  132. package/vendor/audio/darwin-arm64/FFmpeg.LICENSE +502 -0
  133. package/vendor/audio/darwin-arm64/LAME.LICENSE +481 -0
  134. package/vendor/audio/darwin-arm64/Opus.LICENSE +44 -0
  135. package/vendor/audio/darwin-arm64/ffmpeg.gz +0 -0
  136. package/vendor/audio/darwin-arm64/manifest.json +39 -0
  137. package/vendor/audio/darwin-x64/FFmpeg.LICENSE +502 -0
  138. package/vendor/audio/darwin-x64/LAME.LICENSE +481 -0
  139. package/vendor/audio/darwin-x64/Opus.LICENSE +44 -0
  140. package/vendor/audio/darwin-x64/ffmpeg.gz +0 -0
  141. package/vendor/audio/darwin-x64/manifest.json +39 -0
  142. package/vendor/audio/linux-arm64/FFmpeg.LICENSE +502 -0
  143. package/vendor/audio/linux-arm64/LAME.LICENSE +481 -0
  144. package/vendor/audio/linux-arm64/Opus.LICENSE +44 -0
  145. package/vendor/audio/linux-arm64/ffmpeg.gz +0 -0
  146. package/vendor/audio/linux-arm64/manifest.json +39 -0
  147. package/vendor/audio/linux-x64/FFmpeg.LICENSE +502 -0
  148. package/vendor/audio/linux-x64/LAME.LICENSE +481 -0
  149. package/vendor/audio/linux-x64/Opus.LICENSE +44 -0
  150. package/vendor/audio/linux-x64/ffmpeg.gz +0 -0
  151. package/vendor/audio/linux-x64/manifest.json +39 -0
  152. package/vendor/audio/sources/build-audio-encoder.sh +76 -0
  153. package/vendor/audio/sources/ffmpeg-7.0.2.tar.xz +0 -0
  154. package/vendor/audio/sources/lame-3.100.tar.gz +0 -0
  155. package/vendor/audio/sources/opus-1.5.2.tar.gz +0 -0
  156. package/vendor/audio/sources/pkgconf-2.3.0.tar.xz +0 -0
  157. package/vendor/audio/sources/sources.lock.json +30 -0
  158. package/vendor/audio/win32-x64/FFmpeg.LICENSE +502 -0
  159. package/vendor/audio/win32-x64/LAME.LICENSE +481 -0
  160. package/vendor/audio/win32-x64/Opus.LICENSE +44 -0
  161. package/vendor/audio/win32-x64/ffmpeg.gz +0 -0
  162. package/vendor/audio/win32-x64/manifest.json +39 -0
package/README.md CHANGED
@@ -1,13 +1,19 @@
1
1
  # Arena CodeAgent Harness
2
2
 
3
- 这是一个基于 `message.json` 与 `tools.json` 所定义消息和工具契约的 TypeScript
3
+ 版本变化见 [Changelog](CHANGELOG.md),发布前请按[发版流程](docs/MODES_AND_RELEASES.md#发布节奏)
4
+ 更新变更记录并运行 `npm run check:changelog`。
5
+
6
+ 这是一个基于各模式正式提示词与工具契约的 TypeScript
4
7
  CodeAgent harness。运行时只依赖 Node.js,不再依赖 Python:
5
8
 
6
- - `arena -p ...`:headless 单轮入口,支持文本、完整 JSON 和 NDJSON 流。
9
+ - `arena -p ...` / `arena --file prompt.txt`:headless 单轮入口,支持文本、完整 JSON 和 NDJSON 流。
7
10
  - `arena tui`:终端多轮对话,实时输出并自动保存、恢复历史。
8
11
  - `arena serve`:HTTP daemon、Web UI 与双通道 SSE 实时接口。
9
12
  - Agent loop:流式 assistant → tool call → tool result → assistant。
10
- - 15 个工具按照 `tools.json` 中的 Draft-07 schema 提供参数校验。
13
+ - Agent Arena 的 15 个工具按照 `spec/tools.json` 中的 Draft-07 schema 提供参数校验;Code Arena 使用各模式的工具契约。
14
+
15
+ 查看代码或修改配置前,可先看[源码入口与历史资料](docs/MODES_AND_RELEASES.md#源码入口与历史资料),
16
+ 其中区分了当前提示词、生成文件、测试样本和历史审计脚本。
11
17
 
12
18
  本文提及的第三方产品、模型与服务名称仅用于说明兼容性和配置方式,不表示相关
13
19
  权利人对本项目的认可或背书;相关商标归各自权利人所有。使用者应自行确认并遵守
@@ -72,6 +78,10 @@ Linux libc 分别构建;不能把 macOS 上生成的文件直接用于 Linux
72
78
  | HTML | `--mode html` | 空工作区,生成单个 `index.html` |
73
79
  | Full Stack | `--mode full-stack` | Next.js + PostgreSQL scaffold |
74
80
 
81
+ Code Arena 默认模型步数上限为 React `75`、HTML `25`、Full Stack `75`。
82
+ 三个模式的 `read_file` 都能向支持视觉输入的模型提供不超过 10 MB 的 PNG/JPEG 图片;
83
+ 其他二进制文件仍会返回错误。
84
+
75
85
  四种模式共用同一套配置、provider、agent loop、上下文压缩、历史记录、服务端、
76
86
  TUI 和 SDK,只替换模式对应的 system prompt、tools 与可选 scaffold:
77
87
 
@@ -86,10 +96,106 @@ code-harness -p '做一个产品页'
86
96
  code-harness -p '做一个单文件网页' --mode html
87
97
  ```
88
98
 
99
+ 当提示词较大时,可以把 UTF-8 文本写入文件,并让 CLI 在进程启动后读取:
100
+
101
+ ```bash
102
+ arena --file prompt.txt --mode arena
103
+ code-harness --file prompt.txt --mode react
104
+ ```
105
+
106
+ `--file` 与 `-p`/`--prompt` 互斥。相对路径以启动命令时的当前目录为基准。与
107
+ `arena -p "$(<prompt.txt)"` 不同,命令行中只传递较短的文件路径,因此不会让
108
+ 提示词本身触发 Linux 单个命令行参数的大小上限。
109
+
89
110
  也可以设置 `ARENA_HARNESS_MODE=arena|react|html|full-stack`。迁移期继续接受
90
111
  `CODE_HARNESS_MODE`、`ARENA_MODE` 及旧模式别名。详细目录和发包决策见
91
112
  [`docs/MODES_AND_RELEASES.md`](docs/MODES_AND_RELEASES.md)。
92
113
 
114
+ ### 中途快照与单步执行
115
+
116
+ Headless CLI 可通过 JavaScript Hook 决定何时继续或创建 Harbor 快照。工具校验在
117
+ 整批工具执行前触发;输出重复在主模型生成正文或 reasoning 时触发;Agent Arena
118
+ 的压缩事件在达到阈值且存在可压缩历史时、执行压缩前触发。
119
+
120
+ | 类型 | 含义 |
121
+ | --- | --- |
122
+ | `valid` | 工具和参数合法 |
123
+ | `unknown_tool` | 工具名称不存在 |
124
+ | `invalid_json` | 工具参数不是合法 JSON |
125
+ | `invalid_arguments` | 参数不符合工具 schema |
126
+ | `repetitive_output` | 模型持续重复同一字符或片段 |
127
+ | `before_compaction` | Agent Arena 即将执行摘要式或 rolling session memory 压缩 |
128
+
129
+ | Hook 返回值 | 后续行为 |
130
+ | --- | --- |
131
+ | `continue` 或 `false` | 正常继续 |
132
+ | `snapshot_continue` 或 `true` | 快照后继续当前生成、工具执行或压缩 |
133
+ | `snapshot_exit` | 保存快照后暂停退出;触发处的生成、工具批次或压缩不再继续 |
134
+
135
+ 例如,将以下内容保存为 `policy.mjs`:
136
+
137
+ ```js
138
+ export default function (event) {
139
+ const stop = event.type === "repetitive_output"
140
+ || event.toolCalls.some(call => call.validation !== "valid");
141
+ return stop ? "snapshot_exit" : "continue";
142
+ }
143
+ ```
144
+
145
+ 在启动进程的环境中配置(工作区 `.env` 不用于加载 Hook 或快照配置):
146
+
147
+ ```bash
148
+ export ARENA_MIDTURN_CHECKPOINT_HOOK=/absolute/path/policy.mjs
149
+ export HARBOR_REMOTE_COMMAND_BASE_URL="http://<harbor-host>:<port>"
150
+ export HARBOR_REMOTE_COMMAND_API_KEY="<api-key>"
151
+ ```
152
+
153
+ 要在第 `k` 次压缩执行前停止,可将 `policy.mjs` 写成:
154
+
155
+ ```js
156
+ const k = 3; // 正整数;1 表示第一次压缩前停止
157
+ export default event =>
158
+ event.type === "before_compaction" && event.compaction.count >= k
159
+ ? "snapshot_exit"
160
+ : "continue";
161
+ ```
162
+
163
+ 两种压缩模式共用 `event.compaction.count`:历史中已完成且未撤销的压缩次数 + 1,
164
+ 因此第 `k` 次暂停时只完成了 `k - 1` 次压缩。续跑或切换 `ARENA_COMPACTION_MODE`
165
+ 会从历史恢复计数;后台记忆更新、失败的压缩和已撤销的记录不增加次数。
166
+ 事件还提供 `compaction.mode`(`summary` / `rolling`)、`source`(`outer` / `inner`)、
167
+ `estimatedTokensBefore` 和 `thresholdTokens`,以及空的 `toolCalls` 数组。
168
+ 暂停不会发起本次摘要或强制记忆更新,也不会应用已缓存的记忆;在途后台更新会收束后再快照。
169
+ 结果为 `status: "paused"`、`stop_reason: "checkpoint"`,CLI 退出码为 0。
170
+ 续跑前需提高 `k` 或调整 Hook,否则会在同一次压缩前再次暂停。
171
+
172
+ 配置 Hook 后自动检测输出重复,命中时由 Hook 决定继续、快照或退出;未配置 Hook
173
+ 时按默认流程执行。检测连续重复至少 512 个 Unicode 字符、至少 4 次的片段,
174
+ 片段最长 1024 字符,忽略纯空白和标点填充。
175
+ 正文与 reasoning 分开检测,工具参数和工具结果不参与;每次生成最多触发一次重复事件。
176
+ 事件的 `repetition.channel` 和 `repetition.sample` 提供通道与最多 256 字符的诊断片段。
177
+
178
+ 快照保存触发点之前的 `history.json`;压缩事件保留完整的压缩前历史。
179
+ 恢复快照对应环境后,使用原 session ID 续跑同一个 user turn,不追加用户消息;
180
+ 被中止的生成或压缩会重新执行:
181
+
182
+ ```bash
183
+ arena --continue --session-id demo
184
+ ```
185
+
186
+ `--single-step` 每次只完成一次模型回复及其整批工具调用,保存结果后退出。
187
+ 它无需 Hook,也不自行创建快照;下次续跑从已保存的下一步开始。
188
+
189
+ ```bash
190
+ arena -p "完成这个任务" --session-id demo --single-step
191
+ arena --continue --session-id demo --single-step
192
+ ```
193
+
194
+ 正常暂停返回 `status: "paused"`、退出码 `0`,`stop_reason` 为 `checkpoint` 或
195
+ `single_step`;Hook 快照退出优先于单步限制。最终回复仍返回 `success`,需要输入
196
+ 时返回 `needs_input`,Hook、快照或保存失败时返回 `error` 并以非零退出码结束。
197
+ `--continue` 要求会话尚未完成,不能同时传入新 prompt、文件或附件。
198
+
93
199
  如需完全替换模型收到的 system prompt,可在任一运行入口传入
94
200
  `--system-prompt <text>`:
95
201
 
@@ -100,9 +206,15 @@ arena serve --system-prompt '只使用中文回复。'
100
206
  ```
101
207
 
102
208
  该参数只在运行时覆盖第一条 system message,不会修改 canonical prompt fixture;
103
- 续跑已有 session 时,当前传入的值会替换历史中的 system message。未传参数时,
209
+ 续跑已有 session 时,使用当前参数重新生成运行时 system message。未传参数时,
104
210
  日期、workspace 和 mode 对应的现有 prompt 生成逻辑保持不变。
105
211
 
212
+ history.json、增量日志和返回的 messages 只保存对话历史(含 compaction),不保存运行时生成的开头 system;中途出现的 system 消息会保留。
213
+ 新会话和续跑均在构建模型请求时前置运行时 system;官方以 user 开头的历史可直接续跑。
214
+ 旧版带开头 system 的本地历史仍可读取,该旧前缀不作为本次指令,后续写入会移除它。
215
+ 每次运行(包括 resume)都会重新探测当前工作区和 Git 状态来填充仓库提示词,
216
+ 不从旧 system 或会话 metadata 恢复仓库信息;因此不同轮次的 system 可以变化。
217
+
106
218
  如需保留当前 mode 生成的 system prompt,只在末尾追加任务或部署指令,可传入
107
219
  `--append-system-prompt <text>`:
108
220
 
@@ -144,19 +256,13 @@ Messages,`gemini/` 使用 Google 的 OpenAI-compatible 接口;`openai/` 或
144
256
  无前缀模型使用 OpenAI 传输,其中模型名包含 `gpt`(大小写不敏感)时默认
145
257
  使用 Responses,其余使用 Chat Completions。无前缀且名称包含 `claude` 时
146
258
  使用 Anthropic Messages。显式 provider 前缀与 `ARENA_OPENAI_API` 始终优先。
147
- LiteLLM 官方提供 Python SDK 或独立 Proxy,因此
148
- 纯 TypeScript 版本使用
149
- [LiteLLM Proxy](https://docs.litellm.ai/) 作为多 provider 网关:
259
+ 模型出口只有 Chat Completions、Responses API 和 Anthropic Messages 三种协议。
260
+ 端点和密钥统一使用 `OPENAI_BASE_URL` / `OPENAI_API_KEY`,兼容旧的
261
+ `ARENA_API_BASE` / `ARENA_API_KEY`。`LITELLM_PROXY_URL` 和 `LITELLM_API_KEY`
262
+ 已不再读取,即使仍在环境中也不会影响地址、认证、协议选择或参数格式。
263
+ provider 前缀用于选择协议,发送给服务端时会剥离。
150
264
 
151
- ```bash
152
- export LITELLM_PROXY_URL=http://127.0.0.1:4000/v1
153
- export LITELLM_API_KEY=sk-litellm
154
- export OPENAI_MODEL=anthropic/claude-sonnet-4-20250514
155
- ```
156
-
157
- 设置 `LITELLM_PROXY_URL` 时,模型名会原样发送给 LiteLLM。没有 Proxy 时,
158
- 也支持直连:`openai/` 与 `gemini/` 前缀走各自的 OpenAI-compatibility 接口,
159
- `anthropic/` 前缀直接使用原生 Anthropic Messages 协议(`/v1/messages`):
265
+ 配置示例:
160
266
 
161
267
  ```bash
162
268
  # OpenAI
@@ -190,8 +296,13 @@ Anthropic Messages payload 并使用 `x-api-key`;OpenAI 路由根据上述规
190
296
  回合会把这些 output items 连同 function output 一起完整回放到下一次 `input`,
191
297
  从而在不依赖 provider 端存储的情况下保留隐藏 reasoning。请求显式包含
192
298
  `reasoning.encrypted_content`;GPT-5.6 还会使用 `reasoning.context=all_turns`。
299
+ 设置 `ARENA_RESPONSES_REPLAY_REASONING_STATE=false` 可在发送 Responses 请求时,
300
+ 移除 `input` 中所有 `reasoning` item 的 `encrypted_content` 和 `id`;默认值为
301
+ `true`。其他字段(如 `summary`)、工具调用和消息 ID 均保留,本地历史仍保存完整
302
+ output items。该开关只控制下一轮回传,不阻止服务端返回加密内容,也不影响 Chat
303
+ Completions 或 Anthropic Messages;关闭后模型无法通过这两个字段恢复历史隐藏推理。
193
304
  设置 `ARENA_OPENAI_API=chat` 或 `responses` 可覆盖 GPT 的自动选择。
194
- 该开关不作用于 LiteLLM、Gemini 或 Anthropic 路由。如未设置统一入口,
305
+ 该开关不作用于 Gemini 或 Anthropic 路由。如未设置统一入口,
195
306
  Anthropic 可回退到 `ANTHROPIC_BASE_URL` /
196
307
  `ANTHROPIC_API_KEY`。
197
308
 
@@ -205,7 +316,7 @@ claude-code-proxy),收到 401 后会自动切换鉴权方式并记住。流
205
316
  Anthropic 直连在原生协议上完整支持 thinking。旧版 Claude 和非 Claude
206
317
  兼容网关把 `ARENA_ENABLE_THINKING` / `ARENA_THINKING_BUDGET` 映射为
207
318
  `thinking.type=enabled` / `thinking.budget_tokens`;Claude Opus/Sonnet 4.6+
208
- 以及 Claude 5、Fable、Mythos 使用 `thinking.type=adaptive`,不再发送固定
319
+ 以及 Haiku 5.5+、Fable、Mythos 使用 `thinking.type=adaptive`,不再发送固定
209
320
  budget。Anthropic 要求启用 thinking 时不调整 temperature,此时不发送该
210
321
  参数。带签名的 thinking/redacted_thinking block 会原样保存在对应 assistant
211
322
  消息的 `reasoning_details` 字段——
@@ -235,12 +346,12 @@ ARENA_ENABLE_THINKING=true
235
346
  ARENA_THINKING_BUDGET=81920
236
347
  ARENA_PRESERVE_THINKING=true
237
348
  ARENA_CONTEXT_WINDOW=1000000
238
- ARENA_CONTEXT_COMPACTION_RATIO=0.8
239
- ARENA_COMPACTION_SUMMARY_MAX_TOKENS=8192
349
+ ARENA_CONTEXT_COMPACTION_RATIO=0.25
350
+ ARENA_COMPACTION_SUMMARY_MAX_TOKENS=131072
240
351
  ```
241
352
 
242
353
  可选的 `ARENA_REASONING_EFFORT` 支持 `low`、`medium`、`high`、`xhigh`
243
- 和 `max` 五档;Chat Completions 请求使用 `reasoning_effort`,Responses
354
+ 、`max` 和 `ultra` 六档;Chat Completions 请求使用 `reasoning_effort`,Responses
244
355
  请求使用 `reasoning.effort`,原生 Anthropic Messages 请求映射为
245
356
  `output_config.effort`。不会按模型静默
246
357
  降档;目标模型或兼容网关不支持所选档位时,由服务端返回明确错误。
@@ -253,6 +364,46 @@ ARENA_DISALLOWED_TOOLS=["bash","write_file"]
253
364
 
254
365
  被禁用的工具不会发送给模型;即使模型仍构造了该工具调用,执行层也会
255
366
  在产生副作用前拒绝。
367
+
368
+ Agent Arena 可通过 Merge MCP 调用 Notion 和 Google Calendar,应用范围由
369
+ `ARENA_CONNECTORS` JSON 数组控制。先在 Merge 创建 Tool Pack 和 Registered User,
370
+ 让该用户通过 Merge Link 连接目标应用,再配置同一环境的 Access Key:
371
+
372
+ ```sh
373
+ # 写入本地 .env;三个 Merge 值从自己的 Merge 环境取得。
374
+ ARENA_CONNECTORS='["notion","google_calendar"]'
375
+ ARENA_MERGE_ACCESS_KEY=...
376
+ ARENA_MERGE_TOOL_PACK_ID=...
377
+ ARENA_MERGE_REGISTERED_USER_ID=...
378
+ ```
379
+
380
+ 首次可运行 `arena -p '加载 Google Calendar 工具并读取我的时区,不要修改日历'`。
381
+ Tool Pack 需启用所选应用的完整捕获目录:Notion 22 个、Calendar 42 个,见
382
+ [目录源文件](spec/connectors/catalog.json)。缺少工具时会明确报错;上游新增的工具不会自动暴露给模型。
383
+
384
+ 未设置、空字符串或 `[]` 时,提示词和工具集合保持原样。非空时在基础工具后注册
385
+ `list_connector_tools`,并在普通/Repo 系统提示词末尾加入捕获的 `Connected apps`
386
+ 段落,列出数组中的应用。目前只接受 `notion`、`google_calendar`;重复项去重,
387
+ 保留输入顺序,未知名称或非法 JSON 会在启动时报错。Code Arena 忽略此配置。
388
+
389
+ 模型调用 `list_connector_tools` 后,harness 才连接 Merge、加载该应用的参数定义,
390
+ 并注册业务工具。每条新用户消息都需要重新发现;暂停续跑只恢复目录,不重放已经执行的操作。
391
+ 数组表示本次会话允许哪些应用,加载目录成功也不保证账号具有全部读写权限。
392
+ 缺少配置、授权失败或目录不完整时返回明确错误;业务调用按历史日志包装结果。
393
+ `ARENA_DISALLOWED_TOOLS` 禁用发现工具时也会省略应用提示词;显式 `--system-prompt`
394
+ 仍然完整覆盖系统提示词。
395
+
396
+ 默认连接 `https://ah-api.merge.dev`,可用 `ARENA_MERGE_API_BASE` 配置区域地址;
397
+ `ARENA_CONNECTOR_TIMEOUT_MS` 默认 60000。超时或取消不会自动重试写操作,但远端可能
398
+ 已经完成写入,需先查询确认。当前一个进程使用一个 Registered User,Web 会话共用该账号;
399
+ 尚未提供各 Web 用户独立登录和账号绑定界面。
400
+
401
+ 可选应用提示词的唯一维护源是 `spec/connectors/system-prompt.txt`,由构建脚本嵌入
402
+ 生成数据;关闭 connector 时继续使用 `spec/message.json` 的原始提示词。
403
+ 工具名称、描述和顺序来自日志;完整参数使用 Merge 实时 schema,发现入口仍是根据
404
+ `service` 参数实现的最小结构。已通过 40 次历史调用的本地回放比对,尚未用真实账号验证。
405
+ 配置步骤、已验证范围和剩余差异见 [Connector 接入分析](docs/research/AGENT_ARENA_CONNECTOR_BACKEND.md)。
406
+
256
407
  `ARENA_EXTRA_BODY` 会在配置加载时解析为 JSON 对象,既作为最后一层请求
257
408
  参数覆盖,也会原样写入 `history.json` 的
258
409
  `metadata.parameters.extra_body`。
@@ -264,34 +415,107 @@ ARENA_DISALLOWED_TOOLS=["bash","write_file"]
264
415
  Jina Reader。
265
416
  该密钥不会作为主模型连接的密钥。
266
417
 
267
- Harness 会在每次模型调用前估算消息、reasoning、工具 schema 和图片占用;达到
268
- `ARENA_CONTEXT_WINDOW × ARENA_CONTEXT_COMPACTION_RATIO`(默认 80%)时,调用
269
- 当前模型把已完成的旧回合生成结构化续作摘要。只有发给模型的内存上下文会把
270
- 旧前缀替换为符合既有会话格式的 leading `<arena-system-message>`;持久化的
271
- `history.json.messages` 保留原始消息,不会因压缩删除或替换前缀。当前用户回合及
272
- 其后的 tool-call / result 链在模型上下文中保持原样。摘要重点保留目标、硬约束、
273
- 发现、已完成/未完成事项、下一步和相关文件路径;摘要调用默认关闭 thinking,
274
- 输出上限由
275
- `ARENA_COMPACTION_SUMMARY_MAX_TOKENS` 控制(默认 8192)。
418
+ Agent Arena 通过 `ARENA_COMPACTION_MODE` 选择压缩方式,默认 `rolling`;设置为
419
+ `summary` 可切换回原来的摘要式压缩,适用于 headless CLI、TUI 和服务端:
420
+
421
+ ```bash
422
+ ARENA_COMPACTION_MODE=summary arena -p "继续任务" --session-id demo
423
+ ```
424
+
425
+ `summary` 达到阈值时同步生成完整摘要,保留当前用户回合及其工具调用,不启动或恢复
426
+ 后台记忆缓存。当前实现位于 `src/summary-compaction.ts`,与 Code Arena 共用
427
+ 摘要系统提示词、请求提示词和 continuation preamble。
428
+ 摘要按 `Goal`、`Instructions`、`Discoveries`、`Accomplished` 等章节生成,使用
429
+ 主模型、temperature=0、关闭 thinking,默认输出上限为 8192。
430
+ `ARENA_COMPACTION_SUMMARY_MAX_TOKENS` 可覆盖上限;`rolling` 的默认上限仍为 131072。
431
+ 两种模式默认阈值均为 250k;只有窗口和比例两个环境变量都配置有效时才使用乘积。
432
+ 摘要模式沿用旧的
433
+ 本地 token 估算。旧实现只归档当前用户回合之前的内容,因此单个长用户回合没有
434
+ 可归档前缀时不会强制裁切。Code Arena 保持摘要式压缩,不受此环境变量影响。
435
+
436
+ Agent Arena 在两种模式下都保存已应用的 `compaction` 历史记录;resume 或切换模式
437
+ 后继续使用这些记录,不丢失已压缩内容。历史 metadata 记录本次压缩方式,但下一次
438
+ 运行仍以当前环境变量为准,未设置时回到 `rolling`。
439
+
440
+ 以下后台记忆机制适用于 `rolling` 模式。
441
+
442
+ Agent Arena 在每次模型调用前检查新增对话预算:自上次成功记忆更新以来,
443
+ 可归档内容的本地估算达到 `ARENA_MEMORY_UPDATE_TOKENS`(默认 24000)时,
444
+ 启动一个后台记忆更新任务。24000 是根据历史日志选择的暂定策略,不是已确认的官方常数。
445
+ 已有记忆、系统提示词和工具定义不计入新增预算;最新完整的模型/工具步骤保留原文。
446
+ 更新完成只刷新缓存,主模型继续使用原上下文。
447
+
448
+ 整体上下文达到压缩阈值时才回注最新记忆。Agent Arena 默认固定为 250,000 tokens;
449
+ 只有 `ARENA_CONTEXT_WINDOW` 与 `ARENA_CONTEXT_COMPACTION_RATIO` 两项都配置了
450
+ 有效值,才改用两者的乘积。未配置、只配置一项或任一值无效时,阈值仍为 250,000。
451
+ 计数优先采用最近一次主模型请求返回的 `total_tokens`,并只对
452
+ 该 assistant 之后追加的 tool 和 user 消息做本地增量估算;缺少 `total_tokens` 时,
453
+ 先尝试由 input/output usage 相加得到,若 output usage 也缺失则回退到 input usage
454
+ 加 assistant 及后续消息的本地估算。没有可靠 usage 锚点时回退到全量本地估算。
455
+ 有正在进行的更新则等待完成,必要时提前发起更新。
456
+ Code Arena 仍使用窗口与比例相乘的阈值,压缩比例默认是 70%。
457
+ 每个会话只运行一个更新任务,失败保留原上下文,源内容推进后允许重试。
458
+ Agent Arena 使用捕获的 session-memory 协议:只输出变化章节并合并,保留未输出章节,
459
+ 固定维护当前状态、任务、用户约束、工作区、已执行操作、外部来源、失败经验和关键结果。
460
+ 章节正文使用 `o200k_base` 计数,不包含标题和开头的斜体章节说明;超过每章节
461
+ 1500 tokens 时,下一次更新提示模型精简,不直接截断内容。
462
+
463
+ 主模型上下文通过新的 leading `<arena-system-message>` 回注记忆,最近完整的
464
+ 模型/工具步骤保持原样。后台记忆保存在 `history.json.session_memory` 中,包含最新
465
+ 成功快照、覆盖边界和前缀校验值,以及最近更新的状态;运行中的变化先写入增量日志,
466
+ 结束时合并进 `history.json`。实际触发压缩时,
467
+ 在 `history.json.messages` 中插入官方 dump 形式的 `role: "compaction"` 记录:
468
+ 正文为 `content: [{type: "text", text: "摘要正文"}]`,`compaction` 包含 `ratio`、
469
+ `tokens_at_compaction` 和 `source`。普通消息格式不变,原始历史不删除。
470
+ 压缩记录插在已覆盖前缀与保留步骤之间,其位置就是已应用摘要的恢复边界。
471
+ 请求与 resume 使用系统提示词、最后一条未撤销压缩记录的摘要、
472
+ 以及其后的消息;`compaction` 角色只用于历史,不发送给模型。后台更新仍不会提前
473
+ 改变上下文。`undone: true` 的压缩记录不生效。
474
+ 这些字段及 `outer`/`inner` 值来自下载的官方 dump;本地将回合开始/工具循环内的
475
+ 压缩分别标为 `outer`/`inner`。token 数和比例使用本地估算,不代表已复刻官方计数。
476
+
477
+ Agent Arena 的 `--single-step` 默认恢复后台记忆缓存,普通 turn 续跑默认不恢复。
478
+ `--restore-session-memory` 可显式开启,`--no-restore-session-memory` 可显式关闭;
479
+ 也可设置 `ARENA_RESTORE_SESSION_MEMORY=true|false`(适用于 CLI、TUI 和服务端,
480
+ 命令行标志优先)。恢复前会校验记忆覆盖的历史前缀;不匹配的缓存会被丢弃。
481
+ 关闭恢复不会删除仍有效的已存快照。恢复缓存本身不触发压缩,也不影响历史里
482
+ 已应用的 `compaction` 记录。
483
+
484
+ `memory.update` 事件的 `status` 为 `pending`、`success`、`error` 或 `cancelled`;
485
+ JSON 最终结果的 `memory_update` 和历史里的 `session_memory.update` 也保留最近状态。
486
+ 失败时 `code` 区分 `MEMORY_REQUEST_FAILED`(请求失败)、`MEMORY_EMPTY_RESPONSE`、
487
+ `MEMORY_INCOMPLETE_RESPONSE`(截断或异常结束)、`MEMORY_INVALID_PATCH`、
488
+ `MEMORY_STALE_HISTORY`(前缀失配);取消为 `MEMORY_CANCELLED`,恢复上次进程遗留的
489
+ `pending` 状态时记为 `MEMORY_INTERRUPTED`。新更新失败会保留本次运行已有的成功快照。
490
+ 没有更新记录表示尚未尝试更新。
491
+
492
+ 单步暂停会等待在途更新完成,再保存历史;主任务完成、失败、取消、返回待输入状态,
493
+ 以及外部快照前,会取消未完成的记忆请求并等待退出,再保存最终状态。已成功生成的
494
+ 记忆会保留,未完成的输出不会作为有效记忆使用。
495
+ Agent Arena 记忆更新使用主模型、temperature=1、thinking
496
+ 开启、thinking budget=81920,输出上限由 `ARENA_COMPACTION_SUMMARY_MAX_TOKENS`
497
+ 控制(rolling 默认 131072,summary 默认 8192)。Code Arena 默认上限仍为 8192。
276
498
 
277
499
  `ARENA_CONTEXT_WINDOW` 应设置为所用模型的真实上下文窗口;默认 `1000000` 是
278
- 当前默认模型的暂定值。token 数目前是本地保守估算,并非 provider tokenizer
279
- 的精确计数。如果摘要请求失败,Harness 会保留原历史并继续正常模型请求,避免
500
+ 当前默认模型的暂定值。Agent Arena 的 usage 基线来自 provider;最近 assistant
501
+ 之后的新增上下文以及缺少 usage 时的完整上下文仍使用本地保守估算。如果摘要请求失败,Harness 会保留原历史并继续正常模型请求,避免
280
502
  摘要服务的瞬时故障直接中断任务。
281
503
 
282
504
  Responses 与其他协议一样,会在每个工具 step 前检查是否需要压缩。由于所有
283
505
  reasoning/function-call 状态都由本地 history 携带,压缩不再需要切断或重建 provider
284
- 端 response 链。压缩会把已完成的旧回合替换成续作摘要,同时保留当前 user turn 里的
506
+ 端 response 链。Agent Arena 压缩已归档的前缀,同时保留最新完整步骤里的
285
507
  加密 reasoning、function call 和 function output;下一次 Responses 请求直接回放这份
286
- 压缩后的本地上下文。持久化的原始 `history.json` 仍保持 append-only。
508
+ 压缩后的本地上下文;完整历史及压缩记录保存在 `history.json.messages`。
287
509
 
288
510
  每次模型请求都会通过 OpenAI-compatible 的 `stream_options.include_usage`
289
511
  请求 token 用量,并把服务端在最终 chunk 上报的 `usage` 对象原样记录到该轮
290
512
  assistant 消息的 `usage` 字段(`history.json` 与事件流中均可见),供训练
291
513
  和成本统计使用。再次发送历史上下文时,`usage` 会在 wire 边界剥离,不会发回
292
514
  服务端。`model.complete` 事件同样携带该轮 `usage`;上下文压缩的摘要调用
293
- 不会在历史中留下消息,其用量通过 `compaction` 事件上报,只在事件流中可
294
- 见。不支持 `stream_options` 的服务可通过 `ARENA_EXTRA_BODY` 覆盖该参数。
515
+ 不会在历史中留下普通消息,其用量通过 `compaction` 事件上报。Agent Arena 还通过
516
+ `memory.update` 上报记忆更新用量,并将最近一次更新返回的 usage 保存在
517
+ `session_memory.update.usage` 中。不支持 `stream_options` 的服务可通过
518
+ `ARENA_EXTRA_BODY` 覆盖该参数。
295
519
 
296
520
  模型请求遇到网络错误、HTTP 408/409/429 或 5xx 时会自动重试,默认最多重试
297
521
  10 次。没有 `Retry-After` 时使用带随机抖动的指数退避,单次等待默认最多
@@ -383,6 +607,13 @@ Arena 还维护一份有顺序的模型输入模态表,在第一次 HTTP 调
383
607
  当主模型被明确判定为不支持图片时,可设置
384
608
  `ARENA_IMAGE_INPUT_FALLBACK_MODEL`,让普通 Arena 的图片 `read_file` 先由该模型
385
609
  转换为文字;主模型支持图片、模型未知或 Code Arena 模式均不触发此 fallback。
610
+ 可进一步用 `ARENA_IMAGE_INPUT_FALLBACK_BASE_URL`、
611
+ `ARENA_IMAGE_INPUT_FALLBACK_API_KEY` 和 `ARENA_IMAGE_INPUT_FALLBACK_PROTOCOL`
612
+ 为 fallback 设置完全独立的连接;协议支持 `openai-chat-completions`、
613
+ `openai-responses` 和 `anthropic-messages`。例如 Anthropic 主模型可以把图片交给
614
+ 使用 OpenAI 兼容端点的 Gemini。任一独立连接参数出现后,fallback 不再继承主模型
615
+ 的 base URL、认证或协议;未填写的连接项改用所选 provider 的标准
616
+ 环境变量与默认端点。仅配置 fallback model 时仍保持原有的主连接继承行为。
386
617
  fallback 不继承 `ARENA_EXTRA_BODY` 或主模型生成参数;原生 Anthropic Messages
387
618
  因协议要求会单独使用 `max_tokens: 16384`。
388
619
  同一条件下,用户直接附加的图片不会发送给主模型;已有附件文件列表会保留,并提示
@@ -429,7 +660,8 @@ arena -p 'redraw this photo with the reference face' \
429
660
  每个附件会被保存到工作区 `uploads/`(模型内可通过
430
661
  `/home/user/uploads/<name>` 访问;重名自动加 `-2`、`-3` 后缀,URL 无扩展名
431
662
  时按图片字节特征补全扩展名)。本轮 user message 与 daemon 附件上传共用同
432
- 一构造逻辑,并使用统一的会话部件格式:图片部件
663
+ 一构造逻辑。本地图片只列入附件清单,由模型按需调用 `read_file` 查看;
664
+ 网络图片使用统一的会话部件格式:图片部件
433
665
  `{"image": ...}` 在前,随后是 prompt 文本 `{"text": ...}`,最后追加一条
434
666
  `<arena-system-message>`,列出保存到 `/home/user/uploads/` 的文件名。单条
435
667
  消息最多 10 个附件,单个附件上限 20 MB、总量上限 50 MB,与 daemon 一致。
@@ -438,12 +670,19 @@ arena -p 'redraw this photo with the reference face' \
438
670
  一律使用 `{"image": ...}` / `{"text": ...}` 原生部件并保持字段格式一致;仅在
439
671
  向模型发起 chat-completions 请求的边界处转换为
440
672
  OpenAI 风格的 `image_url` 部件。`--attach` 传 URL 时 `image` 直接记录该
441
- URL(模型服务端会自行下载;恢复历史上下文时要求该 URL 仍可访问);本地文件
442
- 没有可引用的 URL,`image` 存自包含的 base64
443
- data-URI。
673
+ URL(模型服务端会自行下载;恢复历史上下文时要求该 URL 仍可访问);若网络图片
674
+ 需要缩放或转码,则记录处理后的 Base64 data-URI。本地图片在 attach 时不生成
675
+ `image` 部件;后续 `read_file` 返回图片时仍可使用 Base64 data-URI。
444
676
 
445
- `stream-json` 每行一个 JSON 对象。`start`、`message`、`result` 事件中的
446
- `messages` 与输入样例保持相同契约:
677
+ `stream-json` 每行一个 JSON 对象。四种模式的 headless(Agent Arena、React、HTML、Full Stack)
678
+ 的 `message` 事件只带新增的 `message`;`start` 和最终 `result` 保留完整 `messages`,
679
+ 避免评测端缓存大量重复历史。需要每个 `message` 事件都带完整 `messages` 的旧消费者,
680
+ 可设置 `ARENA_STREAM_HISTORY=full` 恢复旧输出,默认值为 `incremental`。
681
+ 旧变量 `ARENA_CODE_STREAM_HISTORY` 作为所有模式的兼容别名保留,`ARENA_STREAM_HISTORY` 优先。
682
+ 增量模式使用 JSON Unicode 转义,解析后的文字和图片内容保持原样。
683
+ SDK 和 daemon 的事件保持原有格式。
684
+
685
+ 事件中的消息与输入样例保持相同契约:
447
686
 
448
687
  - system/user:`role` + `content`;支持图片输入的模型在读取图片后会持久化
449
688
  一条 multimodal user message
@@ -685,9 +924,9 @@ message。未提供位置时,prompt 会明确标记位置不可用,不会使
685
924
  接口,并通过 URI 编码的 `x-file-name` header 传递文件名;再把返回的
686
925
  `attachment.path` 作为 `{ "type": "file", "path": "uploads/..." }`
687
926
  part 放入实时消息。每个文件最大 20 MB,每条消息最多 10 个文件、合计
688
- 50 MB。服务端会把文件写入该 session 的 `/home/user/uploads/` 映射目录,
689
- 图片作为 multimodal content 注入,并在 user message 最后追加
690
- `arena-system-message` 附件清单。附件引用只能指向本 session 的
927
+ 50 MB。服务端会把文件写入该 session 工作区的 `uploads/` 目录,
928
+ 并在 user message 最后追加 `arena-system-message` 附件清单。
929
+ 上传的图片由模型按需调用 `read_file` 查看。附件引用只能指向本 session 的
691
930
  `uploads/`,不能读取其他 workspace 路径或其他 session 的文件。
692
931
 
693
932
  SSE 支持 `Last-Event-ID` 断线续传,事件环同时受条数和字节预算约束,不缓存完整
@@ -715,7 +954,7 @@ retry 文件会删除。
715
954
  看到 WebUI 的 Retry 按钮:
716
955
 
717
956
  ```bash
718
- # 初始模型节点失败:失败后 history 只有 system/user;点一次 Retry 后成功
957
+ # 初始模型节点失败:失败后 history 只有 user;点一次 Retry 后成功
719
958
  ARENA_MODEL=mock/manual-retry-initial \
720
959
  ARENA_MODEL_RETRY_BASE_MS=0 ARENA_MODEL_RETRY_MAX_MS=0 npm run dev
721
960
 
@@ -760,7 +999,10 @@ curl -N -X POST \
760
999
 
761
1000
  构建时会将两份规范生成为 `dist` 内的 JS 数据模块;npm 包不包含原始
762
1001
  `spec/` 目录,运行时也不依赖仓库文件。执行工具前仍使用 AJV 校验原始
763
- Draft-07 schema。repo mode 的动态提示词模板保存在 `src/prompts.ts`:
1002
+ Draft-07 schema。普通模式的审计模板也从同一份生成数据取得;`src/prompts.ts`
1003
+ 中直接维护的正文用于 Repo 模式。
1004
+
1005
+ Agent Arena 提供以下工具:
764
1006
 
765
1007
  `bash`、`ask_user`、`add_voice`、`edit_file`、`read_file`、`fetch_page`、
766
1008
  `web_search`、`write_file`、`image_search`、`present_file`、`stop_process`、
@@ -769,8 +1011,10 @@ Draft-07 schema。repo mode 的动态提示词模板保存在 `src/prompts.ts`
769
1011
  新版生产导出的前 50 条脱敏请求单独保存在
770
1012
  [`tasks/v2`](tasks/v2/README.md),没有与原有 replay 数据混放。该目录记录了
771
1013
  2 GB 源文件的 SHA-256、全量解析统计、tool schema 变体和可重复执行的拆分命令;
772
- `npm run refresh:arena-v2-contract` 用它刷新 canonical prompt/tool fixtures 并重新
773
- 生成 `src/canonical-data.ts`。
1014
+ 每条历史样本在 `request.messages` 中保留当时的系统提示词,不另存一份提示词 TXT。
1015
+ `npm run refresh:arena-v2-contract` 是从这批旧数据重新导入契约的历史维护命令,
1016
+ 会用首条样本覆盖 `spec/message.json` 并重新生成派生数据;它不用于日常构建或
1017
+ 获取最新官方提示词。
774
1018
 
775
1019
  `bash` 只用于一次性命令:超时结果为 `status=timeout`,被信号终止为
776
1020
  `status=killed`,并会清理命令遗留的后台进程。需要跨 tool call 存活的 server、
@@ -809,9 +1053,10 @@ Arena 的 `fetch_page` 和 Code Arena 的 `web_fetch` 默认仍使用本地抓
809
1053
  `ROUTIFY_API_KEY`。
810
1054
 
811
1055
  `generate_image`、`generate_speech` 使用 ModelRouter/Routify,并从工作区
812
- `.env` 读取同一个 `ROUTIFY_API_KEY`。图片使用 Google 兼容协议调用;可用
813
- `ROUTIFY_VERTEX_BASE_URL` 覆盖默认根地址。语音使用 ModelRouter 的火山引擎
814
- `/protocol/volcengine/api/v1/tts` 路由,不需要新增服务密钥。
1056
+ `.env` 读取同一个 `ROUTIFY_API_KEY`。GPT Image 图片生成与编辑使用 OpenAI 图片
1057
+ 协议,可用 `ROUTIFY_OPENAI_BASE_URL` 覆盖 ModelRouter 根地址。显式选择 Gemini
1058
+ 图片模型及默认 Gemini TTS 时使用 Google 协议,可用 `ROUTIFY_VERTEX_BASE_URL`
1059
+ 覆盖对应根地址,不需要新增服务密钥。
815
1060
 
816
1061
  `fetch_page` 使用 Defuddle 将 HTML 正文转换为 Markdown,并使用
817
1062
  `pdf-parse` 提取 PDF 的前 30 页。响应按 Markdown 边界稳定分块,同一轮内
@@ -826,8 +1071,10 @@ XLSX 仍可提取文字和表格内容,旧版 DOC、PPT、XLS 会先通过 Lib
826
1071
  内容继续发送给模型。所有工具结果另有 1,000,000 字符的最终兜底。当前不做
827
1072
  OCR,扫描版 PDF 只会返回无可提取文字的提示。
828
1073
 
829
- 图片和语音的默认 ModelRouter 路由分别为 `gemini-2.5-flash-image` 和
830
- `doubao-tts`,可用 `ARENA_IMAGE_MODEL`、`ARENA_SPEECH_MODEL` 覆盖。图片生成结果和
1074
+ Agent Arena 图片和语音的默认 ModelRouter 路由分别为 `gemini-3.1-flash-image` 和
1075
+ `gemini-3.1-flash-tts-preview`,可用 `ARENA_IMAGE_MODEL`、`ARENA_SPEECH_MODEL` 覆盖。
1076
+ Code Arena 图片默认使用 `gpt-image-2.5-sunburst`;配置时优先使用
1077
+ `CODE_HARNESS_IMAGE_MODEL`,其次使用显式设置的 `ARENA_IMAGE_MODEL`。图片生成结果和
831
1078
  搜索到的图片会立即下载到工作区;每次图片搜索的结果按生产命名保存为
832
1079
  `image-search/<query slug>-<序号>.<扩展名>`,并作为可预览文件显示在 Web UI。
833
1080
  语音生成先由 `add_voice` 生成两个试听候选并让用户选择,选择结果注册为稳定的
@@ -836,9 +1083,12 @@ OCR,扫描版 PDF 只会返回无可提取文字的提示。
836
1083
  它会按语言和 `voice_identity` 确定性注册一个默认音色,并在结果中以
837
1084
  `virtual: true` 标记。返回的 `voice_id` 可直接用于后续 `generate_speech`。
838
1085
  也可用
839
- `ARENA_SPEECH_VOICE` 强制覆盖底层音色。豆包响应中的 Base64 数据已经是 MP3、RIFF/WAV
840
- 或 Ogg Opus;`.ogg` 与 `.opus` 都保存 Ogg Opus 数据,所有支持的输出均不依赖
841
- 本地 `ffmpeg` 或 `lame`。
1086
+ `ARENA_SPEECH_VOICE` 强制覆盖底层音色(例如 `Kore`)。Gemini 返回的 PCM 在内存中
1087
+ 封装为 WAV;MP3 和 Ogg Opus 使用随包携带的精简静态 ffmpeg 转换。`.ogg` 与
1088
+ `.opus` 都保存 Ogg Opus 数据。单文件发行版内嵌压缩转码器,npm 包携带平台资产;
1089
+ 转换时释放到操作系统私有临时目录并在结束后清理,不搜索系统 ffmpeg、不运行时下载。
1090
+ 显式设置 `ARENA_SPEECH_MODEL=doubao-tts` 可继续使用原火山引擎路由及豆包音色。
1091
+ 从源码运行压缩语音前执行 `npm run prepare:audio`;发布打包会自动准备全部资产。
842
1092
 
843
1093
  所有路径工具都限制在 `--workspace` 内并检查符号链接逃逸。`bash` 是本机
844
1094
  子进程,不是操作系统安全沙箱;多租户或不可信模型部署应使用容器或 VM。
package/dist/agent.d.ts CHANGED
@@ -1,10 +1,13 @@
1
1
  import { type ArenaConfig } from "./config.js";
2
+ import { type StoredSessionMemory, type SessionMemoryCache } from "./compaction.js";
2
3
  import { EventSink } from "./protocol.js";
3
4
  import { type HarnessMode } from "./mode.js";
4
5
  import { ToolExecutor } from "./tools.js";
6
+ import { type CheckpointAction, type MidturnCheckpointHook, type MidturnCheckpointHookEvent } from "./midturn-checkpoint-hook.js";
5
7
  import type { AgentResult, AskUserHandler, ClientContext, Message, ModelClient, RepoContext, ToolDefinition, UserTurnMessage, WorkspaceContext } from "./types.js";
6
8
  interface AgentOptions {
7
9
  client?: ModelClient;
10
+ imageInputFallbackClient?: ModelClient;
8
11
  tools?: ToolExecutor;
9
12
  askUserHandler?: AskUserHandler;
10
13
  sessionId?: string;
@@ -12,7 +15,18 @@ interface AgentOptions {
12
15
  signal?: AbortSignal;
13
16
  clientContext?: ClientContext;
14
17
  workspaceContext?: WorkspaceContext;
15
- onCheckpoint?: (messages: readonly Message[], tools: readonly ToolDefinition[]) => Promise<void>;
18
+ onCheckpoint?: (messages: readonly Message[], tools: readonly ToolDefinition[], sessionMemory?: StoredSessionMemory | null) => Promise<void>;
19
+ /** Inspect repetition, tool-call batches and pending Agent Arena compactions. */
20
+ midturnCheckpointHook?: MidturnCheckpointHook;
21
+ /** Materialize the current history and create the external snapshot. */
22
+ onMidturnCheckpoint?: (messages: readonly Message[], tools: readonly ToolDefinition[], event: MidturnCheckpointHookEvent, action: Exclude<CheckpointAction, "continue">) => Promise<void>;
23
+ /** Finish durable history before emitting a successful pause result. */
24
+ onPause?: (messages: readonly Message[], tools: readonly ToolDefinition[]) => Promise<void>;
25
+ /** Share background updates across invocations. Restoration is a run option. */
26
+ sessionMemory?: SessionMemoryCache;
27
+ storedSessionMemory?: StoredSessionMemory;
28
+ /** Explicit Agent Arena override of the resolved compaction threshold. */
29
+ compactionThresholdTokens?: number;
16
30
  onModelFailure?: (messages: readonly Message[], error: unknown) => void;
17
31
  /** Reuse immutable loaded history messages instead of cloning the full prefix. */
18
32
  reuseHistoryMessages?: boolean;
@@ -21,12 +35,20 @@ interface AgentRunOptions {
21
35
  interrupted?: boolean;
22
36
  userMessage?: UserTurnMessage;
23
37
  resumeFromHistory?: boolean;
38
+ singleStep?: boolean;
39
+ /** Defaults to singleStep. Restores the cache without forcing compaction. */
40
+ restoreSessionMemory?: boolean;
24
41
  }
25
- export declare function resolveRepoContext(workspace: string, history?: Message[], mode?: HarnessMode): RepoContext | undefined;
42
+ export declare function isContinuableHistory(messages: readonly Message[]): boolean;
43
+ export declare function resolveRepoContext(workspace: string, _history?: Message[], mode?: HarnessMode): RepoContext | undefined;
26
44
  export declare class Agent {
27
45
  private readonly config;
28
46
  private readonly sink;
29
47
  private readonly options;
48
+ private checkpointQueue;
49
+ private checkpointError?;
50
+ private persistedMemory?;
51
+ private readonly memoryCache;
30
52
  private readonly client;
31
53
  private readonly tools;
32
54
  private readonly workspaceContext?;
@@ -34,7 +56,11 @@ export declare class Agent {
34
56
  readonly sessionId: string;
35
57
  constructor(config: ArenaConfig, sink: EventSink, options?: AgentOptions);
36
58
  run(prompt: string, history?: Message[], runOptions?: AgentRunOptions): Promise<AgentResult>;
59
+ private runTurn;
37
60
  private checkpoint;
61
+ private finish;
62
+ private makeCheckpoint;
63
+ private pause;
38
64
  private maybeCompact;
39
65
  private assistantTurn;
40
66
  private retryBackoff;