fix(todo): keep active step ahead of pending rows

This commit is contained in:
LeonSGP43
2026-06-09 23:19:32 +08:00
committed by Teknium
parent 89fa829802
commit 0e7151ceae
2 changed files with 63 additions and 5 deletions

View File

@@ -14,8 +14,9 @@ class TestWriteAndRead:
]
result = store.write(items)
assert len(result) == 2
assert result[0]["id"] == "1"
assert result[1]["status"] == "in_progress"
assert result[0]["id"] == "2"
assert result[0]["status"] == "in_progress"
assert result[1]["id"] == "1"
def test_write_deduplicates_duplicate_ids(self):
@@ -26,8 +27,21 @@ class TestWriteAndRead:
{"id": "1", "content": "Latest version", "status": "in_progress"},
])
assert result == [
{"id": "2", "content": "Other task", "status": "pending"},
{"id": "1", "content": "Latest version", "status": "in_progress"},
{"id": "2", "content": "Other task", "status": "pending"},
]
def test_write_moves_active_item_before_earlier_pending_step(self):
store = TodoStore()
result = store.write([
{"id": "1", "content": "Already done", "status": "completed"},
{"id": "2", "content": "Verify freed space", "status": "pending"},
{"id": "3", "content": "Move archives to Trash", "status": "in_progress"},
])
assert result == [
{"id": "1", "content": "Already done", "status": "completed"},
{"id": "3", "content": "Move archives to Trash", "status": "in_progress"},
{"id": "2", "content": "Verify freed space", "status": "pending"},
]
@@ -91,6 +105,23 @@ class TestMergeMode:
items = store.read()
assert len(items) == 2
def test_merge_reorders_active_item_ahead_of_earlier_pending_step(self):
store = TodoStore()
store.write([
{"id": "1", "content": "Completed", "status": "completed"},
{"id": "2", "content": "Verify freed space", "status": "pending"},
{"id": "3", "content": "Move archives to Trash", "status": "pending"},
])
result = store.write(
[{"id": "3", "status": "in_progress"}],
merge=True,
)
assert result == [
{"id": "1", "content": "Completed", "status": "completed"},
{"id": "3", "content": "Move archives to Trash", "status": "in_progress"},
{"id": "2", "content": "Verify freed space", "status": "pending"},
]
class TestTodoToolFunction:
def test_read_mode(self):

View File

@@ -67,7 +67,9 @@ class TodoStore:
"""
if not merge:
# Replace mode: new list entirely
self._items = [self._validate(t) for t in self._dedupe_by_id(todos)]
self._items = self._normalize_order(
[self._validate(t) for t in self._dedupe_by_id(todos)]
)
else:
# Merge mode: update existing items by id, append new ones
existing = {item["id"]: item for item in self._items}
@@ -97,7 +99,7 @@ class TodoStore:
if current["id"] not in seen:
rebuilt.append(current)
seen.add(current["id"])
self._items = rebuilt
self._items = self._normalize_order(rebuilt)
# Bound total item count so a replayed/oversized list can't grow the
# re-injection block without limit. Keep the highest-priority head
# (list order is priority).
@@ -200,6 +202,31 @@ class TodoStore:
last_index[item_id] = i
return [todos[i] for i in sorted(last_index.values())]
@staticmethod
def _normalize_order(items: List[Dict[str, str]]) -> List[Dict[str, str]]:
"""Lift the active step ahead of any earlier unfinished placeholders."""
active_index = next(
(i for i, item in enumerate(items) if item["status"] == "in_progress"),
None,
)
if active_index is None:
return items
pending_index = next(
(
i for i, item in enumerate(items[:active_index])
if item["status"] == "pending"
),
None,
)
if pending_index is None:
return items
normalized = items.copy()
active_item = normalized.pop(active_index)
normalized.insert(pending_index, active_item)
return normalized
def todo_tool(
todos: Optional[List[Dict[str, Any]]] = None,