An agent runs ONE command and is working: it announces presence, claims the open task addressed to it, and prints the task + its handoff + the next step. Removes the per-agent kickoff copy-paste. - src/cli/commands/start.ts: `agenthub start --agent <name> --role <role>`. "Addressed to <agent>" = task title starts with "<agent>:" OR a handoff has toAgent=<agent>. Claims it, prints task body + handoff + the review gate reminder. No addressed task → lists open role tasks to pick. Works on local + --server paths. - templates: implementer guides (AGENTS/CODEX/KIMI.md) now lead with "On your first turn, RUN `agenthub start …` — do not just summarize", so a fresh session self-onboards from one trigger word. - tests: start auto-claims the addressed task, ignores others, no-ops on empty queue; templates test updated for the new commands. 116/116 green. Bump 0.2.2 -> 0.3.0. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
100 lines
3.9 KiB
TypeScript
100 lines
3.9 KiB
TypeScript
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 <name> --role <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 <agent>" = task title starts with "<agent>:" (the delegation
|
|
* convention) OR a handoff for that task has toAgent === <agent>.
|
|
*/
|
|
export async function startAgent(opts: {
|
|
serverUrl?: string;
|
|
projectCwd: string;
|
|
agent: string;
|
|
role: string;
|
|
}): Promise<void> {
|
|
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 <id> --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.`);
|
|
}
|