agenthub/src/cli/commands/message.ts
chahinebrini 3f1fb76a84 feat(agenthub): TSK-0120 — split messaging from /activity into a /messages conversation view
- MessageSchema: status enum unread|delivered|read|acked + replyTo (additive, RW-compatible)
- messageService: listInbox transitions unread->delivered on fetch (agent-scoped);
  markMessageDelivered (idempotent, never downgrades); ackMessage(cwd,id,by?)
- routes: GET /messages HTML branch -> renderMessagesHtml; GET /messages/:id;
  POST /messages/:id/ack (mirror of /read, emits message/updated status=acked)
- server/messages.ts: 2-column conversation view (list grouped by pair + unread badge,
  thread bubbles aligned by ?as=, replyTo indentation, TSK pill, live via /events)
- activity.ts: drop message rows (tasks-only hard separation)
- ui-shared: HeaderPage +messages + nav link
- CLI: message ack <id> [--by], message reply <parentId> --from --text [--task];
  remoteClient ackMessage + getMessage
- tests: +message-receipts.test.ts, server.test.ts activity/messages/receipt-chain

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 00:56:16 +02:00

61 lines
2.2 KiB
TypeScript

import { createMessage, listInbox, markMessageRead, ackMessage, getMessage } from '../../core/services/messageService.js';
import type { Message } from '../../core/schema.js';
export function messageSend(
cwd: string,
opts: { from: string; to: string; text: string; taskId?: string },
): void {
const m = createMessage(cwd, opts);
console.log(`AgentHub: Message sent ${m.id} (${m.from}${m.to})`);
}
export function inboxList(cwd: string, opts: { agent: string; unreadOnly?: boolean }): void {
const msgs = listInbox(cwd, opts.agent, { unreadOnly: opts.unreadOnly });
if (msgs.length === 0) {
console.log(`No messages for ${opts.agent}.`);
return;
}
for (const m of msgs) {
const flag = m.status === 'unread' ? '●' : ' ';
console.log(`${flag} ${m.id} ${m.from}${m.to}${m.taskId ? ` [${m.taskId}]` : ''}: ${m.text}`);
}
}
export function messageRead(cwd: string, id: string): void {
const m = markMessageRead(cwd, id);
console.log(`AgentHub: Message read ${m.id} (${m.from}${m.to})`);
}
export function inboxMarkRead(cwd: string, opts: { agent: string; unreadOnly?: boolean }): void {
const msgs = listInbox(cwd, opts.agent, { unreadOnly: opts.unreadOnly });
for (const m of msgs) markMessageRead(cwd, m.id);
console.log(`AgentHub: marked ${msgs.length} message${msgs.length === 1 ? '' : 's'} read for ${opts.agent}`);
}
export function messageAck(cwd: string, id: string, by?: string): void {
const m = ackMessage(cwd, id, by);
console.log(`AgentHub: Message acked ${m.id} (${m.from}${m.to})${by ? ` by ${by}` : ''}`);
}
/**
* Reply to a message: loads the parent, sends a new message back to the parent's
* sender (to = parent.from), links it via replyTo, and inherits the parent's
* taskId unless one is given.
*/
export function messageReply(
cwd: string,
parentId: string,
opts: { from: string; text: string; taskId?: string },
): Message {
const { message: parent } = getMessage(cwd, parentId);
const m = createMessage(cwd, {
from: opts.from,
to: parent.from,
text: opts.text,
taskId: opts.taskId ?? parent.taskId,
replyTo: parentId,
});
console.log(`AgentHub: Reply sent ${m.id} (${m.from}${m.to}) ↩ ${parentId}`);
return m;
}