Gate inbox delivery on worker-side steer ACK

Replace the fire-and-forget steer with a correlated ACK round-trip so main
only marks an inbox row delivered once the worker confirms the message
landed on the live Agent. Steer against a run that is still in the
`preparing` window or has already started `closing` now falls back to the
wake + inbox projection path instead of being silently dropped.

Worker publishes `run-agent-state` transitions at Agent construction and
teardown; main tracks these on `ActiveRunContext.state` and refuses to
steer runs that are not `agent_active`. A rejected or timed-out ACK
triggers `wakeAgentIfPossible` so the next `prepareRun` replays the row.
This commit is contained in:
ZacharyZhang-NY committed 2026-04-19 14:38:04 -04:00
1 parent ad9205fcc9
commit e917c614c9
12 files changed
+264 -57

No files matched your search

@@ -7,6 +7,7 @@ import type { createBudgetEnforcementService } from "../budget-enforcement-servi
import type { createSessionManagementService } from "../session-management-service";
import type { PiRuntimeServices } from "../service-graph-pi-runtime";
import type { NotificationOptions } from "../notification-service";
import type { RuntimeMessageInjector } from "../agent-runtime";
import type { LateBinding } from "./late-binding";
export interface WorkerServiceDeps {
@@ -29,6 +30,12 @@ export interface WorkerServiceDeps {
budgetService: ReturnType<typeof createBudgetEnforcementService>;
sessionService: ReturnType<typeof createSessionManagementService>;
piRuntimeRef: { current: PiRuntimeServices | null };
/**
* Resolver for the late-constructed runtime message injector. The worker
* message handler needs it to route `steer-ack` child messages, but the
* injector is assembled after the worker service itself.
*/
getRuntimeMessageInjector: () => RuntimeMessageInjector | null;
}
/**
@@ -52,6 +59,7 @@ export function buildWorkerService(deps: WorkerServiceDeps) {
budgetService,
sessionService,
piRuntimeRef,
getRuntimeMessageInjector,
} = deps;
const wakeAgentIfPossible = (agentId: string | null | undefined, companyId: string, trigger: string) =>
@@ -83,5 +91,6 @@ export function buildWorkerService(deps: WorkerServiceDeps) {
budgetService,
sessionService,
getRuntimeBridge: () => piRuntimeRef.current?.runtimeBridge ?? null,
getRuntimeMessageInjector,
});
}
+4
View File
@@ -26,6 +26,10 @@ export class LateBinding<T> {
return this.value;
}
isAssigned(): boolean {
return this.assigned;
}
clear(): void {
this.value = null;
this.assigned = false;