/** * subagent-lifecycle-events.ts — Subscribe to @gotgenes/pi-subagents' child * lifecycle events and keep the SubagentSessionRegistry in sync. * * @gotgenes/pi-subagents publishes its child-execution lifecycle on the Pi * event bus (ADR 0002): it no longer calls this package's service directly. * We register the child on `session-created` and unregister it on `disposed`. * * The channel names and payload shapes are declared independently here (the two * packages must not depend on each other under jiti) and MUST match the * publisher in `@gotgenes/pi-subagents` (`src/lifecycle/child-lifecycle.ts`). * * The `session-created` handler MUST stay synchronous: the core emits it on the * same synchronous call stack immediately before `bindExtensions()`, and the * event bus dispatches listeners synchronously, so a synchronous handler lands * the registry entry before binding proceeds. Introducing an `await` before * `registry.register(...)` would break the pre-bind ordering. */ import type { SubagentSessionRegistry } from "./subagent-registry"; /** Emitted by the core after session creation, before `bindExtensions()`. */ export const SUBAGENT_CHILD_SESSION_CREATED = "subagents:child:session-created"; /** Emitted by the core in the run's `finally` (success and error). */ export const SUBAGENT_CHILD_DISPOSED = "subagents:child:disposed"; /** Minimal event-bus surface this module needs (subscribe only). */ interface LifecycleEventBus { on(channel: string, handler: (data: unknown) => void): () => void; } /** Fields read from the `session-created` payload (ISP). */ interface ChildSessionCreatedEvent { /** Child session id — the registry key. Must match the publisher. */ sessionId: string; parentSessionId?: string; } /** Fields read from the `disposed` payload (ISP). */ interface ChildDisposedEvent { /** Child session id — the registry key. Must match the publisher. */ sessionId: string; } /** * Subscribe to the subagent child lifecycle. * * @returns an unsubscribe that detaches both handlers (call during * `session_shutdown`). */ export function subscribeSubagentLifecycle( events: LifecycleEventBus, registry: SubagentSessionRegistry, ): () => void { const unsubCreated = events.on(SUBAGENT_CHILD_SESSION_CREATED, (data) => { const event = data as ChildSessionCreatedEvent; registry.register(event.sessionId, { parentSessionId: event.parentSessionId, }); }); const unsubDisposed = events.on(SUBAGENT_CHILD_DISPOSED, (data) => { const event = data as ChildDisposedEvent; registry.unregister(event.sessionId); }); return () => { unsubCreated(); unsubDisposed(); }; }