From 9f2adce7b35dad93a41366f1667894bb07920601 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Thu, 8 Oct 2026 02:25:31 -0400 Subject: [PATCH] Q2: read_messages lists only mentions the agent has not answered Co-Authored-By: Claude Opus 5.5 --- src/main/services/agent-read-messages.ts | 7 ++++++- src/main/services/agent-tools.ts | 2 +- tests/unit/mention-resolution.test.ts | 18 ++++++++++++++++++ 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/main/services/agent-read-messages.ts b/src/main/services/agent-read-messages.ts index 731a5f27..9b7f901a 100644 --- a/src/main/services/agent-read-messages.ts +++ b/src/main/services/agent-read-messages.ts @@ -43,10 +43,15 @@ export function readMessagesTool(deps: AgentToolDeps, ctx: AgentToolContext, arg left join agents a on a.id = m.sender_id and m.sender_type = 'agent' left join employees e on e.id = m.sender_id and m.sender_type = 'employee' where cm.target_type = 'agent' and cm.target_id = ? and m.org_id = ? and m.deleted_at is null + and not exists ( + select 1 from chat_messages reply + where reply.thread_id = m.thread_id and reply.sender_type = 'agent' and reply.sender_id = cm.target_id + and reply.created_at > coalesce(m.edited_at, m.created_at) and reply.deleted_at is null + ) order by m.created_at desc limit ? `).all(ctx.agent.id, ctx.orgId, limit) as Array<{ body_md: string; sender_name: string; thread_id: string; channel_name: string; space_type: string }>; - if (rows.length === 0) return "No recent mentions."; + if (rows.length === 0) return "No unanswered mentions."; return rows.reverse().map((m) => { const where = m.space_type === "direct" ? "in your direct conversation" : `in #${m.channel_name}`; return `[${m.sender_name} ${where}] (thread=${m.thread_id}): ${m.body_md}`; diff --git a/src/main/services/agent-tools.ts b/src/main/services/agent-tools.ts index 5bb3e9ae..0ebb08dc 100644 --- a/src/main/services/agent-tools.ts +++ b/src/main/services/agent-tools.ts @@ -129,7 +129,7 @@ export const AGENT_TOOLS: ToolDefinition[] = [ }, { name: "read_messages", - description: "Read recent messages from a channel, a direct conversation, or your direct mentions. Check what your teammates have been saying before you respond or start work.", + description: "Read recent messages from a channel, a direct conversation, or (with no arguments) the mentions you have not answered in their thread yet. Check what your teammates have been saying before you respond or start work.", parameters: { type: "object", additionalProperties: false, diff --git a/tests/unit/mention-resolution.test.ts b/tests/unit/mention-resolution.test.ts index f9c8cbed..f7b492bc 100644 --- a/tests/unit/mention-resolution.test.ts +++ b/tests/unit/mention-resolution.test.ts @@ -124,5 +124,23 @@ describe("mention resolution", () => { }; const inbox = dispatchAgentTool(deps, danaCtx, call("read_messages", {})); expect(inbox).toContain("check the deployment"); + + // Once Dana answers in that thread, the mention is handled and leaves her inbox. + const threadId = /thread=([^)]+)\)/.exec(inbox)?.[1]; + expect(threadId).toBeTruthy(); + dispatchAgentTool(deps, danaCtx, call("send_message", { channel: "engineering", body: "Deployment checked.", thread_id: threadId })); + expect(dispatchAgentTool(deps, danaCtx, call("read_messages", {}))).toBe("No unanswered mentions."); + + dispatchAgentTool(deps, ceoCtx, call("send_message", { channel: "engineering", body: "@dana one more: ship it", thread_id: threadId })); + const next = dispatchAgentTool(deps, danaCtx, call("read_messages", {})); + expect(next).toContain("ship it"); + expect(next).not.toContain("check the deployment"); + + // A mention added later by editing an old message is unanswered again. + dispatchAgentTool(deps, ceoCtx, call("send_message", { channel: "engineering", body: "status?", thread_id: threadId })); + const plainId = (connection.db.prepare("select id from chat_messages where body_md = 'status?'").get() as { id: string }).id; + dispatchAgentTool(deps, danaCtx, call("send_message", { channel: "engineering", body: "All good.", thread_id: threadId })); + deps.chat.editMessage({ orgId, messageId: plainId, bodyMd: "@dana status on the rollback?", actorType: "agent", actorId: ceoId }); + expect(dispatchAgentTool(deps, danaCtx, call("read_messages", {}))).toContain("status on the rollback"); }); });