diff --git a/AgentCompany-New/src/main/services/agent-tools.ts b/AgentCompany-New/src/main/services/agent-tools.ts index 75ed4f9d..2f6d084a 100644 --- a/AgentCompany-New/src/main/services/agent-tools.ts +++ b/AgentCompany-New/src/main/services/agent-tools.ts @@ -110,6 +110,19 @@ export const AGENT_TOOLS: ToolDefinition[] = [ required: ["channel", "body"], }, }, + { + name: "read_messages", + description: "Read recent messages from a channel or your direct mentions. Check what your teammates have been saying before you respond or start work.", + parameters: { + type: "object", + additionalProperties: false, + properties: { + channel: { type: "string", description: "Channel name: 'engineering', 'product', 'general'. Omit to read your recent @mentions across all channels." }, + limit: { type: "number", description: "Number of recent messages (default 10, max 30)." }, + }, + required: [], + }, + }, { name: "create_document", description: "Create a company document (PRD, spec, memo, report, runbook, proposal, policy, guide). Use this for structured deliverables that the company should keep versioned.", @@ -144,6 +157,8 @@ export function dispatchAgentTool( return writeDeliverableTool(deps, ctx, call.arguments); case "send_message": return sendMessageTool(deps, ctx, call.arguments); + case "read_messages": + return readMessagesTool(deps, ctx, call.arguments); case "create_document": return createDocumentTool(deps, ctx, call.arguments); default: @@ -334,6 +349,45 @@ function joinNames(names: string[]): string { return `${names.slice(0, -1).join(", ")}, and ${names[names.length - 1]}`; } +function readMessagesTool(deps: AgentToolDeps, ctx: AgentToolContext, args: JsonRecord): string { + const channel = optionalString(args.channel); + const limit = Math.min(Math.max(Number(args.limit) || 10, 1), 30); + if (channel) { + const space = deps.db.prepare( + "select id from chat_spaces where org_id = ? and lower(name) = lower(?) and space_type <> 'direct' limit 1", + ).get(ctx.orgId, channel) as { id: string } | undefined; + if (!space) throw new Error(`No channel named "${channel}".`); + const rows = deps.db.prepare(` + select m.body_md, + coalesce(a.name, e.display_name, m.sender_type) as sender_name, + m.thread_id + from chat_messages m + 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 m.space_id = ? and m.org_id = ? and m.deleted_at is null + order by m.created_at desc + limit ? + `).all(space.id, ctx.orgId, limit) as Array<{ body_md: string; sender_name: string; thread_id: string }>; + if (rows.length === 0) return `No recent messages in #${channel}.`; + return rows.reverse().map((m) => `[${m.sender_name}] (thread=${m.thread_id}): ${m.body_md}`).join("\n"); + } + const rows = deps.db.prepare(` + select m.body_md, + coalesce(a.name, e.display_name, m.sender_type) as sender_name, + m.thread_id, s.name as channel_name + from chat_mentions cm + join chat_messages m on m.id = cm.message_id + join chat_spaces s on s.id = m.space_id + 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 + 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 }>; + if (rows.length === 0) return "No recent mentions."; + return rows.reverse().map((m) => `[${m.sender_name} in #${m.channel_name}] (thread=${m.thread_id}): ${m.body_md}`).join("\n"); +} + function createDocumentTool(deps: AgentToolDeps, ctx: AgentToolContext, args: JsonRecord): string { const title = requireString(args.title, "title"); const docType = requireString(args.doc_type, "doc_type"); diff --git a/AgentCompany-New/src/main/services/task-prompts.ts b/AgentCompany-New/src/main/services/task-prompts.ts index 60fa0325..bc0e0d6d 100644 --- a/AgentCompany-New/src/main/services/task-prompts.ts +++ b/AgentCompany-New/src/main/services/task-prompts.ts @@ -92,8 +92,8 @@ export function buildAgentInstructions(input: AgentInstructionContext): string { `You are ${agent.name}, ${agent.title || agent.role} in ${agent.department} at ${input.companyName}. You are a real member of this company with your own judgment and voice — a colleague, not a generic assistant.`, roleVoice(input), "Work like a sharp person: take in what is actually in front of you, decide the smallest real next step, do it, then tell whoever needs to know. Do not narrate busywork.", - "Your tools are how you act: hire_agent builds your team, create_task delegates and assigns work by name, update_task_status tracks it, write_deliverable produces real files, create_document writes versioned company documents (PRDs, specs, memos, reports), and send_message is how you talk to everyone.", - "Talk like a person on a team, not a status bot: address teammates by @name, say what you genuinely think, ask when you are unsure, and reply when someone asks you something. Post where the work lives — 'engineering', 'product', or otherwise 'general' — and keep a back-and-forth in one thread by passing the thread_id a previous send_message returned. Hand work to a teammate with /handoff @Name and the file_id write_deliverable gives you.", + "Your tools are how you act: hire_agent builds your team, create_task delegates and assigns work by name, update_task_status tracks it, write_deliverable produces real files, create_document writes versioned company documents (PRDs, specs, memos, reports), read_messages catches you up on what teammates said, and send_message is how you talk to everyone.", + "Talk like a person on a team, not a status bot: address teammates by @name, say what you genuinely think, ask when you are unsure, and reply when someone asks you something. Before starting work, check your messages with read_messages to see if anyone needs you. Post where the work lives — 'engineering', 'product', or otherwise 'general' — and keep a back-and-forth in one thread by passing the thread_id a previous send_message returned. Hand work to a teammate with /handoff @Name and the file_id write_deliverable gives you.", ].join("\n"); } @@ -179,7 +179,7 @@ export function buildTaskPrompt(input: { const managerReference = input.managerName ? `${manager} (${agentMention(input.managerName)})` : manager; lines.push( "", - `Do the work and write real output with write_deliverable, then tell ${managerReference} and the team what you did and what is next — in your own words, the way you would bring a colleague up to speed, not as a form. Hand files over with /handoff and attachment_file_ids, reply in the assignment thread if you were given one, and when the work is genuinely done move the task to done.`, + `First, read_messages to check if anyone has said something you should respond to — reply to them like a colleague, not a report. Then do the work: write real output with write_deliverable, discuss decisions and blockers with ${managerReference} and the team in the relevant channel, and when the work is genuinely done move the task to done. Hand files over with /handoff and attachment_file_ids. Keep conversation going — ask questions, share your thinking, push back if something seems wrong.`, ); } return lines.join("\n");