diff --git a/dashboard/pages/skills.js b/dashboard/pages/skills.js index 621d7dc..42e719f 100644 --- a/dashboard/pages/skills.js +++ b/dashboard/pages/skills.js @@ -156,6 +156,7 @@ async function quickRunSkill(name) { + `, ` @@ -165,11 +166,32 @@ async function quickRunSkill(name) { async function executeSkillRun(name) { const input = document.getElementById('qrsInput').value; const agent = document.getElementById('qrsAgent').value; + const runBtn = document.querySelector('#modalContainer .btn-primary'); + const resultArea = document.getElementById('skillResult'); + + if (runBtn) { runBtn.disabled = true; runBtn.textContent = '⏳ Running...'; } + if (resultArea) { + resultArea.style.display = 'block'; + resultArea.innerHTML = '
Executing skill...
'; + } + try { const r = await api.runSkill(name, input, agent); - closeModal(); - showToast(`"${name}" dispatched to ${r.agent} #${r.run_id}`, 'success'); + if (resultArea) { + const outputText = r.output || '(no output)'; + resultArea.innerHTML = ` +
+
+ ✓ Completed — ${r.agent} #${r.run_id} +
+
${escapeHtml(outputText)}
+
`; + } + if (runBtn) { runBtn.textContent = '✓ Done'; runBtn.disabled = false; } } catch (err) { - showToast(`Error: ${err.message}`, 'error'); + if (resultArea) { + resultArea.innerHTML = `
Error
${escapeHtml(err.message)}
`; + } + if (runBtn) { runBtn.textContent = '▶ Run'; runBtn.disabled = false; } } } diff --git a/server.py b/server.py index 0eb6950..fbfc6f4 100644 --- a/server.py +++ b/server.py @@ -205,6 +205,10 @@ def run_skill(name: str, req: Optional[SkillRunRequest] = None): agent_choice = req.agent if req else "auto" skill_input = req.input if req else "" + # Read skill files + skill_md = read_file(path / "SKILL.md") + learnings = read_file(path / "learnings.md") + # Determine which agent based on skill type if agent_choice == "auto": devops_keywords = ["devops", "audit", "deploy", "k8s", "gcp", "infra", "terraform"] @@ -214,23 +218,65 @@ def run_skill(name: str, req: Optional[SkillRunRequest] = None): elif any(k in name for k in research_keywords): agent_choice = "gemini" else: - agent_choice = "opencode" + # Check SKILL.md for explicit agent assignment + for line in skill_md.split('\n'): + line = line.strip() + if "Primary:" in line: + candidate = line.split(":")[-1].strip().lower() + if candidate in ("opencode", "hermes", "gemini"): + agent_choice = candidate + break + if agent_choice == "auto": + agent_choice = "opencode" + + # Build prompt from skill instructions + learnings + user input + prompt = f"Execute the '{name}' skill.\n\n" + if skill_md: + prompt += f"## Skill Instructions\n{skill_md}\n\n" + if learnings and learnings.strip(): + prompt += f"## Past Learnings\n{learnings}\n\n" + if skill_input: + prompt += f"## User Input\n{skill_input}" + + run_id = str(uuid.uuid4())[:8] + + # Execute via agent + try: + response_text = execute_agent(agent_choice, prompt) + except subprocess.TimeoutExpired: + response_text = f"⏱ Skill '{name}' timed out on agent '{agent_choice}'." + except FileNotFoundError: + response_text = f"⚠ Agent '{agent_choice}' CLI not installed. Install it and try again." + except Exception as e: + response_text = f"⚠ Error executing skill: {str(e)}" + + # Save output to learnings.md + timestamp = get_timestamp()[:10] + existing = read_file(path / "learnings.md") + new_entry = ( + f"\n## {timestamp} (Run {run_id})\n" + f"- Agent: {agent_choice}\n" + f"- Input: {skill_input or '(none)'}\n" + f"- Output: {response_text[:500]}\n" + ) + write_file(path / "learnings.md", existing + new_entry) # Log execution - run_id = str(uuid.uuid4())[:8] append_audit({ "action": "skill_run", "skill": name, "agent": agent_choice, "run_id": run_id, + "output_preview": response_text[:100], }) return { - "status": "started", + "status": "completed", "run_id": run_id, "skill": name, "agent": agent_choice, - "message": f"Skill '{name}' dispatched to {agent_choice}", + "output": response_text, + "message": f"Skill '{name}' completed via {agent_choice}", } @app.get("/api/skills/{name}/eval")