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 <linked_folder> 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 <noreply@anthropic.com>
This commit is contained in:
@@ -454,10 +454,11 @@ async def _handle_task_brief_request(
|
|||||||
request_id = frame.get("request_id") or str(uuid4())
|
request_id = frame.get("request_id") or str(uuid4())
|
||||||
session_id = frame.get("session_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 ""
|
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(
|
logger.info(
|
||||||
"device_ws: task_brief_request_start user=%s req=%s task=%s [cache_miss]",
|
"device_ws: task_brief_request_start user=%s req=%s task=%s project=%s [cache_miss]",
|
||||||
user_id, request_id, task_id,
|
user_id, request_id, task_id, project_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
if not task_id:
|
if not task_id:
|
||||||
@@ -486,7 +487,7 @@ async def _handle_task_brief_request(
|
|||||||
response_chunks: list[str] = []
|
response_chunks: list[str] = []
|
||||||
|
|
||||||
try:
|
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)
|
formatter = StreamFormatter(request_id=request_id)
|
||||||
async for ws_frame in formatter.format(event_stream):
|
async for ws_frame in formatter.format(event_stream):
|
||||||
if ws_frame.type == "stream_text": # type: ignore[union-attr]
|
if ws_frame.type == "stream_text": # type: ignore[union-attr]
|
||||||
|
|||||||
@@ -95,6 +95,21 @@ def format_folder_manifest(manifest: dict | None) -> str:
|
|||||||
return header + body + "</linked_folder>"
|
return header + body + "</linked_folder>"
|
||||||
|
|
||||||
|
|
||||||
|
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:
|
def _datetime_context_injection(context: dict[str, Any]) -> str:
|
||||||
"""Build a comprehensive DATE CONTEXT block with pre-computed ms-epoch boundaries for common ranges."""
|
"""Build a comprehensive DATE CONTEXT block with pre-computed ms-epoch boundaries for common ranges."""
|
||||||
fp = context.get("format_prefs")
|
fp = context.get("format_prefs")
|
||||||
@@ -1456,6 +1471,7 @@ async def run_task_brief_research_stream(
|
|||||||
user_id: str,
|
user_id: str,
|
||||||
task_id: str,
|
task_id: str,
|
||||||
context: dict[str, Any],
|
context: dict[str, Any],
|
||||||
|
project_id: str | None = None,
|
||||||
) -> AsyncGenerator[tuple[str, Any], None]:
|
) -> AsyncGenerator[tuple[str, Any], None]:
|
||||||
"""Stage-1 executive assistant: deep research for one task.
|
"""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 ``<canvas kind="...">...</canvas>`` block
|
The final concatenated text may contain a ``<canvas kind="...">...</canvas>`` block
|
||||||
which the WS handler strips and emits as a ``canvas_draft`` mutation.
|
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)
|
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.
|
# Inject task_id so the agent knows what to look up first.
|
||||||
research_message = (
|
research_message = (
|
||||||
@@ -1481,6 +1499,12 @@ async def run_task_brief_research_stream(
|
|||||||
prepared_context,
|
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(
|
async for event in _run_single_agent_stream(
|
||||||
user_id=user_id,
|
user_id=user_id,
|
||||||
system_prompt=system_prompt,
|
system_prompt=system_prompt,
|
||||||
|
|||||||
Reference in New Issue
Block a user