Skip to content

Commit 54f6b8d

Browse files
authored
Merge branch 'agentscope-ai:main' into fix/unstable-computehash
2 parents d9ab32c + a17033a commit 54f6b8d

10 files changed

Lines changed: 151 additions & 26 deletions

File tree

agentscope-core/src/main/java/io/agentscope/core/plan/PlanNotebook.java

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -853,18 +853,13 @@ public Mono<String> finishPlan(
853853

854854
currentPlan.finish(state, outcome);
855855

856+
String message =
857+
String.format("The current plan is finished successfully as '%s'.", stateStr);
858+
856859
return storage.addPlan(currentPlan)
857-
.then(
858-
Mono.defer(
859-
() -> {
860-
currentPlan = null;
861-
return triggerPlanChangeHooks()
862-
.thenReturn(
863-
String.format(
864-
"The current plan is finished"
865-
+ " successfully as '%s'.",
866-
stateStr));
867-
}));
860+
.then(triggerPlanChangeHooks())
861+
.then(Mono.fromRunnable(() -> currentPlan = null))
862+
.thenReturn(message);
868863
}
869864

870865
/** View the historical plans. */

agentscope-core/src/main/java/io/agentscope/core/tool/ToolExecutor.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,8 @@ private Mono<ToolResultBlock> executeWithInfrastructure(
336336
logger.warn("Tool call failed: {}", toolCall.getName(), e);
337337
String errorMsg = ExceptionUtils.getErrorMessage(e);
338338
return Mono.just(
339-
ToolResultBlock.error("Tool execution failed: " + errorMsg));
339+
ToolResultBlock.error("Tool execution failed: " + errorMsg)
340+
.withIdAndName(toolCall.getId(), toolCall.getName()));
340341
});
341342
}
342343

agentscope-core/src/test/java/io/agentscope/core/plan/PlanNotebookToolTest.java

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -806,4 +806,61 @@ void testMultipleHooksAndIdOverwrite() {
806806
assertEquals(1, count2[0], "hook2 should be called");
807807
assertEquals(1, count3[0], "New hook1 should be called");
808808
}
809+
810+
@Test
811+
void testChangeHookReceivesNonNullPlanOnFinishPlan() {
812+
Plan[] capturedPlan = {null};
813+
notebook.addChangeHook("finishHook", (nb, plan) -> capturedPlan[0] = plan);
814+
815+
List<SubTask> subtasks = List.of(new SubTask("Task1", "Desc1", "Outcome1"));
816+
notebook.createPlanWithSubTasks("Plan", "Desc", "Outcome", subtasks).block();
817+
818+
notebook.finishPlan("done", "All done").block();
819+
820+
assertNotNull(capturedPlan[0], "Hook should receive the finished plan, not null");
821+
assertEquals("Plan", capturedPlan[0].getName());
822+
assertEquals(PlanState.DONE, capturedPlan[0].getState());
823+
}
824+
825+
@Test
826+
void testChangeHookReceivesNonNullPlanOnFinishPlanAbandoned() {
827+
Plan[] capturedPlan = {null};
828+
notebook.addChangeHook("abandonHook", (nb, plan) -> capturedPlan[0] = plan);
829+
830+
List<SubTask> subtasks = List.of(new SubTask("Task1", "Desc1", "Outcome1"));
831+
notebook.createPlanWithSubTasks("Plan", "Desc", "Outcome", subtasks).block();
832+
833+
notebook.finishPlan("abandoned", "Not needed").block();
834+
835+
assertNotNull(capturedPlan[0], "Hook should receive the abandoned plan, not null");
836+
assertEquals(PlanState.ABANDONED, capturedPlan[0].getState());
837+
}
838+
839+
@Test
840+
void testGetCurrentPlanVisibleDuringFinishPlanHook() {
841+
Plan[] capturedViaGetter = {null};
842+
notebook.addChangeHook(
843+
"getterHook", (nb, plan) -> capturedViaGetter[0] = nb.getCurrentPlan());
844+
845+
List<SubTask> subtasks = List.of(new SubTask("Task1", "Desc1", "Outcome1"));
846+
notebook.createPlanWithSubTasks("Plan", "Desc", "Outcome", subtasks).block();
847+
848+
notebook.finishPlan("done", "Done").block();
849+
850+
assertNotNull(
851+
capturedViaGetter[0],
852+
"getCurrentPlan() should return the plan during hook execution");
853+
assertEquals("Plan", capturedViaGetter[0].getName());
854+
}
855+
856+
@Test
857+
void testCurrentPlanIsNullAfterFinishPlanCompletes() {
858+
List<SubTask> subtasks = List.of(new SubTask("Task1", "Desc1", "Outcome1"));
859+
notebook.createPlanWithSubTasks("Plan", "Desc", "Outcome", subtasks).block();
860+
861+
notebook.finishPlan("done", "Done").block();
862+
863+
assertNull(
864+
notebook.getCurrentPlan(), "currentPlan should be null after finishPlan completes");
865+
}
809866
}

