@qwen-code/arena-agent 0.0.10-dev.9 → 0.0.10-test.0
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 +61 -10
- package/CHANGELOG.md +140 -11
- package/README.md +309 -46
- package/dist/agent.d.ts +20 -6
- package/dist/agent.js +303 -73
- package/dist/agent.js.map +1 -1
- package/dist/anthropic.d.ts +1 -1
- package/dist/anthropic.js +46 -57
- package/dist/anthropic.js.map +1 -1
- package/dist/arena-history.d.ts +12 -0
- package/dist/arena-history.js +119 -0
- package/dist/arena-history.js.map +1 -0
- package/dist/ask-user.js +44 -2
- package/dist/ask-user.js.map +1 -1
- package/dist/attachment-limits.d.ts +4 -0
- package/dist/attachment-limits.js +10 -0
- package/dist/attachment-limits.js.map +1 -0
- package/dist/attachments.d.ts +4 -2
- package/dist/attachments.js +9 -11
- package/dist/attachments.js.map +1 -1
- package/dist/brave-search.js +11 -7
- package/dist/brave-search.js.map +1 -1
- package/dist/canonical-data.d.ts +9 -0
- package/dist/canonical-data.js +12 -3
- package/dist/canonical-data.js.map +1 -1
- package/dist/cli.js +140 -40
- package/dist/cli.js.map +1 -1
- package/dist/code-tool-schema.d.ts +3 -0
- package/dist/code-tool-schema.js +92 -0
- package/dist/code-tool-schema.js.map +1 -0
- package/dist/code-tools.js +63 -26
- package/dist/code-tools.js.map +1 -1
- package/dist/compaction-threshold.d.ts +3 -0
- package/dist/compaction-threshold.js +6 -0
- package/dist/compaction-threshold.js.map +1 -0
- package/dist/compaction.d.ts +24 -7
- package/dist/compaction.js +242 -66
- package/dist/compaction.js.map +1 -1
- package/dist/config.d.ts +17 -0
- package/dist/config.js +30 -5
- package/dist/config.js.map +1 -1
- package/dist/connector-errors.d.ts +16 -0
- package/dist/connector-errors.js +69 -0
- package/dist/connector-errors.js.map +1 -0
- package/dist/connector-files.d.ts +31 -0
- package/dist/connector-files.js +249 -0
- package/dist/connector-files.js.map +1 -0
- package/dist/connector-input.d.ts +10 -0
- package/dist/connector-input.js +79 -0
- package/dist/connector-input.js.map +1 -0
- package/dist/connector-mcp.d.ts +60 -0
- package/dist/connector-mcp.js +474 -0
- package/dist/connector-mcp.js.map +1 -0
- package/dist/connectors.d.ts +13 -0
- package/dist/connectors.js +69 -0
- package/dist/connectors.js.map +1 -0
- package/dist/fetch-page.js +2 -2
- package/dist/fetch-page.js.map +1 -1
- package/dist/history.d.ts +15 -5
- package/dist/history.js +59 -14
- package/dist/history.js.map +1 -1
- package/dist/image-choice.d.ts +23 -0
- package/dist/image-choice.js +190 -0
- package/dist/image-choice.js.map +1 -0
- package/dist/image-input-fallback.js +5 -0
- package/dist/image-input-fallback.js.map +1 -1
- package/dist/image-model.d.ts +6 -0
- package/dist/image-model.js +32 -0
- package/dist/image-model.js.map +1 -0
- package/dist/midturn-checkpoint-hook.d.ts +14 -1
- package/dist/midturn-checkpoint-hook.js.map +1 -1
- package/dist/mode.js +1 -1
- package/dist/mode.js.map +1 -1
- package/dist/model-capabilities.js +18 -8
- package/dist/model-capabilities.js.map +1 -1
- package/dist/model-router-media.js +55 -3
- package/dist/model-router-media.js.map +1 -1
- package/dist/model-stream.d.ts +16 -0
- package/dist/model-stream.js +129 -0
- package/dist/model-stream.js.map +1 -0
- package/dist/openai-responses.d.ts +2 -1
- package/dist/openai-responses.js +85 -88
- package/dist/openai-responses.js.map +1 -1
- package/dist/process-manager.js +9 -9
- package/dist/process-manager.js.map +1 -1
- package/dist/prompts.d.ts +0 -2
- package/dist/prompts.js +10 -60
- package/dist/prompts.js.map +1 -1
- package/dist/protocol.d.ts +5 -1
- package/dist/protocol.js +23 -2
- package/dist/protocol.js.map +1 -1
- package/dist/provider.d.ts +18 -0
- package/dist/provider.js +120 -51
- package/dist/provider.js.map +1 -1
- package/dist/repo.d.ts +1 -1
- package/dist/repo.js +26 -4
- package/dist/repo.js.map +1 -1
- package/dist/routify-web-fetch.js +2 -2
- package/dist/routify-web-fetch.js.map +1 -1
- package/dist/server.js +55 -27
- package/dist/server.js.map +1 -1
- package/dist/session-memory-prompts.d.ts +3 -1
- package/dist/session-memory-prompts.js +4 -2
- package/dist/session-memory-prompts.js.map +1 -1
- package/dist/side-query.js +5 -0
- package/dist/side-query.js.map +1 -1
- package/dist/spec.d.ts +5 -4
- package/dist/spec.js +30 -11
- package/dist/spec.js.map +1 -1
- package/dist/startup-config.d.ts +39 -0
- package/dist/startup-config.js +347 -0
- package/dist/startup-config.js.map +1 -0
- package/dist/startup-main-config.d.ts +63 -0
- package/dist/startup-main-config.js +94 -0
- package/dist/startup-main-config.js.map +1 -0
- package/dist/startup-tool-config.d.ts +8 -0
- package/dist/startup-tool-config.js +116 -0
- package/dist/startup-tool-config.js.map +1 -0
- package/dist/summary-compaction.d.ts +29 -0
- package/dist/{legacy-compaction.js → summary-compaction.js} +79 -14
- package/dist/summary-compaction.js.map +1 -0
- package/dist/tools.d.ts +5 -1
- package/dist/tools.js +164 -54
- package/dist/tools.js.map +1 -1
- package/dist/tui.js +26 -6
- package/dist/tui.js.map +1 -1
- package/dist/types.d.ts +21 -1
- package/dist/web/app.js +30 -5
- package/dist/web/app.js.map +1 -1
- package/dist/workspace.js +61 -14
- package/dist/workspace.js.map +1 -1
- package/docs/BINARY_DISTRIBUTION.md +44 -8
- package/docs/MODES_AND_RELEASES.md +79 -6
- package/docs/SDK.md +1 -0
- package/modes/code-arena/capture-provenance.json +9 -6
- package/modes/code-arena/full-stack/dependencies.lock.json +8670 -0
- package/modes/code-arena/full-stack/system-prompt.txt +1 -1
- package/modes/code-arena/html/system-prompt.txt +1 -1
- package/modes/code-arena/react/dependencies.lock.json +2707 -0
- package/package.json +7 -2
- package/scripts/README.md +23 -4
- package/web/styles.css +22 -0
- package/dist/legacy-compaction.d.ts +0 -16
- package/dist/legacy-compaction.js.map +0 -1
package/dist/tools.js
CHANGED
|
@@ -2,6 +2,7 @@ import { spawn } from "node:child_process";
|
|
|
2
2
|
import { copyFileSync, existsSync, mkdirSync, realpathSync } from "node:fs";
|
|
3
3
|
import { open, readFile, realpath, stat, writeFile } from "node:fs/promises";
|
|
4
4
|
import path from "node:path";
|
|
5
|
+
import { createHash } from "node:crypto";
|
|
5
6
|
import { Ajv } from "ajv";
|
|
6
7
|
import addFormats from "ajv-formats";
|
|
7
8
|
import { detect as detectEncoding } from "chardet";
|
|
@@ -10,12 +11,16 @@ import mime from "mime/lite";
|
|
|
10
11
|
import { AskUserHandlerError } from "./ask-user.js";
|
|
11
12
|
import { executeBraveImageSearch, executeBraveWebSearch, } from "./brave-search.js";
|
|
12
13
|
import { CodeToolExecutor } from "./code-tools.js";
|
|
14
|
+
import { schemaOrderedValue } from "./arena-history.js";
|
|
15
|
+
import { ConnectorTools, connectorFailureText } from "./connector-mcp.js";
|
|
13
16
|
import { supportsImageInput } from "./config.js";
|
|
14
17
|
import { extractStructuredDocument, isStructuredDocument, } from "./document-reader.js";
|
|
15
18
|
import { FetchPageReader } from "./fetch-page.js";
|
|
16
19
|
import { replaceFirstText } from "./file-edit.js";
|
|
17
20
|
import { generatedImageBytesForExtension, prepareImageForModel, } from "./image.js";
|
|
18
21
|
import { imageTextFromFallbackModel } from "./image-input-fallback.js";
|
|
22
|
+
import { offerGeneratedImages } from "./image-choice.js";
|
|
23
|
+
import { arenaImageModel, imageOptionModels } from "./image-model.js";
|
|
19
24
|
import { isCodeArenaMode } from "./mode.js";
|
|
20
25
|
import { generateImageWithModelRouter, generateSpeechWithModelRouter, } from "./model-router-media.js";
|
|
21
26
|
import { processManagerForWorkspace, } from "./process-manager.js";
|
|
@@ -595,11 +600,14 @@ async function responseBytes(response, limit = 4 * 1024 * 1024) {
|
|
|
595
600
|
}
|
|
596
601
|
class ArenaToolExecutor {
|
|
597
602
|
config;
|
|
603
|
+
interactionBoundaries = new Set();
|
|
604
|
+
interactionQueue = Promise.resolve();
|
|
598
605
|
workspace;
|
|
599
606
|
workspaceReal;
|
|
600
607
|
tmpReal;
|
|
601
608
|
varTmpReal;
|
|
602
609
|
validators = new Map();
|
|
610
|
+
connectors;
|
|
603
611
|
pendingVisualContent = new Map();
|
|
604
612
|
askUserHandler;
|
|
605
613
|
imageInputFallbackClient;
|
|
@@ -617,6 +625,7 @@ class ArenaToolExecutor {
|
|
|
617
625
|
// The captured runtime uses Zod object parsing: unknown object keys are
|
|
618
626
|
// stripped before dispatch instead of being emitted as validation issues.
|
|
619
627
|
removeAdditional: "all",
|
|
628
|
+
useDefaults: true,
|
|
620
629
|
});
|
|
621
630
|
// ask_user needs a handler: TUI/WebUI collect real user input, while
|
|
622
631
|
// headless `-p` may delegate to a configured remote User Agent. add_voice
|
|
@@ -636,7 +645,18 @@ class ArenaToolExecutor {
|
|
|
636
645
|
this.varTmpReal = realpathSync("/var/tmp");
|
|
637
646
|
this.processManager = processManagerForWorkspace(this.workspaceReal);
|
|
638
647
|
this.voiceRegistry = voiceRegistryForWorkspace(this.workspaceReal);
|
|
639
|
-
|
|
648
|
+
const askUserHandler = options.askUserHandler;
|
|
649
|
+
this.askUserHandler = askUserHandler ? (callId, questions, context) => {
|
|
650
|
+
// TUI/WebUI own one active question panel. Parallel tools can prepare
|
|
651
|
+
// auditions concurrently, but must not overwrite another pending answer.
|
|
652
|
+
const response = this.interactionQueue.then(() => {
|
|
653
|
+
(this.signal || context?.signal)?.throwIfAborted();
|
|
654
|
+
this.interactionBoundaries.add(callId);
|
|
655
|
+
return askUserHandler(callId, questions, context);
|
|
656
|
+
});
|
|
657
|
+
this.interactionQueue = response.then(() => { }, () => { });
|
|
658
|
+
return response;
|
|
659
|
+
} : undefined;
|
|
640
660
|
this.askUserEnabled = Boolean(options.askUserHandler);
|
|
641
661
|
if (config.imageInputFallbackModel?.trim()) {
|
|
642
662
|
const connection = imageInputFallbackConnection(config);
|
|
@@ -648,6 +668,9 @@ class ArenaToolExecutor {
|
|
|
648
668
|
}
|
|
649
669
|
this.signal = options.signal;
|
|
650
670
|
this.workspaceContext = options.workspaceContext;
|
|
671
|
+
if ((config.connectors?.length || config.connectedConnectors !== undefined) && !config.disallowedTools?.includes("list_connector_tools")) {
|
|
672
|
+
this.connectors = new ConnectorTools(config.connectors || [], config.mergeConnectors, config.disallowedTools, options.signal, { workspace: this.workspace, connected: config.connectedConnectors, resolvePath: raw => this.confinedPath(raw) });
|
|
673
|
+
}
|
|
651
674
|
this.pageReader = new FetchPageReader({ signal: options.signal });
|
|
652
675
|
this.routifyPageReader = new RoutifyFetchPageReader({
|
|
653
676
|
signal: options.signal,
|
|
@@ -660,21 +683,57 @@ class ArenaToolExecutor {
|
|
|
660
683
|
type: "string",
|
|
661
684
|
validate: (value) => URL.canParse(value),
|
|
662
685
|
});
|
|
663
|
-
for (const definition of toolDefinitions(this.workspaceContext)) {
|
|
664
|
-
|
|
686
|
+
for (const definition of toolDefinitions(this.workspaceContext, "arena", config.connectors, config.connectedConnectors)) {
|
|
687
|
+
const parameters = structuredClone(definition.function.parameters);
|
|
688
|
+
// The latest wire schema moves this limit into the description. Keep the
|
|
689
|
+
// documented command bound enforced internally without changing that schema.
|
|
690
|
+
if (definition.function.name === "bash" || definition.function.name === "start_process") {
|
|
691
|
+
record(record(parameters.properties).command).maxLength = 64_000;
|
|
692
|
+
}
|
|
693
|
+
this.validators.set(definition.function.name, this.ajv.compile(parameters));
|
|
665
694
|
}
|
|
666
695
|
}
|
|
667
696
|
// The tool schemas offered to the model for this session. Identical to the
|
|
668
697
|
// canonical trace set, adjusted for the selected workspace runtime and minus
|
|
669
698
|
// ask_user when no handler is wired. add_voice stays available headlessly.
|
|
670
699
|
availableTools() {
|
|
671
|
-
let definitions =
|
|
700
|
+
let definitions = this.allDefinitions();
|
|
672
701
|
if (!this.askUserEnabled) {
|
|
673
702
|
definitions = definitions.filter((definition) => definition.function.name !== "ask_user");
|
|
674
703
|
}
|
|
675
704
|
const disallowed = new Set(this.config.disallowedTools || []);
|
|
676
705
|
return definitions.filter((definition) => !disallowed.has(definition.function.name));
|
|
677
706
|
}
|
|
707
|
+
allDefinitions() {
|
|
708
|
+
return [...toolDefinitions(this.workspaceContext, "arena", this.config.connectors, this.config.connectedConnectors),
|
|
709
|
+
...(this.connectors?.definitions() || [])];
|
|
710
|
+
}
|
|
711
|
+
async beginConnectorTurn(history) {
|
|
712
|
+
await this.connectors?.beginTurn(history);
|
|
713
|
+
}
|
|
714
|
+
async closeConnectors() { await this.connectors?.close(); }
|
|
715
|
+
historyCall(call) {
|
|
716
|
+
if (call.function.arguments === "")
|
|
717
|
+
return call;
|
|
718
|
+
const parsed = this.parseArguments(call);
|
|
719
|
+
if (parsed.error !== undefined)
|
|
720
|
+
return call;
|
|
721
|
+
const name = call.function.name;
|
|
722
|
+
let value = parsed.value;
|
|
723
|
+
const validate = this.validators.get(name) || this.connectors?.validator(name);
|
|
724
|
+
const schema = this.allDefinitions().find(definition => definition.function.name === name)?.function.parameters;
|
|
725
|
+
if (validate && schema) {
|
|
726
|
+
const checked = this.validateArguments(name, value, validate);
|
|
727
|
+
if (checked.validationMessage === undefined)
|
|
728
|
+
value = this.connectors?.has(name)
|
|
729
|
+
? this.connectors.historyValue(name, checked.value) : schemaOrderedValue(checked.value, schema);
|
|
730
|
+
}
|
|
731
|
+
const argumentsText = JSON.stringify(value);
|
|
732
|
+
return argumentsText === call.function.arguments ? call : { ...call, function: { ...call.function, arguments: argumentsText } };
|
|
733
|
+
}
|
|
734
|
+
takeInteractionBoundary(callId) {
|
|
735
|
+
return this.interactionBoundaries.delete(callId);
|
|
736
|
+
}
|
|
678
737
|
parseArguments(call) {
|
|
679
738
|
const rawArguments = typeof call.function?.arguments === "string"
|
|
680
739
|
? call.function.arguments
|
|
@@ -704,10 +763,16 @@ class ArenaToolExecutor {
|
|
|
704
763
|
return { rawArguments, value };
|
|
705
764
|
}
|
|
706
765
|
validateArguments(name, value, validate) {
|
|
707
|
-
|
|
766
|
+
let validatedArguments;
|
|
767
|
+
try {
|
|
768
|
+
validatedArguments = (this.connectors?.has(name) ? this.connectors.normalizeArguments(name, value) : structuredClone(value));
|
|
769
|
+
}
|
|
770
|
+
catch {
|
|
771
|
+
return { validationMessage: connectorFailureText };
|
|
772
|
+
}
|
|
708
773
|
if (!validate(validatedArguments)) {
|
|
709
774
|
return {
|
|
710
|
-
validationMessage: arenaValidationError(name, value, validate.errors),
|
|
775
|
+
validationMessage: this.connectors?.has(name) ? connectorFailureText : arenaValidationError(name, value, validate.errors),
|
|
711
776
|
};
|
|
712
777
|
}
|
|
713
778
|
if (name === "ask_user") {
|
|
@@ -730,7 +795,7 @@ class ArenaToolExecutor {
|
|
|
730
795
|
? call.function.arguments
|
|
731
796
|
: "{}";
|
|
732
797
|
const parsed = this.parseArguments(call);
|
|
733
|
-
const validate = this.validators.get(name);
|
|
798
|
+
const validate = this.validators.get(name) || this.connectors?.validator(name);
|
|
734
799
|
if (this.config.disallowedTools?.includes(name) || !validate) {
|
|
735
800
|
return {
|
|
736
801
|
id,
|
|
@@ -740,7 +805,7 @@ class ArenaToolExecutor {
|
|
|
740
805
|
validation: "unknown_tool",
|
|
741
806
|
validationMessage: this.config.disallowedTools?.includes(name)
|
|
742
807
|
? `tool is disallowed: ${name}`
|
|
743
|
-
: unavailableToolError(name,
|
|
808
|
+
: this.connectors?.recognizes(name) ? connectorFailureText : unavailableToolError(name, this.allDefinitions().map((definition) => definition.function.name)),
|
|
744
809
|
};
|
|
745
810
|
}
|
|
746
811
|
if (parsed.error !== undefined) {
|
|
@@ -749,7 +814,7 @@ class ArenaToolExecutor {
|
|
|
749
814
|
name,
|
|
750
815
|
rawArguments: parsed.rawArguments,
|
|
751
816
|
validation: "invalid_json",
|
|
752
|
-
validationMessage: arenaJsonParsingError(name, parsed.invalidJsonText, parsed.error),
|
|
817
|
+
validationMessage: this.connectors?.has(name) ? connectorFailureText : arenaJsonParsingError(name, parsed.invalidJsonText, parsed.error),
|
|
753
818
|
};
|
|
754
819
|
}
|
|
755
820
|
const validated = this.validateArguments(name, parsed.value, validate);
|
|
@@ -788,12 +853,12 @@ class ArenaToolExecutor {
|
|
|
788
853
|
if (this.config.disallowedTools?.includes(name)) {
|
|
789
854
|
throw new Error(`tool is disallowed: ${name}`);
|
|
790
855
|
}
|
|
791
|
-
const validate = this.validators.get(name);
|
|
856
|
+
const validate = this.validators.get(name) || this.connectors?.validator(name);
|
|
792
857
|
if (!validate) {
|
|
793
858
|
return {
|
|
794
859
|
role: "tool",
|
|
795
860
|
tool_call_id: callId,
|
|
796
|
-
content: unavailableToolError(name,
|
|
861
|
+
content: this.connectors?.recognizes(name) ? connectorFailureText : unavailableToolError(name, this.allDefinitions().map((definition) => definition.function.name)),
|
|
797
862
|
};
|
|
798
863
|
}
|
|
799
864
|
const parsed = this.parseArguments(call);
|
|
@@ -801,7 +866,7 @@ class ArenaToolExecutor {
|
|
|
801
866
|
return {
|
|
802
867
|
role: "tool",
|
|
803
868
|
tool_call_id: callId,
|
|
804
|
-
content: arenaJsonParsingError(name, parsed.invalidJsonText, parsed.error),
|
|
869
|
+
content: this.connectors?.has(name) ? connectorFailureText : arenaJsonParsingError(name, parsed.invalidJsonText, parsed.error),
|
|
805
870
|
};
|
|
806
871
|
}
|
|
807
872
|
const argumentsValue = parsed.value;
|
|
@@ -860,7 +925,7 @@ class ArenaToolExecutor {
|
|
|
860
925
|
return {
|
|
861
926
|
role: "tool",
|
|
862
927
|
tool_call_id: callId,
|
|
863
|
-
content: json(this.runtimeFailureResult(name, validatedArguments, error, startedAt)),
|
|
928
|
+
content: json(this.runtimeFailureResult(name, validatedArguments, error, startedAt, name === "generate_image" ? context?.signal : undefined)),
|
|
864
929
|
};
|
|
865
930
|
}
|
|
866
931
|
const content = typeof result === "string" ? result : json(result);
|
|
@@ -893,20 +958,20 @@ class ArenaToolExecutor {
|
|
|
893
958
|
};
|
|
894
959
|
}
|
|
895
960
|
}
|
|
896
|
-
runtimeFailureResult(name, values, error, startedAt) {
|
|
897
|
-
if (this.signal?.aborted)
|
|
961
|
+
runtimeFailureResult(name, values, error, startedAt, contextSignal) {
|
|
962
|
+
if ((this.signal || contextSignal)?.aborted)
|
|
898
963
|
return { status: "aborted" };
|
|
899
964
|
const message = errorMessage(error);
|
|
900
965
|
const durationMs = Date.now() - startedAt;
|
|
901
966
|
if (name === "bash") {
|
|
902
967
|
return {
|
|
903
|
-
status: "shell_error",
|
|
904
|
-
stderr: message,
|
|
905
968
|
stdout: "",
|
|
969
|
+
stdout_truncated: false,
|
|
970
|
+
stderr: message,
|
|
971
|
+
stderr_truncated: false,
|
|
906
972
|
exit_code: null,
|
|
973
|
+
status: "shell_error",
|
|
907
974
|
duration_ms: durationMs,
|
|
908
|
-
stderr_truncated: false,
|
|
909
|
-
stdout_truncated: false,
|
|
910
975
|
};
|
|
911
976
|
}
|
|
912
977
|
if (name === "start_process") {
|
|
@@ -929,8 +994,8 @@ class ArenaToolExecutor {
|
|
|
929
994
|
if (name === "get_process_output") {
|
|
930
995
|
return {
|
|
931
996
|
status: "shell_error",
|
|
932
|
-
log_tail: message,
|
|
933
997
|
exit_code: null,
|
|
998
|
+
log_tail: message,
|
|
934
999
|
listening_ports: [],
|
|
935
1000
|
};
|
|
936
1001
|
}
|
|
@@ -987,6 +1052,11 @@ class ArenaToolExecutor {
|
|
|
987
1052
|
};
|
|
988
1053
|
}
|
|
989
1054
|
async dispatch(name, values, toolCallId, context) {
|
|
1055
|
+
if (name === "list_connector_tools" && this.connectors) {
|
|
1056
|
+
return this.connectors.discover(String(values.service));
|
|
1057
|
+
}
|
|
1058
|
+
if (this.connectors?.has(name))
|
|
1059
|
+
return this.connectors.call(name, values);
|
|
990
1060
|
// Canned replay responses are served in execute() before schema
|
|
991
1061
|
// validation (the lookup consumes entries — single serving point).
|
|
992
1062
|
switch (name) {
|
|
@@ -1002,7 +1072,7 @@ class ArenaToolExecutor {
|
|
|
1002
1072
|
case "edit_file":
|
|
1003
1073
|
return this.editFile(String(values.path), String(values.old_text), String(values.new_text));
|
|
1004
1074
|
case "read_file":
|
|
1005
|
-
return this.readWorkspaceFile(String(values.path), toolCallId);
|
|
1075
|
+
return this.readWorkspaceFile(String(values.path), toolCallId, values.offset, values.limit);
|
|
1006
1076
|
case "fetch_page":
|
|
1007
1077
|
return routifyWebFetchEnabled()
|
|
1008
1078
|
? this.routifyPageReader.read(String(values.url), Number(values.chunkIndex || 0))
|
|
@@ -1023,7 +1093,7 @@ class ArenaToolExecutor {
|
|
|
1023
1093
|
? path.resolve(this.workspaceContext.cwd)
|
|
1024
1094
|
: "/home/user")), Number(values.startup_wait || 5));
|
|
1025
1095
|
case "generate_image":
|
|
1026
|
-
return this.generateImage(String(values.file_path), String(values.prompt), values.images);
|
|
1096
|
+
return this.generateImage(String(values.file_path), String(values.prompt), values.images, values.offer_options === true, toolCallId, context);
|
|
1027
1097
|
case "generate_speech":
|
|
1028
1098
|
return this.generateSpeech(String(values.file_path), String(values.text), String(values.voice_id), values.language === undefined ? undefined : String(values.language));
|
|
1029
1099
|
case "get_process_output":
|
|
@@ -1180,9 +1250,8 @@ class ArenaToolExecutor {
|
|
|
1180
1250
|
}
|
|
1181
1251
|
return new Promise((resolve, reject) => {
|
|
1182
1252
|
const env = this.sandboxEnvironment();
|
|
1183
|
-
//
|
|
1184
|
-
//
|
|
1185
|
-
// 必须杀整组,并在宽限期后强制结算兜底。
|
|
1253
|
+
// A process group lets cancellation stop descendants too. A wait timeout
|
|
1254
|
+
// is different: the captured contract permits the command to continue.
|
|
1186
1255
|
const child = spawn("/bin/bash", ["-lc", command], {
|
|
1187
1256
|
cwd: workdir,
|
|
1188
1257
|
env,
|
|
@@ -1219,10 +1288,8 @@ class ArenaToolExecutor {
|
|
|
1219
1288
|
clearTimeout(timeout);
|
|
1220
1289
|
if (forceSettle)
|
|
1221
1290
|
clearTimeout(forceSettle);
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
// servers belong in start_process.
|
|
1225
|
-
killTree("SIGKILL");
|
|
1291
|
+
if (!timedOut || this.signal?.aborted)
|
|
1292
|
+
killTree("SIGKILL");
|
|
1226
1293
|
this.signal?.removeEventListener("abort", abort);
|
|
1227
1294
|
if (this.signal?.aborted) {
|
|
1228
1295
|
reject(this.signal.reason || new Error("operation cancelled"));
|
|
@@ -1231,13 +1298,13 @@ class ArenaToolExecutor {
|
|
|
1231
1298
|
const out = stdout.result();
|
|
1232
1299
|
const err = stderr.result();
|
|
1233
1300
|
resolve({
|
|
1234
|
-
status: timedOut ? "timeout" : signal ? "killed" : "completed",
|
|
1235
|
-
stderr: err.text,
|
|
1236
1301
|
stdout: out.text,
|
|
1302
|
+
stdout_truncated: out.truncated,
|
|
1303
|
+
stderr: err.text,
|
|
1304
|
+
stderr_truncated: err.truncated,
|
|
1237
1305
|
exit_code: timedOut ? null : code,
|
|
1306
|
+
status: timedOut ? "timeout" : signal ? "killed" : "completed",
|
|
1238
1307
|
duration_ms: Date.now() - startedAt,
|
|
1239
|
-
stderr_truncated: err.truncated,
|
|
1240
|
-
stdout_truncated: out.truncated,
|
|
1241
1308
|
});
|
|
1242
1309
|
};
|
|
1243
1310
|
// 宽限期结束时升级为组级 SIGKILL 再结算:abort 只发 SIGTERM,
|
|
@@ -1250,8 +1317,12 @@ class ArenaToolExecutor {
|
|
|
1250
1317
|
};
|
|
1251
1318
|
const timeout = setTimeout(() => {
|
|
1252
1319
|
timedOut = true;
|
|
1253
|
-
|
|
1254
|
-
|
|
1320
|
+
finish(null, null);
|
|
1321
|
+
// Keep draining the pipes so a continuing command cannot block or get
|
|
1322
|
+
// SIGPIPE. Do not keep the host CLI alive just to collect discarded output.
|
|
1323
|
+
child.unref();
|
|
1324
|
+
child.stdout.unref?.();
|
|
1325
|
+
child.stderr.unref?.();
|
|
1255
1326
|
}, timeoutSeconds * 1000);
|
|
1256
1327
|
const abort = () => {
|
|
1257
1328
|
killTree("SIGTERM");
|
|
@@ -1259,10 +1330,12 @@ class ArenaToolExecutor {
|
|
|
1259
1330
|
};
|
|
1260
1331
|
this.signal?.addEventListener("abort", abort, { once: true });
|
|
1261
1332
|
child.stdout.on("data", (chunk) => {
|
|
1262
|
-
|
|
1333
|
+
if (!settled)
|
|
1334
|
+
stdout.append(chunk);
|
|
1263
1335
|
});
|
|
1264
1336
|
child.stderr.on("data", (chunk) => {
|
|
1265
|
-
|
|
1337
|
+
if (!settled)
|
|
1338
|
+
stderr.append(chunk);
|
|
1266
1339
|
});
|
|
1267
1340
|
child.once("error", (error) => {
|
|
1268
1341
|
if (settled)
|
|
@@ -1406,7 +1479,7 @@ class ArenaToolExecutor {
|
|
|
1406
1479
|
}
|
|
1407
1480
|
return { status: "success", message: `Edited ${rawPath}.` };
|
|
1408
1481
|
}
|
|
1409
|
-
async readWorkspaceFile(rawPath, toolCallId) {
|
|
1482
|
+
async readWorkspaceFile(rawPath, toolCallId, offset, limit) {
|
|
1410
1483
|
let target;
|
|
1411
1484
|
let fileSize;
|
|
1412
1485
|
let sample;
|
|
@@ -1489,7 +1562,10 @@ class ArenaToolExecutor {
|
|
|
1489
1562
|
return result;
|
|
1490
1563
|
}
|
|
1491
1564
|
if (kind === "text") {
|
|
1492
|
-
const
|
|
1565
|
+
const start = (offset ?? 1) - 1;
|
|
1566
|
+
const selected = offset === undefined && limit === undefined ? data
|
|
1567
|
+
: Buffer.from(decodeText(data).split("\n").slice(start, limit === undefined ? undefined : start + limit).join("\n"), "utf8");
|
|
1568
|
+
const text = readTextResult(selected);
|
|
1493
1569
|
return {
|
|
1494
1570
|
kind: "text",
|
|
1495
1571
|
lines: data.reduce((count, byte) => count + (byte === 0x0a ? 1 : 0), 1),
|
|
@@ -1571,7 +1647,15 @@ class ArenaToolExecutor {
|
|
|
1571
1647
|
return { status: "success" };
|
|
1572
1648
|
}
|
|
1573
1649
|
async webSearch(query, depth) {
|
|
1574
|
-
|
|
1650
|
+
const result = await executeBraveWebSearch(query, depth, { signal: this.signal });
|
|
1651
|
+
// Keep the shared provider/Code Arena envelope unchanged. Agent Arena's
|
|
1652
|
+
// live history emits title before URL; restored history has its own order.
|
|
1653
|
+
if (!Array.isArray(result.results))
|
|
1654
|
+
return result;
|
|
1655
|
+
return { ...result, results: result.results.map(value => {
|
|
1656
|
+
const { id, title, url, ...rest } = record(value);
|
|
1657
|
+
return { id, title, url, ...rest };
|
|
1658
|
+
}) };
|
|
1575
1659
|
}
|
|
1576
1660
|
async imageSearch(query, count) {
|
|
1577
1661
|
const payload = await executeBraveImageSearch(query, count, {
|
|
@@ -1619,10 +1703,10 @@ class ArenaToolExecutor {
|
|
|
1619
1703
|
mkdirSync(path.dirname(target), { recursive: true });
|
|
1620
1704
|
await writeFile(target, await responseBytes(imageResponse));
|
|
1621
1705
|
saved.push({
|
|
1622
|
-
title: stringValue(item.title),
|
|
1623
1706
|
file_path: this.relativePath(target),
|
|
1624
|
-
source_url: sourceUrl,
|
|
1625
1707
|
thumbnail_url: thumbnailUrl,
|
|
1708
|
+
title: stringValue(item.title),
|
|
1709
|
+
source_url: sourceUrl,
|
|
1626
1710
|
});
|
|
1627
1711
|
break;
|
|
1628
1712
|
}
|
|
@@ -1647,13 +1731,13 @@ class ArenaToolExecutor {
|
|
|
1647
1731
|
const target = await this.confinedPath(rawPath);
|
|
1648
1732
|
if (!(await stat(target)).isFile())
|
|
1649
1733
|
throw new Error(rawPath);
|
|
1650
|
-
return {
|
|
1734
|
+
return { status: "success", path: rawPath };
|
|
1651
1735
|
}
|
|
1652
1736
|
catch {
|
|
1653
1737
|
return { status: "error", message: "Error calling present file tool" };
|
|
1654
1738
|
}
|
|
1655
1739
|
}
|
|
1656
|
-
async generateImage(rawPath, prompt, images) {
|
|
1740
|
+
async generateImage(rawPath, prompt, images, offerOptions = false, toolCallId = "generate_image", context) {
|
|
1657
1741
|
if (!rawPath) {
|
|
1658
1742
|
return { status: "error", message: "File path cannot be empty." };
|
|
1659
1743
|
}
|
|
@@ -1665,8 +1749,8 @@ class ArenaToolExecutor {
|
|
|
1665
1749
|
message: "File path must end in .jpg, .jpeg, or .png.",
|
|
1666
1750
|
};
|
|
1667
1751
|
}
|
|
1668
|
-
|
|
1669
|
-
|
|
1752
|
+
if (!offerOptions)
|
|
1753
|
+
mkdirSync(path.dirname(target), { recursive: true });
|
|
1670
1754
|
const references = [];
|
|
1671
1755
|
for (const imagePath of images || []) {
|
|
1672
1756
|
const source = await this.confinedPath(imagePath);
|
|
@@ -1691,14 +1775,26 @@ class ArenaToolExecutor {
|
|
|
1691
1775
|
mimeType,
|
|
1692
1776
|
});
|
|
1693
1777
|
}
|
|
1694
|
-
const
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
}
|
|
1701
|
-
|
|
1778
|
+
const size = process.env.ARENA_IMAGE_SIZE || "1024*1024";
|
|
1779
|
+
const generate = async (imageModel) => {
|
|
1780
|
+
const generated = await generateImageWithModelRouter({
|
|
1781
|
+
images: references, model: imageModel, prompt, signal: this.signal || context?.signal, size,
|
|
1782
|
+
});
|
|
1783
|
+
return generatedImageBytesForExtension(generated.bytes, generated.mimeType, extension);
|
|
1784
|
+
};
|
|
1785
|
+
if (offerOptions) {
|
|
1786
|
+
const models = imageOptionModels();
|
|
1787
|
+
return offerGeneratedImages({
|
|
1788
|
+
workspace: this.workspaceReal, stateRoot: this.config.stateRoot,
|
|
1789
|
+
target, rawPath, callId: toolCallId, context,
|
|
1790
|
+
signal: this.signal || context?.signal, models, generate,
|
|
1791
|
+
fingerprint: JSON.stringify({ target, rawPath, prompt, size, models,
|
|
1792
|
+
images: references.map(image => ({ mimeType: image.mimeType,
|
|
1793
|
+
sha256: createHash("sha256").update(image.bytes).digest("hex") })) }),
|
|
1794
|
+
askUser: this.askUserHandler, confinedPath: filePath => this.confinedPath(filePath),
|
|
1795
|
+
});
|
|
1796
|
+
}
|
|
1797
|
+
await writeFile(target, await generate(arenaImageModel()));
|
|
1702
1798
|
return {
|
|
1703
1799
|
status: "success",
|
|
1704
1800
|
file_path: rawPath,
|
|
@@ -1763,6 +1859,20 @@ export class ToolExecutor {
|
|
|
1763
1859
|
availableTools() {
|
|
1764
1860
|
return this.implementation.availableTools();
|
|
1765
1861
|
}
|
|
1862
|
+
historyCall(call) {
|
|
1863
|
+
return this.implementation instanceof ArenaToolExecutor ? this.implementation.historyCall(call) : call;
|
|
1864
|
+
}
|
|
1865
|
+
takeInteractionBoundary(callId) {
|
|
1866
|
+
return this.implementation instanceof ArenaToolExecutor && this.implementation.takeInteractionBoundary(callId);
|
|
1867
|
+
}
|
|
1868
|
+
async beginConnectorTurn(history) {
|
|
1869
|
+
if (this.implementation instanceof ArenaToolExecutor)
|
|
1870
|
+
await this.implementation.beginConnectorTurn(history);
|
|
1871
|
+
}
|
|
1872
|
+
async closeConnectors() {
|
|
1873
|
+
if (this.implementation instanceof ArenaToolExecutor)
|
|
1874
|
+
await this.implementation.closeConnectors();
|
|
1875
|
+
}
|
|
1766
1876
|
inspect(call) {
|
|
1767
1877
|
return this.implementation.inspect(call);
|
|
1768
1878
|
}
|