navigateTree() was unconditionally calling buildSessionContext() twice —
once to build stateContext for agent.replaceMessages, and again after
the session_tree emit to capture any hook-driven mutations. 6 of 7
callers discard result.sessionContext, so they paid an O(N) walk for
nothing.
Gate both the emit and the post-emit rebuild behind
extensionRunner.hasHandlers("session_tree"), mirroring the
session_before_tree guard at the top of the same function. When no
handlers are registered, stateContext is returned directly (the
intermediate ops don't mutate SessionManager).