agentscope-core/src/test/java/io/agentscope/core/tool/coding/ShellCommandToolTest.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -873,10 +873,17 @@ void reproduceLargeFileCatDeadlock() throws Exception {
873873
@Test
874874
@DisplayName("Should handle pre-created large test file without deadlock")
875875
@EnabledOnOs({OS.LINUX, OS.MAC})
876-
void reproduceLargeFileCatDeadlockWithTestResource() {
876+
void reproduceLargeFileCatDeadlockWithTestResource() throws Exception {
877877
// Use the pre-created test resource file (20KB)
878+
// Use Paths.get(url.toURI()) instead of url.getPath() to properly decode
879+
// URL-encoded characters (e.g. Chinese characters in project path)
878880
String resourcePath =
879-
getClass().getClassLoader().getResource("large_output_test.txt").getPath();
881+
Paths.get(
882+
getClass()
883+
.getClassLoader()
884+
.getResource("large_output_test.txt")
885+
.toURI())
886+
.toString();
880887
String command = "cat " + resourcePath;
881888

882889
// Set a reasonable timeout - should complete quickly with the fix

agentscope-examples/documentation/graceful-shutdown/pom.xml

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,6 @@
6767
<groupId>org.springframework.boot</groupId>
6868
<artifactId>spring-boot-starter-web</artifactId>
6969
</dependency>
70-
71-
<dependency>
72-
<groupId>org.projectlombok</groupId>
73-
<artifactId>lombok</artifactId>
74-
</dependency>
7570
</dependencies>
7671

7772
<build>

agentscope-examples/documentation/graceful-shutdown/src/main/java/io/agentscope/examples/shutdown/e2e/AgentService.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@
3636
import java.util.LinkedHashMap;
3737
import java.util.Map;
3838
import java.util.stream.Collectors;
39-
import lombok.extern.slf4j.Slf4j;
39+
import org.slf4j.Logger;
40+
import org.slf4j.LoggerFactory;
4041
import org.springframework.beans.factory.annotation.Value;
4142
import org.springframework.http.HttpStatus;
4243
import org.springframework.http.ResponseEntity;
@@ -45,10 +46,11 @@
4546
/**
4647
* Service for managing agent instances and chat operations.
4748
*/
48-
@Slf4j
4949
@Service
5050
public class AgentService {
5151

52+
private static final Logger log = LoggerFactory.getLogger(AgentService.class);
53+
5254
private static final String DATA_ANALYZE_SYS_PROMPT =
5355
"You are a data analysis assistant. "
5456
+ "When asked to analyze a dataset, follow these steps in order:\n"

agentscope-harness/src/main/java/io/agentscope/harness/agent/tool/FilesystemTool.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,13 @@ public String editFile(
8686
@ToolParam(name = "new_string", description = "Replacement text") String newString,
8787
@ToolParam(
8888
name = "replace_all",
89-
description = "Replace all occurrences (default: false)")
90-
boolean replaceAll) {
89+
description = "Replace all occurrences (default: false)",
90+
required = false)
91+
Boolean replaceAll) {
92+
boolean shouldReplaceAll = Boolean.TRUE.equals(replaceAll);
9193
EditResult r =
92-
abstractFilesystem.edit(runtimeContext, path, oldString, newString, replaceAll);
94+
abstractFilesystem.edit(
95+
runtimeContext, path, oldString, newString, shouldReplaceAll);
9396
return r.isSuccess()
9497
? "Edited " + r.path() + " (" + r.occurrences() + " replacement(s))"
9598
: "Error: " + r.error();
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright 2024-2026 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.agentscope.harness.agent.tool;
17+
18+
import static org.junit.jupiter.api.Assertions.assertTrue;
19+
import static org.mockito.ArgumentMatchers.eq;
20+
import static org.mockito.Mockito.mock;
21+
import static org.mockito.Mockito.verify;
22+
import static org.mockito.Mockito.when;
23+
24+
import io.agentscope.core.agent.RuntimeContext;
25+
import io.agentscope.harness.agent.filesystem.AbstractFilesystem;
26+
import io.agentscope.harness.agent.filesystem.model.EditResult;
27+
import org.junit.jupiter.api.BeforeEach;
28+
import org.junit.jupiter.api.Test;
29+
30+
/** Unit tests for {@link FilesystemTool}. */
31+
class FilesystemToolTest {
32+
33+
private static final RuntimeContext RT = RuntimeContext.empty();
34+
35+
private AbstractFilesystem filesystem;
36+
private FilesystemTool tool;
37+
38+
@BeforeEach
39+
void setUp() {
40+
filesystem = mock(AbstractFilesystem.class);
41+
tool = new FilesystemTool(filesystem);
42+
}
43+
44+
@Test
45+
void editFile_omittedReplaceAll_defaultsToFalse() {
46+
when(filesystem.edit(eq(RT), eq("f.txt"), eq("old"), eq("new"), eq(false)))
47+
.thenReturn(EditResult.ok("f.txt", 1));
48+
49+
String result = tool.editFile(RT, "f.txt", "old", "new", null);
50+
51+
assertTrue(result.startsWith("Edited "));
52+
verify(filesystem).edit(RT, "f.txt", "old", "new", false);
53+
}
54+
55+
@Test
56+
void editFile_replaceAllTrue_passesTrueToFilesystem() {
57+
when(filesystem.edit(eq(RT), eq("f.txt"), eq("old"), eq("new"), eq(true)))
58+
.thenReturn(EditResult.ok("f.txt", 2));
59+
60+
String result = tool.editFile(RT, "f.txt", "old", "new", true);
61+
62+
assertTrue(result.contains("2 replacement"));
63+
verify(filesystem).edit(RT, "f.txt", "old", "new", true);
64+
}
65+
}

docs/en/task/model.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ DashScopeChatModel model = DashScopeChatModel.builder()
159159
DashScopeChatModel model = DashScopeChatModel.builder()
160160
.apiKey(System.getenv("DASHSCOPE_API_KEY"))
161161
.modelName("qwen3-max")
162-
.enableThinking(true) // Automatically enables streaming
162+
.enableThinking(true) // Enables thinking mode and automatically enables streaming
163163
.defaultOptions(GenerateOptions.builder()
164164
.thinkingBudget(5000) // Token budget for thinking
165165
.build())

docs/zh/task/model.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ DashScopeChatModel model = DashScopeChatModel.builder()
159159
DashScopeChatModel model = DashScopeChatModel.builder()
160160
.apiKey(System.getenv("DASHSCOPE_API_KEY"))
161161
.modelName("qwen3-max")
162-
.enableThinking(true) // 自动启用流式输出
162+
.enableThinking(true) // 启用思考模式,并自动启用流式输出
163163
.defaultOptions(GenerateOptions.builder()
164164
.thinkingBudget(5000) // 思考 token 预算
165165
.build())

0 commit comments

Comments
 (0)