fix(todo): keep active step ahead of pending rows
This commit is contained in:
@@ -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):
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user