feat(03-01): add searchMessages backend with LIKE query, IPC channel, preload bridge, and tests

- Add searchMessages method to AppDatabase with LIKE query, wildcard escaping, channel scoping, ORDER BY created_at DESC with limit
- Add searchMessages IPC channel and searchMessagesSchema to contracts.ts
- Register IPC handler in ipc-agent-coordination-handlers.ts with Zod validation
- Wire searchMessages through preload bridge
- Add 8 unit tests covering body/subject search, wildcard escaping, channel scoping, empty results, limit, and ordering
This commit is contained in:
ZacharyZhang-NY committed 2026-03-22 14:29:59 -04:00
1 parent 14da6b5ede
commit ce2c3be065
5 files changed
+251 -1

No files matched your search

+20
View File
@@ -2675,6 +2675,26 @@ export class AppDatabase {
return limit > 0 ? messages.slice(-limit) : messages;
}
searchMessages(companyId: string, query: string, opts?: {
channel?: string;
channelTargetId?: string | null;
limit?: number;
}): AgentMessageRecord[] {
const escaped = query.replace(/[%_]/g, "\\$&");
const pattern = `%${escaped}%`;
let sql = `SELECT id, company_id AS companyId, from_agent_id AS fromAgentId,
to_agent_id AS toAgentId, channel, channel_target_id AS channelTargetId,
subject, body, priority, read_at AS readAt, parent_message_id AS parentMessageId,
attachments_json AS attachmentsJson, created_at AS createdAt
FROM agent_messages WHERE company_id = ? AND (subject LIKE ? ESCAPE '\\' OR body LIKE ? ESCAPE '\\')`;
const params: SQLInputValue[] = [companyId, pattern, pattern];
if (opts?.channel) { sql += " AND channel = ?"; params.push(opts.channel); }
if (opts?.channelTargetId !== undefined && opts?.channelTargetId !== null) { sql += " AND channel_target_id = ?"; params.push(opts.channelTargetId); }
sql += " ORDER BY created_at DESC LIMIT ?";
params.push(opts?.limit ?? 50);
return this.readAll<AgentMessageRecord>(sql, ...params);
}
markMessageRead(messageId: string, companyId: string) {
this.markAgentMessageRead(messageId, companyId, BOARD_READER_ID);
}
@@ -4,6 +4,7 @@ import {
autoAssignSchema,
markMessageReadSchema,
messageQuerySchema,
searchMessagesSchema,
} from "@shared/contracts";
import { fail, ok, type RegisterDomainHandlersDependencies } from "./ipc-domain-common";
import { dispatchAgentMessage } from "./message-dispatch";
@@ -94,6 +95,17 @@ export function registerAgentCoordinationHandlers({
return ok(true);
});
registerHandle(IPC_CHANNELS.searchMessages, async (_event, payload) => {
const parsed = searchMessagesSchema.parse(payload);
const companyError = ensureCompanyExists(parsed.companyId);
if (companyError) return companyError;
return ok(db.searchMessages(parsed.companyId, parsed.query, {
channel: parsed.channel,
channelTargetId: parsed.channelTargetId,
limit: parsed.limit,
}));
});
registerHandle(IPC_CHANNELS.autoAssignTask, async (_event, payload) => {
const parsed = autoAssignSchema.parse(payload);
const companyError = ensureCompanyExists(parsed.companyId);