agenthub/src/server/index.ts
chahinebrini 7ca6c859f2 feat(board+team): interactive board, animated team tree, token budget, mDNS, realtime architect review + codex/kimi autostart
Board (/board):
- Drag an AGENT chip onto a task card to (re)assign it (realtime-notified).
- Drag a task card to a column to change status; open→in_progress auto-assigns
  from the title's "<agent>:" prefix — no manual agent picking.
- "+ New task" composer (agent dropdown removed; agent comes from the title).
- Live Cost & Budget panel.

Team (/team): live SSE sync of busy state + always-on ambient animation
(connector shimmer, idle glow) that brightens to a busy pulse when an agent
works. Org-chart hierarchy stays.

Token accounting: new budgetService/rosterService + GET /budget and /agents.
Real doneTokens + time-on-task estimate capped at 45 min/task (avoids the
wall-clock overcount that produced multi-million-token totals), blended
per-model EUR cost + optional budget bars. All estimates flagged "~".

Autostart: `agent setup` writes deterministic SessionStart hooks for Codex
(~/.codex/config.toml) and Kimi (~/.kimi-code/config.toml), not just Claude
Code. Verified: both auto-enter the agenthub_work loop.

Realtime architect review: agenthub_work is role-aware — architect/reviewer
blocks on SSE and wakes when a task hits review (no manual watcher re-arm).

mDNS: server advertises agenthub.local (bonjour-service) so the hub is
reachable in a browser on the LAN without an IP.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 03:23:48 +02:00

63 lines
2.3 KiB
TypeScript

import Fastify from 'fastify';
import { registerRoutes } from './routes.js';
import { resolveAdvertiseUrl, startDiscoveryBroadcaster } from '../discovery.js';
import { startMdnsAdvertise, type MdnsHandle } from './mdns.js';
import { startEntityWatcher } from './fsWatch.js';
import { startStatusAutoRefresh } from './statusRefresh.js';
export function buildApp(cwd: string) {
const app = Fastify({ logger: false });
registerRoutes(app, cwd);
return app;
}
export async function startServer(cwd: string, options: { port?: number; host?: string } = {}): Promise<{ app: Fastify.FastifyInstance; url: string }> {
const app = buildApp(cwd);
const port = options.port ?? 3377;
const host = options.host ?? '127.0.0.1';
let broadcaster: ReturnType<typeof startDiscoveryBroadcaster> | undefined;
let mdns: MdnsHandle | undefined;
// Emit SSE events for CLI/file writes too, not just REST mutations.
const stopWatcher = startEntityWatcher(cwd);
// Keep status/latest.md fresh on every change so agents never read a stale snapshot.
const stopStatusRefresh = startStatusAutoRefresh(cwd);
app.addHook('onClose', async () => {
broadcaster?.stop();
mdns?.stop();
stopWatcher();
stopStatusRefresh();
});
try {
await app.listen({ port, host });
const address = app.server.address();
const actualPort = typeof address === 'string' ? port : (address?.port ?? port);
const url = `http://${host === '0.0.0.0' ? '127.0.0.1' : host}:${actualPort}`;
console.log(`AgentHub: server listening on ${url}`);
const advertiseUrl = resolveAdvertiseUrl(host, actualPort);
broadcaster = startDiscoveryBroadcaster(advertiseUrl);
// Advertise a browsable LAN hostname over mDNS (best-effort).
mdns = startMdnsAdvertise({ port: actualPort });
if (mdns) {
const portSuffix = actualPort === 80 ? '' : `:${actualPort}`;
console.log(`AgentHub: reachable in a browser at http://${mdns.hostname}${portSuffix}`);
}
return { app, url };
} catch (err) {
const e = err as NodeJS.ErrnoException;
if (e.code === 'EADDRINUSE') {
console.error(
`Port ${port} is already in use by another process that is not AgentHub. ` +
`Stop it, or start with a different --port.`,
);
} else {
console.error(err);
}
process.exit(1);
}
}