Class: Norn::Mode
- Inherits:
-
Object
- Object
- Norn::Mode
- Defined in:
- lib/norn/mode.rb
Direct Known Subclasses
Constant Summary collapse
- ABSTRACT_METHODS =
[:interactive?, :allowed_capabilities, :instructions, :banner_name]
Instance Attribute Summary collapse
-
#input ⇒ Object
readonly
Returns the value of attribute input.
-
#messages ⇒ Object
readonly
Returns the value of attribute messages.
-
#output ⇒ Object
readonly
Returns the value of attribute output.
Instance Method Summary collapse
- #allowed_capabilities ⇒ Object
- #banner_name ⇒ Object
-
#compile_system_prompt ⇒ Object
Dynamically compiles the system prompt by aggregating sandbox configs, mode-level instructions, and LLM directives registered by all authorized tools.
-
#execute_tool(tool_name, args) ⇒ Object
Secure gatekeeper to execute registered tools safely under capability & danger policies.
-
#initialize(input: $stdin, output: $stdout) ⇒ Mode
constructor
A new instance of Mode.
- #instructions ⇒ Object
- #interactive? ⇒ Boolean
-
#start(prompt = nil) ⇒ Object
The master orchestrator for any Mode (handles both single-turn tasks and multi-turn REPL loops).
Constructor Details
#initialize(input: $stdin, output: $stdout) ⇒ Mode
Returns a new instance of Mode.
13 14 15 16 17 |
# File 'lib/norn/mode.rb', line 13 def initialize(input: $stdin, output: $stdout) @input = input @output = output @messages = [] end |
Instance Attribute Details
#input ⇒ Object (readonly)
Returns the value of attribute input.
9 10 11 |
# File 'lib/norn/mode.rb', line 9 def input @input end |
#messages ⇒ Object (readonly)
Returns the value of attribute messages.
9 10 11 |
# File 'lib/norn/mode.rb', line 9 def @messages end |
#output ⇒ Object (readonly)
Returns the value of attribute output.
9 10 11 |
# File 'lib/norn/mode.rb', line 9 def output @output end |
Instance Method Details
#allowed_capabilities ⇒ Object
27 28 29 |
# File 'lib/norn/mode.rb', line 27 def allowed_capabilities raise NotImplementedError, "#{self.class} must implement #allowed_capabilities" end |
#banner_name ⇒ Object
35 36 37 |
# File 'lib/norn/mode.rb', line 35 def raise NotImplementedError, "#{self.class} must implement #banner_name" end |
#compile_system_prompt ⇒ Object
Dynamically compiles the system prompt by aggregating sandbox configs, mode-level instructions, and LLM directives registered by all authorized tools.
254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 |
# File 'lib/norn/mode.rb', line 254 def compile_system_prompt system_parts = [] # 1. Sandbox Info (Strictly environment metadata) system_parts << Norn.config.sandbox_info if Norn.config.sandbox_info && !Norn.config.sandbox_info.empty? # 2. Compile instructions segment inst_conf = Norn.config.instructions || {} # Determine base instructions base_inst = inst_conf[:base] || instructions # Prepend rules if inst_conf[:prepend].is_a?(Array) inst_conf[:prepend].each do |p| system_parts << p if p && !p.strip.empty? end end # Base instructions system_parts << base_inst if base_inst && !base_inst.empty? # Append rules from config if inst_conf[:append].is_a?(Array) inst_conf[:append].each do |a| system_parts << a if a && !a.strip.empty? end end # 3. Disclose Available Skills (Catalog) available_xml = Norn::SkillRegistry.generate_catalog_xml if available_xml && !available_xml.empty? catalog_section = <<~TEXT #{available_xml} You have access to specialized skills. If a skill is relevant to the user's task, you MUST activate it first to see its complete instructions and resources. To activate a skill, call the 'activate_skill' tool with its name. TEXT system_parts << catalog_section end # 4. Inject Active Skills Instructions active_skills = Norn::SkillRegistry.active_skills if active_skills.any? active_parts = ["ACTIVE SKILLS AND INSTRUCTIONS:"] active_skills.each do |skill| active_parts << "=== #{skill.name} ===\n#{skill.instructions}" end system_parts << active_parts.join("\n\n") end # Gather tools authorized under the active mode's permitted capabilities = Norn::ToolRegistry.registered_tools.select do |tool| (tool.required_capabilities - allowed_capabilities).empty? end # Retrieve and format all plugin/tool-defined prompt directions tool_directives = .map(&:system_instructions).compact if tool_directives.any? compiled_directives = "TOOL EXECUTION DIRECTIVES:\n" + tool_directives.map { |d| "⢠#{d}" }.join("\n") system_parts << compiled_directives end system_parts.reject(&:empty?).join("\n\n") end |
#execute_tool(tool_name, args) ⇒ Object
Secure gatekeeper to execute registered tools safely under capability & danger policies
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 |
# File 'lib/norn/mode.rb', line 121 def execute_tool(tool_name, args) # Ensure arguments are fully symbolized for consistent key access throughout the gatekeeper and diff engine args = symbolize_keys(args) tool = Norn::ToolRegistry.resolve(tool_name) if tool.nil? return Success("Error: Tool '#{tool_name}' not found in registry.") end # Pre-flight Session Approval Evaluation session = nil begin session = Norn["session"] rescue => e # Ignore container errors if session not registered end if session && tool.session_approved?(session, args) @output.puts "ā” Session approved: #{tool.name}" return proceed_to_execution(tool, args) end # Load our newly encapsulated UI Gatekeeper service require "norn/ui/gatekeeper" gatekeeper = Norn::UI::Gatekeeper.new(input: @input, output: @output) # 1. Capability Authorization Check required_caps = tool.capabilities_for(args) missing_caps = required_caps - allowed_capabilities unless missing_caps.empty? # Handle capability escalation if interactive? = gatekeeper.(tool_name, missing_caps, args) if @output.puts "š Operation authorized by user." allowed_capabilities.concat(missing_caps) else @output.puts "š« Operation blocked by user." return handle_gatekeeper_fallback(tool, args, gatekeeper) end else # Non-interactive block-by-default on capability violations @output.puts "\nā Security Block: Tool '#{tool_name}' requires #{missing_caps.join(', ')} which is unauthorized in non-interactive mode." return Failure(Norn::FailurePayload.new( Norn::ToolError.new("Security violation: Unauthorized capabilities requested in non-interactive mode: #{missing_caps.join(', ')}."), { tool: tool_name, missing_capabilities: missing_caps, mode: self.class.name } )) end end # 2. In-Flight Interactive Danger Guards if tool.dangerous?(args) && interactive? = gatekeeper.(tool, args) if == :session @output.puts "ā” Action authorized and approved for the rest of this session." if session session.append(:session_approvals, tool.session_approval_pattern(args)) end elsif @output.puts "š Action authorized." else @output.puts "š« Action aborted by user." return handle_gatekeeper_fallback(tool, args, gatekeeper) end end # 3. Proceed to safe execution proceed_to_execution(tool, args) end |
#instructions ⇒ Object
31 32 33 |
# File 'lib/norn/mode.rb', line 31 def instructions raise NotImplementedError, "#{self.class} must implement #instructions" end |
#interactive? ⇒ Boolean
23 24 25 |
# File 'lib/norn/mode.rb', line 23 def interactive? raise NotImplementedError, "#{self.class} must implement #interactive?" end |
#start(prompt = nil) ⇒ Object
The master orchestrator for any Mode (handles both single-turn tasks and multi-turn REPL loops)
40 41 42 |
# File 'lib/norn/mode.rb', line 40 def start(prompt = nil) raise NotImplementedError, "#{self.class} must implement #start" end |