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

@@ -51,7 +51,8 @@ export interface RuntimeMessageInjectorDeps {
interface PendingSteer {
recipientAgentId: string;
companyId: string;
messageId: string;
runId: string;
message: AgentMessageRecord;
timer: ReturnType<typeof setTimeout>;
}
@@ -116,7 +117,8 @@ export class RuntimeMessageInjector {
this.pending.set(correlationId, {
recipientAgentId,
companyId,
messageId: message.id,
runId: ctx.runId,
message,
timer,
});
return true;
@@ -145,17 +147,24 @@ export class RuntimeMessageInjector {
if (accepted) {
try {
this.deps.db.markAgentInboxDelivered(entry.recipientAgentId, [entry.messageId]);
this.deps.runtimeBridge.registerInboundInjection(entry.runId, entry.message);
} catch (error) {
this.deps.logger.warn(
`[injector] markAgentInboxDelivered failed for ${entry.messageId}: ${error instanceof Error ? error.message : String(error)}`,
`[injector] registerInboundInjection failed for ${entry.message.id}: ${error instanceof Error ? error.message : String(error)}`,
);
}
try {
this.deps.db.markAgentInboxDelivered(entry.recipientAgentId, [entry.message.id]);
} catch (error) {
this.deps.logger.warn(
`[injector] markAgentInboxDelivered failed for ${entry.message.id}: ${error instanceof Error ? error.message : String(error)}`,
);
}
return;
}
this.deps.logger.warn(
`[injector] steer rejected (${source}) for message ${entry.messageId} to agent ${entry.recipientAgentId}; falling back to wake + inbox projection`,
`[injector] steer rejected (${source}) for message ${entry.message.id} to agent ${entry.recipientAgentId}; falling back to wake + inbox projection`,
);
const wake = this.deps.getWakeAgent();