Class: PermissionRequest

Inherits:
ApplicationRecord show all
Defined in:
app/models/permission_request.rb

Overview

One "may I run this?" from an agent in ask-first mode (ยง permissions). The claude process is ALIVE and blocked while this is pending โ€” the MCP shim polls for the verdict and the poll keeps the run's heartbeat fresh.

Constant Summary collapse

STATUSES =
%w[pending allowed denied auto_denied].freeze

Instance Method Summary collapse

Instance Method Details

#auto_allowed?Boolean

Does an existing pattern (column pre-approved list or earlier allow-for-this-run) already cover this request?

Returns:

  • (Boolean)


46
47
48
49
50
51
52
53
# File 'app/models/permission_request.rb', line 46

def auto_allowed?
  patterns = Array(run.card.column.policy["allowed_commands"]) + Array(run.briefing["allowed_patterns"])
  if tool_name == "Bash"
    patterns.any? { |p| p.present? && command.to_s.start_with?(p.strip) }
  else
    patterns.map(&:strip).include?(tool_name)
  end
end

#describeObject



15
16
17
# File 'app/models/permission_request.rb', line 15

def describe
  tool_name == "Bash" && command.present? ? "`#{command.truncate(120)}`" : tool_name
end

#pending?Boolean

Returns:

  • (Boolean)


13
# File 'app/models/permission_request.rb', line 13

def pending? = status == "pending"

#resolve!(verdict, message: nil, always: false, actor: "user") ⇒ Object

The single verdict path โ€” buttons, chat replies, and the shim's timeout all land here. always remembers the pattern for the rest of the run.



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'app/models/permission_request.rb', line 21

def resolve!(verdict, message: nil, always: false, actor: "user")
  return unless pending?

  update!(status: verdict, message: message.presence, answered_at: Time.current)
  card = run.card

  if verdict == "allowed" && always
    pattern = tool_name == "Bash" ? command.to_s : tool_name
    patterns = Array(run.briefing["allowed_patterns"]) | [pattern]
    run.update!(briefing: run.briefing.merge("allowed_patterns" => patterns))
  end

  text =
    case verdict
    when "allowed"     then "๐Ÿ” Approved #{describe}#{" โ€” and anything matching it for the rest of this run" if always}"
    when "auto_denied" then "๐Ÿ” #{describe} auto-denied (no answer in time) โ€” the agent was told to adapt or park"
    else                    "๐Ÿ” Denied #{describe}#{": โ€œ#{message}โ€" if message.present?}"
    end
  card.log!("status_change", actor: actor, run: run, text: text)
  card.update!(status: "working") if card.needs_input? && run.permission_requests.pending.none?
  broadcast_resolution
end