From ed6c5b01d0e52a771a79b9a0a3264114b1f60f4e Mon Sep 17 00:00:00 2001 From: chahinebrini Date: Mon, 29 Jun 2026 04:04:04 +0200 Subject: [PATCH] feat(messaging): read receipts + ack convention MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes the fire-and-forget gap the user hit: the sender now sees when a message is read. When markMessageRead fires, the read event carries the reader, and the watch stream renders it as a receipt: AgentHub: ✓ Read MSG-0002 read by windows-claude Plus an ack convention in the implementer guide: when you act on a message- request, send a brief "on it …" + a result message — so the sender sees progress, not silence. (Board-side activity feed with messages is folded into TSK-0032.) Bump 0.8.0 -> 0.8.1. Co-Authored-By: Claude Opus 4.8 --- package.json | 2 +- src/cli/commands/watch.ts | 4 ++++ src/cli/index.ts | 2 +- src/core/templates.ts | 4 ++++ src/server/routes.ts | 7 ++++++- 5 files changed, 16 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index c2396ac..80b1b99 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "agenthub", - "version": "0.8.0", + "version": "0.8.1", "description": "Local coordination layer for AI coding agents", "type": "module", "main": "./dist/index.js", diff --git a/src/cli/commands/watch.ts b/src/cli/commands/watch.ts index a5eceae..6c06ed8 100644 --- a/src/cli/commands/watch.ts +++ b/src/cli/commands/watch.ts @@ -80,6 +80,10 @@ function describeEvent(event: AgentHubEvent): { label: string; detail: string } case 'memory': return { label: 'Memory', detail: event.title ?? '' }; case 'message': + // A read message surfaces to the SENDER as a delivery/read receipt. + if (event.action === 'updated' && event.status === 'read') { + return { label: '✓ Read', detail: event.assignedTo ? `read by ${event.assignedTo}` : 'read' }; + } return { label: 'Message', detail: event.title ?? '' }; default: // 'agent' presence events are formatted in formatEvent() before reaching diff --git a/src/cli/index.ts b/src/cli/index.ts index 50f1a81..28854d1 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -117,7 +117,7 @@ async function runRemote(serverUrl: string, fn: () => Promise): Promise', 'AgentHub server URL (env: AGENTHUB_SERVER)'); program diff --git a/src/core/templates.ts b/src/core/templates.ts index 447a354..8bf72f6 100644 --- a/src/core/templates.ts +++ b/src/core/templates.ts @@ -59,6 +59,10 @@ claims it, and returns the task + its handoff. Then: then \`agenthub task review \` + \`agenthub memory add …\`, then \`agenthub work\` again (run it in the background so the wait doesn't tie up your turn). +**Messages:** \`agenthub_work\` surfaces messages addressed to you (or check \`agenthub_inbox\`). +When you act on a message-request, send a short ack with \`agenthub_message\` ("on it ...") and +a result message when done — so the sender sees progress + outcome, not silence. + ⚠️ NEVER close a task yourself (no \`agenthub_task_done\` / \`agenthub task done\`) — only the architect approves and closes. You drive the tools yourself; the human doesn't type them for you. `; diff --git a/src/server/routes.ts b/src/server/routes.ts index bf05508..eb6d3ef 100644 --- a/src/server/routes.ts +++ b/src/server/routes.ts @@ -300,7 +300,12 @@ export async function registerRoutes(app: FastifyInstance, cwd: string): Promise const { id } = request.params as { id: string }; try { const message = markMessageRead(cwd, id); - emitChange({ type: 'message', action: 'updated', id: message.id, status: 'read' }, message.updatedAt); + // Read receipt: carry the reader (to) so the sender's stream shows + // "✓ MSG-xxxx read by ". + emitChange( + { type: 'message', action: 'updated', id: message.id, status: 'read', title: `${message.from} → ${message.to}`, assignedTo: message.to }, + message.updatedAt, + ); return message; } catch (err) { return badRequest(reply, err instanceof Error ? err.message : 'Message not found');