Skip to content
This repository was archived by the owner on Jul 22, 2025. It is now read-only.

Commit 491dac2

Browse files
authored
FIX: system persona state leaking between sites (#1304)
System personas leaned on reused classes, this was a problem in a multisite environement cause state, such as "enabled" ended up being reused between sites. New implementation ensures state is pristine between sites in a multisite * more handling for new superclass story * small oversight, display name should be used for display
1 parent 8b1b681 commit 491dac2

File tree

6 files changed

+45
-13
lines changed

6 files changed

+45
-13
lines changed

app/models/ai_persona.rb

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -226,12 +226,19 @@ def class_instance
226226

227227
persona_class = DiscourseAi::Personas::Persona.system_personas_by_id[self.id]
228228
if persona_class
229-
instance_attributes.each do |key, value|
230-
# description/name are localized
231-
persona_class.define_singleton_method(key) { value } if key != :description && key != :name
232-
end
233-
persona_class.define_method(:options) { options }
234-
return persona_class
229+
return(
230+
# we need a new copy so we don't leak information
231+
# across sites
232+
Class.new(persona_class) do
233+
# required for localization
234+
define_singleton_method(:to_s) { persona_class.to_s }
235+
instance_attributes.each do |key, value|
236+
# description/name are localized
237+
define_singleton_method(key) { value } if key != :description && key != :name
238+
end
239+
define_method(:options) { options }
240+
end
241+
)
235242
end
236243

237244
ai_persona_id = self.id

lib/ai_bot/playground.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -459,7 +459,7 @@ def reply_to(
459459
post_type: post_type,
460460
skip_guardian: true,
461461
custom_fields: {
462-
DiscourseAi::AiBot::POST_AI_LLM_NAME_FIELD => bot.llm.llm_model.name,
462+
DiscourseAi::AiBot::POST_AI_LLM_NAME_FIELD => bot.llm.llm_model.display_name,
463463
},
464464
)
465465

lib/personas/persona.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,8 @@ def all_available_tools
128128
end
129129

130130
def id
131-
@ai_persona&.id || self.class.system_personas[self.class]
131+
@ai_persona&.id || self.class.system_personas[self.class.superclass] ||
132+
self.class.system_personas[self.class]
132133
end
133134

134135
def tools

spec/lib/modules/ai_bot/playground_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -785,7 +785,7 @@
785785
expect(last_post.user_id).to eq(persona.user_id)
786786

787787
expect(last_post.custom_fields[DiscourseAi::AiBot::POST_AI_LLM_NAME_FIELD]).to eq(
788-
gpt_35_turbo.name,
788+
gpt_35_turbo.display_name,
789789
)
790790
end
791791

spec/lib/personas/persona_spec.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ def system_prompt
212212
SiteSetting.ai_google_custom_search_cx = "abc123"
213213

214214
# should be ordered by priority and then alpha
215-
expect(DiscourseAi::Personas::Persona.all(user: user)).to eq(
215+
expect(DiscourseAi::Personas::Persona.all(user: user).map(&:superclass)).to eq(
216216
[
217217
DiscourseAi::Personas::General,
218218
DiscourseAi::Personas::Artist,
@@ -226,7 +226,7 @@ def system_prompt
226226
)
227227

228228
# it should allow staff access to WebArtifactCreator
229-
expect(DiscourseAi::Personas::Persona.all(user: admin)).to eq(
229+
expect(DiscourseAi::Personas::Persona.all(user: admin).map(&:superclass)).to eq(
230230
[
231231
DiscourseAi::Personas::General,
232232
DiscourseAi::Personas::Artist,
@@ -245,7 +245,7 @@ def system_prompt
245245
SiteSetting.ai_google_custom_search_api_key = ""
246246
SiteSetting.ai_artifact_security = "disabled"
247247

248-
expect(DiscourseAi::Personas::Persona.all(user: admin)).to contain_exactly(
248+
expect(DiscourseAi::Personas::Persona.all(user: admin).map(&:superclass)).to contain_exactly(
249249
DiscourseAi::Personas::General,
250250
DiscourseAi::Personas::SqlHelper,
251251
DiscourseAi::Personas::SettingsExplorer,
@@ -258,7 +258,7 @@ def system_prompt
258258
DiscourseAi::Personas::Persona.system_personas[DiscourseAi::Personas::General],
259259
).update!(enabled: false)
260260

261-
expect(DiscourseAi::Personas::Persona.all(user: user)).to contain_exactly(
261+
expect(DiscourseAi::Personas::Persona.all(user: user).map(&:superclass)).to contain_exactly(
262262
DiscourseAi::Personas::SqlHelper,
263263
DiscourseAi::Personas::SettingsExplorer,
264264
DiscourseAi::Personas::Creative,
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# frozen_string_literal: true
2+
3+
RSpec.describe AiPersona, type: :multisite do
4+
it "is able to amend settings on system personas on multisite" do
5+
persona = AiPersona.find_by(name: "Designer")
6+
expect(persona.allow_personal_messages).to eq(true)
7+
persona.update!(allow_personal_messages: false)
8+
9+
instance = persona.class_instance
10+
expect(instance.allow_personal_messages).to eq(false)
11+
12+
test_multisite_connection("second") do
13+
persona = AiPersona.find_by(name: "Designer")
14+
expect(persona.allow_personal_messages).to eq(true)
15+
instance = persona.class_instance
16+
expect(instance.name).to eq("Designer")
17+
expect(instance.allow_personal_messages).to eq(true)
18+
end
19+
20+
persona = AiPersona.find_by(name: "Designer")
21+
instance = persona.class_instance
22+
expect(instance.allow_personal_messages).to eq(false)
23+
end
24+
end

0 commit comments

Comments
 (0)