From 7113d852455f7c514a9b8ab254e4eb8c550c96ef Mon Sep 17 00:00:00 2001 From: alkinun Date: Sat, 18 Jul 2026 12:54:38 +0300 Subject: [PATCH] Studio: address Deep Research lifecycle review --- studio/backend/core/research_runs.py | 2 + studio/backend/storage/research_runs_db.py | 1 + .../tests/test_research_runs_storage.py | 37 +++++++++++++++++++ .../src/features/chat/api/chat-adapter.ts | 4 +- .../test_deep_research_frontend_contract.py | 1 + 5 files changed, 44 insertions(+), 1 deletion(-) diff --git a/studio/backend/core/research_runs.py b/studio/backend/core/research_runs.py index 16fd1ae3b0..636196f473 100644 --- a/studio/backend/core/research_runs.py +++ b/studio/backend/core/research_runs.py @@ -385,6 +385,8 @@ class ResearchSupervisor: self._stopping.set() try: if self._task is not None: + for cancel_event in self._cancel_events.values(): + cancel_event.set() self._task.cancel() try: await self._task diff --git a/studio/backend/storage/research_runs_db.py b/studio/backend/storage/research_runs_db.py index 52010141ff..874511defc 100644 --- a/studio/backend/storage/research_runs_db.py +++ b/studio/backend/storage/research_runs_db.py @@ -865,6 +865,7 @@ def reset_execution_steps(run_id: str, worker_id: str | None = None) -> bool: conn.commit() return False conn.execute("DELETE FROM research_plan_steps WHERE run_id = ?", (run_id,)) + conn.execute("DELETE FROM research_sources WHERE run_id = ?", (run_id,)) conn.commit() return True except Exception: diff --git a/studio/backend/tests/test_research_runs_storage.py b/studio/backend/tests/test_research_runs_storage.py index 36d7660bbc..b89a4e80af 100644 --- a/studio/backend/tests/test_research_runs_storage.py +++ b/studio/backend/tests/test_research_runs_storage.py @@ -448,6 +448,43 @@ def test_recovery_releases_expired_leases(research_home, status): assert claimed["status"] == expected +def test_execution_reset_clears_steps_and_sources(research_home): + _create() + plan = research_db.set_plan("run-1", _plan()) + research_db.approve("run-1", plan["planRevision"], plan["planHash"]) + research_db.claim_next("worker-1") + research_db.upsert_execution_step( + "run-1", 0, "Old step", "old query", "completed", worker_id = "worker-1" + ) + research_db.upsert_source("run-1", 0, "https://old.example", "Old", "Stale", "worker-1") + + assert research_db.reset_execution_steps("run-1", "worker-1") is True + run = research_db.get_run("run-1") + assert run["steps"] == [] + assert run["sources"] == [] + + +def test_supervisor_stop_signals_tool_cancellation_before_task_cancelled(research_home): + from core.research_runs import ResearchSupervisor + async def scenario(): + supervisor = ResearchSupervisor(SimpleNamespace(state = SimpleNamespace())) + cancel_event = supervisor._cancel_event("run-1") + + async def active_run(): + try: + await asyncio.Event().wait() + except asyncio.CancelledError: + assert cancel_event.is_set() + raise + + supervisor._task = asyncio.create_task(active_run()) + await asyncio.sleep(0) + await supervisor.stop() + assert cancel_event.is_set() + + asyncio.run(scenario()) + + def test_sources_are_normalized_by_url(research_home): _create() research_db.upsert_source("run-1", 0, "https://example.com/a", "Old", "one") diff --git a/studio/frontend/src/features/chat/api/chat-adapter.ts b/studio/frontend/src/features/chat/api/chat-adapter.ts index e2b16e9aa4..74a652f1fe 100644 --- a/studio/frontend/src/features/chat/api/chat-adapter.ts +++ b/studio/frontend/src/features/chat/api/chat-adapter.ts @@ -2036,7 +2036,9 @@ export function createOpenAIStreamAdapter( autoinject_min_score: runtime.ragAutoInjectMinScore, } : { - thread_id: resolvedThreadId, + ...(runtime.ragEnabled + ? { thread_id: resolvedThreadId } + : {}), ...(researchProjectId ? { project_id: researchProjectId } : {}), diff --git a/tests/studio/test_deep_research_frontend_contract.py b/tests/studio/test_deep_research_frontend_contract.py index 646f8effe7..6b5d7ddfb0 100644 --- a/tests/studio/test_deep_research_frontend_contract.py +++ b/tests/studio/test_deep_research_frontend_contract.py @@ -59,6 +59,7 @@ def test_research_mode_is_single_chat_and_detaches_without_cancel() -> None: assert "signal: researchFollowController.signal" in adapter assert "beginExternalResearchFollow(" in adapter assert "ragScope" in adapter + assert "runtime.ragEnabled\n ? { thread_id: resolvedThreadId }" in adapter create_block = adapter.split("createdRun = await createResearchRun({", 1)[1].split("});", 1)[0] assert "modelId:" not in create_block assert "prompt," not in create_block