Class: Norn::PluginManager
- Inherits:
-
Object
- Object
- Norn::PluginManager
- Defined in:
- lib/norn/plugin_manager.rb
Class Attribute Summary collapse
-
.active_plugins ⇒ Object
Returns the value of attribute active_plugins.
Class Method Summary collapse
-
.declare_hook(event, desc:) ⇒ Object
Phase 1: Declare a new lifecycle event or custom hook in the system.
- .declared_hooks ⇒ Object
- .ensure_active_plugins! ⇒ Object
-
.register_core_hooks! ⇒ Object
Auto-wire declared standard Norn core hooks.
-
.reset! ⇒ Object
Reset all subscribers, hooks, and active plugins (useful for testing).
-
.subscribe(event, &block) ⇒ Object
Subscribe a block to a specific hook event.
-
.trigger(event, *args) ⇒ Object
Trigger an informational event (parallel/notification only).
-
.trigger_middleware(event, initial_payload) ⇒ Object
Trigger a sequential ROP middleware pipeline.
Class Attribute Details
.active_plugins ⇒ Object
Returns the value of attribute active_plugins.
14 15 16 |
# File 'lib/norn/plugin_manager.rb', line 14 def active_plugins @active_plugins end |
Class Method Details
.declare_hook(event, desc:) ⇒ Object
Phase 1: Declare a new lifecycle event or custom hook in the system
45 46 47 48 49 |
# File 'lib/norn/plugin_manager.rb', line 45 def declare_hook(event, desc:) @lock.synchronize do @declared_hooks[event.to_sym] = desc end end |
.declared_hooks ⇒ Object
51 52 53 |
# File 'lib/norn/plugin_manager.rb', line 51 def declared_hooks @declared_hooks end |
.ensure_active_plugins! ⇒ Object
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
# File 'lib/norn/plugin_manager.rb', line 16 def ensure_active_plugins! return unless @active_plugins.empty? && Norn::Plugin.registered_plugins.any? # Guard against recursive instantiation on the same thread return if Thread.current[:norn_instantiating_plugins] @lock.synchronize do return unless @active_plugins.empty? end Thread.current[:norn_instantiating_plugins] = true begin plugins = Norn::Plugin.registered_plugins.map(&:new) @lock.synchronize do @active_plugins = plugins if @active_plugins.empty? end ensure Thread.current[:norn_instantiating_plugins] = false end end |
.register_core_hooks! ⇒ Object
Auto-wire declared standard Norn core hooks
152 153 154 155 156 157 158 159 160 161 162 163 164 |
# File 'lib/norn/plugin_manager.rb', line 152 def register_core_hooks! declare_hook :on_boot, desc: "Fires when Norn finishes loading dependencies." declare_hook :on_tool_register, desc: "Fires to populate the global ToolRegistry." declare_hook :on_cli_register, desc: "Fires to let plugins register custom CLI commands." declare_hook :on_mode_register, desc: "Fires to register custom execution modes." declare_hook :on_slash_commands_register, desc: "Fires to let plugins register custom slash commands." declare_hook :on_user_input, desc: "ROP middleware. Intercepts and processes raw user input before execution." declare_hook :before_llm_call, desc: "Fires before calling LLM. Allows inspecting/mutating messages." declare_hook :after_llm_call, desc: "Fires after LLM call completes. Informational." declare_hook :on_render_response, desc: "ROP middleware. Formats/renders response text in-place." declare_hook :after_tool_call, desc: "Fires after a tool execution completes. Informational." declare_hook :after_llm_response, desc: "Fires when an LLM response with metadata is received. Informational." end |
.reset! ⇒ Object
Reset all subscribers, hooks, and active plugins (useful for testing)
143 144 145 146 147 148 149 |
# File 'lib/norn/plugin_manager.rb', line 143 def reset! @lock.synchronize do @subscribers.clear @declared_hooks.clear @active_plugins.clear end end |
.subscribe(event, &block) ⇒ Object
Subscribe a block to a specific hook event
38 39 40 41 42 |
# File 'lib/norn/plugin_manager.rb', line 38 def subscribe(event, &block) @lock.synchronize do @subscribers[event.to_sym] << block end end |
.trigger(event, *args) ⇒ Object
Trigger an informational event (parallel/notification only). Does not mutate state, and does not halt on failure.
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
# File 'lib/norn/plugin_manager.rb', line 57 def trigger(event, *args) # 1. Call legacy block-based subscribers blocks = @lock.synchronize { @subscribers[event.to_sym].dup } blocks.each do |block| begin block.call(*args) rescue => e warn "Norn Plugin Trigger Warning: #{event} subscriber failed - #{e.}" end end # Auto-instantiate registered plugins if active_plugins is empty (crucial for spec environments) ensure_active_plugins! # 2. Call matching instance methods on registered OO plugin instances @active_plugins.each do |plugin| if plugin.respond_to?(event.to_sym) begin plugin.send(event.to_sym, *args) rescue => e warn "Norn Plugin Trigger Warning: #{event} on #{plugin.class} failed - #{e.}" end end end end |
.trigger_middleware(event, initial_payload) ⇒ Object
Trigger a sequential ROP middleware pipeline. Sequentially reduces a Hash payload. If any subscriber fails or returns a Failure, the pipeline checks its recovery policy. If the policy is :recover, Norn logs a non-fatal warning and recovers the previous success payload, otherwise it derails.
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
# File 'lib/norn/plugin_manager.rb', line 87 def trigger_middleware(event, initial_payload) # 1. Accumulate all subscribers (legacy blocks + OO plugin instances) subscribers = @lock.synchronize { @subscribers[event.to_sym].dup } # Auto-instantiate registered plugins if active_plugins is empty (crucial for spec environments) ensure_active_plugins! @active_plugins.each do |plugin| subscribers << plugin if plugin.respond_to?(event.to_sym) end # 2. Sequentially reduce payload using monadic composition (Kleisli reduce) subscribers.reduce(Success(initial_payload)) do |result, subscriber| result.bind do |payload| policy = resolve_policy(subscriber, event) begin output = if subscriber.respond_to?(:call) subscriber.call(payload) else subscriber.send(event.to_sym, payload) end # Standardize returned output to a monadic Result if output.is_a?(Dry::Monads::Result) if output.failure? && policy == :recover # Non-fatal Failure: Report and recover previous success payload! report_non_fatal_failure(output.failure, event, subscriber) Success(payload) else output end elsif output.is_a?(Hash) Success(output) else # If the subscriber returned nil/nothing, preserve input payload unchanged Success(payload) end rescue => e # Catch subscriber exceptions and handle according to policy failure = Norn::FailurePayload.new(e, { event: event, payload: payload }) if policy == :recover # Non-fatal Exception: Report and recover previous success payload! report_non_fatal_failure(failure, event, subscriber) Success(payload) else # Fatal Exception: Derail immediately to Failure track Failure(failure) end end end end end |