77//! session links, REST seed rows, and in-band registrations — enters through
88//! the single [`OrchestrationChildTracker::observe_child`] entry point.
99//!
10+ //! `FamilyDrainMode` captures the one behavioral axis between orchestrator
11+ //! and shared-session observer: who pushes the server cursor and who receives
12+ //! the parent's own inbox events. It says nothing about authenticated
13+ //! ownership, permissions, or pane capability.
14+ //!
1015//! Pill-bar broadcasts (`ChildSpawned` / `ChildStatusChanged`) are emitted
1116//! via the `ctx` so downstream views can react without polling.
1217//!
@@ -50,9 +55,11 @@ pub enum ChildSignal {
5055 Lifecycle ( api:: LifecycleEventType ) ,
5156 /// A REST seed row (cold-start seed / restore fetch). Boxed because the
5257 /// task row dwarfs the other variants.
58+ #[ allow( dead_code) ]
5359 Seeded ( Box < AmbientAgentTask > ) ,
5460 /// A child created in this process, already backed by a local
5561 /// conversation that its executor hydrates.
62+ #[ allow( dead_code) ]
5663 Registered ,
5764}
5865
@@ -67,6 +74,7 @@ pub struct TrackedChild {
6774 /// `true` for every placeholder the tracker materializes on behalf of a
6875 /// run hosted elsewhere. `false` only for in-band children, which already
6976 /// own a real local conversation and are tracked for status only.
77+ #[ allow( dead_code) ]
7078 pub is_remote_child : bool ,
7179}
7280
@@ -160,8 +168,9 @@ impl OrchestrationChildTracker {
160168
161169 /// Discovery via `child_agent_started`. Idempotent: an already-known child
162170 /// only continues hydrating, while the first sighting of a genuinely new
163- /// out-of-band run kicks off a single metadata fetch to create its
164- /// placeholder.
171+ /// out-of-band run inserts a pending `TrackedChild` immediately — before
172+ /// the async metadata fetch completes — so later `Lifecycle` and
173+ /// `SessionLinked` signals see a known child and are processed.
165174 fn apply_started (
166175 & mut self ,
167176 task_id : AmbientAgentTaskId ,
@@ -174,9 +183,24 @@ impl OrchestrationChildTracker {
174183 self . maybe_request_pane_materialization ( task_id, ctx) ;
175184 return ;
176185 }
177- // New out-of-band child: start (or dedupe) discovery. The placeholder
178- // is created when the fetch completes (a cache hit resolves inline; an
179- // in-flight fetch resolves on a later re-drive).
186+ // Insert a placeholder TrackedChild immediately so lifecycle and
187+ // session-linked signals that arrive before the async metadata fetch
188+ // completes see tracker_known=true. Any session_id that arrived
189+ // before this signal is also applied now.
190+ let session_id = self . pending_session_ids . remove ( & task_id) ;
191+ self . insert_child (
192+ task_id,
193+ run_id,
194+ TrackedChild {
195+ session_id,
196+ last_state : None ,
197+ pane_materialized : false ,
198+ is_remote_child : true ,
199+ } ,
200+ ctx,
201+ ) ;
202+ // Also kick the metadata fetch to get real task state, session_id,
203+ // and conversation token for transcript / live-attach decisions.
180204 self . spawn_metadata_fetch ( task_id, run_id, ctx) ;
181205 }
182206
@@ -192,7 +216,8 @@ impl OrchestrationChildTracker {
192216 kind : api:: LifecycleEventType ,
193217 ctx : & mut ModelContext < OrchestrationEventStreamer > ,
194218 ) {
195- if self . children . contains_key ( & task_id) {
219+ let tracker_known = self . children . contains_key ( & task_id) ;
220+ if tracker_known {
196221 let status = conversation_status_from_lifecycle_event_type ( kind) ;
197222 // Write status through immediately so the pill bar badge reflects
198223 // the lifecycle transition without waiting for a redraw cycle.
@@ -228,11 +253,17 @@ impl OrchestrationChildTracker {
228253 self . maybe_request_pane_materialization ( task_id, ctx) ;
229254 return ;
230255 }
231- // Lifecycle for an unknown run: only self-heal a real discovery miss,
232- // not a run whose fetch is already in flight.
233- if !self . metadata_fetches . contains ( run_id) {
234- self . spawn_metadata_fetch ( task_id, run_id, ctx) ;
235- }
256+ // Lifecycle for an unknown run is a complete discovery backstop:
257+ // insert once (emitting ChildSpawned), start/dedupe metadata hydration,
258+ // and publish the status immediately. This handles a missed or
259+ // reordered child_agent_started event without a tracker-only ghost.
260+ self . apply_started ( task_id, run_id, ctx) ;
261+ let status = conversation_status_from_lifecycle_event_type ( kind) ;
262+ ctx. emit ( OrchestrationEventStreamerEvent :: ChildStatusChanged {
263+ parent_task_id : self . parent_task_id ,
264+ run_id : run_id. to_string ( ) ,
265+ status,
266+ } ) ;
236267 }
237268
238269 /// Registers a child created in this process against its existing local
0 commit comments