8be9c76f8c
Salvaged from PR #64188 (@Bartok9), re-reviewed against the #64229 ownership ledger (landed in #84923). Delivery parity (survived): - Module-level invoke_hook/invoke_middleware/has_hook/has_middleware lazily run plugin discovery via _delivery_manager(), so surfaces that never import model_tools (dashboards, TUI slash workers, query mode, cron, gateway platform events) deliver plugin callbacks instead of silently dropping them (#50776, #67597, #67890, #50937). - _delivery_manager() joins any in-flight background discovery first and tolerates test doubles that monkeypatch get_plugin_manager(). Symmetric force-reload (survived): - agent/shell_hooks.py gains re_register_config_hooks(); the force branch of discover_and_load() calls it after a successful sweep, restoring config.yaml shell hooks that the ledger-driven unload wiped but cannot restore (they are config-owned, not plugin-owned) (#60036). - unload(plugin=None) now sweeps pre-ledger _plugin_tool_names entries out of the process-global tools.registry, mirroring the platform-name sweep that already existed, so zombie tools cannot survive a force reload in long-lived pre-ledger processes (#60050). Superseded by the ownership ledger (dropped from #64188): - _unload_global_plugin_registrations() bulk tool/platform teardown — the ledger's reverse-order handle disposal with previous-entry restoration covers it more precisely. - tools.registry/platform_registry displaced-entry LIFO restore stacks — the ledger's restore_registration() identity-checked previous-entry restoration made them redundant. - Discovery serialization lock + double-checked singleton — main already has _discovery_lock on every discover/unload path and a keyed, lock-guarded per-home manager cache (#24714 concern is covered). Fixes tracked under #64178 (#50776, #60036, #60050, #24714, #67798, #50937, #67597, #67890, #31480 — #31480 already handled on main by _parse_hooks_block warn+suggest).