import { remoteClient } from '../remoteClient.js'; import { listTasks as svcListTasks, getTask as svcGetTask, claimTask as svcClaimTask, } from '../../core/services/taskService.js'; import { listHandoffs as svcListHandoffs, getHandoff as svcGetHandoff } from '../../core/services/handoffService.js'; /** * `agenthub start --agent --role ` — one-command onboarding for an * agent. Announces presence, claims the open task addressed to this agent, and * prints the task body + its handoff + the next step, so the agent can begin * immediately without the human stitching commands together. * * "Addressed to " = task title starts with ":" (the delegation * convention) OR a handoff for that task has toAgent === . */ export async function startAgent(opts: { serverUrl?: string; projectCwd: string; agent: string; role: string; }): Promise { const { serverUrl, projectCwd, agent, role } = opts; const a = agent.toLowerCase(); // 1. Announce (presence is best-effort — never block onboarding on it). if (serverUrl) { try { await remoteClient.announce(serverUrl, agent, role); } catch { /* ignore */ } } console.log(`AgentHub: ${agent} joined (${role})`); // 2. Gather open role tasks + handoffs (handoffs carry taskId + toAgent). const tasks = serverUrl ? await remoteClient.listTasks(serverUrl, { role, status: 'open' }) : svcListTasks(projectCwd, { role, status: 'open' }); const handoffs = serverUrl ? await remoteClient.listHandoffs(serverUrl) : svcListHandoffs(projectCwd); const addressedByHandoff = new Set( handoffs .filter((h) => h.toAgent && String(h.toAgent).toLowerCase() === a && h.taskId) .map((h) => String(h.taskId)), ); const mine = tasks.filter( (t) => (t.title ?? '').toLowerCase().startsWith(`${a}:`) || addressedByHandoff.has(t.id), ); // 3. Nothing addressed → guide the agent instead of guessing. if (mine.length === 0) { if (tasks.length === 0) { console.log(`AgentHub: no open ${role} tasks — waiting for the architect to delegate.`); } else { console.log(`AgentHub: no task addressed to ${agent}. Open ${role} tasks:`); for (const t of tasks) console.log(` ${t.id} ${t.title ?? ''}`); console.log(`→ claim one yourself: agenthub task claim --agent ${agent}`); } return; } // 4. Claim the addressed task. const task = mine[0]; if (serverUrl) await remoteClient.claimTask(serverUrl, task.id, agent); else svcClaimTask(projectCwd, task.id, agent); console.log(`AgentHub: Task claimed ${task.id} ${task.title ?? ''}`); // 5. Print the task body. try { const detail = serverUrl ? await remoteClient.getTask(serverUrl, task.id) : svcGetTask(projectCwd, task.id); if (detail.body && detail.body.trim()) { console.log(`\n─ Task ${task.id} ──────────────`); console.log(detail.body.trim()); } } catch { /* body is optional */ } // 6. Print the handoff for this task (scope + acceptance criteria). const hof = handoffs.find((h) => h.taskId === task.id); if (hof) { try { const hd = serverUrl ? await remoteClient.getHandoff(serverUrl, hof.id) : svcGetHandoff(projectCwd, hof.id); console.log(`\n─ Handoff ${hof.id} ──────────────`); console.log(hd.handoff.summary); if (hd.body && hd.body.trim()) console.log(hd.body.trim()); } catch { /* handoff is optional */ } } // 7. Next steps — the review gate, spelled out. console.log(`\n─ Next ──────────────`); console.log(`Implement the task, then submit for review: agenthub task review ${task.id}`); console.log(`Report what you did: agenthub memory add --title "${task.id} result" --category implementation --content "…"`); console.log(`Only the architect closes a task (\`done\`). If reopened, address the feedback and review again.`); }