Write thread events for taskless communication runs

recordThreadEvent used to drop the event whenever `envelope.taskId` was
null, so communication wakes produced nothing in the thread view even
while the agent actively replied to peers. Taskless runs now write into
a per-agent general thread (`ensureAgentCommunicationThread`), so the UI
surfaces peer conversations alongside task activity.

Peer-authored user turns inside the transcript used to be labelled with
the recipient's id — pi erases the sender once the message is projected.
A new `inboundCorrelations` map on `RuntimeBridge` pins each pi-message
timestamp to its source `agent_messages` row at injection time, so the
thread-event writer can recover the real author id and `senderKind` on
`message_end`. prepareRun projections (no `message_end` fires for
priorMessages) write their thread event directly as the projection lands
so they are not silently lost.
This commit is contained in:
ZacharyZhang-NY committed 2026-04-19 14:51:26 -04:00
1 parent 1959d218d9
commit 1b8e6d384a
3 files changed
+250 -41

No files matched your search

@@ -35,6 +35,42 @@ export function ensureTaskThread(db: AppDatabase, taskId: string, companyId: str
});
}
export function findAgentCommunicationThread(db: AppDatabase, agentId: string, companyId: string) {
const existing = db.listThreads({
companyId,
type: "general" as ThreadType,
relatedEntityType: "agent",
relatedEntityId: agentId,
limit: 1,
});
return existing[0] ?? null;
}
/**
* Returns (or creates) the single "general" thread that accumulates an
* agent's taskless communication activity. Task runs already write into
* a task-scoped thread; taskless communication runs would otherwise drop
* their conversation events on the floor, leaving the thread view blank
* even while the agent actively replies to peers.
*/
export function ensureAgentCommunicationThread(
db: AppDatabase,
agentId: string,
companyId: string,
agentLabel: string,
) {
const existing = findAgentCommunicationThread(db, agentId, companyId);
if (existing) return existing;
return db.createThread({
companyId,
title: `${agentLabel} · communication`,
type: "general" as ThreadType,
relatedEntityType: "agent",
relatedEntityId: agentId,
});
}
export function findApprovalThread(db: AppDatabase, approvalId: string, companyId: string) {
const existing = db.listThreads({
companyId,