feat(messaging): route messages to live runs via pi steer
Incoming messages (human → agent, agent → agent, channel broadcast) persisted to the inbox and were only projected into the recipient's transcript on the next `prepareRun`. When the recipient was already running, every message waited for the next wake — a Slack-like system operating at heartbeat cadence, not in real time. `RuntimeMessageInjector` closes the gap: it looks up the recipient's active run through `RuntimeBridge.findActiveRunForAgent`, projects the persisted `AgentMessageRecord` via the existing inbox-projection helper, and posts a `steer-run` ParentMessage into the worker. The worker's runner enqueues the projection on the Agent's steering queue; pi drains it at the next turn boundary and emits the usual `message_end` → `persistMessage` sequence, so the session store shadow is preserved without double-writing. `dispatchAgentMessage` gains an optional `injectRuntimeMessage` hook used by every API-side dispatch. Injection succeeds → we mark the inbox row delivered and skip `wakeAgentIfPossible`. Injection fails (no active run) → the legacy inbox + wake path still fires, so sleeping agents continue to receive their messages on the next turn. The injector is owned by the service graph alongside `workerService` and `piRuntimeRef`, which gives it both the worker postMessage channel and the RuntimeBridge active-run map without additional coupling. Internal dispatch call sites (sweepStaleReviews, automation actions) leave the hook unset; those system messages target idle agents by design and do not need the runtime path.
This commit is contained in:
1 parent
795008f56b
commit
5ee3d1394a
7 files changed
+151
-2
No files matched your search
@@ -109,6 +109,7 @@ export function registerMessageRoutes(server: AgentApiServer) {
|
||||
if (!agentId) return null;
|
||||
return onWakeAgent(agentId, cid, trigger);
|
||||
},
|
||||
injectRuntimeMessage: server.callbacks.injectRuntimeMessage,
|
||||
onHumanMessage: server.callbacks.onHumanMessage,
|
||||
},
|
||||
parsed.data,
|
||||
|
||||
@@ -2,7 +2,7 @@ import { createServer, type IncomingMessage, type ServerResponse } from "node:ht
|
||||
import log from "electron-log/main.js";
|
||||
import { validateAgentToken, type TokenPayload } from "../agent-auth";
|
||||
import type { AppDatabase } from "../database";
|
||||
import type { DesktopEvent, SectionId } from "@shared/types";
|
||||
import type { AgentMessageRecord, DesktopEvent, SectionId } from "@shared/types";
|
||||
|
||||
/**
|
||||
* Decode an HTTP request body to a UTF-8 string, handling the common Windows
|
||||
@@ -64,6 +64,13 @@ export interface ApiServerEventCallbacks {
|
||||
onDocumentCreated: (companyId: string, authorAgentId: string | null, projectId: string | null, documentId: string, documentTitle: string, documentType: string, taskId: string | null) => void;
|
||||
onAutoAssignTask: (taskId: string, companyId: string) => string | null;
|
||||
onHumanMessage: (companyId: string, toAgentId?: string | null, threadId?: string | null, waitStateId?: string | null) => void;
|
||||
/**
|
||||
* Tries to hand a freshly persisted message to the recipient's live
|
||||
* pi run via the runtime steer channel. Returns true when the worker
|
||||
* accepted the steer; dispatch should then skip the regular wake
|
||||
* pipeline for this recipient.
|
||||
*/
|
||||
injectRuntimeMessage: (recipientAgentId: string, companyId: string, message: AgentMessageRecord) => boolean;
|
||||
emitEvent: (event: DesktopEvent) => void;
|
||||
notify: (options: { title: string; body: string; urgency: "critical" | "informational"; navigation?: { section: SectionId; entityId?: string }; batchKey?: string }) => void;
|
||||
}
|
||||
@@ -145,6 +152,7 @@ export class AgentApiServer {
|
||||
onDocumentCreated: callbacks?.onDocumentCreated ?? (() => {}),
|
||||
onAutoAssignTask: callbacks?.onAutoAssignTask ?? (() => null),
|
||||
onHumanMessage: callbacks?.onHumanMessage ?? (() => {}),
|
||||
injectRuntimeMessage: callbacks?.injectRuntimeMessage ?? (() => false),
|
||||
emitEvent: callbacks?.emitEvent ?? (() => {}),
|
||||
notify: callbacks?.notify ?? (() => {}),
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user