@qwen-code/arena-agent 0.0.10-dev.10 → 0.0.10-dev.12
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/.env.example +12 -1
- package/CHANGELOG.md +37 -1
- package/README.md +106 -28
- package/dist/agent.d.ts +1 -1
- package/dist/agent.js +90 -30
- package/dist/agent.js.map +1 -1
- package/dist/anthropic.js +17 -15
- package/dist/anthropic.js.map +1 -1
- package/dist/canonical-data.d.ts +2 -0
- package/dist/canonical-data.js +2 -0
- package/dist/canonical-data.js.map +1 -1
- package/dist/code-tools.js +34 -9
- package/dist/code-tools.js.map +1 -1
- package/dist/compaction.d.ts +3 -2
- package/dist/compaction.js +87 -21
- package/dist/compaction.js.map +1 -1
- package/dist/config.d.ts +6 -0
- package/dist/config.js +5 -0
- package/dist/config.js.map +1 -1
- package/dist/connector-mcp.d.ts +47 -0
- package/dist/connector-mcp.js +314 -0
- package/dist/connector-mcp.js.map +1 -0
- package/dist/connectors.d.ts +8 -0
- package/dist/connectors.js +57 -0
- package/dist/connectors.js.map +1 -0
- package/dist/midturn-checkpoint-hook.d.ts +14 -1
- package/dist/midturn-checkpoint-hook.js.map +1 -1
- package/dist/model-router-media.js +50 -0
- package/dist/model-router-media.js.map +1 -1
- package/dist/prompts.d.ts +0 -1
- package/dist/prompts.js +10 -36
- package/dist/prompts.js.map +1 -1
- package/dist/provider.js +3 -0
- package/dist/provider.js.map +1 -1
- package/dist/server.js +4 -2
- package/dist/server.js.map +1 -1
- package/dist/session-memory-prompts.js +1 -1
- package/dist/session-memory-prompts.js.map +1 -1
- package/dist/spec.d.ts +5 -4
- package/dist/spec.js +15 -9
- package/dist/spec.js.map +1 -1
- package/dist/{legacy-compaction.d.ts → summary-compaction.d.ts} +7 -0
- package/dist/{legacy-compaction.js → summary-compaction.js} +54 -10
- package/dist/summary-compaction.js.map +1 -0
- package/dist/tools.d.ts +3 -1
- package/dist/tools.js +35 -9
- package/dist/tools.js.map +1 -1
- package/dist/types.d.ts +4 -0
- package/dist/workspace.js +61 -14
- package/dist/workspace.js.map +1 -1
- package/docs/MODES_AND_RELEASES.md +55 -1
- package/modes/code-arena/full-stack/dependencies.lock.json +8670 -0
- package/modes/code-arena/react/dependencies.lock.json +2707 -0
- package/package.json +2 -1
- package/scripts/README.md +23 -4
- package/dist/legacy-compaction.js.map +0 -1
package/.env.example
CHANGED
|
@@ -64,6 +64,16 @@ ARENA_HARNESS_MODE=arena
|
|
|
64
64
|
# ARENA_TOOL_CHOICE=auto
|
|
65
65
|
# JSON array of exact tool names hidden from the model and rejected if called.
|
|
66
66
|
# ARENA_DISALLOWED_TOOLS=["bash","write_file"]
|
|
67
|
+
# Agent Arena connectors through Merge MCP (unset, blank or [] disables it).
|
|
68
|
+
# Enable the captured catalog in the Tool Pack; connect apps for the Registered
|
|
69
|
+
# User through Merge Link. The key and user must belong to the same environment.
|
|
70
|
+
# One configured Registered User is shared by all sessions of this process.
|
|
71
|
+
# ARENA_CONNECTORS=["notion","google_calendar"]
|
|
72
|
+
# ARENA_MERGE_ACCESS_KEY=
|
|
73
|
+
# ARENA_MERGE_TOOL_PACK_ID=
|
|
74
|
+
# ARENA_MERGE_REGISTERED_USER_ID=
|
|
75
|
+
# ARENA_MERGE_API_BASE=https://ah-api.merge.dev
|
|
76
|
+
# ARENA_CONNECTOR_TIMEOUT_MS=60000
|
|
67
77
|
# ARENA_THINKING_BUDGET=81920
|
|
68
78
|
# ARENA_ENABLE_THINKING=true
|
|
69
79
|
# ARENA_PRESERVE_THINKING=true
|
|
@@ -150,6 +160,7 @@ ROUTIFY_API_KEY=
|
|
|
150
160
|
# ROUTIFY_WEB_SEARCH_URL=https://routify-pub.alibaba-inc.com/protocol/compatible-mode/res/v1/llm/context
|
|
151
161
|
# ROUTIFY_IMAGE_SEARCH_URL=https://routify-pub.alibaba-inc.com/protocol/brave/res/v1/images/search
|
|
152
162
|
# ROUTIFY_VERTEX_BASE_URL=https://routify-pub.alibaba-inc.com/protocol/vertex/v1beta
|
|
163
|
+
# ROUTIFY_OPENAI_BASE_URL=https://routify-pub.alibaba-inc.com/protocol/openai/v1
|
|
153
164
|
# ARENA_WEB_SEARCH_MODEL=brave.llm-context
|
|
154
165
|
# CODE_HARNESS_WEB_SEARCH_MODEL=brave.llm-context
|
|
155
166
|
# Optional fetch_page/web_fetch path: Firecrawl first, then Jina Reader.
|
|
@@ -159,7 +170,7 @@ ROUTIFY_API_KEY=
|
|
|
159
170
|
# CODE_HARNESS_WEB_FETCH_MAX_BYTES=40000
|
|
160
171
|
# ARENA_IMAGE_MODEL=gemini-3.1-flash-image
|
|
161
172
|
# ARENA_IMAGE_SIZE=1024*1024
|
|
162
|
-
# CODE_HARNESS_IMAGE_MODEL=
|
|
173
|
+
# CODE_HARNESS_IMAGE_MODEL=gpt-image-2.5-sunburst
|
|
163
174
|
# CODE_HARNESS_IMAGE_SIZE=1024*1024
|
|
164
175
|
# ARENA_SPEECH_MODEL=gemini-3.1-flash-tts-preview
|
|
165
176
|
# ARENA_SPEECH_VOICE=Kore
|
package/CHANGELOG.md
CHANGED
|
@@ -12,6 +12,40 @@
|
|
|
12
12
|
|
|
13
13
|
## [Unreleased]
|
|
14
14
|
|
|
15
|
+
## [0.0.10-dev.12] - 2026-10-15
|
|
16
|
+
|
|
17
|
+
### Agent Arena
|
|
18
|
+
|
|
19
|
+
- 摘要压缩接入服务端 token 用量,并支持长单轮工具任务。
|
|
20
|
+
|
|
21
|
+
### Code Arena
|
|
22
|
+
|
|
23
|
+
- 摘要压缩接入服务端 token 用量,支持长单轮工具任务,并在续跑时恢复已应用的摘要。
|
|
24
|
+
- 固定 React 和 Full Stack 模板的完整安装依赖,避免上游新版本发布导致初始化失败,并保留已有项目的依赖锁。
|
|
25
|
+
- React 与 Full Stack 的命令执行支持毫秒级 `timeout`,默认 180 秒,并拒绝空命令;React 可执行 Codex 的 `shell_command` 调用。
|
|
26
|
+
|
|
27
|
+
### 通用
|
|
28
|
+
|
|
29
|
+
- 压缩计数改善 SVG 等大工具输出的估算,避免重复计入已压缩内容。
|
|
30
|
+
- 模型因长度限制截断时保留部分输出并报告失败,避免将未完成的任务标为成功。
|
|
31
|
+
- 修复 Responses 多工具调用的事件错配,确保工具结果、文件展示和用户输入请求对应正确调用。
|
|
32
|
+
|
|
33
|
+
## [0.0.10-dev.11] - 2026-10-11
|
|
34
|
+
|
|
35
|
+
### Agent Arena
|
|
36
|
+
|
|
37
|
+
- 新增实验性 Notion、Google Calendar 接入,使用 `ARENA_CONNECTORS` 选择应用,并通过 `ARENA_MERGE_*` 配置已授权的 Merge 账号。
|
|
38
|
+
- Headless Hook 新增 `before_compaction` 事件,可按压缩次数在摘要式或 rolling session memory 压缩前快照暂停,续跑时延续计数。
|
|
39
|
+
|
|
40
|
+
### Code Arena
|
|
41
|
+
|
|
42
|
+
- `generate_image` 默认模型改为 `gpt-image-2.5-sunburst`,继续通过 ModelRouter 调用。
|
|
43
|
+
|
|
44
|
+
### 通用
|
|
45
|
+
|
|
46
|
+
- 修复 Claude Haiku 5.5 开启 thinking 时因旧参数格式报 400 的问题。
|
|
47
|
+
- 修复 Gemini 通过 Chat Completions 调用工具后因签名丢失而无法继续的问题。
|
|
48
|
+
|
|
15
49
|
## [0.0.10-dev.10] - 2026-10-07
|
|
16
50
|
|
|
17
51
|
### Agent Arena
|
|
@@ -214,7 +248,9 @@
|
|
|
214
248
|
- `bash` 用于一次性命令,区分超时与信号终止,并清理遗留后台进程;需要持续运行
|
|
215
249
|
的任务改用进程工具。预览端口支持 E2B Host 探测及按需返回告警。
|
|
216
250
|
|
|
217
|
-
[Unreleased]: https://gitlab.alibaba-inc.com/know-how/arena-harness/-/compare/v0.0.10-dev.
|
|
251
|
+
[Unreleased]: https://gitlab.alibaba-inc.com/know-how/arena-harness/-/compare/v0.0.10-dev.12...master
|
|
252
|
+
[0.0.10-dev.12]: https://gitlab.alibaba-inc.com/know-how/arena-harness/-/compare/v0.0.10-dev.11...v0.0.10-dev.12
|
|
253
|
+
[0.0.10-dev.11]: https://gitlab.alibaba-inc.com/know-how/arena-harness/-/compare/v0.0.10-dev.10...v0.0.10-dev.11
|
|
218
254
|
[0.0.10-dev.10]: https://gitlab.alibaba-inc.com/know-how/arena-harness/-/compare/v0.0.10-dev.9...v0.0.10-dev.10
|
|
219
255
|
[0.0.10-dev.9]: https://gitlab.alibaba-inc.com/know-how/arena-harness/-/compare/v0.0.10-dev.8...v0.0.10-dev.9
|
|
220
256
|
[0.0.10-dev.8]: https://gitlab.alibaba-inc.com/know-how/arena-harness/-/compare/v0.0.10-dev.7...v0.0.10-dev.8
|
package/README.md
CHANGED
|
@@ -3,14 +3,17 @@
|
|
|
3
3
|
版本变化见 [Changelog](CHANGELOG.md),发布前请按[发版流程](docs/MODES_AND_RELEASES.md#发布节奏)
|
|
4
4
|
更新变更记录并运行 `npm run check:changelog`。
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
这是一个基于各模式正式提示词与工具契约的 TypeScript
|
|
7
7
|
CodeAgent harness。运行时只依赖 Node.js,不再依赖 Python:
|
|
8
8
|
|
|
9
9
|
- `arena -p ...` / `arena --file prompt.txt`:headless 单轮入口,支持文本、完整 JSON 和 NDJSON 流。
|
|
10
10
|
- `arena tui`:终端多轮对话,实时输出并自动保存、恢复历史。
|
|
11
11
|
- `arena serve`:HTTP daemon、Web UI 与双通道 SSE 实时接口。
|
|
12
12
|
- Agent loop:流式 assistant → tool call → tool result → assistant。
|
|
13
|
-
- 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
|
+
其中区分了当前提示词、生成文件、测试样本和历史审计脚本。
|
|
14
17
|
|
|
15
18
|
本文提及的第三方产品、模型与服务名称仅用于说明兼容性和配置方式,不表示相关
|
|
16
19
|
权利人对本项目的认可或背书;相关商标归各自权利人所有。使用者应自行确认并遵守
|
|
@@ -111,7 +114,8 @@ code-harness --file prompt.txt --mode react
|
|
|
111
114
|
### 中途快照与单步执行
|
|
112
115
|
|
|
113
116
|
Headless CLI 可通过 JavaScript Hook 决定何时继续或创建 Harbor 快照。工具校验在
|
|
114
|
-
整批工具执行前触发;输出重复在主模型生成正文或 reasoning
|
|
117
|
+
整批工具执行前触发;输出重复在主模型生成正文或 reasoning 时触发;Agent Arena
|
|
118
|
+
的压缩事件在达到阈值且存在可压缩历史时、执行压缩前触发。
|
|
115
119
|
|
|
116
120
|
| 类型 | 含义 |
|
|
117
121
|
| --- | --- |
|
|
@@ -120,12 +124,13 @@ Headless CLI 可通过 JavaScript Hook 决定何时继续或创建 Harbor 快照
|
|
|
120
124
|
| `invalid_json` | 工具参数不是合法 JSON |
|
|
121
125
|
| `invalid_arguments` | 参数不符合工具 schema |
|
|
122
126
|
| `repetitive_output` | 模型持续重复同一字符或片段 |
|
|
127
|
+
| `before_compaction` | Agent Arena 即将执行摘要式或 rolling session memory 压缩 |
|
|
123
128
|
|
|
124
129
|
| Hook 返回值 | 后续行为 |
|
|
125
130
|
| --- | --- |
|
|
126
131
|
| `continue` 或 `false` | 正常继续 |
|
|
127
|
-
| `snapshot_continue` 或 `true` |
|
|
128
|
-
| `snapshot_exit` |
|
|
132
|
+
| `snapshot_continue` 或 `true` | 快照后继续当前生成、工具执行或压缩 |
|
|
133
|
+
| `snapshot_exit` | 保存快照后暂停退出;触发处的生成、工具批次或压缩不再继续 |
|
|
129
134
|
|
|
130
135
|
例如,将以下内容保存为 `policy.mjs`:
|
|
131
136
|
|
|
@@ -145,14 +150,34 @@ export HARBOR_REMOTE_COMMAND_BASE_URL="http://<harbor-host>:<port>"
|
|
|
145
150
|
export HARBOR_REMOTE_COMMAND_API_KEY="<api-key>"
|
|
146
151
|
```
|
|
147
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
|
+
|
|
148
172
|
配置 Hook 后自动检测输出重复,命中时由 Hook 决定继续、快照或退出;未配置 Hook
|
|
149
173
|
时按默认流程执行。检测连续重复至少 512 个 Unicode 字符、至少 4 次的片段,
|
|
150
174
|
片段最长 1024 字符,忽略纯空白和标点填充。
|
|
151
175
|
正文与 reasoning 分开检测,工具参数和工具结果不参与;每次生成最多触发一次重复事件。
|
|
152
176
|
事件的 `repetition.channel` 和 `repetition.sample` 提供通道与最多 256 字符的诊断片段。
|
|
153
177
|
|
|
154
|
-
|
|
155
|
-
续跑同一个 user turn
|
|
178
|
+
快照保存触发点之前的 `history.json`;压缩事件保留完整的压缩前历史。
|
|
179
|
+
恢复快照对应环境后,使用原 session ID 续跑同一个 user turn,不追加用户消息;
|
|
180
|
+
被中止的生成或压缩会重新执行:
|
|
156
181
|
|
|
157
182
|
```bash
|
|
158
183
|
arena --continue --session-id demo
|
|
@@ -291,7 +316,7 @@ claude-code-proxy),收到 401 后会自动切换鉴权方式并记住。流
|
|
|
291
316
|
Anthropic 直连在原生协议上完整支持 thinking。旧版 Claude 和非 Claude
|
|
292
317
|
兼容网关把 `ARENA_ENABLE_THINKING` / `ARENA_THINKING_BUDGET` 映射为
|
|
293
318
|
`thinking.type=enabled` / `thinking.budget_tokens`;Claude Opus/Sonnet 4.6+
|
|
294
|
-
以及
|
|
319
|
+
以及 Haiku 5.5+、Fable、Mythos 使用 `thinking.type=adaptive`,不再发送固定
|
|
295
320
|
budget。Anthropic 要求启用 thinking 时不调整 temperature,此时不发送该
|
|
296
321
|
参数。带签名的 thinking/redacted_thinking block 会原样保存在对应 assistant
|
|
297
322
|
消息的 `reasoning_details` 字段——
|
|
@@ -339,6 +364,46 @@ ARENA_DISALLOWED_TOOLS=["bash","write_file"]
|
|
|
339
364
|
|
|
340
365
|
被禁用的工具不会发送给模型;即使模型仍构造了该工具调用,执行层也会
|
|
341
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
|
+
|
|
342
407
|
`ARENA_EXTRA_BODY` 会在配置加载时解析为 JSON 对象,既作为最后一层请求
|
|
343
408
|
参数覆盖,也会原样写入 `history.json` 的
|
|
344
409
|
`metadata.parameters.extra_body`。
|
|
@@ -357,21 +422,24 @@ Agent Arena 通过 `ARENA_COMPACTION_MODE` 选择压缩方式,默认 `rolling`
|
|
|
357
422
|
ARENA_COMPACTION_MODE=summary arena -p "继续任务" --session-id demo
|
|
358
423
|
```
|
|
359
424
|
|
|
360
|
-
`summary`
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
与当前 Code Arena 使用相同的摘要系统提示词、请求提示词和 continuation preamble。
|
|
425
|
+
`summary` 达到阈值时同步生成完整摘要,优先保留当前用户回合,不启动或恢复
|
|
426
|
+
后台记忆缓存。当前实现位于 `src/summary-compaction.ts`,与 Code Arena 共用
|
|
427
|
+
摘要系统提示词、请求提示词和 continuation preamble。
|
|
364
428
|
摘要按 `Goal`、`Instructions`、`Discoveries`、`Accomplished` 等章节生成,使用
|
|
365
429
|
主模型、temperature=0、关闭 thinking,默认输出上限为 8192。
|
|
366
430
|
`ARENA_COMPACTION_SUMMARY_MAX_TOKENS` 可覆盖上限;`rolling` 的默认上限仍为 131072。
|
|
367
431
|
两种模式默认阈值均为 250k;只有窗口和比例两个环境变量都配置有效时才使用乘积。
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
432
|
+
两种模式及 Code Arena 都优先使用最近一次主请求的服务端 usage,仅估算此后新增的内容。
|
|
433
|
+
大段文本取字符估算与带 10% 余量的 `o200k_base` 分词估算的较大值,减少 SVG 等
|
|
434
|
+
密集文本的低估;本地分词不保证与所选模型完全一致。长用户回合没有更早回合可归档,
|
|
435
|
+
或当前回合本身达到阈值时,摘要模式也可归档回合内较早的步骤,保留最近完整步骤的
|
|
436
|
+
工具调用、全部结果及图片;Code Arena 的摘要压缩也支持此行为。
|
|
437
|
+
Code Arena 始终使用摘要模式,阈值仍为窗口与比例的乘积,默认比例 70%。
|
|
438
|
+
|
|
439
|
+
Agent Arena 两种模式和 Code Arena 都保存已应用的 `compaction` 历史记录,续跑时恢复
|
|
440
|
+
压缩后的上下文。原始历史保留服务端 usage,但已压缩上下文中的旧 usage 不再作为计数基线。
|
|
441
|
+
Agent Arena 历史 metadata 记录本次压缩方式;下一次运行仍以当前环境变量为准,
|
|
442
|
+
未设置时回到 `rolling`,切换模式也会恢复已应用的压缩记录。
|
|
375
443
|
|
|
376
444
|
以下后台记忆机制适用于 `rolling` 模式。
|
|
377
445
|
|
|
@@ -434,7 +502,9 @@ Agent Arena 记忆更新使用主模型、temperature=1、thinking
|
|
|
434
502
|
|
|
435
503
|
`ARENA_CONTEXT_WINDOW` 应设置为所用模型的真实上下文窗口;默认 `1000000` 是
|
|
436
504
|
当前默认模型的暂定值。Agent Arena 的 usage 基线来自 provider;最近 assistant
|
|
437
|
-
之后的新增上下文以及缺少 usage
|
|
505
|
+
之后的新增上下文以及缺少 usage 时的完整上下文仍使用本地保守估算。压缩后,保留消息
|
|
506
|
+
中的旧 usage 仅用于原始历史的用量统计;下一次主请求完成后重新建立计数基线。
|
|
507
|
+
如果摘要请求失败,Harness 会保留原历史并继续正常模型请求,避免
|
|
438
508
|
摘要服务的瞬时故障直接中断任务。
|
|
439
509
|
|
|
440
510
|
Responses 与其他协议一样,会在每个工具 step 前检查是否需要压缩。由于所有
|
|
@@ -935,7 +1005,10 @@ curl -N -X POST \
|
|
|
935
1005
|
|
|
936
1006
|
构建时会将两份规范生成为 `dist` 内的 JS 数据模块;npm 包不包含原始
|
|
937
1007
|
`spec/` 目录,运行时也不依赖仓库文件。执行工具前仍使用 AJV 校验原始
|
|
938
|
-
Draft-07 schema
|
|
1008
|
+
Draft-07 schema。普通模式的审计模板也从同一份生成数据取得;`src/prompts.ts`
|
|
1009
|
+
中直接维护的正文用于 Repo 模式。
|
|
1010
|
+
|
|
1011
|
+
Agent Arena 提供以下工具:
|
|
939
1012
|
|
|
940
1013
|
`bash`、`ask_user`、`add_voice`、`edit_file`、`read_file`、`fetch_page`、
|
|
941
1014
|
`web_search`、`write_file`、`image_search`、`present_file`、`stop_process`、
|
|
@@ -944,8 +1017,10 @@ Draft-07 schema。repo mode 的动态提示词模板保存在 `src/prompts.ts`
|
|
|
944
1017
|
新版生产导出的前 50 条脱敏请求单独保存在
|
|
945
1018
|
[`tasks/v2`](tasks/v2/README.md),没有与原有 replay 数据混放。该目录记录了
|
|
946
1019
|
2 GB 源文件的 SHA-256、全量解析统计、tool schema 变体和可重复执行的拆分命令;
|
|
947
|
-
|
|
948
|
-
|
|
1020
|
+
每条历史样本在 `request.messages` 中保留当时的系统提示词,不另存一份提示词 TXT。
|
|
1021
|
+
`npm run refresh:arena-v2-contract` 是从这批旧数据重新导入契约的历史维护命令,
|
|
1022
|
+
会用首条样本覆盖 `spec/message.json` 并重新生成派生数据;它不用于日常构建或
|
|
1023
|
+
获取最新官方提示词。
|
|
949
1024
|
|
|
950
1025
|
`bash` 只用于一次性命令:超时结果为 `status=timeout`,被信号终止为
|
|
951
1026
|
`status=killed`,并会清理命令遗留的后台进程。需要跨 tool call 存活的 server、
|
|
@@ -984,9 +1059,10 @@ Arena 的 `fetch_page` 和 Code Arena 的 `web_fetch` 默认仍使用本地抓
|
|
|
984
1059
|
`ROUTIFY_API_KEY`。
|
|
985
1060
|
|
|
986
1061
|
`generate_image`、`generate_speech` 使用 ModelRouter/Routify,并从工作区
|
|
987
|
-
`.env` 读取同一个 `ROUTIFY_API_KEY
|
|
988
|
-
`
|
|
989
|
-
|
|
1062
|
+
`.env` 读取同一个 `ROUTIFY_API_KEY`。GPT Image 图片生成与编辑使用 OpenAI 图片
|
|
1063
|
+
协议,可用 `ROUTIFY_OPENAI_BASE_URL` 覆盖 ModelRouter 根地址。显式选择 Gemini
|
|
1064
|
+
图片模型及默认 Gemini TTS 时使用 Google 协议,可用 `ROUTIFY_VERTEX_BASE_URL`
|
|
1065
|
+
覆盖对应根地址,不需要新增服务密钥。
|
|
990
1066
|
|
|
991
1067
|
`fetch_page` 使用 Defuddle 将 HTML 正文转换为 Markdown,并使用
|
|
992
1068
|
`pdf-parse` 提取 PDF 的前 30 页。响应按 Markdown 边界稳定分块,同一轮内
|
|
@@ -1001,8 +1077,10 @@ XLSX 仍可提取文字和表格内容,旧版 DOC、PPT、XLS 会先通过 Lib
|
|
|
1001
1077
|
内容继续发送给模型。所有工具结果另有 1,000,000 字符的最终兜底。当前不做
|
|
1002
1078
|
OCR,扫描版 PDF 只会返回无可提取文字的提示。
|
|
1003
1079
|
|
|
1004
|
-
图片和语音的默认 ModelRouter 路由分别为 `gemini-3.1-flash-image` 和
|
|
1005
|
-
`gemini-3.1-flash-tts-preview`,可用 `ARENA_IMAGE_MODEL`、`ARENA_SPEECH_MODEL`
|
|
1080
|
+
Agent Arena 图片和语音的默认 ModelRouter 路由分别为 `gemini-3.1-flash-image` 和
|
|
1081
|
+
`gemini-3.1-flash-tts-preview`,可用 `ARENA_IMAGE_MODEL`、`ARENA_SPEECH_MODEL` 覆盖。
|
|
1082
|
+
Code Arena 图片默认使用 `gpt-image-2.5-sunburst`;配置时优先使用
|
|
1083
|
+
`CODE_HARNESS_IMAGE_MODEL`,其次使用显式设置的 `ARENA_IMAGE_MODEL`。图片生成结果和
|
|
1006
1084
|
搜索到的图片会立即下载到工作区;每次图片搜索的结果按生产命名保存为
|
|
1007
1085
|
`image-search/<query slug>-<序号>.<扩展名>`,并作为可预览文件显示在 Web UI。
|
|
1008
1086
|
语音生成先由 `add_voice` 生成两个试听候选并让用户选择,选择结果注册为稳定的
|
package/dist/agent.d.ts
CHANGED
|
@@ -16,7 +16,7 @@ interface AgentOptions {
|
|
|
16
16
|
clientContext?: ClientContext;
|
|
17
17
|
workspaceContext?: WorkspaceContext;
|
|
18
18
|
onCheckpoint?: (messages: readonly Message[], tools: readonly ToolDefinition[], sessionMemory?: StoredSessionMemory | null) => Promise<void>;
|
|
19
|
-
/** Inspect
|
|
19
|
+
/** Inspect repetition, tool-call batches and pending Agent Arena compactions. */
|
|
20
20
|
midturnCheckpointHook?: MidturnCheckpointHook;
|
|
21
21
|
/** Materialize the current history and create the external snapshot. */
|
|
22
22
|
onMidturnCheckpoint?: (messages: readonly Message[], tools: readonly ToolDefinition[], event: MidturnCheckpointHookEvent, action: Exclude<CheckpointAction, "continue">) => Promise<void>;
|
package/dist/agent.js
CHANGED
|
@@ -389,6 +389,7 @@ export class Agent {
|
|
|
389
389
|
return await this.runTurn(prompt, history, runOptions);
|
|
390
390
|
}
|
|
391
391
|
finally {
|
|
392
|
+
await this.tools.closeConnectors?.();
|
|
392
393
|
await settleSessionMemory(this.memoryCache);
|
|
393
394
|
await this.checkpointQueue;
|
|
394
395
|
}
|
|
@@ -412,9 +413,11 @@ export class Agent {
|
|
|
412
413
|
// carrying the old runtime configuration into the next request.
|
|
413
414
|
const dialogue = withoutLeadingSystemMessage(history || []).map((message) => normalizeToolCallHistory(message, this.config.mode));
|
|
414
415
|
const trajectory = completeInterruptedToolCalls(this.options.reuseHistoryMessages ? [...dialogue] : structuredClone(dialogue));
|
|
416
|
+
const connectors = this.config.disallowedTools?.includes("list_connector_tools")
|
|
417
|
+
? undefined : this.config.connectors;
|
|
415
418
|
const runtimeSystemMessage = repoContext
|
|
416
|
-
? repoSystemMessage(clientContext, repoContext, this.config.mode, this.workspaceContext, this.config.maxTurns)
|
|
417
|
-
: systemMessage(clientContext, this.workspaceContext, this.config.mode, this.config.maxTurns);
|
|
419
|
+
? repoSystemMessage(clientContext, repoContext, this.config.mode, this.workspaceContext, this.config.maxTurns, connectors)
|
|
420
|
+
: systemMessage(clientContext, this.workspaceContext, this.config.mode, this.config.maxTurns, connectors);
|
|
418
421
|
applySystemPromptConfig(runtimeSystemMessage, this.config);
|
|
419
422
|
if (runOptions.resumeFromHistory) {
|
|
420
423
|
if (!isContinuableHistory(dialogue)) {
|
|
@@ -424,6 +427,9 @@ export class Agent {
|
|
|
424
427
|
else {
|
|
425
428
|
trajectory.push(userMessage);
|
|
426
429
|
}
|
|
430
|
+
// A new human reply requires discovery again. Same-turn continuation only
|
|
431
|
+
// reloads schemas from paired discovery results; it never repeats writes.
|
|
432
|
+
await this.tools.beginConnectorTurn?.(runOptions.resumeFromHistory ? dialogue : undefined);
|
|
427
433
|
// Compaction projects model input with a runtime system prefix; the lossless
|
|
428
434
|
// trajectory and its checkpoints remain in the official dialogue format.
|
|
429
435
|
const messages = modelContextFromTrajectory([runtimeSystemMessage, ...trajectory]);
|
|
@@ -466,9 +472,11 @@ export class Agent {
|
|
|
466
472
|
this.sink.message(this.sessionId, recorded, trajectory);
|
|
467
473
|
await this.checkpoint(trajectory);
|
|
468
474
|
};
|
|
469
|
-
await this.maybeCompact(messages, trajectory);
|
|
470
|
-
this.sink.start(this.sessionId, trajectory);
|
|
471
475
|
try {
|
|
476
|
+
const compaction = await this.maybeCompact(messages, trajectory);
|
|
477
|
+
this.sink.start(this.sessionId, trajectory);
|
|
478
|
+
if (compaction === "checkpoint")
|
|
479
|
+
return await this.pause(trajectory, "checkpoint");
|
|
472
480
|
await this.checkpoint(trajectory);
|
|
473
481
|
const maxTurns = this.config.maxTurns
|
|
474
482
|
? Math.max(1, Math.floor(this.config.maxTurns))
|
|
@@ -483,13 +491,15 @@ export class Agent {
|
|
|
483
491
|
// Every transport is replayable from local history. Native Responses
|
|
484
492
|
// stores its encrypted reasoning output items there, so compaction is
|
|
485
493
|
// safe at the same tool-step boundaries as the other protocols.
|
|
486
|
-
if (step > 1)
|
|
487
|
-
await this.
|
|
494
|
+
if (step > 1 && await this.maybeCompact(messages, trajectory, "inner") === "checkpoint") {
|
|
495
|
+
return await this.pause(trajectory, "checkpoint");
|
|
496
|
+
}
|
|
488
497
|
const offeredTools = this.tools.availableTools();
|
|
489
498
|
this.sink.modelStart(this.sessionId, step, offeredTools.map((tool) => tool.function.name));
|
|
490
499
|
let assistant;
|
|
500
|
+
let finishReason;
|
|
491
501
|
try {
|
|
492
|
-
assistant = await this.assistantTurn(messages, offeredTools, trajectory);
|
|
502
|
+
({ assistant, finishReason } = await this.assistantTurn(messages, offeredTools, trajectory));
|
|
493
503
|
}
|
|
494
504
|
catch (error) {
|
|
495
505
|
if (error instanceof CheckpointExit) {
|
|
@@ -509,8 +519,14 @@ export class Agent {
|
|
|
509
519
|
this.options.onModelFailure?.(trajectory, error);
|
|
510
520
|
throw error;
|
|
511
521
|
}
|
|
512
|
-
const finishReason = assistant.tool_calls?.length ? "tool_calls" : "stop";
|
|
513
522
|
this.sink.modelComplete(this.sessionId, step, finishReason, assistant.usage);
|
|
523
|
+
if (finishReason === "length") {
|
|
524
|
+
// A completed but truncated request is not a transport failure. Keep
|
|
525
|
+
// its evidence without retrying it or executing unfinished tool calls.
|
|
526
|
+
await recordMessage(assistant);
|
|
527
|
+
throw new Error("Model response was truncated (finish_reason=length). " +
|
|
528
|
+
"The turn did not complete; any partial output has been preserved.");
|
|
529
|
+
}
|
|
514
530
|
if (assistant.tool_calls?.length &&
|
|
515
531
|
this.options.midturnCheckpointHook) {
|
|
516
532
|
const event = {
|
|
@@ -609,7 +625,7 @@ export class Agent {
|
|
|
609
625
|
return this.sink.result(this.sessionId, status, messages, error, undefined, this.memoryCache.update);
|
|
610
626
|
}
|
|
611
627
|
async makeCheckpoint(messages, tools, event, action) {
|
|
612
|
-
await settleSessionMemory(this.memoryCache);
|
|
628
|
+
await settleSessionMemory(this.memoryCache, event.type !== "before_compaction" || action !== "snapshot_continue");
|
|
613
629
|
await this.checkpoint(messages);
|
|
614
630
|
if (!this.options.onMidturnCheckpoint) {
|
|
615
631
|
throw new Error("mid-turn checkpoint hook triggered without a snapshot handler");
|
|
@@ -636,6 +652,30 @@ export class Agent {
|
|
|
636
652
|
compactionThresholdTokens: isCodeArenaMode(this.config.mode) ? undefined
|
|
637
653
|
: this.options.compactionThresholdTokens ?? this.config.compactionThresholdTokens,
|
|
638
654
|
legacy: isCodeArenaMode(this.config.mode) || this.config.compactionMode === "summary",
|
|
655
|
+
onBeforeCompaction: !isCodeArenaMode(this.config.mode) && this.options.midturnCheckpointHook
|
|
656
|
+
? async (estimatedTokensBefore, thresholdTokens) => {
|
|
657
|
+
const event = {
|
|
658
|
+
type: "before_compaction", sessionId: this.sessionId, toolCalls: [],
|
|
659
|
+
compaction: {
|
|
660
|
+
count: trajectory.filter(message => message.role === "compaction" && !message.undone).length + 1,
|
|
661
|
+
mode: this.config.compactionMode === "summary" ? "summary" : "rolling",
|
|
662
|
+
source, estimatedTokensBefore, thresholdTokens,
|
|
663
|
+
},
|
|
664
|
+
};
|
|
665
|
+
let action;
|
|
666
|
+
try {
|
|
667
|
+
action = checkpointAction(await this.options.midturnCheckpointHook(event));
|
|
668
|
+
if (action !== "continue") {
|
|
669
|
+
await this.makeCheckpoint(trajectory, this.tools.availableTools(), event, action);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
catch (error) {
|
|
673
|
+
throw new CheckpointFailure(error);
|
|
674
|
+
}
|
|
675
|
+
if (action === "snapshot_exit")
|
|
676
|
+
throw new CheckpointExit(event);
|
|
677
|
+
}
|
|
678
|
+
: undefined,
|
|
639
679
|
};
|
|
640
680
|
const contextLength = messages.length;
|
|
641
681
|
const result = settings.legacy
|
|
@@ -647,26 +687,27 @@ export class Agent {
|
|
|
647
687
|
return this.checkpoint(trajectory).catch(() => undefined);
|
|
648
688
|
});
|
|
649
689
|
if (result.compacted) {
|
|
690
|
+
const covered = settings.legacy ? result.coveredMessages : this.memoryCache.state.coveredMessages;
|
|
691
|
+
const retained = contextLength - covered;
|
|
692
|
+
// Insert at the covered boundary, before the retained original tail.
|
|
693
|
+
// Marker position is the resume boundary; no separate index is stored.
|
|
694
|
+
let insertion = trajectory.length;
|
|
695
|
+
let remaining = retained;
|
|
696
|
+
while (remaining > 0 && insertion > 0) {
|
|
697
|
+
if (trajectory[--insertion].role !== "compaction")
|
|
698
|
+
remaining--;
|
|
699
|
+
}
|
|
700
|
+
trajectory.splice(insertion, 0, {
|
|
701
|
+
role: "compaction", content: [{ type: "text", text: result.memory }],
|
|
702
|
+
undone: false,
|
|
703
|
+
compaction: {
|
|
704
|
+
ratio: result.estimatedTokensBefore / this.config.contextWindow,
|
|
705
|
+
tokens_at_compaction: result.estimatedTokensBefore,
|
|
706
|
+
source,
|
|
707
|
+
retained_messages: retained,
|
|
708
|
+
},
|
|
709
|
+
});
|
|
650
710
|
if (!isCodeArenaMode(this.config.mode)) {
|
|
651
|
-
const covered = settings.legacy ? result.coveredMessages : this.memoryCache.state.coveredMessages;
|
|
652
|
-
const retained = contextLength - covered;
|
|
653
|
-
// Insert at the covered boundary, before the retained original tail.
|
|
654
|
-
// Marker position is the resume boundary; no separate index is stored.
|
|
655
|
-
let insertion = trajectory.length;
|
|
656
|
-
let remaining = retained;
|
|
657
|
-
while (remaining > 0 && insertion > 0) {
|
|
658
|
-
if (trajectory[--insertion].role !== "compaction")
|
|
659
|
-
remaining--;
|
|
660
|
-
}
|
|
661
|
-
trajectory.splice(insertion, 0, {
|
|
662
|
-
role: "compaction", content: [{ type: "text", text: result.memory }],
|
|
663
|
-
undone: false,
|
|
664
|
-
compaction: {
|
|
665
|
-
ratio: result.estimatedTokensBefore / this.config.contextWindow,
|
|
666
|
-
tokens_at_compaction: result.estimatedTokensBefore,
|
|
667
|
-
source,
|
|
668
|
-
},
|
|
669
|
-
});
|
|
670
711
|
// Coverage indices referred to the pre-compaction history. The next
|
|
671
712
|
// update reads its prior memory from the newly embedded block.
|
|
672
713
|
this.memoryCache.state = undefined;
|
|
@@ -680,7 +721,11 @@ export class Agent {
|
|
|
680
721
|
}
|
|
681
722
|
return result.compacted;
|
|
682
723
|
}
|
|
683
|
-
catch {
|
|
724
|
+
catch (error) {
|
|
725
|
+
if (error instanceof CheckpointExit)
|
|
726
|
+
return "checkpoint";
|
|
727
|
+
if (error instanceof CheckpointFailure)
|
|
728
|
+
throw error.cause;
|
|
684
729
|
// Compaction is protective rather than task-critical. If its dedicated
|
|
685
730
|
// summary request fails, keep the original history and let the normal
|
|
686
731
|
// model request (and its retry policy) decide whether it still fits.
|
|
@@ -707,6 +752,7 @@ export class Agent {
|
|
|
707
752
|
const calls = new Map();
|
|
708
753
|
const responseItems = new Map();
|
|
709
754
|
let usage;
|
|
755
|
+
let finishReason;
|
|
710
756
|
let reasoningDetails;
|
|
711
757
|
const detectors = this.options.midturnCheckpointHook ? {
|
|
712
758
|
content: new RepetitiveOutputDetector(),
|
|
@@ -775,6 +821,9 @@ export class Agent {
|
|
|
775
821
|
// letting the last non-empty value win.
|
|
776
822
|
if (chunk.usage)
|
|
777
823
|
usage = chunk.usage;
|
|
824
|
+
if (chunk.choices?.[0]?.finish_reason) {
|
|
825
|
+
finishReason = chunk.choices[0].finish_reason;
|
|
826
|
+
}
|
|
778
827
|
if (chunk.reasoning_details?.length) {
|
|
779
828
|
reasoningDetails = chunk.reasoning_details;
|
|
780
829
|
}
|
|
@@ -813,6 +862,11 @@ export class Agent {
|
|
|
813
862
|
if (fragment.function?.arguments) {
|
|
814
863
|
call.function.arguments += String(fragment.function.arguments);
|
|
815
864
|
}
|
|
865
|
+
// Metadata is an opaque value for this index, not a text delta.
|
|
866
|
+
// Later fragments that omit it must not erase an earlier signature.
|
|
867
|
+
if (fragment.extra_content !== undefined) {
|
|
868
|
+
call.extra_content = structuredClone(fragment.extra_content);
|
|
869
|
+
}
|
|
816
870
|
calls.set(index, call);
|
|
817
871
|
this.sink.delta(this.sessionId, {
|
|
818
872
|
tool_calls: [
|
|
@@ -824,6 +878,9 @@ export class Agent {
|
|
|
824
878
|
name: String(fragment.function?.name || ""),
|
|
825
879
|
arguments: String(fragment.function?.arguments || ""),
|
|
826
880
|
},
|
|
881
|
+
...(fragment.extra_content !== undefined
|
|
882
|
+
? { extra_content: structuredClone(fragment.extra_content) }
|
|
883
|
+
: {}),
|
|
827
884
|
},
|
|
828
885
|
],
|
|
829
886
|
});
|
|
@@ -896,7 +953,10 @@ export class Agent {
|
|
|
896
953
|
.sort(([left], [right]) => left - right)
|
|
897
954
|
.map(([, call]) => call);
|
|
898
955
|
}
|
|
899
|
-
return
|
|
956
|
+
return {
|
|
957
|
+
assistant,
|
|
958
|
+
finishReason: finishReason ?? (assistant.tool_calls?.length ? "tool_calls" : "stop"),
|
|
959
|
+
};
|
|
900
960
|
}
|
|
901
961
|
}
|
|
902
962
|
async retryBackoff(attempt) {
|