feat: background task dispatch system with live UI panel
- agent/task_registry.py: file-based JSONL event registry (cross-process) - agent/task_worker.py: autonomous LLM loop with weather/time/memory/web tools - agent/dispatch_mcp.py: MCP tool exposing dispatch_task to the main agent - agent/agent.py: registers dispatch toolset, polls task events → room data - web: slide-out task panel (FAB button + badge), live step streaming via data channel topic 'tasks', status dots (running/completed/failed) - Dockerfile: copies new task_*.py and dispatch_mcp.py files The dispatch MCP runs in its own process; events flow through /tmp/tasks/events.jsonl which the main agent tails every second and forwards to the browser. Tasks run up to 10 LLM iterations with tool calls.
This commit is contained in:
+14
-11
@@ -25,13 +25,16 @@ class TaskWorker:
|
||||
|
||||
async def run(self):
|
||||
from openai import AsyncOpenAI
|
||||
from agent.task_registry import registry as reg
|
||||
try:
|
||||
from agent.task_registry import registry as reg
|
||||
except ImportError:
|
||||
from task_registry import registry as reg
|
||||
|
||||
base_url = os.environ.get("GEMMA_BASE_URL", "http://192.168.86.2:8023/v1")
|
||||
model = os.environ.get("GEMMA_MODEL", "gemma-4-e4b")
|
||||
api_key = os.environ.get("GEMMA_API_KEY", "not-needed")
|
||||
|
||||
await reg.add_step(self.task_id, "thinking", f"Starting: {self.description}")
|
||||
reg.add_step(self.task_id, "thinking", f"Starting: {self.description}")
|
||||
|
||||
client = AsyncOpenAI(base_url=base_url, api_key=api_key)
|
||||
tools = self._load_tools()
|
||||
@@ -44,10 +47,10 @@ class TaskWorker:
|
||||
max_iterations = 10
|
||||
for iteration in range(max_iterations):
|
||||
if self._cancelled:
|
||||
await reg.fail(self.task_id, "Cancelled")
|
||||
reg.fail(self.task_id, "Cancelled")
|
||||
return
|
||||
|
||||
await reg.add_step(
|
||||
reg.add_step(
|
||||
self.task_id, "thinking", f"Step {iteration + 1}: analyzing..."
|
||||
)
|
||||
|
||||
@@ -61,7 +64,7 @@ class TaskWorker:
|
||||
extra_body={"chat_template_kwargs": {"enable_thinking": False}},
|
||||
)
|
||||
except Exception as e:
|
||||
await reg.fail(self.task_id, f"LLM error: {e}")
|
||||
reg.fail(self.task_id, f"LLM error: {e}")
|
||||
return
|
||||
|
||||
msg = response.choices[0].message
|
||||
@@ -74,13 +77,13 @@ class TaskWorker:
|
||||
tool_args = json.loads(tc.function.arguments)
|
||||
except json.JSONDecodeError:
|
||||
tool_args = {}
|
||||
await reg.add_step(
|
||||
reg.add_step(
|
||||
self.task_id,
|
||||
"tool_call",
|
||||
f"{tool_name}({json.dumps(tool_args)})",
|
||||
)
|
||||
result = await self._execute_tool(tool_name, tool_args)
|
||||
await reg.add_step(self.task_id, "tool_result", str(result)[:500])
|
||||
reg.add_step(self.task_id, "tool_result", str(result)[:500])
|
||||
messages.append(
|
||||
{
|
||||
"role": "tool",
|
||||
@@ -91,13 +94,13 @@ class TaskWorker:
|
||||
else:
|
||||
text = (msg.content or "").strip()
|
||||
if not text:
|
||||
await reg.fail(self.task_id, "LLM returned empty response")
|
||||
reg.fail(self.task_id, "LLM returned empty response")
|
||||
return
|
||||
await reg.add_step(self.task_id, "text", text)
|
||||
await reg.complete(self.task_id, text)
|
||||
reg.add_step(self.task_id, "text", text)
|
||||
reg.complete(self.task_id, text)
|
||||
return
|
||||
|
||||
await reg.fail(self.task_id, f"Reached max iterations ({max_iterations})")
|
||||
reg.fail(self.task_id, f"Reached max iterations ({max_iterations})")
|
||||
|
||||
def _system_prompt(self) -> str:
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user