Class: Collavre::AiAgent::SessionProvisioner
- Inherits:
-
Object
- Object
- Collavre::AiAgent::SessionProvisioner
- Defined in:
- app/services/collavre/ai_agent/session_provisioner.rb
Overview
Provisions the identities a Claude Code registration needs:
- one shared ai_user per (human, agent_name) — the Agent identity
- one Topic per (agent, session_id) — the Session identity
Extracted from Api::V1::AgentsController#register so the action stays a thin orchestration of provisioning + inbox share. Behavior is identical to the inlined controller methods it replaces.
Instance Method Summary collapse
-
#find_or_create_agent(agent_name) ⇒ Object
One ai_user per (user, agent_name) so a human's sessions share one Agent identity.
-
#find_or_create_topic(inbox, ai_user, session_id, session_label) ⇒ Object
One Topic per (agent, session_id).
-
#initialize(user) ⇒ SessionProvisioner
constructor
A new instance of SessionProvisioner.
Constructor Details
#initialize(user) ⇒ SessionProvisioner
Returns a new instance of SessionProvisioner.
13 14 15 |
# File 'app/services/collavre/ai_agent/session_provisioner.rb', line 13 def initialize(user) @user = user end |
Instance Method Details
#find_or_create_agent(agent_name) ⇒ Object
One ai_user per (user, agent_name) so a human's sessions share one Agent identity. Same agent_name re-registering reuses the existing row — idempotent retries (and every additional session) don't proliferate agents.
Returns nil when a row with the deterministic email already exists but is owned by someone else or isn't a Claude Channel ai_user. The email format is human-derivable (user.id + slug + digest), so a foreign row could be planted by signup/import; silently reusing it would attach the caller's inbox feedback share to that foreign User and leave the plugin's AgentChannel subscription rejected on ownership mismatch. Caller renders 409 in that case.
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
# File 'app/services/collavre/ai_agent/session_provisioner.rb', line 29 def find_or_create_agent(agent_name) email = session_agent_email(agent_name) existing = User.find_by(email: email) return verified_agent(existing) if existing # routing_expression: nil so the new ai_user is not matchable until the # client claims the per-agent stream via AgentChannel. See the comment on # verified_agent. User.create!( email: email, name: "Claude Channel (#{agent_name})", password: SecureRandom.hex(32), llm_vendor: "anthropic", llm_model: "claude-code", created_by_id: @user.id, searchable: false, routing_expression: nil ) rescue ActiveRecord::RecordNotUnique # A concurrent registration for the same (user, agent_name) won the # users.email unique race. The desired row now exists — re-find and # re-verify ownership instead of surfacing a 500 that aborts one of the # two simultaneously launching plugin instances. verified_agent(User.find_by(email: email)) end |
#find_or_create_topic(inbox, ai_user, session_id, session_label) ⇒ Object
One Topic per (agent, session_id). On re-register (including --resume from the same cwd, which yields the same session_id) the existing topic is reused — even if archived — so the conversation persists instead of orphaning. A fresh session gets a new topic under the same shared agent, which is how one agent fans out to many sessions.
61 62 63 64 65 66 67 68 69 70 71 72 |
# File 'app/services/collavre/ai_agent/session_provisioner.rb', line 61 def find_or_create_topic(inbox, ai_user, session_id, session_label) existing = inbox.topics.find_by(primary_agent_id: ai_user.id, session_id: session_id) return existing if existing label = session_label.to_s.strip.presence || session_id inbox.topics.create!( name: unique_topic_name(inbox, "Claude #{label}"), user: @user, primary_agent_id: ai_user.id, session_id: session_id ) end |