worker: track work as natural task steps

This commit is contained in:
2026-08-14 14:23:58 +09:00
parent 93eea24420
commit ecbd003579
8 changed files with 171 additions and 124 deletions
+3 -1
View File
@@ -3539,7 +3539,9 @@ mod completion_flow_tests {
arguments: args.into(),
});
}
assert!(app.task_store.tasks().is_empty());
let tasks = app.task_store.tasks();
assert_eq!(tasks.len(), 1);
assert_eq!(tasks[0].status, crate::task::TaskStatus::Completed);
}
#[test]
+24 -19
View File
@@ -28,8 +28,8 @@ pub enum TaskStatus {
}
impl TaskStatus {
fn is_active(self) -> bool {
matches!(self, Self::Pending | Self::Inprogress)
fn is_retained(self) -> bool {
!matches!(self, Self::Deleted)
}
}
@@ -125,7 +125,7 @@ impl TaskStore {
if let Some(d) = p.description {
t.description = d;
}
if !t.status.is_active() {
if !t.status.is_retained() {
self.tasks.remove(task_position);
}
}
@@ -145,7 +145,7 @@ impl TaskStore {
fn replace_with(&mut self, tasks: Vec<TaskEntry>) {
let tasks: Vec<_> = tasks
.into_iter()
.filter(|task| task.status.is_active())
.filter(|task| task.status.is_retained())
.collect();
self.next_taskid = tasks
.iter()
@@ -194,7 +194,7 @@ fn parse_snapshot_text(text: &str) -> Option<Vec<TaskEntry>> {
snapshot
.tasks
.into_iter()
.filter(|task| task.status.is_active())
.filter(|task| task.status.is_retained())
.collect(),
)
}
@@ -241,7 +241,7 @@ mod tests {
}
#[test]
fn counts_tracks_only_active_tasks_after_completion() {
fn counts_retains_completed_tasks_until_deleted() {
let mut s = TaskStore::new();
s.apply_tool_call("TaskCreate", r#"{"subject":"a","description":""}"#);
s.apply_tool_call("TaskCreate", r#"{"subject":"b","description":""}"#);
@@ -251,10 +251,13 @@ mod tests {
let c = s.counts();
assert_eq!(c.pending, 1);
assert_eq!(c.inprogress, 1);
assert_eq!(c.completed, 0);
assert_eq!(c.completed, 1);
assert_eq!(c.deleted, 0);
assert_eq!(c.total(), 2);
assert_eq!(c.total(), 3);
assert_eq!(c.active(), 2);
s.apply_tool_call("TaskUpdate", r#"{"taskid":2,"status":"deleted"}"#);
assert!(s.tasks().iter().all(|task| task.taskid != 2));
}
/// Snapshot text matches the wrapping `Worker::try_pre_run_compact` and the
@@ -263,7 +266,7 @@ mod tests {
fn wrap_snapshot(json_body: &str, overview: &str) -> String {
format!(
"[Session TaskStore snapshot]\n\n{overview}\n\n```json\n{json_body}\n```\n\n\
This is the active session task list preserved across compaction. \
This is the retained session task list preserved across compaction. \
The following TaskList tool result presents the same state through the tool lane."
)
}
@@ -288,19 +291,21 @@ mod tests {
}"#;
let text = wrap_snapshot(
body,
"TaskStore: 1 active task(s) (pending: 1, inprogress: 0)",
"TaskStore: 2 task(s) (pending: 1, inprogress: 0, completed: 1)",
);
let mut s = TaskStore::new();
s.apply_tool_call("TaskCreate", r#"{"subject":"stale","description":""}"#);
s.apply_system_message_text(&text);
let tasks = s.tasks();
assert_eq!(tasks.len(), 1);
assert_eq!(tasks[0].taskid, 7);
assert_eq!(tasks[0].status, TaskStatus::Pending);
// Subsequent TaskCreate must continue beyond the highest active taskid
assert_eq!(tasks.len(), 2);
assert_eq!(tasks[0].taskid, 5);
assert_eq!(tasks[0].status, TaskStatus::Completed);
assert_eq!(tasks[1].taskid, 7);
assert_eq!(tasks[1].status, TaskStatus::Pending);
// Subsequent TaskCreate must continue beyond the highest retained taskid
// observed in the snapshot.
s.apply_tool_call("TaskCreate", r#"{"subject":"new","description":""}"#);
assert_eq!(s.tasks()[1].taskid, 8);
assert_eq!(s.tasks()[2].taskid, 8);
}
#[test]
@@ -324,7 +329,7 @@ mod tests {
}
]
}"#;
let text = wrap_snapshot(body, "TaskStore: 1 active task(s)");
let text = wrap_snapshot(body, "TaskStore: 1 task(s)");
let mut s = TaskStore::new();
s.apply_system_message_text(&text);
let t = &s.tasks()[0];
@@ -349,13 +354,13 @@ mod snapshot_format_contract {
fn wrap_pod_style(snapshot_text: &str) -> String {
format!(
"[Session TaskStore snapshot]\n\n{snapshot_text}\n\n\
This is the active session task list preserved across compaction. \
This is the retained session task list preserved across compaction. \
The following TaskList tool result presents the same state through the tool lane."
)
}
fn snapshot_fixture() -> &'static str {
r#"TaskStore: 1 active task(s) (pending: 0, inprogress: 1)
r#"TaskStore: 1 task(s) (pending: 0, inprogress: 1, completed: 0)
```json
{
@@ -372,7 +377,7 @@ mod snapshot_format_contract {
}
fn empty_snapshot_fixture() -> &'static str {
r#"TaskStore: 0 active task(s) (pending: 0, inprogress: 0)
r#"TaskStore: 0 task(s) (pending: 0, inprogress: 0, completed: 0)
```json
{