Repository: agentscope-ai/agentscope-java (2026-07-21T12:50:13Z)

Issue:
[Bug]:`RemoteFilesystem.glob()` 使用 `**/` 模式时无法匹配搜索目录根层文件
## 问题描述

在 Windows 环境中，调用 `RemoteFilesystem.glob()` 使用 `**/hello.txt`、`**/*.txt` 等递归 glob 模式时，无法匹配搜索目录根层的文件。

例如远程文件系统中存在：

```text
/hello.txt
/reports/hello.txt
```

执行：

```java
filesystem.glob(runtimeContext, "**/hello.txt", "/");
```

实际只能匹配：

```text
/reports/hello.txt
```

无法匹配：

```text
/hello.txt
```

文件实际存在于 `BaseStore` 中，问题发生在 `RemoteFilesystem.glob()` 对候选路径进行 glob 过滤的阶段。

## 运行环境

- JDK：17
- 操作系统：Windows
- 文件系统实现：`io.agentscope.harness.agent.filesystem.remote.RemoteFilesystem`
- Store：`InMemoryStore` 和 MySQL `JdbcStore` 均可复现
- AgentScope Java：当前源码版本

## 最小复现代码

```java
@Test
void recursiveGlobShouldMatchFileAtRemoteRoot() {
    InMemoryStore store = new InMemoryStore();
    RemoteFilesystem filesystem =
        new RemoteFilesystem(store, List.of("test"));
    RuntimeContext runtimeContext = RuntimeContext.empty();

    WriteResult writeResult =
        filesystem.write(runtimeContext, "/hello.txt", "hello");

    assertTrue(writeResult.isSuccess());

    GlobResult globResult =
        filesystem.glob(runtimeContext, "**/hello.txt", "/");

    assertTrue(globResult.isSuccess());
    assertThat(globResult.matches())
        .extracting(FileInfo::path)
        .contains("/hello.txt");
}
```

当前实际结果：

```text
globResult.matches() = []
```

如果文件改为嵌套路径：

```java
filesystem.write(
    runtimeContext,
    "/reports/hello.txt",
    "hello"
);
```

则可以被 `**/hello.txt` 匹配。

## 预期行为

`**/hello.txt` 应同时匹配搜索目录根层和嵌套目录中的文件：

```text
/hello.txt
/reports/hello.txt
/reports/archive/hello.txt
```

同理，`**/*.txt` 应同时匹配：

```text
/hello.txt
/reports/report.txt
```

## 实际行为

根层文件被遗漏：

```text
/hello.txt                    未匹配
/reports/hello.txt            可以匹配
/reports/archive/hello.txt    可以匹配
```


Discussion:
根因:递归模式 `**/` 在 Java glob 里要求至少一个目录分隔符,所以匹配不到搜索根目录下的裸文件名(如 `**/hello.txt` 匹配不到 `/hello.txt`)。代码里兜底的 `directMatcher` 对 `**` 开头的 pattern 会塌缩成和主 matcher 一样,失去作用。已提 PR #2344:构造 `directMatcher` 时剥掉前导 `**/`,覆盖索引快路径和全量扫描两条路径,并补了 `**/hello.txt`、`**/*.txt` 两个回归用例(用你给的最小复现验证过)。



Patch:
diff --git a/agentscope-harness/src/main/java/io/agentscope/harness/agent/filesystem/remote/RemoteFilesystem.java b/agentscope-harness/src/main/java/io/agentscope/harness/agent/filesystem/remote/RemoteFilesystem.java
index 9c34f07a17..57efde917d 100644
--- a/agentscope-harness/src/main/java/io/agentscope/harness/agent/filesystem/remote/RemoteFilesystem.java
+++ b/agentscope-harness/src/main/java/io/agentscope/harness/agent/filesystem/remote/RemoteFilesystem.java
@@ -425,8 +425,19 @@ public GlobResult glob(RuntimeContext runtimeContext, String pattern, String pat
                                         + (effectivePattern.startsWith("**")
                                                 ? effectivePattern
                                                 : "**/" + effectivePattern));
