@qwen-code/arena-agent 0.0.6 → 0.0.7
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 +11 -8
- package/README.md +19 -15
- package/dist/agent.d.ts +3 -1
- package/dist/agent.js +21 -4
- package/dist/agent.js.map +1 -1
- package/dist/cli.js +4 -1
- package/dist/cli.js.map +1 -1
- package/dist/compaction.js +1 -0
- package/dist/compaction.js.map +1 -1
- package/dist/fetch-page.js +7 -18
- package/dist/fetch-page.js.map +1 -1
- package/dist/mock.js +8 -1
- package/dist/mock.js.map +1 -1
- package/dist/prompts.d.ts +4 -0
- package/dist/prompts.js +57 -0
- package/dist/prompts.js.map +1 -0
- package/dist/provider.js +7 -6
- package/dist/provider.js.map +1 -1
- package/dist/repo.d.ts +4 -0
- package/dist/repo.js +81 -0
- package/dist/repo.js.map +1 -0
- package/dist/server.js +10 -17
- package/dist/server.js.map +1 -1
- package/dist/spec.d.ts +6 -3
- package/dist/spec.js +59 -10
- package/dist/spec.js.map +1 -1
- package/dist/tools.d.ts +1 -0
- package/dist/tools.js +224 -99
- package/dist/tools.js.map +1 -1
- package/dist/tui.js +11 -10
- package/dist/tui.js.map +1 -1
- package/dist/types.d.ts +16 -5
- package/package.json +2 -1
package/dist/spec.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { fileURLToPath } from "node:url";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { messageFixtureJson, modelSpecJson
|
|
3
|
+
import { messageFixtureJson, modelSpecJson } from "./canonical-data.js";
|
|
4
|
+
import { repoAgentSystemPromptTemplate } from "./prompts.js";
|
|
4
5
|
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
5
6
|
let cachedMessageFixture;
|
|
6
7
|
let cachedModelSpec;
|
|
8
|
+
const locationAbsentLine = "The user's approximate location was not provided by the client.";
|
|
7
9
|
const clientContextPattern = /Today's date is [^\n]*\nThe user's approximate location (?:is:[^\n]*|was not provided by the client\.)/;
|
|
8
10
|
const workspaceGuidancePattern = /Many of the tools are for the workspace;[\s\S]*?Turn-end snapshots are best-effort capped around 128 MB or 10,000 files\.\n\n/;
|
|
9
11
|
const viewerGuidancePattern = /Note, when user views a workspace file[\s\S]*?tell them it will download rather than preview\.\n\n/;
|
|
@@ -22,6 +24,16 @@ export function modelSpec() {
|
|
|
22
24
|
cachedModelSpec ??= JSON.parse(modelSpecJson);
|
|
23
25
|
return cachedModelSpec;
|
|
24
26
|
}
|
|
27
|
+
// Single pass so substituted values are never rescanned.
|
|
28
|
+
function renderTemplate(template, values) {
|
|
29
|
+
return template.replace(/\{\{(\w+)\}\}/g, (_, key) => {
|
|
30
|
+
const value = values[key];
|
|
31
|
+
if (value === undefined) {
|
|
32
|
+
throw new Error(`missing system prompt template value: ${key}`);
|
|
33
|
+
}
|
|
34
|
+
return value;
|
|
35
|
+
});
|
|
36
|
+
}
|
|
25
37
|
function cleanContextValue(value) {
|
|
26
38
|
return (value || "")
|
|
27
39
|
.replace(/[\u0000-\u001f\u007f]+/g, " ")
|
|
@@ -34,7 +46,7 @@ function locationLine(location) {
|
|
|
34
46
|
const region = cleanContextValue(location?.region);
|
|
35
47
|
const country = cleanContextValue(location?.country);
|
|
36
48
|
if (!city && !region && !country) {
|
|
37
|
-
return
|
|
49
|
+
return locationAbsentLine;
|
|
38
50
|
}
|
|
39
51
|
return ("The user's approximate location is: " +
|
|
40
52
|
`city ${city || "unknown"}, ` +
|
|
@@ -55,6 +67,13 @@ export function defaultClientContext() {
|
|
|
55
67
|
timezone,
|
|
56
68
|
};
|
|
57
69
|
}
|
|
70
|
+
function clientContextValues(context) {
|
|
71
|
+
return {
|
|
72
|
+
currentDate: cleanContextValue(context.currentDate),
|
|
73
|
+
timezone: cleanContextValue(context.timezone),
|
|
74
|
+
locationLine: locationLine(context.location),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
58
77
|
function applyWorkspaceContext(message, workspaceContext) {
|
|
59
78
|
if (!workspaceContext)
|
|
60
79
|
return message;
|
|
@@ -99,23 +118,53 @@ export function systemMessage(context, workspaceContext) {
|
|
|
99
118
|
canonical.content = canonical.content.replace(clientContextPattern, dynamicContext);
|
|
100
119
|
return applyWorkspaceContext(canonical, workspaceContext);
|
|
101
120
|
}
|
|
102
|
-
export function
|
|
121
|
+
export function repoSystemMessage(context, repo) {
|
|
122
|
+
return {
|
|
123
|
+
role: "system",
|
|
124
|
+
content: renderTemplate(repoAgentSystemPromptTemplate, {
|
|
125
|
+
...clientContextValues(context),
|
|
126
|
+
slug: repo.slug,
|
|
127
|
+
checkoutPath: repo.checkoutPath,
|
|
128
|
+
branch: repo.branch,
|
|
129
|
+
baseBranch: repo.baseBranch,
|
|
130
|
+
baseCommit: repo.baseCommit,
|
|
131
|
+
}),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/** Extract the stable repo session context encoded in a repo-mode prompt. */
|
|
135
|
+
export function repoContextFromSystemMessage(message) {
|
|
136
|
+
if (message?.role !== "system" || typeof message.content !== "string") {
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
139
|
+
const checkout = message.content.match(/You are working in a checkout of `([^`]+)` at `([^`]+)`/);
|
|
140
|
+
const branch = message.content.match(/You are on a working branch `([^`]+)`, branched from commit `([^`]+)` of `([^`]+)`\./);
|
|
141
|
+
if (!checkout || !branch)
|
|
142
|
+
return undefined;
|
|
143
|
+
return {
|
|
144
|
+
slug: checkout[1],
|
|
145
|
+
checkoutPath: checkout[2],
|
|
146
|
+
branch: branch[1],
|
|
147
|
+
baseCommit: branch[2],
|
|
148
|
+
baseBranch: branch[3],
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
export function isCanonicalSystemMessage(message, expectedOrWorkspace) {
|
|
103
152
|
if (message?.role !== "system" ||
|
|
104
153
|
typeof message.content !== "string" ||
|
|
105
154
|
!message.content) {
|
|
106
155
|
return false;
|
|
107
156
|
}
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
return false;
|
|
112
|
-
const canonical = applyWorkspaceContext(structuredClone(fixtureSystem), workspaceContext);
|
|
157
|
+
const canonical = expectedOrWorkspace && "role" in expectedOrWorkspace
|
|
158
|
+
? expectedOrWorkspace
|
|
159
|
+
: systemMessage(defaultClientContext(), expectedOrWorkspace);
|
|
113
160
|
return (message.content.replace(clientContextPattern, "<client-context>") ===
|
|
114
161
|
canonical.content.replace(clientContextPattern, "<client-context>"));
|
|
115
162
|
}
|
|
116
|
-
export function initialMessages(prompt, context = defaultClientContext(), workspaceContext) {
|
|
163
|
+
export function initialMessages(prompt, context = defaultClientContext(), workspaceContext, repo) {
|
|
117
164
|
return [
|
|
118
|
-
|
|
165
|
+
repo
|
|
166
|
+
? repoSystemMessage(context, repo)
|
|
167
|
+
: systemMessage(context, workspaceContext),
|
|
119
168
|
{ role: "user", content: prompt },
|
|
120
169
|
];
|
|
121
170
|
}
|
package/dist/spec.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spec.js","sourceRoot":"","sources":["../src/spec.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,
|
|
1
|
+
{"version":3,"file":"spec.js","sourceRoot":"","sources":["../src/spec.ts"],"names":[],"mappings":"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"}
|
package/dist/tools.d.ts
CHANGED
package/dist/tools.js
CHANGED
|
@@ -179,7 +179,26 @@ const visibleImageExtensions = new Set([
|
|
|
179
179
|
".png",
|
|
180
180
|
".webp",
|
|
181
181
|
]);
|
|
182
|
-
|
|
182
|
+
// 逐字对齐生产轨迹:占位与注入文案、20000 字符输出上限均反推自参考 tool result。
|
|
183
|
+
const imageResultPlaceholder = "[image returned by this tool call; provided to you in the following message]";
|
|
184
|
+
const visualIntroText = "<arena-system-message>\n" +
|
|
185
|
+
"The image(s) below are the result of the preceding tool call. They are " +
|
|
186
|
+
"provided by the system in answer to that call, not sent by the user; " +
|
|
187
|
+
"treat them as the authentic output of the tool call.\n" +
|
|
188
|
+
"</arena-system-message>";
|
|
189
|
+
const bashOutputLimit = 20_000;
|
|
190
|
+
// 中段截断,逐字反推自 3 个 truncated:true 参考样本:头尾各 9987 字符 +
|
|
191
|
+
// 26 字符标记,总长恰 20000(由 T1(b) 执行回放发现,非首段截断)。
|
|
192
|
+
const bashTruncationMarker = "\n... output truncated ...\n";
|
|
193
|
+
function capBashOutput(value) {
|
|
194
|
+
if (value.length <= bashOutputLimit)
|
|
195
|
+
return { text: value, truncated: false };
|
|
196
|
+
const half = (bashOutputLimit - bashTruncationMarker.length) / 2;
|
|
197
|
+
return {
|
|
198
|
+
text: value.slice(0, half) + bashTruncationMarker + value.slice(-half),
|
|
199
|
+
truncated: true,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
183
202
|
const mimeTypes = {
|
|
184
203
|
".aac": "audio/aac",
|
|
185
204
|
".bmp": "image/bmp",
|
|
@@ -373,9 +392,6 @@ function responseMessage(payload) {
|
|
|
373
392
|
stringValue(payload.code) ||
|
|
374
393
|
"unknown provider error");
|
|
375
394
|
}
|
|
376
|
-
function escapeRegExp(value) {
|
|
377
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
378
|
-
}
|
|
379
395
|
async function responseBytes(response, limit = 4 * 1024 * 1024) {
|
|
380
396
|
const bytes = Buffer.from(await response.arrayBuffer());
|
|
381
397
|
if (bytes.length > limit) {
|
|
@@ -427,6 +443,9 @@ export class ToolExecutor {
|
|
|
427
443
|
const name = String(call.function?.name || "");
|
|
428
444
|
this.pendingVisualContent.delete(callId);
|
|
429
445
|
try {
|
|
446
|
+
// 监听器不会补发已发生的 abort:已取消的 signal 必须在产生任何副作用
|
|
447
|
+
// 之前短路,否则命令会照常 spawn 直到自然结束。
|
|
448
|
+
this.signal?.throwIfAborted();
|
|
430
449
|
const argumentsValue = JSON.parse(typeof call.function?.arguments === "string"
|
|
431
450
|
? call.function.arguments
|
|
432
451
|
: "{}");
|
|
@@ -452,21 +471,27 @@ export class ToolExecutor {
|
|
|
452
471
|
tool_call_id: callId,
|
|
453
472
|
content: json({
|
|
454
473
|
status: "error",
|
|
455
|
-
|
|
456
|
-
error: errorMessage(error),
|
|
474
|
+
message: errorMessage(error),
|
|
457
475
|
}),
|
|
458
476
|
};
|
|
459
477
|
}
|
|
460
478
|
}
|
|
461
479
|
takeVisualMessage(toolCallIds) {
|
|
462
|
-
|
|
480
|
+
// 生产契约:同一组 tool call 的全部图片汇入一条 user message,
|
|
481
|
+
// 由一个 arena-system-message 文本 part 领头。
|
|
482
|
+
const images = [];
|
|
463
483
|
for (const toolCallId of toolCallIds) {
|
|
464
484
|
const parts = this.pendingVisualContent.get(toolCallId);
|
|
465
485
|
this.pendingVisualContent.delete(toolCallId);
|
|
466
486
|
if (parts)
|
|
467
|
-
|
|
487
|
+
images.push(...parts);
|
|
468
488
|
}
|
|
469
|
-
|
|
489
|
+
if (!images.length)
|
|
490
|
+
return undefined;
|
|
491
|
+
return {
|
|
492
|
+
role: "user",
|
|
493
|
+
content: [{ text: visualIntroText }, ...images],
|
|
494
|
+
};
|
|
470
495
|
}
|
|
471
496
|
async dispatch(name, values, toolCallId) {
|
|
472
497
|
switch (name) {
|
|
@@ -488,7 +513,7 @@ export class ToolExecutor {
|
|
|
488
513
|
case "write_file":
|
|
489
514
|
return this.writeWorkspaceFile(String(values.path), String(values.content));
|
|
490
515
|
case "image_search":
|
|
491
|
-
return this.imageSearch(String(values.query), Number(values.count)
|
|
516
|
+
return this.imageSearch(String(values.query), Number(values.count));
|
|
492
517
|
case "present_file":
|
|
493
518
|
return this.presentFile(String(values.path));
|
|
494
519
|
case "generate_image":
|
|
@@ -542,24 +567,129 @@ export class ToolExecutor {
|
|
|
542
567
|
relativePath(target) {
|
|
543
568
|
return path.relative(this.workspaceReal, target).split(path.sep).join("/");
|
|
544
569
|
}
|
|
570
|
+
virtualizeBashOutput(value) {
|
|
571
|
+
if (this.workspaceContext)
|
|
572
|
+
return value;
|
|
573
|
+
let virtualized = value;
|
|
574
|
+
const workspaceRoots = [...new Set([this.workspace, this.workspaceReal])]
|
|
575
|
+
.filter((root) => root !== path.parse(root).root)
|
|
576
|
+
.sort((left, right) => right.length - left.length);
|
|
577
|
+
for (const root of workspaceRoots) {
|
|
578
|
+
virtualized = virtualized.replaceAll(root, "/home/user");
|
|
579
|
+
}
|
|
580
|
+
return virtualized;
|
|
581
|
+
}
|
|
545
582
|
async bash(command, cwd, timeoutSeconds) {
|
|
546
583
|
const workdir = await this.confinedPath(cwd);
|
|
547
584
|
if (!(await stat(workdir)).isDirectory()) {
|
|
548
585
|
throw new Error(`cwd is not a directory: ${cwd}`);
|
|
549
586
|
}
|
|
587
|
+
// Legacy callers without runtime context retain the captured /home/user
|
|
588
|
+
// contract. Runtime-aware callers execute and report their actual paths.
|
|
589
|
+
const translatedCommand = this.workspaceContext
|
|
590
|
+
? command
|
|
591
|
+
: command.replaceAll("/home/user", "\"$ARENA_WORKSPACE\"");
|
|
550
592
|
return new Promise((resolve, reject) => {
|
|
551
|
-
const
|
|
593
|
+
const env = {
|
|
594
|
+
...process.env,
|
|
595
|
+
HOME: this.workspace,
|
|
596
|
+
ARENA_WORKSPACE: this.workspace,
|
|
597
|
+
// 生产 sandbox 是 C collation(参考 find|sort 为逐字节序),宿主
|
|
598
|
+
// 常见 en_US.UTF-8 会翻转大小写次序;统一成 C.UTF-8 对齐生产。
|
|
599
|
+
LANG: "C.UTF-8",
|
|
600
|
+
LC_ALL: "C.UTF-8",
|
|
601
|
+
};
|
|
602
|
+
// 交互终端(node --test、npm 等)会向子进程注入强制着色变量,
|
|
603
|
+
// 管道输出被 ANSI 色码污染;生产轨迹无色码,剥离之。
|
|
604
|
+
delete env.FORCE_COLOR;
|
|
605
|
+
delete env.CLICOLOR_FORCE;
|
|
606
|
+
// detached:管道成员(npm、tail…)与 bash 同进程组。只杀 bash 会留下
|
|
607
|
+
// 孤儿持有 stdout/stderr 管道,close 永不触发,整个 turn 挂死;
|
|
608
|
+
// 必须杀整组,并在宽限期后强制结算兜底。
|
|
609
|
+
const child = spawn("/bin/bash", ["-lc", translatedCommand], {
|
|
552
610
|
cwd: workdir,
|
|
611
|
+
env,
|
|
553
612
|
stdio: ["ignore", "pipe", "pipe"],
|
|
613
|
+
detached: process.platform !== "win32",
|
|
554
614
|
});
|
|
615
|
+
const startedAt = Date.now();
|
|
555
616
|
let stdout = "";
|
|
556
617
|
let stderr = "";
|
|
557
618
|
let timedOut = false;
|
|
619
|
+
let settled = false;
|
|
620
|
+
let killRequested = false;
|
|
621
|
+
let forceSettle;
|
|
622
|
+
const killTree = (signal) => {
|
|
623
|
+
try {
|
|
624
|
+
if (process.platform !== "win32" && child.pid) {
|
|
625
|
+
process.kill(-child.pid, signal);
|
|
626
|
+
}
|
|
627
|
+
else {
|
|
628
|
+
child.kill(signal);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
catch (error) {
|
|
632
|
+
// ESRCH:组已消失。EPERM:Darwin 在组正被回收时对重复 kill 的
|
|
633
|
+
// 返回值,同样视作已处理。
|
|
634
|
+
const code = error.code;
|
|
635
|
+
if (code !== "ESRCH" && code !== "EPERM")
|
|
636
|
+
throw error;
|
|
637
|
+
}
|
|
638
|
+
};
|
|
639
|
+
const finish = (code) => {
|
|
640
|
+
if (settled)
|
|
641
|
+
return;
|
|
642
|
+
settled = true;
|
|
643
|
+
clearTimeout(timeout);
|
|
644
|
+
if (forceSettle)
|
|
645
|
+
clearTimeout(forceSettle);
|
|
646
|
+
// 组长退出会立刻触发 close 并取消宽限定时器;取消/超时一旦请求过
|
|
647
|
+
// 击杀,就必须在结算时补一发组级 SIGKILL,否则忽略 TERM 且关闭了
|
|
648
|
+
// 继承管道的后台子进程会活过取消。正常退出不清场——模型可能有意
|
|
649
|
+
// 留下后台服务供后续调用使用。
|
|
650
|
+
if (killRequested)
|
|
651
|
+
killTree("SIGKILL");
|
|
652
|
+
this.signal?.removeEventListener("abort", abort);
|
|
653
|
+
if (this.signal?.aborted) {
|
|
654
|
+
reject(this.signal.reason || new Error("operation cancelled"));
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
// 生产契约:status 恒为 "completed"(包括非零退出码),超时无参考样本,
|
|
658
|
+
// 按最小猜测同样返回 completed + exit_code null,stderr 附超时说明。
|
|
659
|
+
if (timedOut) {
|
|
660
|
+
stderr += (stderr ? "\n" : "") + `command exceeded ${timeoutSeconds}s`;
|
|
661
|
+
}
|
|
662
|
+
const out = capBashOutput(this.virtualizeBashOutput(stdout));
|
|
663
|
+
const err = capBashOutput(this.virtualizeBashOutput(stderr));
|
|
664
|
+
resolve({
|
|
665
|
+
status: "completed",
|
|
666
|
+
stderr: err.text,
|
|
667
|
+
stdout: out.text,
|
|
668
|
+
exit_code: timedOut ? null : code,
|
|
669
|
+
duration_ms: Date.now() - startedAt,
|
|
670
|
+
stderr_truncated: err.truncated,
|
|
671
|
+
stdout_truncated: out.truncated,
|
|
672
|
+
});
|
|
673
|
+
};
|
|
674
|
+
// 宽限期结束时升级为组级 SIGKILL 再结算:abort 只发 SIGTERM,
|
|
675
|
+
// trap TERM 的进程会活过 Promise 拒绝并继续改写工作区。
|
|
676
|
+
const armForceSettle = () => {
|
|
677
|
+
forceSettle ??= setTimeout(() => {
|
|
678
|
+
killTree("SIGKILL");
|
|
679
|
+
finish(null);
|
|
680
|
+
}, 2000);
|
|
681
|
+
};
|
|
558
682
|
const timeout = setTimeout(() => {
|
|
559
683
|
timedOut = true;
|
|
560
|
-
|
|
684
|
+
killRequested = true;
|
|
685
|
+
killTree("SIGKILL");
|
|
686
|
+
armForceSettle();
|
|
561
687
|
}, timeoutSeconds * 1000);
|
|
562
|
-
const abort = () =>
|
|
688
|
+
const abort = () => {
|
|
689
|
+
killRequested = true;
|
|
690
|
+
killTree("SIGTERM");
|
|
691
|
+
armForceSettle();
|
|
692
|
+
};
|
|
563
693
|
this.signal?.addEventListener("abort", abort, { once: true });
|
|
564
694
|
child.stdout.on("data", (chunk) => {
|
|
565
695
|
stdout += chunk.toString("utf8");
|
|
@@ -567,30 +697,17 @@ export class ToolExecutor {
|
|
|
567
697
|
child.stderr.on("data", (chunk) => {
|
|
568
698
|
stderr += chunk.toString("utf8");
|
|
569
699
|
});
|
|
570
|
-
child.once("error",
|
|
571
|
-
|
|
700
|
+
child.once("error", (error) => {
|
|
701
|
+
if (settled)
|
|
702
|
+
return;
|
|
703
|
+
settled = true;
|
|
572
704
|
clearTimeout(timeout);
|
|
705
|
+
if (forceSettle)
|
|
706
|
+
clearTimeout(forceSettle);
|
|
573
707
|
this.signal?.removeEventListener("abort", abort);
|
|
574
|
-
|
|
575
|
-
reject(this.signal.reason || new Error("operation cancelled"));
|
|
576
|
-
return;
|
|
577
|
-
}
|
|
578
|
-
if (timedOut) {
|
|
579
|
-
resolve({
|
|
580
|
-
status: "timeout",
|
|
581
|
-
exit_code: null,
|
|
582
|
-
stdout: "",
|
|
583
|
-
stderr: `command exceeded ${timeoutSeconds}s`,
|
|
584
|
-
});
|
|
585
|
-
return;
|
|
586
|
-
}
|
|
587
|
-
resolve({
|
|
588
|
-
status: code === 0 ? "success" : "error",
|
|
589
|
-
exit_code: code,
|
|
590
|
-
stdout,
|
|
591
|
-
stderr,
|
|
592
|
-
});
|
|
708
|
+
reject(error);
|
|
593
709
|
});
|
|
710
|
+
child.once("close", (code) => finish(code));
|
|
594
711
|
});
|
|
595
712
|
}
|
|
596
713
|
async askUser(questions, toolCallId) {
|
|
@@ -610,35 +727,42 @@ export class ToolExecutor {
|
|
|
610
727
|
async editFile(rawPath, oldText, newText) {
|
|
611
728
|
const target = await this.confinedPath(rawPath);
|
|
612
729
|
const source = await readFile(target, "utf8");
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
};
|
|
625
|
-
}
|
|
626
|
-
updated = source.replace(pattern, newText);
|
|
730
|
+
// 仅精确匹配:曾有空白容错回退,但 T1(b) 回放证实生产没有——case-003
|
|
731
|
+
// 四个 old_text 精确缺失的 edit 生产全部返回 Context not found,而回退
|
|
732
|
+
// 会静默改错文件;全部参考成功样本也都能走精确路径复现。
|
|
733
|
+
// 注意:spec/tools.json 的 edit_file description(与生产逐字一致,受
|
|
734
|
+
// T1(c) 门禁保护)声称支持 fuzzy matching——那是上游自身的描述与行为
|
|
735
|
+
// 不一致,按实测行为对齐,描述不改。见 TRAJECTORY_FIDELITY.md §5。
|
|
736
|
+
if (!source.includes(oldText)) {
|
|
737
|
+
return {
|
|
738
|
+
status: "error",
|
|
739
|
+
message: "Context not found. Read the file to verify the text exists.",
|
|
740
|
+
};
|
|
627
741
|
}
|
|
628
|
-
await writeFile(target,
|
|
742
|
+
await writeFile(target, source.replace(oldText, newText), "utf8");
|
|
629
743
|
return { status: "success", message: `Edited ${rawPath}.` };
|
|
630
744
|
}
|
|
631
745
|
async readWorkspaceFile(rawPath, toolCallId) {
|
|
632
746
|
const target = await this.confinedPath(rawPath);
|
|
633
|
-
|
|
747
|
+
let data;
|
|
748
|
+
try {
|
|
749
|
+
data = await readFile(target);
|
|
750
|
+
}
|
|
751
|
+
catch {
|
|
752
|
+
// 逐字对齐生产错误文案,路径按调用方原样回显。
|
|
753
|
+
return {
|
|
754
|
+
status: "error",
|
|
755
|
+
message: `Failed to read workspace file: ${rawPath}`,
|
|
756
|
+
};
|
|
757
|
+
}
|
|
634
758
|
const extension = path.extname(target).toLowerCase();
|
|
635
759
|
const kind = workspaceFileKind(target, data.subarray(0, 4096));
|
|
636
|
-
const relativePath = this.relativePath(target);
|
|
637
|
-
const result = {
|
|
638
|
-
status: "success",
|
|
639
|
-
path: relativePath,
|
|
640
|
-
};
|
|
641
760
|
if (isStructuredDocument(target)) {
|
|
761
|
+
// 文档提取无参考样本(known-unknown),保留本地扩展键。
|
|
762
|
+
const result = {
|
|
763
|
+
status: "success",
|
|
764
|
+
path: this.relativePath(target),
|
|
765
|
+
};
|
|
642
766
|
const extracted = await extractStructuredDocument(target, data, this.signal);
|
|
643
767
|
result.content = extracted.content;
|
|
644
768
|
result.mime_type =
|
|
@@ -655,34 +779,37 @@ export class ToolExecutor {
|
|
|
655
779
|
result.truncated = true;
|
|
656
780
|
if (extracted.warning)
|
|
657
781
|
result.warning = extracted.warning;
|
|
782
|
+
return result;
|
|
658
783
|
}
|
|
659
|
-
|
|
660
|
-
|
|
784
|
+
if (kind === "text") {
|
|
785
|
+
const content = decodeText(data);
|
|
786
|
+
return {
|
|
787
|
+
kind: "text",
|
|
788
|
+
lines: content.split("\n").length,
|
|
789
|
+
size: Buffer.byteLength(content, "utf8"),
|
|
790
|
+
content,
|
|
791
|
+
};
|
|
661
792
|
}
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
if (kind === "image") {
|
|
669
|
-
if (data.length > 20 * 1024 * 1024) {
|
|
670
|
-
throw new Error(`image exceeds 20 MB: ${rawPath}`);
|
|
671
|
-
}
|
|
672
|
-
result.content = imagePlaceholder;
|
|
673
|
-
this.pendingVisualContent.set(toolCallId, [
|
|
674
|
-
{
|
|
675
|
-
text: "<arena-system-message>" +
|
|
676
|
-
`The image below is the genuine content returned by read_file for ${JSON.stringify(relativePath)}.` +
|
|
677
|
-
"</arena-system-message>",
|
|
678
|
-
},
|
|
679
|
-
{
|
|
680
|
-
image: `data:${mimeType};base64,${data.toString("base64")}`,
|
|
681
|
-
},
|
|
682
|
-
]);
|
|
793
|
+
const mimeType = mime.getType(target) ||
|
|
794
|
+
mimeTypes[extension] ||
|
|
795
|
+
"application/octet-stream";
|
|
796
|
+
if (kind === "image") {
|
|
797
|
+
if (data.length > 20 * 1024 * 1024) {
|
|
798
|
+
throw new Error(`image exceeds 20 MB: ${rawPath}`);
|
|
683
799
|
}
|
|
800
|
+
this.pendingVisualContent.set(toolCallId, [
|
|
801
|
+
{
|
|
802
|
+
image: `data:${mimeType};base64,${data.toString("base64")}`,
|
|
803
|
+
},
|
|
804
|
+
]);
|
|
805
|
+
return [{ text: imageResultPlaceholder }];
|
|
684
806
|
}
|
|
685
|
-
return
|
|
807
|
+
return {
|
|
808
|
+
status: "success",
|
|
809
|
+
path: this.relativePath(target),
|
|
810
|
+
mime_type: mimeType,
|
|
811
|
+
size: data.length,
|
|
812
|
+
};
|
|
686
813
|
}
|
|
687
814
|
async writeWorkspaceFile(rawPath, content) {
|
|
688
815
|
const target = await this.confinedPath(rawPath);
|
|
@@ -720,28 +847,25 @@ export class ToolExecutor {
|
|
|
720
847
|
const searchResults = Array.isArray(searchInfo.search_results)
|
|
721
848
|
? searchInfo.search_results
|
|
722
849
|
: [];
|
|
723
|
-
const choices = Array.isArray(output.choices) ? output.choices : [];
|
|
724
|
-
const answer = stringValue(record(record(choices[0]).message).content);
|
|
725
850
|
const limit = { "1": 3, "2": 5, "3": 8 }[depth] || 5;
|
|
851
|
+
// 参考轨迹条目:{id, url, title, description[, pageAge]};
|
|
852
|
+
// DashScope 无 pageAge,按可选键省略。
|
|
726
853
|
return {
|
|
727
854
|
status: "success",
|
|
728
|
-
query,
|
|
729
|
-
answer,
|
|
730
855
|
results: searchResults.slice(0, limit).map((value, index) => {
|
|
731
856
|
const item = record(value);
|
|
732
857
|
return {
|
|
733
858
|
id: Number(item.index) || index + 1,
|
|
734
|
-
title: stringValue(item.title),
|
|
735
859
|
url: stringValue(item.url),
|
|
736
|
-
|
|
860
|
+
title: stringValue(item.title),
|
|
861
|
+
description: stringValue(item.content) ||
|
|
737
862
|
stringValue(item.snippet) ||
|
|
738
863
|
stringValue(item.summary),
|
|
739
|
-
site_name: stringValue(item.site_name),
|
|
740
864
|
};
|
|
741
865
|
}),
|
|
742
866
|
};
|
|
743
867
|
}
|
|
744
|
-
async imageSearch(query, count
|
|
868
|
+
async imageSearch(query, count) {
|
|
745
869
|
const model = process.env.ARENA_IMAGE_SEARCH_MODEL || "qwen3.7-plus";
|
|
746
870
|
const payload = await dashScopeJson("compatible", "/responses", {
|
|
747
871
|
method: "POST",
|
|
@@ -770,13 +894,14 @@ export class ToolExecutor {
|
|
|
770
894
|
throw new Error("image_search returned malformed image results");
|
|
771
895
|
}
|
|
772
896
|
}
|
|
773
|
-
const
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
.
|
|
777
|
-
.
|
|
897
|
+
const saved = [];
|
|
898
|
+
// 参考轨迹命名:image-search/<query slug 前 40 字符>-<序号>.<扩展名>。
|
|
899
|
+
const slug = query
|
|
900
|
+
.toLowerCase()
|
|
901
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
902
|
+
.slice(0, 40);
|
|
778
903
|
for (const value of results) {
|
|
779
|
-
if (
|
|
904
|
+
if (saved.length >= count)
|
|
780
905
|
break;
|
|
781
906
|
const item = record(value);
|
|
782
907
|
const sourceUrl = stringValue(item.url) ||
|
|
@@ -794,13 +919,14 @@ export class ToolExecutor {
|
|
|
794
919
|
continue;
|
|
795
920
|
const normalizedType = contentType.split(";")[0]?.trim().toLowerCase();
|
|
796
921
|
const extension = Object.entries(mimeTypes).find(([, mime]) => mime === normalizedType)?.[0] || ".jpg";
|
|
797
|
-
const target = await this.confinedPath(`
|
|
922
|
+
const target = await this.confinedPath(`image-search/${slug}-${saved.length + 1}${extension}`);
|
|
798
923
|
mkdirSync(path.dirname(target), { recursive: true });
|
|
799
924
|
await writeFile(target, await responseBytes(imageResponse));
|
|
800
|
-
|
|
801
|
-
path: this.relativePath(target),
|
|
802
|
-
source_url: sourceUrl,
|
|
925
|
+
saved.push({
|
|
803
926
|
title: stringValue(item.title),
|
|
927
|
+
file_path: this.relativePath(target),
|
|
928
|
+
source_url: sourceUrl,
|
|
929
|
+
thumbnail_url: stringValue(item.thumbnail_url) || sourceUrl,
|
|
804
930
|
});
|
|
805
931
|
}
|
|
806
932
|
catch (error) {
|
|
@@ -809,7 +935,7 @@ export class ToolExecutor {
|
|
|
809
935
|
// A single inaccessible search result should not fail the whole search.
|
|
810
936
|
}
|
|
811
937
|
}
|
|
812
|
-
return { status: "success",
|
|
938
|
+
return { status: "success", results: saved };
|
|
813
939
|
}
|
|
814
940
|
async presentFile(rawPath) {
|
|
815
941
|
const target = await this.confinedPath(rawPath);
|
|
@@ -912,7 +1038,6 @@ export class ToolExecutor {
|
|
|
912
1038
|
return {
|
|
913
1039
|
status: "success",
|
|
914
1040
|
file_path: this.relativePath(target),
|
|
915
|
-
task_id: taskId,
|
|
916
1041
|
};
|
|
917
1042
|
}
|
|
918
1043
|
async generateSpeech(rawPath, text, language, voiceIdentity = {}) {
|