fix: background tasks now fetch real page content; add task-status tools
Rebuild & Deploy / rebuild (push) Successful in 58s

Background research tasks were producing garbage or failing because the
self-hosted Firecrawl /v1/scrape defaults to a ~30s timeout that's too short
for heavy news/research sites, and the small LLM won't reliably chain a
second scrape call after a search that only returns snippets.

task_worker.py:
- web_search now auto-fetches the top results directly from the container
  (which has working outbound internet) and returns their real text, so the
  model gets actual page content in one call instead of snippet echoes.
- web_scrape requests timeout: 90000 (Firecrawl renders heavy sites but needs
  >30s), and falls back to a direct container fetch with tag-stripping if
  Firecrawl times out or returns nothing.

task_registry.py / dispatch_mcp.py:
- Add task_status(id) and list_tasks() tools so Hope can check the live state
  of any dispatched task (pending / running / completed / failed / STALLED).
- Track last_activity per task and surface a stall heuristic.
- Move the status summary into task_registry (no mcp dependency) so it's
  unit-testable.

agent.py: prompt tells Hope about task_status / list_tasks.
tests: add task-status summary and last-activity regression tests.
This commit is contained in:
snowspeeder committed 2026-08-30 06:54:57 -04:00
1 parent df8aa64504
commit d4ea4fc984
5 files changed
+217 -30

No files matched your search

+31
View File
@@ -48,6 +48,7 @@ class Task:
error: str | None = None
created_at: float = field(default_factory=time.time)
completed_at: float | None = None
last_activity: float = field(default_factory=time.time)
class TaskRegistry:
@@ -78,6 +79,7 @@ class TaskRegistry:
task = self._tasks.get(task_id)
if task:
task.steps.append(TaskStep(role=role, content=content))
task.last_activity = time.time()
if task.status == TaskStatus.PENDING:
task.status = TaskStatus.RUNNING
self._save_task(task)
@@ -163,8 +165,37 @@ class TaskRegistry:
"error": task.error,
"created_at": task.created_at,
"completed_at": task.completed_at,
"last_activity": task.last_activity,
}
# Global singleton — one per process
registry = TaskRegistry()
def status_summary(task_id: str, stalled_s: float = 45.0) -> str:
"""Human-readable live status for one task, plus a stall hint.
Kept on the registry module (no MCP dependency) so it can be reused and
unit-tested; the dispatch MCP's task_status/list_tasks tools call this.
"""
t = registry.get(task_id)
if not t:
return f"Task {task_id} was not found."
status = t.get("status", "unknown")
steps = t.get("steps") or []
created = t.get("created_at") or 0
last = t.get("last_activity") or created
elapsed = time.time() - created
idle = time.time() - last
parts = [f"Task {task_id} ({t.get('description', '')[:60]}): {status.upper()}"]
parts.append(f"elapsed {elapsed:,.0f}s, {len(steps)} step(s)")
if idle > 0.5:
parts.append(f"last activity {idle:,.0f}s ago")
if status in ("pending", "running") and idle > stalled_s:
parts.append(f"STALLED (no activity for {idle:,.0f}s)")
if t.get("error"):
parts.append(f"error: {t['error']}")
if t.get("result"):
parts.append(f"result: {t['result'][:240]}")
return ". ".join(parts)