diff --git a/src/cli/commands/startAgent.ts b/src/cli/commands/startAgent.ts new file mode 100644 index 0000000..ea47113 --- /dev/null +++ b/src/cli/commands/startAgent.ts @@ -0,0 +1,101 @@ +import { loadConfig } from '../../core/config.js'; +import { resolveAgentName } from '../../core/services/identityService.js'; +import { resolvePending } from '../../core/services/checkinService.js'; +import { remoteClient } from '../remoteClient.js'; + +/** + * `agenthub start ` — der EINE Befehl, mit dem eine frische + * Agenten-Session anfängt. + * + * Motivation (CEO 2026-07-29): „Die Prompts sind mega lang, ich brauche eine + * Version, die ich immer verwenden kann." Statt einen Textblock in jede + * Session zu kopieren, druckt dieser Befehl das verbindliche Briefing selbst — + * inklusive dessen, was gerade auf den Agenten wartet. Der Mensch tippt eine + * Zeile, der Agent liest den Rest hier. + * + * Absichtlich reiner Text (kein JSON): die Ausgabe landet im Kontext eines + * LLM-Agenten und muss als Anweisung lesbar sein. + */ + +export interface StartAgentOptions { + serverUrl?: string; + projectCwd: string; + agent: string; + role?: string; +} + +function briefing(agent: string, role: string): string { + return [ + `Du bist "${agent}" im AgentHub (Rolle: ${role}).`, + '', + 'SO ARBEITEST DU:', + `1. Ruf JETZT agenthub_work(agent="${agent}", role="${role}", timeoutSec=50) auf.`, + ' Danach nach JEDEM abgeschlossenen Schritt erneut — auch nach agenthub_task_review.', + ' Beende deinen Turn nie, ohne agenthub_work neu gestartet zu haben.', + '2. Deine Aufgabe steht IMMER im Handoff des Tasks. Lies ihn, bevor du baust.', + `3. Logge nach jedem Teilschritt: agenthub_task_log(id, text, agent="${agent}").`, + '', + 'WÄHREND DER ARBEIT BIST DU TAUB — das ist der wichtigste Punkt:', + 'Solange du einen Task ausführst, erreichen dich KEINE Events. Der einzige', + 'Draht nach außen ist die ANTWORT von agenthub_task_log. Deshalb ist Loggen', + 'keine Höflichkeit, sondern dein Empfang.', + '', + ' - Enthält die Antwort `pending.interrupted`, wurde dir der Task', + ' zurückgegeben, abgebrochen oder entzogen: SOFORT aufhören, NICHT', + ' einreichen, `pending.interrupted.action` befolgen.', + ' - Enthält sie ungelesene Nachrichten: mit agenthub_inbox lesen und antworten.', + ` - Für lange Strecken ohne Log-Zeile: agenthub_checkin(agent="${agent}", taskId="").`, + '', + 'NACH DEM EINREICHEN (DEC-0035):', + 'Mit agenthub_task_review bist du GEBUNDEN, bis der Architekt abnimmt oder', + 'zurückweist. Du bekommst bewusst keine neue Task — das ist kein Fehler.', + 'Bleib im Loop, du wirst geweckt.', + '', + 'GRENZEN: Du markierst NIE selbst done (das ist das Gate des Architekten).', + 'Du pushst nie ohne Freigabe. Bei Unklarheit: agenthub_ask statt raten.', + ].join('\n'); +} + +export async function startAgentSession(options: StartAgentOptions): Promise { + const { projectCwd, serverUrl } = options; + + let agent = options.agent; + let role = options.role; + try { + agent = resolveAgentName(projectCwd, options.agent); + if (!role) { + const config = loadConfig(projectCwd); + const architect = config.roles?.architect?.preferredAgent; + role = agent === architect ? 'architect' : (config.agents?.[agent]?.role ?? 'implementer'); + } + } catch { + role = role ?? 'implementer'; + } + + if (serverUrl) { + try { + await remoteClient.announce(serverUrl, agent, role); + } catch { + // Präsenz ist best-effort — ein nicht erreichbarer Hub darf den Start + // nicht verhindern, das Briefing gilt trotzdem. + } + } + + console.log(briefing(agent, role ?? 'implementer')); + + // Was liegt gerade an? Damit die Session nicht blind in den Loop geht. + try { + const pending = serverUrl + ? ((await remoteClient.getPending(serverUrl, agent)) as ReturnType) + : resolvePending(projectCwd, agent); + const lines: string[] = []; + if (pending.waitingTasks.length) lines.push(`Offen für dich: ${pending.waitingTasks.join(', ')}`); + if (pending.awaitingReview.length) lines.push(`Wartet auf Architekten-Review: ${pending.awaitingReview.join(', ')} — du bist so lange gebunden.`); + if (pending.unreadCount) lines.push(`Ungelesene Nachrichten: ${pending.unreadCount}`); + console.log(''); + console.log(lines.length ? `AKTUELL:\n ${lines.join('\n ')}` : 'AKTUELL: nichts offen — geh in den Loop und warte.'); + } catch { + console.log(''); + console.log('AKTUELL: Status nicht abrufbar — geh trotzdem in den Loop.'); + } +} diff --git a/src/cli/index.ts b/src/cli/index.ts index 49d1ace..eddc57e 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -15,6 +15,7 @@ import { update } from './commands/update.js'; import { watchEvents } from './commands/watch.js'; import { startAgent } from './commands/start.js'; import { workAgent } from './commands/work.js'; +import { startAgentSession } from './commands/startAgent.js'; import { startMcpServer } from '../mcp/server.js'; import { installMcp } from '../mcp/install.js'; import { loadConfig, saveConfig } from '../core/config.js'; @@ -866,17 +867,33 @@ export function createProgram(cwd: string): Command { }); // ─── start (onboarding) ──────────────────────────────────────────────────── + // Session-Einstieg für einen Agenten. `agenthub start codex` (positional) ist + // die Form, die ein Mensch in eine frische Session tippt — sie druckt zuerst + // das verbindliche Briefing (Loop, Check-in-Kanal, DEC-0035) und macht dann + // das Onboarding (anmelden, adressierten Task claimen, Handoff zeigen). + // `--agent` bleibt für Skripte/Hooks erhalten. program - .command('start') - .description('Onboard: announce, claim the task addressed to you, print it + its handoff') - .requiredOption('--agent ', 'Agent name') - .option('--role ', 'Role (default: implementer)', 'implementer') - .action(async (options) => { + .command('start [agent]') + .description('Start an agent session: briefing + announce + claim the task addressed to you') + .option('--agent ', 'Agent name (alternative to the positional argument)') + .option('--role ', 'Role (default: from the roster, else implementer)') + .option('--no-briefing', 'Skip the standing briefing (onboarding output only)') + .action(async (positional: string | undefined, options: { agent?: string; role?: string; briefing?: boolean }) => { + const agent = positional ?? options.agent; + if (!agent) { + console.error('Which agent? Usage: agenthub start (e.g. agenthub start codex)'); + process.exit(1); + } const { serverUrl, projectCwd } = await resolveContext(program, cwd); + if (options.briefing !== false) { + await startAgentSession({ serverUrl, projectCwd, agent, role: options.role }); + console.log(''); + } + const role = options.role ?? 'implementer'; if (serverUrl) { - await runRemote(serverUrl, () => startAgent({ serverUrl, projectCwd, agent: options.agent, role: options.role })); + await runRemote(serverUrl, () => startAgent({ serverUrl, projectCwd, agent, role })); } else { - await startAgent({ projectCwd, agent: options.agent, role: options.role }); + await startAgent({ projectCwd, agent, role }); } });