feat(autostart): agenthub agent setup — SessionStart hook enters the work loop
Closes the dormancy gap: an agent no longer needs to be told "call agenthub_work". `agenthub agent setup --agent <name> --role <role>` writes a verified Claude Code SessionStart hook (.claude/settings.json) whose command runs `agenthub hook-context`, which prints the work-loop instruction injected into every new session — so the agent claims tasks AND surfaces messages from the first turn, no manual trigger. - agentSetup.ts: agentSetup() (idempotent hook write, project or --user scope) + hookContext() (the injected instruction). - Honest limit (per Claude Code docs): SessionStart context is a strong nudge the model reads, not a hard directive; the guide reinforces it. Can't block startup. - Codex/Kimi aren't Claude Code — their guide already nudges agenthub_work first. Run once per agent machine. Bump 0.8.1 -> 0.9.0. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
ed6c5b01d0
commit
d00f2812f9
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "agenthub",
|
||||
"version": "0.8.1",
|
||||
"version": "0.9.0",
|
||||
"description": "Local coordination layer for AI coding agents",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
|
||||
65
src/cli/commands/agentSetup.ts
Normal file
65
src/cli/commands/agentSetup.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { homedir } from 'node:os';
|
||||
import { findProjectRoot } from '../../core/paths.js';
|
||||
|
||||
/**
|
||||
* Auto-start: make a Claude Code agent enter the agenthub work-loop the moment a
|
||||
* session starts — no manual "call agenthub_work" needed.
|
||||
*
|
||||
* Mechanism: a verified Claude Code `SessionStart` hook (.claude/settings.json)
|
||||
* whose command runs `agenthub hook-context`, which prints the work-loop
|
||||
* instruction. That text is injected into the new session as context.
|
||||
*
|
||||
* Honest limit (per the Claude Code docs): SessionStart context is a strong
|
||||
* NUDGE the model reads, not a hard directive it must obey — so the guide
|
||||
* reinforces it. It also can't block startup. Codex/Kimi aren't Claude Code;
|
||||
* their guide (CODEX.md / KIMI.md) carries the same "call agenthub_work first"
|
||||
* nudge.
|
||||
*/
|
||||
|
||||
/** Printed by the SessionStart hook → injected as session context. */
|
||||
export function hookContext(opts: { agent: string; role: string }): void {
|
||||
const { agent, role } = opts;
|
||||
process.stdout.write(
|
||||
`AgentHub auto-start — you are "${agent}" (${role}). Before anything else, run:\n` +
|
||||
` agenthub work --agent ${agent} --role ${role}\n` +
|
||||
`(or call the agenthub_work tool with { agent: "${agent}", role: "${role}" }), then LOOP: ` +
|
||||
`work -> implement -> agenthub_task_review -> work. agenthub_work claims your tasks AND ` +
|
||||
`surfaces your messages. Never run agenthub_task_done. Keep looping so you stay reachable.\n`,
|
||||
);
|
||||
}
|
||||
|
||||
export function agentSetup(cwd: string, opts: { agent: string; role: string; user?: boolean }): void {
|
||||
const base = opts.user ? homedir() : (findProjectRoot(cwd) ?? cwd);
|
||||
const dir = join(base, '.claude');
|
||||
const settingsPath = join(dir, 'settings.json');
|
||||
const command = `agenthub hook-context --agent ${opts.agent} --role ${opts.role}`;
|
||||
const hookGroup = { matcher: 'startup|resume', hooks: [{ type: 'command', command }] };
|
||||
|
||||
let settings: { hooks?: { SessionStart?: unknown[] } } = {};
|
||||
if (existsSync(settingsPath)) {
|
||||
try {
|
||||
settings = JSON.parse(readFileSync(settingsPath, 'utf-8')) as typeof settings;
|
||||
} catch {
|
||||
settings = {};
|
||||
}
|
||||
}
|
||||
if (!settings.hooks) settings.hooks = {};
|
||||
if (!Array.isArray(settings.hooks.SessionStart)) settings.hooks.SessionStart = [];
|
||||
|
||||
// Idempotent: drop any prior agenthub hook (identified by the command), re-add.
|
||||
settings.hooks.SessionStart = (settings.hooks.SessionStart as Array<{ hooks?: Array<{ command?: string }> }>).filter(
|
||||
(g) => !(g?.hooks ?? []).some((h) => typeof h?.command === 'string' && h.command.includes('agenthub hook-context')),
|
||||
);
|
||||
(settings.hooks.SessionStart as unknown[]).push(hookGroup);
|
||||
|
||||
mkdirSync(dir, { recursive: true });
|
||||
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
|
||||
|
||||
const out = (s: string) => process.stdout.write(s + '\n');
|
||||
out(`AgentHub: auto-start hook written -> ${settingsPath}`);
|
||||
out(` New Claude Code sessions ${opts.user ? '(all projects)' : 'in this project'} start as "${opts.agent}" (${opts.role}) and enter the work loop.`);
|
||||
out(' Note: SessionStart context is a strong nudge, not a hard guarantee — the guide reinforces it.');
|
||||
out(' Codex/Kimi are not Claude Code: their guide already nudges agenthub_work on first turn.');
|
||||
}
|
||||
@ -6,6 +6,7 @@ import { taskCreate, taskList, taskShow, taskClaim, taskDone, taskReview, taskRe
|
||||
import { handoffCreate, handoffRead, handoffList } from './commands/handoff.js';
|
||||
import { decisionCreate, decisionList } from './commands/decision.js';
|
||||
import { messageSend, inboxList } from './commands/message.js';
|
||||
import { agentSetup, hookContext } from './commands/agentSetup.js';
|
||||
import { delegate } from './commands/delegate.js';
|
||||
import { serverStart } from './commands/server.js';
|
||||
import { update } from './commands/update.js';
|
||||
@ -117,7 +118,7 @@ async function runRemote(serverUrl: string, fn: () => Promise<void>): Promise<vo
|
||||
export function createProgram(cwd: string): Command {
|
||||
const program = new Command('agenthub')
|
||||
.description('Local coordination layer for AI coding agents')
|
||||
.version('0.8.1')
|
||||
.version('0.9.0')
|
||||
.option('--server <url>', 'AgentHub server URL (env: AGENTHUB_SERVER)');
|
||||
|
||||
program
|
||||
@ -493,6 +494,29 @@ export function createProgram(cwd: string): Command {
|
||||
}
|
||||
});
|
||||
|
||||
// ─── auto-start ──────────────────────────────────────────────────────────
|
||||
const agentCmd = new Command('agent').description('Per-agent machine setup');
|
||||
agentCmd
|
||||
.command('setup')
|
||||
.description('Auto-start: write a Claude Code SessionStart hook so sessions enter the work loop')
|
||||
.requiredOption('--agent <name>', 'This machine\'s agent name')
|
||||
.requiredOption('--role <role>', 'This agent\'s role (implementer / architect / tester)')
|
||||
.option('--user', 'Install for all projects (~/.claude) instead of just this one')
|
||||
.action((options: { agent: string; role: string; user?: boolean }) => {
|
||||
agentSetup(cwd, { agent: options.agent, role: options.role, user: options.user });
|
||||
});
|
||||
program.addCommand(agentCmd);
|
||||
|
||||
// Called by the SessionStart hook; prints the work-loop instruction as context.
|
||||
program
|
||||
.command('hook-context')
|
||||
.description('Print the auto-start work-loop instruction (used by the SessionStart hook)')
|
||||
.requiredOption('--agent <name>', 'Agent name')
|
||||
.requiredOption('--role <role>', 'Agent role')
|
||||
.action((options: { agent: string; role: string }) => {
|
||||
hookContext({ agent: options.agent, role: options.role });
|
||||
});
|
||||
|
||||
program
|
||||
.command('delegate')
|
||||
.description('Suggest or auto-delegate open tasks')
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user