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
@@ -6,6 +6,7 @@ import { BrowserManager } from "./browser-manager";
|
||||
import type { DesktopResult } from "@shared/contracts";
|
||||
import { createConnectorHealthService } from "./connector-health-service";
|
||||
import { createPiRuntimeServices, type PiRuntimeServices } from "./service-graph-pi-runtime";
|
||||
import { RuntimeMessageInjector } from "./agent-runtime";
|
||||
import type { DesktopEvent } from "@shared/types";
|
||||
import type { NotificationOptions } from "./notification-service";
|
||||
import type { createAutomationService } from "./automation-service";
|
||||
@@ -98,6 +99,7 @@ export function createServiceGraph(deps: ServiceGraphDependencies) {
|
||||
let updaterService: ReturnType<typeof createUpdaterService> | null = null;
|
||||
let appLifecycleService: ReturnType<typeof createLifecycle> | null = null;
|
||||
let leaf: ReturnType<typeof createLeafServices> | null = null;
|
||||
let runtimeMessageInjector: RuntimeMessageInjector | null = null;
|
||||
const piRuntimeRef: { current: PiRuntimeServices | null } = { current: null };
|
||||
|
||||
// Late-bound slots resolve circular construction: worker needs orchestration
|
||||
@@ -171,6 +173,17 @@ export function createServiceGraph(deps: ServiceGraphDependencies) {
|
||||
piRuntimeRef,
|
||||
});
|
||||
|
||||
runtimeMessageInjector = new RuntimeMessageInjector({
|
||||
db,
|
||||
runtimeBridge: piRuntimeRef.current.runtimeBridge,
|
||||
postToWorker: (message) => {
|
||||
const worker = workerService?.getWorker();
|
||||
if (!worker) throw new Error("Worker is not initialised — cannot deliver steer message.");
|
||||
worker.postMessage(message);
|
||||
},
|
||||
logger,
|
||||
});
|
||||
|
||||
orchestrationService = buildOrchestrationService({
|
||||
db,
|
||||
logger,
|
||||
@@ -233,6 +246,7 @@ export function createServiceGraph(deps: ServiceGraphDependencies) {
|
||||
orchestrationService = null;
|
||||
updaterService = null;
|
||||
appLifecycleService = null;
|
||||
runtimeMessageInjector = null;
|
||||
leaf = null;
|
||||
},
|
||||
lateBindings: [
|
||||
@@ -259,5 +273,6 @@ export function createServiceGraph(deps: ServiceGraphDependencies) {
|
||||
getOrCreateBudgetEnforcementService: () => requireLeaf().budgetEnforcementService,
|
||||
getOrCreateSessionManagementService: () => requireLeaf().sessionManagementService,
|
||||
getPiRuntime: () => piRuntimeRef.current,
|
||||
getRuntimeMessageInjector: () => runtimeMessageInjector,
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user