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: *