diff --git a/crates/op-orchestrator/src/run.rs b/crates/op-orchestrator/src/run.rs index a719fcc6e..7c533a5a9 100644 --- a/crates/op-orchestrator/src/run.rs +++ b/crates/op-orchestrator/src/run.rs @@ -542,6 +542,14 @@ impl Orchestrator { }); } + // Let the reveal sweep FINISH before cleanup restructures the tree: + // `finalize_design`'s ReplaceSubtree allocates fresh ids that were + // never registered with the reveal overlay, so a section still + // mid-animation snaps in all at once and the agent cursor loses its + // target (measured: the tail of a run popped in "一口气" while + // earlier sections streamed). Worker-thread wait, abort-aware. + crate::subagent::wait_for_reveal_drain(self.agent_indicator_epoch, abort); + // -- 阶段 4:清理 --(有内容才跑;空 root 无可清理) // Append mode (skip_root_insertion): scope cleanup to ONLY the roots // this run inserted (post-remap ids from each outcome) so pre-existing diff --git a/crates/op-orchestrator/src/subagent.rs b/crates/op-orchestrator/src/subagent.rs index 9e0929b81..07e86ddb6 100644 --- a/crates/op-orchestrator/src/subagent.rs +++ b/crates/op-orchestrator/src/subagent.rs @@ -332,6 +332,32 @@ pub(crate) async fn run_subtask_with_reveal_at( } } +/// Block (worker thread, abort-aware) until the reveal overlay's scheduled +/// sweep for `epoch` has finished playing. The finalize passes swap subtrees +/// via `ReplaceSubtree`, whose fresh ids were never registered with the +/// overlay — restructuring mid-sweep snaps the still-animating tail of the +/// design in at once and orphans the agent cursor. Capped so a stuck clock +/// can't hang the run. +pub(crate) fn wait_for_reveal_drain(epoch: Option, abort: &crate::types::AbortFlag) { + let Some(epoch) = epoch else { + return; + }; + let cap = reveal_now_millis().saturating_add(15_000); + loop { + if abort.is_set() { + return; + } + let now = reveal_now_millis(); + let Some(end) = op_editor_core::agent_indicators::latest_reveal_end_ms(epoch) else { + return; + }; + if now >= end || now >= cap { + return; + } + std::thread::sleep(std::time::Duration::from_millis((end - now).min(120))); + } +} + pub(crate) fn reveal_now_millis() -> u64 { SystemTime::now() .duration_since(UNIX_EPOCH)