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:
204
src/cli/index.ts
204
src/cli/index.ts
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user