From 0df32e301c73bd8e843773232a3eb5e62f396caa Mon Sep 17 00:00:00 2001
From: guslegend <1670547022@qq.com>
Date: Thu, 4 Jun 2026 23:06:43 +0800
Subject: [PATCH 1/7] fix(tool): repair ToolCallParam copy builder
---
.../main/java/io/agentscope/core/tool/ToolCallParam.java | 4 ++--
.../java/io/agentscope/core/tool/ToolCallParamTest.java | 9 +++++++--
2 files changed, 9 insertions(+), 4 deletions(-)
diff --git a/agentscope-core/src/main/java/io/agentscope/core/tool/ToolCallParam.java b/agentscope-core/src/main/java/io/agentscope/core/tool/ToolCallParam.java
index 1eb7b01e58..8ceeabba6c 100644
--- a/agentscope-core/src/main/java/io/agentscope/core/tool/ToolCallParam.java
+++ b/agentscope-core/src/main/java/io/agentscope/core/tool/ToolCallParam.java
@@ -144,7 +144,7 @@ public static Builder builder() {
*
*
Note: The input map structure is copied so that entries can be modified
* independently, but nested values (typed as Object) remain shared.
- * Immutable fields (toolUseBlock, agent, context, emitter) are shared by reference.
+ * Immutable fields (toolUseBlock, agent, runtimeContext, emitter) are shared by reference.
*
* @param source The existing ToolCallParam to copy values from
* @return A new builder pre-populated with the source's values
@@ -171,7 +171,7 @@ private Builder(ToolCallParam source) {
this.toolUseBlock = source.toolUseBlock;
this.input = source.input.isEmpty() ? null : source.input;
this.agent = source.agent;
- this.context = source.context;
+ this.runtimeContext = source.runtimeContext;
this.emitter = source.emitter;
}
diff --git a/agentscope-core/src/test/java/io/agentscope/core/tool/ToolCallParamTest.java b/agentscope-core/src/test/java/io/agentscope/core/tool/ToolCallParamTest.java
index 5947a8382b..902927ef0a 100644
--- a/agentscope-core/src/test/java/io/agentscope/core/tool/ToolCallParamTest.java
+++ b/agentscope-core/src/test/java/io/agentscope/core/tool/ToolCallParamTest.java
@@ -160,7 +160,9 @@ void testCopyAllFields() {
assertEquals(original.getToolUseBlock(), copy.getToolUseBlock());
assertEquals(original.getInput(), copy.getInput());
assertEquals(original.getAgent(), copy.getAgent());
- assertEquals(original.getContext(), copy.getContext());
+ assertSame(original.getRuntimeContext(), copy.getRuntimeContext());
+ assertNotNull(copy.getContext());
+ assertEquals(original.getContext().get(String.class), copy.getContext().get(String.class));
assertSame(original.getEmitter(), copy.getEmitter());
}
@@ -249,6 +251,7 @@ void testCopyWithNullFields() {
assertEquals(original.getToolUseBlock(), copy.getToolUseBlock());
assertTrue(copy.getInput().isEmpty());
assertNull(copy.getAgent());
+ assertNull(copy.getRuntimeContext());
assertNull(copy.getContext());
}
@@ -287,7 +290,9 @@ void testPreserveImmutableFields() {
// Immutable fields should be the same references
assertSame(original.getToolUseBlock(), copy.getToolUseBlock());
assertSame(original.getAgent(), copy.getAgent());
- assertSame(original.getContext(), copy.getContext());
+ assertSame(original.getRuntimeContext(), copy.getRuntimeContext());
+ assertNotNull(copy.getContext());
+ assertEquals(original.getContext().get(String.class), copy.getContext().get(String.class));
}
@Test
From a4ea6d147820986b2f6003b398aec97354875538 Mon Sep 17 00:00:00 2001
From: guslegend <1670547022@qq.com>
Date: Thu, 11 Jun 2026 21:40:56 +0800
Subject: [PATCH 2/7] fix: normalize workspace listing paths
---
.../agent/workspace/WorkspaceManager.java | 41 ++++++++++--
.../WorkspaceManagerListingTest.java | 64 +++++++++++++++++++
2 files changed, 101 insertions(+), 4 deletions(-)
create mode 100644 agentscope-harness/src/test/java/io/agentscope/harness/agent/workspace/WorkspaceManagerListingTest.java
diff --git a/agentscope-harness/src/main/java/io/agentscope/harness/agent/workspace/WorkspaceManager.java b/agentscope-harness/src/main/java/io/agentscope/harness/agent/workspace/WorkspaceManager.java
index 51f1d9a0cd..a6d7ac1e52 100644
--- a/agentscope-harness/src/main/java/io/agentscope/harness/agent/workspace/WorkspaceManager.java
+++ b/agentscope-harness/src/main/java/io/agentscope/harness/agent/workspace/WorkspaceManager.java
@@ -298,7 +298,7 @@ public List listKnowledgeFiles(RuntimeContext rc) {
if (glob.isSuccess() && glob.matches() != null) {
for (FileInfo fi : glob.matches()) {
if (fi.path() != null && !fi.path().isBlank()) {
- relativePaths.add(normalizeRelativePath(fi.path().trim()));
+ relativePaths.add(normalizeListedPath(fi.path().trim()));
}
}
}
@@ -540,7 +540,10 @@ public Collection listAllTaskRecords(
if (fi.path() == null || fi.path().isBlank()) {
continue;
}
- String rel = normalizeRelativePath(fi.path().trim());
+ String rel = normalizeListedPath(fi.path().trim());
+ if (rel.isEmpty()) {
+ continue;
+ }
Instant mtime = parseInstantQuiet(fi.modifiedAt());
relPaths.put(rel, Optional.ofNullable(mtime));
}
@@ -929,6 +932,36 @@ static String normalizeRelativePath(String relativePath) {
return s;
}
+ /**
+ * Normalizes a path returned by {@link AbstractFilesystem#glob} into a workspace-relative
+ * string when possible.
+ */
+ private String normalizeListedPath(String path) {
+ if (path == null || path.isBlank()) {
+ return "";
+ }
+ String normalized = path.replace('\\', '/').strip();
+ Path workspaceAbs = workspace.toAbsolutePath().normalize();
+ try {
+ Path candidate = Path.of(normalized).normalize();
+ if (candidate.isAbsolute()) {
+ if (candidate.startsWith(workspaceAbs)) {
+ return workspaceAbs.relativize(candidate).toString().replace('\\', '/');
+ }
+ if (normalized.startsWith("/")) {
+ return normalized.substring(1);
+ }
+ return normalized;
+ }
+ } catch (Exception ignored) {
+ // Fall through to the string-based fallback below.
+ }
+ if (normalized.startsWith("/")) {
+ return normalized.substring(1);
+ }
+ return normalized;
+ }
+
/**
* Returns workspace-relative paths of all memory files ({@code MEMORY.md} and {@code
* memory/*.md}). Unions results from the {@link AbstractFilesystem} layer and the local disk,
@@ -946,7 +979,7 @@ public List listMemoryFilePaths(RuntimeContext rc) {
if (glob.isSuccess() && glob.matches() != null) {
for (FileInfo fi : glob.matches()) {
if (fi.path() != null && !fi.path().isBlank()) {
- String rel = normalizeRelativePath(fi.path().trim());
+ String rel = normalizeListedPath(fi.path().trim());
if (!rel.isEmpty()) {
paths.add(rel);
}
@@ -983,7 +1016,7 @@ public List listSessionLogFiles(RuntimeContext rc) {
if (glob.isSuccess() && glob.matches() != null) {
for (FileInfo fi : glob.matches()) {
if (fi.path() != null && !fi.path().isBlank()) {
- String rel = normalizeRelativePath(fi.path().trim());
+ String rel = normalizeListedPath(fi.path().trim());
if (!rel.isEmpty()) {
paths.add(rel);
}
diff --git a/agentscope-harness/src/test/java/io/agentscope/harness/agent/workspace/WorkspaceManagerListingTest.java b/agentscope-harness/src/test/java/io/agentscope/harness/agent/workspace/WorkspaceManagerListingTest.java
new file mode 100644
index 0000000000..c8d0dae32e
--- /dev/null
+++ b/agentscope-harness/src/test/java/io/agentscope/harness/agent/workspace/WorkspaceManagerListingTest.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2024-2026 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.agentscope.harness.agent.workspace;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import io.agentscope.core.agent.RuntimeContext;
+import io.agentscope.harness.agent.filesystem.AbstractFilesystem;
+import io.agentscope.harness.agent.filesystem.spec.LocalFilesystemSpec;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+class WorkspaceManagerListingTest {
+
+ @Test
+ void listKnowledgeFiles_returnsWorkspaceRelativePaths(
+ @TempDir Path project, @TempDir Path workspace) throws IOException {
+ Files.createDirectories(workspace.resolve("knowledge"));
+ Files.writeString(workspace.resolve("knowledge/guide.md"), "guide");
+
+ AbstractFilesystem fs = new LocalFilesystemSpec().project(project).toFilesystem(workspace, null);
+ try (WorkspaceManager wm = new WorkspaceManager(workspace, fs)) {
+ List knowledgeFiles = wm.listKnowledgeFiles(RuntimeContext.empty());
+ assertEquals(1, knowledgeFiles.size(), () -> "Unexpected knowledge files: " + knowledgeFiles);
+ assertEquals(
+ workspace.resolve("knowledge/guide.md").normalize(),
+ knowledgeFiles.get(0).normalize());
+ }
+ }
+
+ @Test
+ void listMemoryFilePaths_returnsWorkspaceRelativePaths(
+ @TempDir Path project, @TempDir Path workspace) throws IOException {
+ Files.writeString(workspace.resolve("MEMORY.md"), "memory");
+ Files.createDirectories(workspace.resolve("memory"));
+ Files.writeString(workspace.resolve("memory/notes.md"), "notes");
+
+ AbstractFilesystem fs = new LocalFilesystemSpec().project(project).toFilesystem(workspace, null);
+ try (WorkspaceManager wm = new WorkspaceManager(workspace, fs)) {
+ List memoryFiles = wm.listMemoryFilePaths(RuntimeContext.empty());
+ assertEquals(2, memoryFiles.size(), () -> "Unexpected memory files: " + memoryFiles);
+ assertTrue(memoryFiles.contains("MEMORY.md"));
+ assertTrue(memoryFiles.contains("memory/notes.md"));
+ }
+ }
+}
From 855d1dfc985cfd2f9726f3035b02a33bd480b1c1 Mon Sep 17 00:00:00 2001
From: guslegend <1670547022@qq.com>
Date: Fri, 12 Jun 2026 06:36:58 +0800
Subject: [PATCH 3/7] fix: normalize project-layer workspace listings
---
.../agent/workspace/WorkspaceManager.java | 47 +++++++++++++++----
.../WorkspaceManagerListingTest.java | 32 +++++++++++++
2 files changed, 70 insertions(+), 9 deletions(-)
diff --git a/agentscope-harness/src/main/java/io/agentscope/harness/agent/workspace/WorkspaceManager.java b/agentscope-harness/src/main/java/io/agentscope/harness/agent/workspace/WorkspaceManager.java
index a6d7ac1e52..4e8a01d841 100644
--- a/agentscope-harness/src/main/java/io/agentscope/harness/agent/workspace/WorkspaceManager.java
+++ b/agentscope-harness/src/main/java/io/agentscope/harness/agent/workspace/WorkspaceManager.java
@@ -34,6 +34,7 @@
import io.agentscope.core.agent.RuntimeContext;
import io.agentscope.harness.agent.filesystem.AbstractFilesystem;
import io.agentscope.harness.agent.filesystem.OverlayFilesystem;
+import io.agentscope.harness.agent.filesystem.local.LocalFilesystem;
import io.agentscope.harness.agent.filesystem.model.FileInfo;
import io.agentscope.harness.agent.filesystem.model.GlobResult;
import io.agentscope.harness.agent.filesystem.model.ReadResult;
@@ -941,25 +942,53 @@ private String normalizeListedPath(String path) {
return "";
}
String normalized = path.replace('\\', '/').strip();
- Path workspaceAbs = workspace.toAbsolutePath().normalize();
try {
Path candidate = Path.of(normalized).normalize();
if (candidate.isAbsolute()) {
- if (candidate.startsWith(workspaceAbs)) {
- return workspaceAbs.relativize(candidate).toString().replace('\\', '/');
+ String workspaceRelative = relativizeIfUnder(candidate, workspace);
+ if (workspaceRelative != null) {
+ return workspaceRelative;
}
- if (normalized.startsWith("/")) {
- return normalized.substring(1);
+ Path lowerRoot = getOverlayLowerRoot();
+ if (lowerRoot != null) {
+ String lowerRelative = relativizeIfUnder(candidate, lowerRoot);
+ if (lowerRelative != null) {
+ return lowerRelative;
+ }
}
- return normalized;
+ return stripLeadingSlashes(normalized);
}
} catch (Exception ignored) {
// Fall through to the string-based fallback below.
}
- if (normalized.startsWith("/")) {
- return normalized.substring(1);
+ return stripLeadingSlashes(normalized);
+ }
+
+ private String relativizeIfUnder(Path candidate, Path root) {
+ Path normalizedRoot = root.toAbsolutePath().normalize();
+ if (!candidate.startsWith(normalizedRoot)) {
+ return null;
+ }
+ return normalizedRoot.relativize(candidate).toString().replace('\\', '/');
+ }
+
+ private String stripLeadingSlashes(String value) {
+ String s = value;
+ while (s.startsWith("/")) {
+ s = s.substring(1);
+ }
+ return s;
+ }
+
+ private Path getOverlayLowerRoot() {
+ if (!(filesystem instanceof OverlayFilesystem overlay)) {
+ return null;
+ }
+ AbstractFilesystem lower = overlay.lower();
+ if (lower instanceof LocalFilesystem localFilesystem) {
+ return localFilesystem.getCwd();
}
- return normalized;
+ return null;
}
/**
diff --git a/agentscope-harness/src/test/java/io/agentscope/harness/agent/workspace/WorkspaceManagerListingTest.java b/agentscope-harness/src/test/java/io/agentscope/harness/agent/workspace/WorkspaceManagerListingTest.java
index c8d0dae32e..c75b17ce4b 100644
--- a/agentscope-harness/src/test/java/io/agentscope/harness/agent/workspace/WorkspaceManagerListingTest.java
+++ b/agentscope-harness/src/test/java/io/agentscope/harness/agent/workspace/WorkspaceManagerListingTest.java
@@ -46,6 +46,22 @@ void listKnowledgeFiles_returnsWorkspaceRelativePaths(
}
}
+ @Test
+ void listKnowledgeFiles_normalizesProjectLayerAbsolutePaths(
+ @TempDir Path project, @TempDir Path workspace) throws IOException {
+ Files.createDirectories(project.resolve("knowledge"));
+ Files.writeString(project.resolve("knowledge/guide.md"), "guide");
+
+ AbstractFilesystem fs = new LocalFilesystemSpec().project(project).toFilesystem(workspace, null);
+ try (WorkspaceManager wm = new WorkspaceManager(workspace, fs)) {
+ List knowledgeFiles = wm.listKnowledgeFiles(RuntimeContext.empty());
+ assertEquals(1, knowledgeFiles.size(), () -> "Unexpected knowledge files: " + knowledgeFiles);
+ assertEquals(
+ workspace.resolve("knowledge/guide.md").normalize(),
+ knowledgeFiles.get(0).normalize());
+ }
+ }
+
@Test
void listMemoryFilePaths_returnsWorkspaceRelativePaths(
@TempDir Path project, @TempDir Path workspace) throws IOException {
@@ -61,4 +77,20 @@ void listMemoryFilePaths_returnsWorkspaceRelativePaths(
assertTrue(memoryFiles.contains("memory/notes.md"));
}
}
+
+ @Test
+ void listMemoryFilePaths_normalizesProjectLayerAbsolutePaths(
+ @TempDir Path project, @TempDir Path workspace) throws IOException {
+ Files.writeString(project.resolve("MEMORY.md"), "memory");
+ Files.createDirectories(project.resolve("memory"));
+ Files.writeString(project.resolve("memory/notes.md"), "notes");
+
+ AbstractFilesystem fs = new LocalFilesystemSpec().project(project).toFilesystem(workspace, null);
+ try (WorkspaceManager wm = new WorkspaceManager(workspace, fs)) {
+ List memoryFiles = wm.listMemoryFilePaths(RuntimeContext.empty());
+ assertEquals(2, memoryFiles.size(), () -> "Unexpected memory files: " + memoryFiles);
+ assertTrue(memoryFiles.contains("MEMORY.md"));
+ assertTrue(memoryFiles.contains("memory/notes.md"));
+ }
+ }
}
From 6a3511d8374d4e88368f9bb4f173b5fa64f30a11 Mon Sep 17 00:00:00 2001
From: guslegend <1670547022@qq.com>
Date: Fri, 12 Jun 2026 18:06:12 +0800
Subject: [PATCH 4/7] test: gate OpenAI official E2E behind ENABLE_E2E_TESTS
---
.../io/agentscope/core/model/OpenAIOfficialAPIE2ETest.java | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/agentscope-core/src/test/java/io/agentscope/core/model/OpenAIOfficialAPIE2ETest.java b/agentscope-core/src/test/java/io/agentscope/core/model/OpenAIOfficialAPIE2ETest.java
index db33b4349b..bb2530132b 100644
--- a/agentscope-core/src/test/java/io/agentscope/core/model/OpenAIOfficialAPIE2ETest.java
+++ b/agentscope-core/src/test/java/io/agentscope/core/model/OpenAIOfficialAPIE2ETest.java
@@ -20,6 +20,7 @@
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
+import io.agentscope.core.e2e.E2ETestCondition;
import io.agentscope.core.formatter.openai.OpenAIChatFormatter;
import io.agentscope.core.message.Msg;
import io.agentscope.core.message.MsgRole;
@@ -33,6 +34,7 @@
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
+import org.junit.jupiter.api.extension.ExtendWith;
import reactor.test.StepVerifier;
/**
@@ -52,6 +54,7 @@
*
* Requirements:
*
+ * - ENABLE_E2E_TESTS=true
* - OPENAI_API_KEY environment variable must be set (official OpenAI API key)
* - Active internet connection
* - Valid OpenAI API quota
@@ -62,6 +65,7 @@
@Tag("e2e")
@Tag("openai")
@Tag("official")
+@ExtendWith(E2ETestCondition.class)
@DisplayName("OpenAI Official API E2E Tests (Real OpenAI API)")
@EnabledIfEnvironmentVariable(
named = "OPENAI_API_KEY",
From f86aad9ed63123d28e55987c425c7209c2d17294 Mon Sep 17 00:00:00 2001
From: guslegend <1670547022@qq.com>
Date: Fri, 12 Jun 2026 18:48:08 +0800
Subject: [PATCH 5/7] chore: sync issue-1704 branch with main
---
.../agentscope/core/tool/ToolCallParam.java | 2 +-
.../core/tool/ToolCallParamTest.java | 5 -----
.../WorkspaceManagerListingTest.java | 22 ++++++++++++++-----
3 files changed, 17 insertions(+), 12 deletions(-)
diff --git a/agentscope-core/src/main/java/io/agentscope/core/tool/ToolCallParam.java b/agentscope-core/src/main/java/io/agentscope/core/tool/ToolCallParam.java
index 8ceeabba6c..0596f1ecb6 100644
--- a/agentscope-core/src/main/java/io/agentscope/core/tool/ToolCallParam.java
+++ b/agentscope-core/src/main/java/io/agentscope/core/tool/ToolCallParam.java
@@ -144,7 +144,7 @@ public static Builder builder() {
*
* Note: The input map structure is copied so that entries can be modified
* independently, but nested values (typed as Object) remain shared.
- * Immutable fields (toolUseBlock, agent, runtimeContext, emitter) are shared by reference.
+ * Immutable fields (toolUseBlock, agent, context, emitter) are shared by reference.
*
* @param source The existing ToolCallParam to copy values from
* @return A new builder pre-populated with the source's values
diff --git a/agentscope-core/src/test/java/io/agentscope/core/tool/ToolCallParamTest.java b/agentscope-core/src/test/java/io/agentscope/core/tool/ToolCallParamTest.java
index 902927ef0a..fec2d4acb3 100644
--- a/agentscope-core/src/test/java/io/agentscope/core/tool/ToolCallParamTest.java
+++ b/agentscope-core/src/test/java/io/agentscope/core/tool/ToolCallParamTest.java
@@ -161,8 +161,6 @@ void testCopyAllFields() {
assertEquals(original.getInput(), copy.getInput());
assertEquals(original.getAgent(), copy.getAgent());
assertSame(original.getRuntimeContext(), copy.getRuntimeContext());
- assertNotNull(copy.getContext());
- assertEquals(original.getContext().get(String.class), copy.getContext().get(String.class));
assertSame(original.getEmitter(), copy.getEmitter());
}
@@ -251,7 +249,6 @@ void testCopyWithNullFields() {
assertEquals(original.getToolUseBlock(), copy.getToolUseBlock());
assertTrue(copy.getInput().isEmpty());
assertNull(copy.getAgent());
- assertNull(copy.getRuntimeContext());
assertNull(copy.getContext());
}
@@ -291,8 +288,6 @@ void testPreserveImmutableFields() {
assertSame(original.getToolUseBlock(), copy.getToolUseBlock());
assertSame(original.getAgent(), copy.getAgent());
assertSame(original.getRuntimeContext(), copy.getRuntimeContext());
- assertNotNull(copy.getContext());
- assertEquals(original.getContext().get(String.class), copy.getContext().get(String.class));
}
@Test
diff --git a/agentscope-harness/src/test/java/io/agentscope/harness/agent/workspace/WorkspaceManagerListingTest.java b/agentscope-harness/src/test/java/io/agentscope/harness/agent/workspace/WorkspaceManagerListingTest.java
index c75b17ce4b..61055cd19a 100644
--- a/agentscope-harness/src/test/java/io/agentscope/harness/agent/workspace/WorkspaceManagerListingTest.java
+++ b/agentscope-harness/src/test/java/io/agentscope/harness/agent/workspace/WorkspaceManagerListingTest.java
@@ -36,10 +36,14 @@ void listKnowledgeFiles_returnsWorkspaceRelativePaths(
Files.createDirectories(workspace.resolve("knowledge"));
Files.writeString(workspace.resolve("knowledge/guide.md"), "guide");
- AbstractFilesystem fs = new LocalFilesystemSpec().project(project).toFilesystem(workspace, null);
+ AbstractFilesystem fs =
+ new LocalFilesystemSpec().project(project).toFilesystem(workspace, null);
try (WorkspaceManager wm = new WorkspaceManager(workspace, fs)) {
List knowledgeFiles = wm.listKnowledgeFiles(RuntimeContext.empty());
- assertEquals(1, knowledgeFiles.size(), () -> "Unexpected knowledge files: " + knowledgeFiles);
+ assertEquals(
+ 1,
+ knowledgeFiles.size(),
+ () -> "Unexpected knowledge files: " + knowledgeFiles);
assertEquals(
workspace.resolve("knowledge/guide.md").normalize(),
knowledgeFiles.get(0).normalize());
@@ -52,10 +56,14 @@ void listKnowledgeFiles_normalizesProjectLayerAbsolutePaths(
Files.createDirectories(project.resolve("knowledge"));
Files.writeString(project.resolve("knowledge/guide.md"), "guide");
- AbstractFilesystem fs = new LocalFilesystemSpec().project(project).toFilesystem(workspace, null);
+ AbstractFilesystem fs =
+ new LocalFilesystemSpec().project(project).toFilesystem(workspace, null);
try (WorkspaceManager wm = new WorkspaceManager(workspace, fs)) {
List knowledgeFiles = wm.listKnowledgeFiles(RuntimeContext.empty());
- assertEquals(1, knowledgeFiles.size(), () -> "Unexpected knowledge files: " + knowledgeFiles);
+ assertEquals(
+ 1,
+ knowledgeFiles.size(),
+ () -> "Unexpected knowledge files: " + knowledgeFiles);
assertEquals(
workspace.resolve("knowledge/guide.md").normalize(),
knowledgeFiles.get(0).normalize());
@@ -69,7 +77,8 @@ void listMemoryFilePaths_returnsWorkspaceRelativePaths(
Files.createDirectories(workspace.resolve("memory"));
Files.writeString(workspace.resolve("memory/notes.md"), "notes");
- AbstractFilesystem fs = new LocalFilesystemSpec().project(project).toFilesystem(workspace, null);
+ AbstractFilesystem fs =
+ new LocalFilesystemSpec().project(project).toFilesystem(workspace, null);
try (WorkspaceManager wm = new WorkspaceManager(workspace, fs)) {
List memoryFiles = wm.listMemoryFilePaths(RuntimeContext.empty());
assertEquals(2, memoryFiles.size(), () -> "Unexpected memory files: " + memoryFiles);
@@ -85,7 +94,8 @@ void listMemoryFilePaths_normalizesProjectLayerAbsolutePaths(
Files.createDirectories(project.resolve("memory"));
Files.writeString(project.resolve("memory/notes.md"), "notes");
- AbstractFilesystem fs = new LocalFilesystemSpec().project(project).toFilesystem(workspace, null);
+ AbstractFilesystem fs =
+ new LocalFilesystemSpec().project(project).toFilesystem(workspace, null);
try (WorkspaceManager wm = new WorkspaceManager(workspace, fs)) {
List memoryFiles = wm.listMemoryFilePaths(RuntimeContext.empty());
assertEquals(2, memoryFiles.size(), () -> "Unexpected memory files: " + memoryFiles);
From 34d4de56b71ce279eed61743c35a3948a57e8246 Mon Sep 17 00:00:00 2001
From: guslegend <1670547022@qq.com>
Date: Fri, 12 Jun 2026 19:31:20 +0800
Subject: [PATCH 6/7] test: expand workspace listing coverage
---
.../WorkspaceManagerListingTest.java | 164 +++++++++++++++++-
1 file changed, 160 insertions(+), 4 deletions(-)
diff --git a/agentscope-harness/src/test/java/io/agentscope/harness/agent/workspace/WorkspaceManagerListingTest.java b/agentscope-harness/src/test/java/io/agentscope/harness/agent/workspace/WorkspaceManagerListingTest.java
index 61055cd19a..7a773ee3d5 100644
--- a/agentscope-harness/src/test/java/io/agentscope/harness/agent/workspace/WorkspaceManagerListingTest.java
+++ b/agentscope-harness/src/test/java/io/agentscope/harness/agent/workspace/WorkspaceManagerListingTest.java
@@ -20,16 +20,122 @@
import io.agentscope.core.agent.RuntimeContext;
import io.agentscope.harness.agent.filesystem.AbstractFilesystem;
+import io.agentscope.harness.agent.filesystem.model.EditResult;
+import io.agentscope.harness.agent.filesystem.model.FileDownloadResponse;
+import io.agentscope.harness.agent.filesystem.model.FileInfo;
+import io.agentscope.harness.agent.filesystem.model.FileUploadResponse;
+import io.agentscope.harness.agent.filesystem.model.GlobResult;
+import io.agentscope.harness.agent.filesystem.model.GrepResult;
+import io.agentscope.harness.agent.filesystem.model.LsResult;
+import io.agentscope.harness.agent.filesystem.model.ReadResult;
+import io.agentscope.harness.agent.filesystem.model.WriteResult;
import io.agentscope.harness.agent.filesystem.spec.LocalFilesystemSpec;
import java.io.IOException;
+import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.time.Duration;
+import java.time.Instant;
import java.util.List;
+import java.util.Map;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
class WorkspaceManagerListingTest {
+ private static final RuntimeContext EMPTY_CONTEXT = RuntimeContext.empty();
+
+ private static final class StaticGlobFilesystem implements AbstractFilesystem {
+ private final GlobResult globResult;
+
+ private StaticGlobFilesystem(GlobResult globResult) {
+ this.globResult = globResult;
+ }
+
+ @Override
+ public LsResult ls(RuntimeContext runtimeContext, String path) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ReadResult read(
+ RuntimeContext runtimeContext, String filePath, int offset, int limit) {
+ return ReadResult.fail("not found");
+ }
+
+ @Override
+ public WriteResult write(RuntimeContext runtimeContext, String filePath, String content) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public EditResult edit(
+ RuntimeContext runtimeContext,
+ String filePath,
+ String oldString,
+ String newString,
+ boolean replaceAll) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public GrepResult grep(
+ RuntimeContext runtimeContext, String pattern, String path, String glob) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public GlobResult glob(RuntimeContext runtimeContext, String pattern, String path) {
+ return globResult;
+ }
+
+ @Override
+ public List uploadFiles(
+ RuntimeContext runtimeContext, List> files) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public List downloadFiles(
+ RuntimeContext runtimeContext, List paths) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public WriteResult delete(RuntimeContext runtimeContext, String path) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public WriteResult move(RuntimeContext runtimeContext, String fromPath, String toPath) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean exists(RuntimeContext runtimeContext, String path) {
+ return false;
+ }
+ }
+
+ private static String invokeNormalizeListedPath(WorkspaceManager wm, String path) {
+ try {
+ Method method =
+ WorkspaceManager.class.getDeclaredMethod("normalizeListedPath", String.class);
+ method.setAccessible(true);
+ return (String) method.invoke(wm, path);
+ } catch (ReflectiveOperationException e) {
+ throw new AssertionError("Failed to invoke normalizeListedPath", e);
+ }
+ }
+
+ private static String stripLeadingSlashes(String value) {
+ String normalized = value.replace('\\', '/').strip();
+ while (normalized.startsWith("/")) {
+ normalized = normalized.substring(1);
+ }
+ return normalized;
+ }
+
@Test
void listKnowledgeFiles_returnsWorkspaceRelativePaths(
@TempDir Path project, @TempDir Path workspace) throws IOException {
@@ -39,7 +145,7 @@ void listKnowledgeFiles_returnsWorkspaceRelativePaths(
AbstractFilesystem fs =
new LocalFilesystemSpec().project(project).toFilesystem(workspace, null);
try (WorkspaceManager wm = new WorkspaceManager(workspace, fs)) {
- List knowledgeFiles = wm.listKnowledgeFiles(RuntimeContext.empty());
+ List knowledgeFiles = wm.listKnowledgeFiles(EMPTY_CONTEXT);
assertEquals(
1,
knowledgeFiles.size(),
@@ -59,7 +165,7 @@ void listKnowledgeFiles_normalizesProjectLayerAbsolutePaths(
AbstractFilesystem fs =
new LocalFilesystemSpec().project(project).toFilesystem(workspace, null);
try (WorkspaceManager wm = new WorkspaceManager(workspace, fs)) {
- List knowledgeFiles = wm.listKnowledgeFiles(RuntimeContext.empty());
+ List knowledgeFiles = wm.listKnowledgeFiles(EMPTY_CONTEXT);
assertEquals(
1,
knowledgeFiles.size(),
@@ -80,7 +186,7 @@ void listMemoryFilePaths_returnsWorkspaceRelativePaths(
AbstractFilesystem fs =
new LocalFilesystemSpec().project(project).toFilesystem(workspace, null);
try (WorkspaceManager wm = new WorkspaceManager(workspace, fs)) {
- List memoryFiles = wm.listMemoryFilePaths(RuntimeContext.empty());
+ List memoryFiles = wm.listMemoryFilePaths(EMPTY_CONTEXT);
assertEquals(2, memoryFiles.size(), () -> "Unexpected memory files: " + memoryFiles);
assertTrue(memoryFiles.contains("MEMORY.md"));
assertTrue(memoryFiles.contains("memory/notes.md"));
@@ -97,10 +203,60 @@ void listMemoryFilePaths_normalizesProjectLayerAbsolutePaths(
AbstractFilesystem fs =
new LocalFilesystemSpec().project(project).toFilesystem(workspace, null);
try (WorkspaceManager wm = new WorkspaceManager(workspace, fs)) {
- List memoryFiles = wm.listMemoryFilePaths(RuntimeContext.empty());
+ List memoryFiles = wm.listMemoryFilePaths(EMPTY_CONTEXT);
assertEquals(2, memoryFiles.size(), () -> "Unexpected memory files: " + memoryFiles);
assertTrue(memoryFiles.contains("MEMORY.md"));
assertTrue(memoryFiles.contains("memory/notes.md"));
}
}
+
+ @Test
+ void listMemoryFilePaths_normalizesRelativeAndForeignAbsolutePaths(@TempDir Path workspace)
+ throws IOException {
+ Path foreignRoot = Files.createTempDirectory("workspace-manager-foreign");
+ Path foreignMemoryFile = foreignRoot.resolve("memory/foreign.md");
+ Files.createDirectories(foreignMemoryFile.getParent());
+ Files.writeString(foreignMemoryFile, "foreign");
+
+ AbstractFilesystem fs =
+ new StaticGlobFilesystem(
+ GlobResult.success(
+ List.of(
+ FileInfo.ofFile("memory/relative.md", 1, ""),
+ FileInfo.ofFile(foreignMemoryFile.toString(), 1, ""))));
+ try (WorkspaceManager wm = new WorkspaceManager(workspace, fs)) {
+ List memoryFiles = wm.listMemoryFilePaths(EMPTY_CONTEXT);
+ assertTrue(memoryFiles.contains("memory/relative.md"));
+ assertTrue(memoryFiles.contains(stripLeadingSlashes(foreignMemoryFile.toString())));
+ }
+ }
+
+ @Test
+ void listAllTaskRecords_ignoresEntriesThatNormalizeToEmpty(@TempDir Path workspace) {
+ AbstractFilesystem fs =
+ new StaticGlobFilesystem(
+ GlobResult.success(
+ List.of(
+ FileInfo.ofFile("/", 1, Instant.now().toString()),
+ FileInfo.ofFile(" ", 1, Instant.now().toString()))));
+ try (WorkspaceManager wm = new WorkspaceManager(workspace, fs)) {
+ assertTrue(
+ wm.listAllTaskRecords(EMPTY_CONTEXT, "agent", Duration.ofMinutes(5)).isEmpty());
+ }
+ }
+
+ @Test
+ void normalizeListedPath_handlesBlankRelativeAndForeignAbsoluteInputs(@TempDir Path workspace) {
+ AbstractFilesystem fs = new StaticGlobFilesystem(GlobResult.success(List.of()));
+ try (WorkspaceManager wm = new WorkspaceManager(workspace, fs)) {
+ assertEquals("", invokeNormalizeListedPath(wm, " "));
+ assertEquals("memory/relative.md", invokeNormalizeListedPath(wm, "memory/relative.md"));
+
+ Path foreignAbsolute =
+ Path.of(System.getProperty("java.io.tmpdir")).resolve("outside.md");
+ assertEquals(
+ stripLeadingSlashes(foreignAbsolute.toString()),
+ invokeNormalizeListedPath(wm, foreignAbsolute.toString()));
+ }
+ }
}
From ca95a35cc00dd258a7a0aa30118b20037ea8dfd5 Mon Sep 17 00:00:00 2001
From: guslegend <1670547022@qq.com>
Date: Sun, 14 Jun 2026 10:24:50 +0800
Subject: [PATCH 7/7] test: cover remaining workspace listing branches
---
.../WorkspaceManagerListingTest.java | 22 +++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/agentscope-harness/src/test/java/io/agentscope/harness/agent/workspace/WorkspaceManagerListingTest.java b/agentscope-harness/src/test/java/io/agentscope/harness/agent/workspace/WorkspaceManagerListingTest.java
index 7a773ee3d5..bab57d9f27 100644
--- a/agentscope-harness/src/test/java/io/agentscope/harness/agent/workspace/WorkspaceManagerListingTest.java
+++ b/agentscope-harness/src/test/java/io/agentscope/harness/agent/workspace/WorkspaceManagerListingTest.java
@@ -259,4 +259,26 @@ void normalizeListedPath_handlesBlankRelativeAndForeignAbsoluteInputs(@TempDir P
invokeNormalizeListedPath(wm, foreignAbsolute.toString()));
}
}
+
+ @Test
+ void normalizeListedPath_handlesWorkspaceAndProjectAbsoluteInputs(
+ @TempDir Path project, @TempDir Path workspace) throws IOException {
+ Files.createDirectories(workspace.resolve("memory"));
+ Files.writeString(workspace.resolve("memory/workspace.md"), "workspace");
+ Files.createDirectories(project.resolve("memory"));
+ Files.writeString(project.resolve("memory/project.md"), "project");
+
+ AbstractFilesystem fs =
+ new LocalFilesystemSpec().project(project).toFilesystem(workspace, null);
+ try (WorkspaceManager wm = new WorkspaceManager(workspace, fs)) {
+ assertEquals(
+ "memory/workspace.md",
+ invokeNormalizeListedPath(
+ wm, workspace.resolve("memory/workspace.md").toString()));
+ assertEquals(
+ "memory/project.md",
+ invokeNormalizeListedPath(wm, project.resolve("memory/project.md").toString()));
+ assertEquals("bad\u0000path", invokeNormalizeListedPath(wm, "bad\u0000path"));
+ }
+ }
}