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