From 56dbb7f4cd3ab64c6ddb14306f623d79b0b6681e Mon Sep 17 00:00:00 2001 From: Roberto Date: Tue, 12 May 2026 11:31:21 +0200 Subject: [PATCH] feat(api): inject folder manifest into task brief agent Add _fetch_project_manifest helper that calls read_project_folder_manifest via execute_on_client. Wire it into run_task_brief_research_stream (new optional project_id param) so the block is prepended to the system prompt when the task belongs to a linked project. Also bind FOLDER_TOOLS into the task-brief tool palette so the agent can read folder files. device_ws extracts project_id / projectId from the task_brief_request frame and forwards it. Co-Authored-By: Claude Sonnet 4.6 --- app/api/routes/device_ws.py | 7 ++++--- app/core/deep_agent.py | 26 +++++++++++++++++++++++++- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/app/api/routes/device_ws.py b/app/api/routes/device_ws.py index 878de4a..2f86e7e 100644 --- a/app/api/routes/device_ws.py +++ b/app/api/routes/device_ws.py @@ -454,10 +454,11 @@ async def _handle_task_brief_request( request_id = frame.get("request_id") or str(uuid4()) session_id = frame.get("session_id") or str(uuid4()) task_id: str = frame.get("task_id") or frame.get("taskId") or "" + project_id: str | None = frame.get("project_id") or frame.get("projectId") or None logger.info( - "device_ws: task_brief_request_start user=%s req=%s task=%s [cache_miss]", - user_id, request_id, task_id, + "device_ws: task_brief_request_start user=%s req=%s task=%s project=%s [cache_miss]", + user_id, request_id, task_id, project_id, ) if not task_id: @@ -486,7 +487,7 @@ async def _handle_task_brief_request( response_chunks: list[str] = [] try: - event_stream = run_task_brief_research_stream(user_id, task_id, context) + event_stream = run_task_brief_research_stream(user_id, task_id, context, project_id=project_id) formatter = StreamFormatter(request_id=request_id) async for ws_frame in formatter.format(event_stream): if ws_frame.type == "stream_text": # type: ignore[union-attr] diff --git a/app/core/deep_agent.py b/app/core/deep_agent.py index a36f8c2..f5c9fe4 100644 --- a/app/core/deep_agent.py +++ b/app/core/deep_agent.py @@ -95,6 +95,21 @@ def format_folder_manifest(manifest: dict | None) -> str: return header + body + "" +async def _fetch_project_manifest(project_id: str) -> dict | None: + """Fetch manifest from Electron via execute_on_client. Returns None if unlinked or error.""" + from app.core.ws_context import execute_on_client + try: + result = await execute_on_client( + action="read_project_folder_manifest", + data={"projectId": project_id}, + ) + if not result or not result.get("folderPath"): + return None + return result + except Exception: + return None + + def _datetime_context_injection(context: dict[str, Any]) -> str: """Build a comprehensive DATE CONTEXT block with pre-computed ms-epoch boundaries for common ranges.""" fp = context.get("format_prefs") @@ -1456,6 +1471,7 @@ async def run_task_brief_research_stream( user_id: str, task_id: str, context: dict[str, Any], + project_id: str | None = None, ) -> AsyncGenerator[tuple[str, Any], None]: """Stage-1 executive assistant: deep research for one task. @@ -1463,8 +1479,10 @@ async def run_task_brief_research_stream( The final concatenated text may contain a ``...`` block which the WS handler strips and emits as a ``canvas_draft`` mutation. """ + from app.agents.folder_agent import FOLDER_TOOLS + prepared_context = await _prepare_context(f"task:{task_id}", context) - tools = _brief_research_tools(user_id, _trace_id_from_context(prepared_context)) + tools = [*_brief_research_tools(user_id, _trace_id_from_context(prepared_context)), *FOLDER_TOOLS] # Inject task_id so the agent knows what to look up first. research_message = ( @@ -1481,6 +1499,12 @@ async def run_task_brief_research_stream( prepared_context, ) + manifest_block = "" + if project_id: + manifest = await _fetch_project_manifest(project_id) + manifest_block = format_folder_manifest(manifest) + system_prompt = system_prompt + ("\n\n" + manifest_block if manifest_block else "") + async for event in _run_single_agent_stream( user_id=user_id, system_prompt=system_prompt,