+        // Java NIO requires a path separator for patterns that start with "**/", so the
+        // recursive matcher does not match files located directly in the search root. Strip the
+        // recursive prefix for a second matcher that covers those root-level files.
+        String directPattern;
+        if (effectivePattern.startsWith("**/")) {
+            directPattern = effectivePattern.substring(3);
+        } else if (effectivePattern.equals("**")) {
+            directPattern = "*";
+        } else {
+            directPattern = effectivePattern;
+        }
         PathMatcher directMatcher =
-                FileSystems.getDefault().getPathMatcher("glob:" + effectivePattern);
+                FileSystems.getDefault().getPathMatcher("glob:" + directPattern);
 
         // Fast path: index has entries for this prefix
         if (index != null && index.hasPrefix(normalizedPath)) {


Test patch:
diff --git a/agentscope-harness/src/test/java/io/agentscope/harness/agent/filesystem/FilesystemGlobTest.java b/agentscope-harness/src/test/java/io/agentscope/harness/agent/filesystem/FilesystemGlobTest.java
index 7121d67c01..622385fd25 100644
--- a/agentscope-harness/src/test/java/io/agentscope/harness/agent/filesystem/FilesystemGlobTest.java
+++ b/agentscope-harness/src/test/java/io/agentscope/harness/agent/filesystem/FilesystemGlobTest.java
@@ -76,4 +76,49 @@ void remote_glob_plainPattern_matchesFilesInSearchRootAndSubdirectories() {
                 Set.of("/agents/demo-session.log.jsonl", "/agents/sub/demo-session.log.jsonl"),
                 paths);
     }
+
+    @Test
+    void remote_glob_recursivePattern_matchesFilesInSearchRootAndSubdirectories() {
+        InMemoryStore store = new InMemoryStore();
+        List<String> ns = List.of("test-ns");
+        store.put(ns, "/hello.txt", Map.of("content", "root file"));
+        store.put(ns, "/reports/hello.txt", Map.of("content", "nested file"));
+        store.put(ns, "/hello.md", Map.of("content", "different extension"));
+
+        RemoteFilesystem fs = new RemoteFilesystem(store, ns);
+        GlobResult result = fs.glob(RT, "**/hello.txt", "/");
+
+        assertTrue(result.isSuccess());
+        Set<String> paths =
+                result.matches().stream()
+                        .map(fi -> fi.path().replace('\\', '/'))
+                        .collect(Collectors.toSet());
+        assertEquals(Set.of("/hello.txt", "/reports/hello.txt"), paths);
+
+        GlobResult wildcardResult = fs.glob(RT, "**/*.txt", "/");
+        assertTrue(wildcardResult.isSuccess());
+        Set<String> wildcardPaths =
+                wildcardResult.matches().stream()
+                        .map(fi -> fi.path().replace('\\', '/'))
+                        .collect(Collectors.toSet());
+        assertEquals(Set.of("/hello.txt", "/reports/hello.txt"), wildcardPaths);
+    }
+
+    @Test
+    void remote_glob_doubleStar_matchesFilesInSearchRootAndSubdirectories() {
+        InMemoryStore store = new InMemoryStore();
+        List<String> ns = List.of("test-ns");
+        store.put(ns, "/root.txt", Map.of("content", "root file"));
+        store.put(ns, "/reports/nested.txt", Map.of("content", "nested file"));
+
+        RemoteFilesystem fs = new RemoteFilesystem(store, ns);
+        GlobResult result = fs.glob(RT, "**", "/");
+
+        assertTrue(result.isSuccess());
+        Set<String> paths =
+                result.matches().stream()
+                        .map(fi -> fi.path().replace('\\', '/'))
+                        .collect(Collectors.toSet());
+        assertEquals(Set.of("/root.txt", "/reports/nested.txt"), paths);
+    }
 }