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 { watchEvents } from './commands/watch.js';
|
||||||
import { startAgent } from './commands/start.js';
|
import { startAgent } from './commands/start.js';
|
||||||
import { workAgent } from './commands/work.js';
|
import { workAgent } from './commands/work.js';
|
||||||
|
import { startAgentSession } from './commands/startAgent.js';
|
||||||
import { startMcpServer } from '../mcp/server.js';
|
import { startMcpServer } from '../mcp/server.js';
|
||||||
import { installMcp } from '../mcp/install.js';
|
import { installMcp } from '../mcp/install.js';
|
||||||
import { loadConfig, saveConfig } from '../core/config.js';
|
import { loadConfig, saveConfig } from '../core/config.js';
|
||||||
@ -866,17 +867,33 @@ export function createProgram(cwd: string): Command {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// ─── start (onboarding) ────────────────────────────────────────────────────
|
// ─── 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
|
program
|
||||||
.command('start')
|
.command('start [agent]')
|
||||||
.description('Onboard: announce, claim the task addressed to you, print it + its handoff')
|
.description('Start an agent session: briefing + announce + claim the task addressed to you')
|
||||||
.requiredOption('--agent <name>', 'Agent name')
|
.option('--agent <name>', 'Agent name (alternative to the positional argument)')
|
||||||
.option('--role <role>', 'Role (default: implementer)', 'implementer')
|
.option('--role <role>', 'Role (default: from the roster, else implementer)')
|
||||||
.action(async (options) => {
|
.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);
|
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) {
|
if (serverUrl) {
|
||||||
await runRemote(serverUrl, () => startAgent({ serverUrl, projectCwd, agent: options.agent, role: options.role }));
|
await runRemote(serverUrl, () => startAgent({ serverUrl, projectCwd, agent, role }));
|
||||||
} else {
|
} else {
|
||||||
await startAgent({ projectCwd, agent: options.agent, role: options.role });
|
await startAgent({ projectCwd, agent, role });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user