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/2] 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/2] 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")); + } + } +}