Direct messages between agents through the hub — architect↔agent and agent↔agent
questions, clarifications and pings that handoffs/memories don't cover. Built as
MCP tools so kimi + codex get it too (not Claude-only), plus REST + CLI.
- Message entity (MSG-NNNN): schema, counter, messages/ dir, messageService
(createMessage / listInbox / listMessages / markMessageRead).
- REST: POST /messages, GET /messages[?agent&unread], POST /messages/:id/read,
with SSE emit. fsWatch + statusRefresh wired ('message' events; status skips them).
- MCP: agenthub_message {from,to,text,taskId?} + agenthub_inbox {agent,unreadOnly?}.
agenthub_work now surfaces + drains unread messages and wakes on message events,
so an agent sees messages inside its work loop. (18 tools total.)
- CLI: `agenthub message <to> <text> --from <agent>` + `agenthub inbox --agent`.
watch stream shows "Message" events (architect-visible).
- Architect-visible by design: the hub stays the coordination point.
Bump 0.7.5 -> 0.8.0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
45 lines
1.4 KiB
TypeScript
45 lines
1.4 KiB
TypeScript
import { existsSync } from 'fs';
|
|
import { dirname, join, parse } from 'path';
|
|
|
|
export type EntityType = 'tasks' | 'handoffs' | 'decisions' | 'memory' | 'messages' | 'status';
|
|
|
|
export function getAgentHubDir(cwd: string = process.cwd()): string {
|
|
return join(cwd, '.agenthub');
|
|
}
|
|
|
|
/**
|
|
* Walk up from `startCwd` to find the nearest directory that contains an
|
|
* initialized AgentHub project (`.agenthub/agenthub.config.json`).
|
|
* Returns the project root, or `undefined` if none is found.
|
|
* This makes the CLI usable from any subdirectory of the project.
|
|
*/
|
|
export function findProjectRoot(startCwd: string = process.cwd()): string | undefined {
|
|
let dir = startCwd;
|
|
const { root } = parse(dir);
|
|
while (true) {
|
|
if (existsSync(join(dir, '.agenthub', 'agenthub.config.json'))) return dir;
|
|
if (dir === root) return undefined;
|
|
dir = dirname(dir);
|
|
}
|
|
}
|
|
|
|
export function getEntityDir(cwd: string, type: EntityType): string {
|
|
return join(getAgentHubDir(cwd), type);
|
|
}
|
|
|
|
export function getCounterPath(cwd: string): string {
|
|
return join(getAgentHubDir(cwd), '.counter.json');
|
|
}
|
|
|
|
export function getConfigPath(cwd: string): string {
|
|
return join(getAgentHubDir(cwd), 'agenthub.config.json');
|
|
}
|
|
|
|
export function getIndexPath(cwd: string): string {
|
|
return join(getAgentHubDir(cwd), '.index.sqlite');
|
|
}
|
|
|
|
export function getStatusPath(cwd: string): string {
|
|
return join(getEntityDir(cwd, 'status'), 'latest.md');
|
|
}
|