Class: Norn::Mode

Inherits:
Object
  • Object
show all
Defined in:
lib/norn/mode.rb

Constant Summary collapse

ABSTRACT_METHODS =
[:interactive?, :allowed_capabilities, :instructions, :banner_name]

Instance Attribute Summary collapse

Instance Method Summary collapse

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

#inputObject (readonly)

Returns the value of attribute input.



9
10
11
# File 'lib/norn/mode.rb', line 9

def input
  @input
end

#messagesObject (readonly)

Returns the value of attribute messages.



9
10
11
# File 'lib/norn/mode.rb', line 9

def messages
  @messages
end

#outputObject (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_capabilitiesObject

Raises:

  • (NotImplementedError)


27
28
29
# File 'lib/norn/mode.rb', line 27

def allowed_capabilities
  raise NotImplementedError, "#{self.class} must implement #allowed_capabilities"
end

Raises:

  • (NotImplementedError)


35
36
37
# File 'lib/norn/mode.rb', line 35

def banner_name
  raise NotImplementedError, "#{self.class} must implement #banner_name"
end

#compile_system_promptObject

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
  authorized_tools = 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 = authorized_tools.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?
      authorized = gatekeeper.authorize_capabilities(tool_name, missing_caps, args)
      if authorized
        @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?
    authorized = gatekeeper.authorize_danger(tool, args)
    if authorized == :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 authorized
      @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

#instructionsObject

Raises:

  • (NotImplementedError)


31
32
33
# File 'lib/norn/mode.rb', line 31

def instructions
  raise NotImplementedError, "#{self.class} must implement #instructions"
end

#interactive?Boolean

Returns:

  • (Boolean)

Raises:

  • (NotImplementedError)


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