@qwen-code/arena-agent 0.0.10-dev.7 → 0.0.10-dev.8-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 +12 -3
- package/CHANGELOG.md +48 -1
- package/README.md +79 -5
- package/dist/agent.d.ts +13 -0
- package/dist/agent.js +122 -6
- package/dist/agent.js.map +1 -1
- package/dist/cli.js +106 -20
- package/dist/cli.js.map +1 -1
- package/dist/code-tools.d.ts +5 -0
- package/dist/code-tools.js +231 -56
- package/dist/code-tools.js.map +1 -1
- package/dist/compaction.d.ts +7 -1
- package/dist/compaction.js +83 -7
- package/dist/compaction.js.map +1 -1
- package/dist/config.js +1 -1
- package/dist/config.js.map +1 -1
- package/dist/history.js +17 -0
- package/dist/history.js.map +1 -1
- package/dist/midturn-checkpoint-hook.d.ts +31 -0
- package/dist/midturn-checkpoint-hook.js +38 -0
- package/dist/midturn-checkpoint-hook.js.map +1 -0
- package/dist/mode.js +1 -1
- package/dist/protocol.d.ts +1 -1
- package/dist/protocol.js +2 -1
- package/dist/protocol.js.map +1 -1
- package/dist/provider.js +6 -1
- package/dist/provider.js.map +1 -1
- package/dist/repetitive-output.d.ts +12 -0
- package/dist/repetitive-output.js +80 -0
- package/dist/repetitive-output.js.map +1 -0
- package/dist/snapshot-client.d.ts +10 -0
- package/dist/snapshot-client.js +39 -0
- package/dist/snapshot-client.js.map +1 -0
- package/dist/spec.js +11 -0
- package/dist/spec.js.map +1 -1
- package/dist/tools.d.ts +2 -0
- package/dist/tools.js +92 -29
- package/dist/tools.js.map +1 -1
- package/dist/types.d.ts +2 -1
- package/package.json +1 -1
package/dist/code-tools.js
CHANGED
|
@@ -13,7 +13,8 @@ import mime from "mime/lite";
|
|
|
13
13
|
import { executeBraveWebSearch } from "./brave-search.js";
|
|
14
14
|
import { FetchPageReader } from "./fetch-page.js";
|
|
15
15
|
import { replaceFirstText, replaceUniqueText } from "./file-edit.js";
|
|
16
|
-
import { generatedImageBytesForExtension } from "./image.js";
|
|
16
|
+
import { detectedImageMimeType, generatedImageBytesForExtension } from "./image.js";
|
|
17
|
+
import { supportsImageInput } from "./model-capabilities.js";
|
|
17
18
|
import { generateImageWithModelRouter, } from "./model-router-media.js";
|
|
18
19
|
import { executeRoutifyWebFetch, routifyWebFetchEnabled, } from "./routify-web-fetch.js";
|
|
19
20
|
import { replayResponseFor } from "./replay-responses.js";
|
|
@@ -187,6 +188,42 @@ const visibleImageExtensions = new Set([
|
|
|
187
188
|
".png",
|
|
188
189
|
".webp",
|
|
189
190
|
]);
|
|
191
|
+
const readableImageExtensions = new Set([".jpeg", ".jpg", ".png"]);
|
|
192
|
+
const maximumReadableImageBytes = 10 * 1024 * 1024;
|
|
193
|
+
function readableImageMimeType(data) {
|
|
194
|
+
if (data.length >= 8 &&
|
|
195
|
+
data.subarray(0, 8).equals(Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])))
|
|
196
|
+
return "image/png";
|
|
197
|
+
if (data.length >= 3 && data[0] === 0xff && data[1] === 0xd8 && data[2] === 0xff)
|
|
198
|
+
return "image/jpeg";
|
|
199
|
+
return undefined;
|
|
200
|
+
}
|
|
201
|
+
function hasReadableImageStructure(data, mediaType) {
|
|
202
|
+
if (detectedImageMimeType(data) !== mediaType)
|
|
203
|
+
return false;
|
|
204
|
+
if (mediaType === "image/jpeg") {
|
|
205
|
+
return data.length >= 4 && data[data.length - 2] === 0xff && data[data.length - 1] === 0xd9;
|
|
206
|
+
}
|
|
207
|
+
if (data.readUInt32BE(16) === 0 || data.readUInt32BE(20) === 0)
|
|
208
|
+
return false;
|
|
209
|
+
let offset = 8;
|
|
210
|
+
let hasImageData = false;
|
|
211
|
+
while (offset + 12 <= data.length) {
|
|
212
|
+
const length = data.readUInt32BE(offset);
|
|
213
|
+
const end = offset + 12 + length;
|
|
214
|
+
if (end > data.length)
|
|
215
|
+
return false;
|
|
216
|
+
const type = data.toString("ascii", offset + 4, offset + 8);
|
|
217
|
+
if (offset === 8 && (type !== "IHDR" || length !== 13))
|
|
218
|
+
return false;
|
|
219
|
+
if (type === "IDAT")
|
|
220
|
+
hasImageData = true;
|
|
221
|
+
if (type === "IEND")
|
|
222
|
+
return length === 0 && hasImageData;
|
|
223
|
+
offset = end;
|
|
224
|
+
}
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
190
227
|
// All 39 successful, unmutated list_files captures use this exact order,
|
|
191
228
|
// independently of whether the requested path is the project root or src/.
|
|
192
229
|
// It is the seed manifest order, not an alphabetical/filesystem order.
|
|
@@ -232,6 +269,7 @@ const visualIntroText = "<code-harness-system-message>\n" +
|
|
|
232
269
|
"</code-harness-system-message>";
|
|
233
270
|
const bashOutputLimit = 20_000;
|
|
234
271
|
const lintOutputLimit = 10_000;
|
|
272
|
+
const grepLineCharacterLimit = 2_000;
|
|
235
273
|
// 中段截断,逐字反推自 3 个 truncated:true 参考样本:头尾各 9987 字符 +
|
|
236
274
|
// 26 字符标记,总长恰 20000(由 T1(b) 执行回放发现,非首段截断)。
|
|
237
275
|
const bashTruncationMarker = "\n... output truncated ...\n";
|
|
@@ -382,6 +420,14 @@ function workspaceFileKind(filePath, sample) {
|
|
|
382
420
|
function json(value) {
|
|
383
421
|
return JSON.stringify(value);
|
|
384
422
|
}
|
|
423
|
+
function capGrepLine(value) {
|
|
424
|
+
// Production captures cap every returned grep line independently. This is
|
|
425
|
+
// intentionally a prefix without a marker, including for the matching line.
|
|
426
|
+
return value.slice(0, grepLineCharacterLimit);
|
|
427
|
+
}
|
|
428
|
+
function capGrepLines(values) {
|
|
429
|
+
return values.map(capGrepLine);
|
|
430
|
+
}
|
|
385
431
|
function errorMessage(error) {
|
|
386
432
|
return error instanceof Error ? error.message : String(error);
|
|
387
433
|
}
|
|
@@ -755,8 +801,8 @@ export class CodeToolExecutor {
|
|
|
755
801
|
this.validators.set("apply_patch", this.ajv.compile(APPLY_PATCH_PARAMETERS));
|
|
756
802
|
}
|
|
757
803
|
}
|
|
758
|
-
// The
|
|
759
|
-
//
|
|
804
|
+
// The tools offered to the model for this session, with captured schemas
|
|
805
|
+
// and descriptions adjusted for current capabilities and workspace runtime.
|
|
760
806
|
availableTools() {
|
|
761
807
|
return structuredClone(this.definitions);
|
|
762
808
|
}
|
|
@@ -764,6 +810,125 @@ export class CodeToolExecutor {
|
|
|
764
810
|
const names = this.definitions.map((tool) => tool.function.name);
|
|
765
811
|
return `Model tried to call unavailable tool '${name}'. Available tools: ${names.join(", ")}.`;
|
|
766
812
|
}
|
|
813
|
+
rawArguments(call) {
|
|
814
|
+
return typeof call.function?.arguments === "string"
|
|
815
|
+
? call.function.arguments
|
|
816
|
+
: "{}";
|
|
817
|
+
}
|
|
818
|
+
normalizeArguments(name, parsedArguments) {
|
|
819
|
+
// Keep the parsed value available to checkpoint hooks while applying the
|
|
820
|
+
// same captured runtime defaults to the independent dispatch value.
|
|
821
|
+
const argumentsValue = (this.config.mode === "react" && typeof parsedArguments === "string"
|
|
822
|
+
? {}
|
|
823
|
+
: structuredClone(parsedArguments));
|
|
824
|
+
// The captured provider advertises these fields as required, but its
|
|
825
|
+
// runtime still applies the documented defaults when a model omits
|
|
826
|
+
// them. Preserve the byte-identical outgoing schema while matching the
|
|
827
|
+
// observed execution behavior.
|
|
828
|
+
if (name === "read_file") {
|
|
829
|
+
argumentsValue.offset ??= 1;
|
|
830
|
+
argumentsValue.limit ??= 2000;
|
|
831
|
+
}
|
|
832
|
+
else if (name === "list_files" || name === "glob_files") {
|
|
833
|
+
argumentsValue.path ??= "";
|
|
834
|
+
}
|
|
835
|
+
else if (this.config.mode === "full-stack" && name === "bash" &&
|
|
836
|
+
argumentsValue && typeof argumentsValue === "object" &&
|
|
837
|
+
!Array.isArray(argumentsValue) && argumentsValue.workdir === undefined) {
|
|
838
|
+
argumentsValue.workdir = "";
|
|
839
|
+
}
|
|
840
|
+
else if (name === "grep_files") {
|
|
841
|
+
argumentsValue.path ??= "";
|
|
842
|
+
argumentsValue.glob ??= "";
|
|
843
|
+
argumentsValue.output_mode ??= "content";
|
|
844
|
+
argumentsValue["-i"] ??= false;
|
|
845
|
+
argumentsValue["-B"] ??= 0;
|
|
846
|
+
argumentsValue["-A"] ??= 0;
|
|
847
|
+
argumentsValue["-C"] ??= 0;
|
|
848
|
+
argumentsValue.context ??= 0;
|
|
849
|
+
}
|
|
850
|
+
return argumentsValue;
|
|
851
|
+
}
|
|
852
|
+
validationMessage(name, value, validate) {
|
|
853
|
+
if (validate(value))
|
|
854
|
+
return undefined;
|
|
855
|
+
const issues = validationIssues(name, value, validate.errors || [], this.definitions);
|
|
856
|
+
return (`Invalid input for tool ${name}: Type validation failed: ` +
|
|
857
|
+
`Value: ${JSON.stringify(value)}.\n` +
|
|
858
|
+
`Error message: ${JSON.stringify(issues, null, 2)}`);
|
|
859
|
+
}
|
|
860
|
+
inspect(call) {
|
|
861
|
+
const id = String(call.id || "");
|
|
862
|
+
const name = String(call.function?.name || "");
|
|
863
|
+
const rawArguments = this.rawArguments(call);
|
|
864
|
+
const validate = this.validators.get(name);
|
|
865
|
+
if (!validate) {
|
|
866
|
+
let parsedArguments = undefined;
|
|
867
|
+
let parsed = false;
|
|
868
|
+
try {
|
|
869
|
+
parsedArguments = JSON.parse(rawArguments);
|
|
870
|
+
parsed = true;
|
|
871
|
+
}
|
|
872
|
+
catch {
|
|
873
|
+
// Tool-name validation takes precedence. Parsing is best-effort here
|
|
874
|
+
// only so a valid argument payload remains available to the hook.
|
|
875
|
+
}
|
|
876
|
+
return {
|
|
877
|
+
id,
|
|
878
|
+
name,
|
|
879
|
+
rawArguments,
|
|
880
|
+
...(parsed ? { arguments: parsedArguments } : {}),
|
|
881
|
+
validation: "unknown_tool",
|
|
882
|
+
validationMessage: this.unavailableToolMessage(name),
|
|
883
|
+
};
|
|
884
|
+
}
|
|
885
|
+
let parsedArguments;
|
|
886
|
+
try {
|
|
887
|
+
parsedArguments = JSON.parse(rawArguments);
|
|
888
|
+
}
|
|
889
|
+
catch (error) {
|
|
890
|
+
return {
|
|
891
|
+
id,
|
|
892
|
+
name,
|
|
893
|
+
rawArguments,
|
|
894
|
+
validation: "invalid_json",
|
|
895
|
+
validationMessage: `Invalid input for tool ${name}: JSON parsing failed: Text: ` +
|
|
896
|
+
`${rawArguments}.\nError message: ${errorMessage(error)}`,
|
|
897
|
+
};
|
|
898
|
+
}
|
|
899
|
+
let argumentsValue;
|
|
900
|
+
try {
|
|
901
|
+
argumentsValue = this.normalizeArguments(name, parsedArguments);
|
|
902
|
+
}
|
|
903
|
+
catch (error) {
|
|
904
|
+
return {
|
|
905
|
+
id,
|
|
906
|
+
name,
|
|
907
|
+
rawArguments,
|
|
908
|
+
arguments: parsedArguments,
|
|
909
|
+
validation: "invalid_arguments",
|
|
910
|
+
validationMessage: errorMessage(error),
|
|
911
|
+
};
|
|
912
|
+
}
|
|
913
|
+
const validationMessage = this.validationMessage(name, argumentsValue, validate);
|
|
914
|
+
if (validationMessage !== undefined) {
|
|
915
|
+
return {
|
|
916
|
+
id,
|
|
917
|
+
name,
|
|
918
|
+
rawArguments,
|
|
919
|
+
arguments: parsedArguments,
|
|
920
|
+
validation: "invalid_arguments",
|
|
921
|
+
validationMessage,
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
return {
|
|
925
|
+
id,
|
|
926
|
+
name,
|
|
927
|
+
rawArguments,
|
|
928
|
+
arguments: parsedArguments,
|
|
929
|
+
validation: "valid",
|
|
930
|
+
};
|
|
931
|
+
}
|
|
767
932
|
async execute(call, _context) {
|
|
768
933
|
const callId = String(call.id || "");
|
|
769
934
|
const name = String(call.function?.name || "");
|
|
@@ -772,40 +937,8 @@ export class CodeToolExecutor {
|
|
|
772
937
|
// 监听器不会补发已发生的 abort:已取消的 signal 必须在产生任何副作用
|
|
773
938
|
// 之前短路,否则命令会照常 spawn 直到自然结束。
|
|
774
939
|
this.signal?.throwIfAborted();
|
|
775
|
-
const parsedArguments = JSON.parse(
|
|
776
|
-
|
|
777
|
-
: "{}");
|
|
778
|
-
// React captures normalize a string-valued argument payload to an empty
|
|
779
|
-
// object before validating required fields (rather than echoing the string).
|
|
780
|
-
const argumentsValue = this.config.mode === "react" && typeof parsedArguments === "string"
|
|
781
|
-
? {}
|
|
782
|
-
: parsedArguments;
|
|
783
|
-
// The captured provider advertises these fields as required, but its
|
|
784
|
-
// runtime still applies the documented defaults when a model omits
|
|
785
|
-
// them. Preserve the byte-identical outgoing schema while matching the
|
|
786
|
-
// observed execution behavior.
|
|
787
|
-
if (name === "read_file") {
|
|
788
|
-
argumentsValue.offset ??= 1;
|
|
789
|
-
argumentsValue.limit ??= 2000;
|
|
790
|
-
}
|
|
791
|
-
else if (name === "list_files" || name === "glob_files") {
|
|
792
|
-
argumentsValue.path ??= "";
|
|
793
|
-
}
|
|
794
|
-
else if (this.config.mode === "full-stack" && name === "bash" &&
|
|
795
|
-
argumentsValue && typeof argumentsValue === "object" &&
|
|
796
|
-
!Array.isArray(argumentsValue) && argumentsValue.workdir === undefined) {
|
|
797
|
-
argumentsValue.workdir = "";
|
|
798
|
-
}
|
|
799
|
-
else if (name === "grep_files") {
|
|
800
|
-
argumentsValue.path ??= "";
|
|
801
|
-
argumentsValue.glob ??= "";
|
|
802
|
-
argumentsValue.output_mode ??= "content";
|
|
803
|
-
argumentsValue["-i"] ??= false;
|
|
804
|
-
argumentsValue["-B"] ??= 0;
|
|
805
|
-
argumentsValue["-A"] ??= 0;
|
|
806
|
-
argumentsValue["-C"] ??= 0;
|
|
807
|
-
argumentsValue.context ??= 0;
|
|
808
|
-
}
|
|
940
|
+
const parsedArguments = JSON.parse(this.rawArguments(call));
|
|
941
|
+
const argumentsValue = this.normalizeArguments(name, parsedArguments);
|
|
809
942
|
const validate = this.validators.get(name);
|
|
810
943
|
if (!validate) {
|
|
811
944
|
return {
|
|
@@ -814,14 +947,12 @@ export class CodeToolExecutor {
|
|
|
814
947
|
content: this.unavailableToolMessage(name),
|
|
815
948
|
};
|
|
816
949
|
}
|
|
817
|
-
|
|
818
|
-
|
|
950
|
+
const validationMessage = this.validationMessage(name, argumentsValue, validate);
|
|
951
|
+
if (validationMessage !== undefined) {
|
|
819
952
|
return {
|
|
820
953
|
role: "tool",
|
|
821
954
|
tool_call_id: callId,
|
|
822
|
-
content:
|
|
823
|
-
`Value: ${JSON.stringify(argumentsValue)}.\n` +
|
|
824
|
-
`Error message: ${JSON.stringify(issues, null, 2)}`,
|
|
955
|
+
content: validationMessage,
|
|
825
956
|
};
|
|
826
957
|
}
|
|
827
958
|
const result = await this.dispatch(name, argumentsValue, callId);
|
|
@@ -869,7 +1000,7 @@ export class CodeToolExecutor {
|
|
|
869
1000
|
return replayed.output;
|
|
870
1001
|
switch (name) {
|
|
871
1002
|
case "read_file":
|
|
872
|
-
return this.readCodeFile(String(values.path), Number(values.offset), Number(values.limit));
|
|
1003
|
+
return this.readCodeFile(String(values.path), Number(values.offset), Number(values.limit), _toolCallId);
|
|
873
1004
|
case "create_file":
|
|
874
1005
|
return this.createCodeFile(String(values.path), String(values.content));
|
|
875
1006
|
case "edit_file":
|
|
@@ -1043,7 +1174,7 @@ export class CodeToolExecutor {
|
|
|
1043
1174
|
await visit(searchRoot);
|
|
1044
1175
|
return files;
|
|
1045
1176
|
}
|
|
1046
|
-
async readCodeFile(rawPath, offset, limit) {
|
|
1177
|
+
async readCodeFile(rawPath, offset, limit, toolCallId) {
|
|
1047
1178
|
const target = await this.confinedPath(rawPath);
|
|
1048
1179
|
let data;
|
|
1049
1180
|
try {
|
|
@@ -1057,10 +1188,50 @@ export class CodeToolExecutor {
|
|
|
1057
1188
|
message: `File "${rawPath}" does not exist`,
|
|
1058
1189
|
};
|
|
1059
1190
|
}
|
|
1191
|
+
if (readableImageExtensions.has(path.extname(target).toLowerCase())) {
|
|
1192
|
+
const mediaType = readableImageMimeType(data);
|
|
1193
|
+
if (!mediaType) {
|
|
1194
|
+
return {
|
|
1195
|
+
status: "error",
|
|
1196
|
+
message: `"${rawPath}" is not a valid image — the file contents are not PNG or JPEG. Re-download or regenerate the file, then read it again.`,
|
|
1197
|
+
};
|
|
1198
|
+
}
|
|
1199
|
+
if (!supportsImageInput(this.config)) {
|
|
1200
|
+
return {
|
|
1201
|
+
status: "error",
|
|
1202
|
+
message: `This model cannot view images. Reference "${rawPath}" by its path instead.`,
|
|
1203
|
+
};
|
|
1204
|
+
}
|
|
1205
|
+
if (data.length > maximumReadableImageBytes) {
|
|
1206
|
+
return {
|
|
1207
|
+
status: "error",
|
|
1208
|
+
message: `Image "${rawPath}" is too large to view (${Math.round(data.length / 1024)} KB, max 10240 KB).`,
|
|
1209
|
+
};
|
|
1210
|
+
}
|
|
1211
|
+
if (!hasReadableImageStructure(data, mediaType)) {
|
|
1212
|
+
return {
|
|
1213
|
+
status: "error",
|
|
1214
|
+
message: `"${rawPath}" is not a valid image — the file contents are not PNG or JPEG. Re-download or regenerate the file, then read it again.`,
|
|
1215
|
+
};
|
|
1216
|
+
}
|
|
1217
|
+
const relativePath = this.relativePath(target);
|
|
1218
|
+
this.pendingVisualContent.set(toolCallId, [
|
|
1219
|
+
{ image: `data:${mediaType};base64,${data.toString("base64")}` },
|
|
1220
|
+
]);
|
|
1221
|
+
return {
|
|
1222
|
+
file: {
|
|
1223
|
+
content: `[Image file ${relativePath} (${Math.round(data.length / 1024)} KB, ${mediaType})]`,
|
|
1224
|
+
contentType: "text/plain",
|
|
1225
|
+
path: relativePath,
|
|
1226
|
+
},
|
|
1227
|
+
image: { mediaType, sizeBytes: data.length },
|
|
1228
|
+
status: "success",
|
|
1229
|
+
};
|
|
1230
|
+
}
|
|
1060
1231
|
if (workspaceFileKind(target, data.subarray(0, 4096)) !== "text") {
|
|
1061
1232
|
return {
|
|
1062
1233
|
status: "error",
|
|
1063
|
-
message: `
|
|
1234
|
+
message: `Cannot read binary file "${rawPath}". Only text files and PNG/JPEG images are supported.`,
|
|
1064
1235
|
};
|
|
1065
1236
|
}
|
|
1066
1237
|
const lines = decodeText(data).split("\n");
|
|
@@ -1478,14 +1649,15 @@ export class CodeToolExecutor {
|
|
|
1478
1649
|
const match = {
|
|
1479
1650
|
path: relative,
|
|
1480
1651
|
lineNumber: index + 1,
|
|
1481
|
-
lineContent: lines[index],
|
|
1652
|
+
lineContent: capGrepLine(lines[index]),
|
|
1482
1653
|
};
|
|
1483
1654
|
if (groupedContext) {
|
|
1484
1655
|
const contextBefore = position === 0
|
|
1485
1656
|
? lines.slice(Math.max(0, index - before), index)
|
|
1486
1657
|
: previousAfter;
|
|
1487
|
-
if (contextBefore.length > 0)
|
|
1488
|
-
match.contextBefore = contextBefore;
|
|
1658
|
+
if (contextBefore.length > 0) {
|
|
1659
|
+
match.contextBefore = capGrepLines(contextBefore);
|
|
1660
|
+
}
|
|
1489
1661
|
const nextMatch = matchingLines[position + 1] ?? lines.length;
|
|
1490
1662
|
previousAfter = lines.slice(index + 1, Math.min(index + 1 + contextSpan, nextMatch));
|
|
1491
1663
|
if (nextMatch === lines.length &&
|
|
@@ -1493,8 +1665,9 @@ export class CodeToolExecutor {
|
|
|
1493
1665
|
index + 1 + contextSpan >= lines.length) {
|
|
1494
1666
|
previousAfter = previousAfter.slice(0, -1);
|
|
1495
1667
|
}
|
|
1496
|
-
if (previousAfter.length > 0)
|
|
1497
|
-
match.contextAfter = previousAfter;
|
|
1668
|
+
if (previousAfter.length > 0) {
|
|
1669
|
+
match.contextAfter = capGrepLines(previousAfter);
|
|
1670
|
+
}
|
|
1498
1671
|
}
|
|
1499
1672
|
else if (this.config.mode === "react") {
|
|
1500
1673
|
// The captured adapter attaches context lines to the preceding
|
|
@@ -1502,8 +1675,9 @@ export class CodeToolExecutor {
|
|
|
1502
1675
|
// match receives contextBefore. Do not duplicate overlapping groups.
|
|
1503
1676
|
if (position === 0 && before > 0) {
|
|
1504
1677
|
const contextBefore = lines.slice(Math.max(0, index - before), index);
|
|
1505
|
-
if (contextBefore.length)
|
|
1506
|
-
match.contextBefore = contextBefore;
|
|
1678
|
+
if (contextBefore.length) {
|
|
1679
|
+
match.contextBefore = capGrepLines(contextBefore);
|
|
1680
|
+
}
|
|
1507
1681
|
}
|
|
1508
1682
|
const nextMatch = matchingLines[position + 1];
|
|
1509
1683
|
const end = Math.min(index + 1 + after, nextMatch ?? searchLineCount);
|
|
@@ -1511,15 +1685,16 @@ export class CodeToolExecutor {
|
|
|
1511
1685
|
if (nextMatch !== undefined && before > 0) {
|
|
1512
1686
|
contextAfter.push(...lines.slice(Math.max(end, index + 1, nextMatch - before), nextMatch));
|
|
1513
1687
|
}
|
|
1514
|
-
if (contextAfter.length)
|
|
1515
|
-
match.contextAfter = contextAfter;
|
|
1688
|
+
if (contextAfter.length) {
|
|
1689
|
+
match.contextAfter = capGrepLines(contextAfter);
|
|
1690
|
+
}
|
|
1516
1691
|
}
|
|
1517
1692
|
else {
|
|
1518
1693
|
if (before > 0) {
|
|
1519
|
-
match.contextBefore = lines.slice(Math.max(0, index - before), index);
|
|
1694
|
+
match.contextBefore = capGrepLines(lines.slice(Math.max(0, index - before), index));
|
|
1520
1695
|
}
|
|
1521
1696
|
if (after > 0) {
|
|
1522
|
-
match.contextAfter = lines.slice(index + 1, index + 1 + after);
|
|
1697
|
+
match.contextAfter = capGrepLines(lines.slice(index + 1, index + 1 + after));
|
|
1523
1698
|
}
|
|
1524
1699
|
}
|
|
1525
1700
|
matches.push(match);
|