feat: release 1.1.0 with responsive task control
This commit is contained in:
1 parent
057f0c3050
commit
bfcd828fa8
37 files changed
+1601
-151
No files matched your search
@@ -1,5 +1,6 @@
|
||||
"""Regression coverage for quoted click targets and prose-only recovery loops."""
|
||||
"""Regression coverage for operator recovery and task pause handling."""
|
||||
|
||||
import asyncio
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import AsyncMock, Mock
|
||||
|
||||
@@ -8,6 +9,7 @@ import pytest
|
||||
|
||||
from artemis.agents.operator.operator import OperatorNode, OperatorNoProgressError
|
||||
from artemis.config.settings import settings
|
||||
from artemis.runtime import task_control
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -135,3 +137,68 @@ async def test_iteration_cap_cannot_restart_no_progress_next_turn(operator, monk
|
||||
with pytest.raises(OperatorNoProgressError, match="预算内未恢复"):
|
||||
await node._invoke_llm_loop(Mock(), [], [], [], state)
|
||||
assert invoke.await_count == 2
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_pause_during_diagnosis_returns_to_fresh_observation(operator, monkeypatch, tmp_path):
|
||||
node, state = operator
|
||||
monkeypatch.setattr(task_control, "TRACES_PATH", tmp_path)
|
||||
started = asyncio.Event()
|
||||
cancelled = asyncio.Event()
|
||||
events = []
|
||||
|
||||
class Lock:
|
||||
def __init__(self, device_id, **kwargs):
|
||||
self.device_id = device_id
|
||||
self.description = "test"
|
||||
self.max_concurrency = None
|
||||
self.concurrency_mode = "per_device"
|
||||
self.ingress = "web"
|
||||
self.lock_scope = "test"
|
||||
|
||||
def release(self):
|
||||
events.append("released")
|
||||
|
||||
def acquire(self, cancel_event=None):
|
||||
events.append("acquired")
|
||||
|
||||
monkeypatch.setattr(task_control, "DeviceExecutionLock", Lock)
|
||||
node.ctx.data_engine = None
|
||||
node.ctx.task_control = task_control.TaskControl("pro-task", Lock("device"))
|
||||
node.ctx.on_task_pause = AsyncMock()
|
||||
node.ctx.on_task_resume = AsyncMock()
|
||||
|
||||
async def diagnose(**kwargs):
|
||||
started.set()
|
||||
try:
|
||||
await asyncio.Event().wait()
|
||||
finally:
|
||||
cancelled.set()
|
||||
|
||||
monkeypatch.setattr("artemis.agents.operator.operator.invoke_tool_with_injection", diagnose)
|
||||
response = AIMessage(
|
||||
content="Inspect the failure",
|
||||
tool_calls=[
|
||||
{"name": "ask_diagnoser", "args": {"query": "why"}, "id": "diagnosis"},
|
||||
{"name": "click", "args": {"target": 13}, "id": "tap"},
|
||||
],
|
||||
)
|
||||
invoke = AsyncMock(return_value=response)
|
||||
monkeypatch.setattr("artemis.agents.operator.operator.acomplete", invoke)
|
||||
tool = SimpleNamespace(name="ask_diagnoser")
|
||||
messages = []
|
||||
execution = asyncio.create_task(node._invoke_llm_loop(Mock(), messages, [tool], [], state))
|
||||
await asyncio.wait_for(started.wait(), 1)
|
||||
task_control.request_pause("pro-task")
|
||||
for _ in range(40):
|
||||
if task_control.read_state("pro-task").get("status") == "task_paused":
|
||||
break
|
||||
await asyncio.sleep(0.01)
|
||||
assert task_control.read_state("pro-task")["status"] == "task_paused"
|
||||
assert cancelled.is_set()
|
||||
assert events == ["released"]
|
||||
task_control.request_resume("pro-task")
|
||||
assert (await asyncio.wait_for(execution, 2))[0] is None
|
||||
assert events == ["released", "acquired"]
|
||||
assert invoke.await_count == 1
|
||||
assert [m.status for m in messages if isinstance(m, ToolMessage)] == ["error", "error"]
|
||||
Reference in new issue
Block a user