feat(05-05): add message and dispatch CLI commands

- Add message command group: list, read, respond
- Add dispatch command group: queue, next, status, complete
- Message list shows pending count and filters by agent/status
- Dispatch status shows ready tasks with priorities
This commit is contained in:
Lukas May
2026-01-30 20:48:22 +01:00
parent e0e03eef86
commit 211411e795

View File

@@ -320,6 +320,210 @@ export function createCli(serverHandler?: (port?: number) => Promise<void>): Com
}
});
// Message command group
const messageCommand = program
.command('message')
.description('View agent messages and questions');
// cw message list [--agent <agentId>] [--status <status>]
messageCommand
.command('list')
.description('List messages from agents')
.option('--agent <agentId>', 'Filter by agent ID')
.option('--status <status>', 'Filter by status (pending, read, responded)')
.action(async (options: { agent?: string; status?: string }) => {
// Validate status if provided
if (options.status && !['pending', 'read', 'responded'].includes(options.status)) {
console.error(`Invalid status: ${options.status}`);
console.error('Valid statuses: pending, read, responded');
process.exit(1);
}
try {
const client = createDefaultTrpcClient();
const messages = await client.listMessages.query({
agentId: options.agent,
status: options.status as 'pending' | 'read' | 'responded' | undefined,
});
if (messages.length === 0) {
console.log('No messages found');
return;
}
// Count pending
const pendingCount = messages.filter(m => m.status === 'pending').length;
if (pendingCount > 0) {
console.log(`(${pendingCount} pending)\n`);
}
console.log('Messages:');
console.log('');
for (const msg of messages) {
const idShort = msg.id.slice(0, 8);
const agentId = msg.senderId?.slice(0, 8) ?? 'user';
const content = msg.content.length > 50 ? msg.content.slice(0, 47) + '...' : msg.content;
const statusLabel = msg.status.toUpperCase();
const createdAt = new Date(msg.createdAt).toLocaleString();
console.log(` ${idShort} ${agentId} ${msg.type} [${statusLabel}] ${createdAt}`);
console.log(` ${content}`);
}
} catch (error) {
console.error('Failed to list messages:', (error as Error).message);
process.exit(1);
}
});
// cw message read <messageId>
messageCommand
.command('read <messageId>')
.description('Read full message content')
.action(async (messageId: string) => {
try {
const client = createDefaultTrpcClient();
const message = await client.getMessage.query({ id: messageId });
console.log(`Message: ${message.id}`);
console.log(` From: ${message.senderType} (${message.senderId ?? 'user'})`);
console.log(` To: ${message.recipientType} (${message.recipientId ?? 'user'})`);
console.log(` Type: ${message.type}`);
console.log(` Status: ${message.status}`);
console.log(` Created: ${new Date(message.createdAt).toLocaleString()}`);
console.log('');
console.log('Content:');
console.log(message.content);
if (message.status === 'pending' && message.requiresResponse) {
console.log('');
console.log('---');
console.log('This message requires a response.');
console.log(`Use: cw message respond ${message.id} "<your response>"`);
}
} catch (error) {
console.error('Failed to read message:', (error as Error).message);
process.exit(1);
}
});
// cw message respond <messageId> <response>
messageCommand
.command('respond <messageId> <response>')
.description('Respond to a message')
.action(async (messageId: string, response: string) => {
try {
const client = createDefaultTrpcClient();
const responseMessage = await client.respondToMessage.mutate({
id: messageId,
response,
});
console.log(`Response recorded (${responseMessage.id.slice(0, 8)})`);
console.log('');
console.log('If the agent is waiting, you may want to resume it:');
console.log(' cw agent list (to see waiting agents)');
console.log(' cw agent resume <name> "<response>"');
} catch (error) {
console.error('Failed to respond to message:', (error as Error).message);
process.exit(1);
}
});
// Dispatch command group
const dispatchCommand = program
.command('dispatch')
.description('Control task dispatch queue');
// cw dispatch queue <taskId>
dispatchCommand
.command('queue <taskId>')
.description('Queue a task for dispatch')
.action(async (taskId: string) => {
try {
const client = createDefaultTrpcClient();
await client.queueTask.mutate({ taskId });
console.log(`Task '${taskId}' queued for dispatch`);
} catch (error) {
console.error('Failed to queue task:', (error as Error).message);
process.exit(1);
}
});
// cw dispatch next
dispatchCommand
.command('next')
.description('Dispatch next available task to an agent')
.action(async () => {
try {
const client = createDefaultTrpcClient();
const result = await client.dispatchNext.mutate();
if (result.success) {
console.log('Task dispatched successfully');
console.log(` Task: ${result.taskId}`);
console.log(` Agent: ${result.agentId}`);
} else {
console.log('Dispatch failed');
console.log(` Task: ${result.taskId || '(none)'}`);
console.log(` Reason: ${result.reason}`);
}
} catch (error) {
console.error('Failed to dispatch:', (error as Error).message);
process.exit(1);
}
});
// cw dispatch status
dispatchCommand
.command('status')
.description('Show dispatch queue status')
.action(async () => {
try {
const client = createDefaultTrpcClient();
const state = await client.getQueueState.query();
console.log('Dispatch Queue Status');
console.log('=====================');
console.log(`Queued: ${state.queued.length}`);
console.log(`Ready: ${state.ready.length}`);
console.log(`Blocked: ${state.blocked.length}`);
if (state.ready.length > 0) {
console.log('');
console.log('Ready tasks:');
for (const task of state.ready) {
const priority = task.priority === 'high' ? '[HIGH]' : task.priority === 'low' ? '[low]' : '';
console.log(` ${task.taskId} ${priority}`);
}
}
if (state.blocked.length > 0) {
console.log('');
console.log('Blocked tasks:');
for (const bt of state.blocked) {
console.log(` ${bt.taskId}: ${bt.reason}`);
}
}
} catch (error) {
console.error('Failed to get queue status:', (error as Error).message);
process.exit(1);
}
});
// cw dispatch complete <taskId>
dispatchCommand
.command('complete <taskId>')
.description('Mark a task as complete')
.action(async (taskId: string) => {
try {
const client = createDefaultTrpcClient();
await client.completeTask.mutate({ taskId });
console.log(`Task '${taskId}' marked as complete`);
} catch (error) {
console.error('Failed to complete task:', (error as Error).message);
process.exit(1);
}
});
return program;
}