Skip to content

Commit 18c0aac

Browse files
authored
feat: Allow finishing subtasks with a specified state and outcome. (#199)
1 parent 17e963b commit 18c0aac

3 files changed

Lines changed: 90 additions & 19 deletions

File tree

agentscope-core/src/main/java/io/agentscope/core/plan/model/SubTask.java

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,19 @@
2222
/**
2323
* Represents a subtask within a plan.
2424
*
25-
* <p>A subtask is a unit of work with a specific goal and expected outcome. It has a state that
25+
* <p>
26+
* A subtask is a unit of work with a specific goal and expected outcome. It has
27+
* a state that
2628
* tracks its progress through the execution lifecycle.
2729
*
28-
* <p><b>Usage Example:</b>
30+
* <p>
31+
* <b>Usage Example:</b>
2932
*
3033
* <pre>{@code
3134
* SubTask task = new SubTask(
32-
* "Setup project",
33-
* "Initialize project structure with proper directory layout",
34-
* "Project scaffolding completed"
35-
* );
35+
* "Setup project",
36+
* "Initialize project structure with proper directory layout",
37+
* "Project scaffolding completed");
3638
*
3739
* task.setState(SubTaskState.IN_PROGRESS);
3840
* // ... execute task
@@ -63,8 +65,10 @@ public SubTask() {
6365
/**
6466
* Create a new subtask.
6567
*
66-
* @param name The subtask name (should be concise, not exceed 10 words)
67-
* @param description The detailed description including constraints and targets
68+
* @param name The subtask name (should be concise, not exceed 10
69+
* words)
70+
* @param description The detailed description including constraints and
71+
* targets
6872
* @param expectedOutcome The expected outcome, specific and measurable
6973
*/
7074
public SubTask(String name, String description, String expectedOutcome) {
@@ -80,7 +84,23 @@ public SubTask(String name, String description, String expectedOutcome) {
8084
* @param outcome The actual outcome achieved
8185
*/
8286
public void finish(String outcome) {
83-
this.state = SubTaskState.DONE;
87+
finish(SubTaskState.DONE, outcome);
88+
}
89+
90+
/**
91+
* Mark the subtask as finished with a specific state and outcome.
92+
*
93+
* @param state The final state (e.g., DONE or ABANDONED)
94+
* @param outcome The actual outcome or reason for abandoning
95+
* @throws IllegalArgumentException if the state is not a terminal state (DONE
96+
* or ABANDONED)
97+
*/
98+
public void finish(SubTaskState state, String outcome) {
99+
if (state != SubTaskState.DONE && state != SubTaskState.ABANDONED) {
100+
throw new IllegalArgumentException(
101+
"SubTask can only be finished with DONE or ABANDONED state, but got: " + state);
102+
}
103+
this.state = state;
84104
this.outcome = outcome;
85105
this.finishedAt = ZonedDateTime.now().format(FORMATTER);
86106
}
@@ -98,6 +118,7 @@ public String toOneLineMarkdown() {
98118
case DONE -> "- [x]";
99119
case ABANDONED -> "- [ ] [Abandoned]";
100120
};
121+
101122
String displayName = (name != null) ? name : "Unnamed Subtask";
102123
return statusPrefix + " " + displayName;
103124
}

agentscope-core/src/test/java/io/agentscope/core/model/transport/OkHttpTransportTest.java

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -266,24 +266,26 @@ void testHttpTransportException() {
266266
}
267267

268268
@Test
269-
void testConnectionTimeout() throws Exception {
270-
// Create a transport with very short timeout
271-
HttpTransportConfig config =
272-
HttpTransportConfig.builder().connectTimeout(Duration.ofMillis(1)).build();
273-
OkHttpTransport shortTimeoutTransport = new OkHttpTransport(config);
269+
void testConnectionRefused() throws Exception {
270+
// Shutdown the mock server to ensure connection failure
271+
mockServer.shutdown();
272+
int port = mockServer.getPort();
273+
274+
HttpTransportConfig config = HttpTransportConfig.defaults();
275+
OkHttpTransport myTransport = new OkHttpTransport(config);
274276

275277
try {
276-
// Use a non-routable IP to trigger connection timeout
277278
HttpRequest request =
278279
HttpRequest.builder()
279-
.url("http://10.255.255.1:12345/timeout")
280+
// Use localhost to avoid network issues and ensure immediate connection
281+
// refused
282+
.url("http://localhost:" + port + "/timeout")
280283
.method("GET")
281284
.build();
282285

283-
assertThrows(
284-
HttpTransportException.class, () -> shortTimeoutTransport.execute(request));
286+
assertThrows(HttpTransportException.class, () -> myTransport.execute(request));
285287
} finally {
286-
shortTimeoutTransport.close();
288+
myTransport.close();
287289
}
288290
}
289291

@@ -299,6 +301,8 @@ void testOkHttpTransportBuilder() {
299301

300302
assertNotNull(builtTransport);
301303
assertNotNull(builtTransport.getClient());
304+
assertEquals(10000, builtTransport.getClient().connectTimeoutMillis());
305+
assertEquals(30000, builtTransport.getClient().readTimeoutMillis());
302306
assertEquals(config, builtTransport.getConfig());
303307
builtTransport.close();
304308
}

agentscope-core/src/test/java/io/agentscope/core/plan/model/SubTaskTest.java

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import static org.junit.jupiter.api.Assertions.assertEquals;
1919
import static org.junit.jupiter.api.Assertions.assertNotNull;
2020
import static org.junit.jupiter.api.Assertions.assertNull;
21+
import static org.junit.jupiter.api.Assertions.assertThrows;
2122
import static org.junit.jupiter.api.Assertions.assertTrue;
2223

2324
import org.junit.jupiter.api.Test;
@@ -57,6 +58,51 @@ void testFinishSubTask() {
5758
assertNotNull(task.getFinishedAt());
5859
}
5960

61+
@Test
62+
void testFinishSubTaskWithState() {
63+
SubTask task = new SubTask("Task", "Desc", "Expected");
64+
65+
task.finish(SubTaskState.ABANDONED, "Given up");
66+
67+
assertEquals(SubTaskState.ABANDONED, task.getState());
68+
assertEquals("Given up", task.getOutcome());
69+
assertNotNull(task.getFinishedAt());
70+
}
71+
72+
@Test
73+
void testFinishSubTaskWithDoneState() {
74+
SubTask task = new SubTask("Task", "Desc", "Expected");
75+
76+
task.finish(SubTaskState.DONE, "Success");
77+
78+
assertEquals(SubTaskState.DONE, task.getState());
79+
assertEquals("Success", task.getOutcome());
80+
assertNotNull(task.getFinishedAt());
81+
}
82+
83+
@Test
84+
void testFinishSubTaskWithInvalidState() {
85+
SubTask task = new SubTask("Task", "Desc", "Expected");
86+
87+
// Should throw exception when attempting to finish with TODO
88+
Exception exception =
89+
assertThrows(
90+
IllegalArgumentException.class,
91+
() -> {
92+
task.finish(SubTaskState.TODO, "Not actually done");
93+
});
94+
assertTrue(exception.getMessage().contains("TODO"));
95+
96+
// Should throw exception when attempting to finish with IN_PROGRESS
97+
exception =
98+
assertThrows(
99+
IllegalArgumentException.class,
100+
() -> {
101+
task.finish(SubTaskState.IN_PROGRESS, "Still working");
102+
});
103+
assertTrue(exception.getMessage().contains("IN_PROGRESS"));
104+
}
105+
60106
@Test
61107
void testToOneLineMarkdown_TodoState() {
62108
SubTask task = new SubTask("Task1", "Desc", "Expected");

0 commit comments

Comments
 (0)