feat(cli): agenthub start <agent> — ein Einzeiler statt Copy-Paste-Prompt
Der Mensch soll pro Session eine Zeile tippen, nicht einen Textblock einfügen (CEO: „die Prompts sind mega lang"). `agenthub start codex` druckt jetzt das verbindliche Briefing selbst — Loop-Pflicht, Taubheit während der Arbeit, pending-Block als einziger Empfangsweg, DEC-0035 — und zeigt anschließend, was gerade auf den Agenten wartet. Kein zweites Kommando: das bestehende `start` (Onboarding: announce, Task claimen, Handoff drucken) nimmt jetzt ein positionales Agent-Argument und stellt das Briefing voran. `--agent` bleibt für Hooks/Skripte, `--no-briefing` gibt das alte Verhalten. Die Rolle kommt aus dem Roster (Architekt wird als solcher erkannt), sodass `agenthub start <name>` für jeden Agenten ohne weitere Flags reicht. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
1f3126b3b5
commit
b5533359d5
101
src/cli/commands/startAgent.ts
Normal file
101
src/cli/commands/startAgent.ts
Normal file
@ -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 <agent>` — 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="<dein Task>").`,
|
||||
'',
|
||||
'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<void> {
|
||||
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<typeof resolvePending>)
|
||||
: 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.');
|
||||
}
|
||||
}
|
||||
@ -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 <name>', 'Agent name')
|
||||
.option('--role <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 <name>', 'Agent name (alternative to the positional argument)')
|
||||
.option('--role <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 <agent> (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 });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user