diff --git a/astrbot/core/astr_main_agent.py b/astrbot/core/astr_main_agent.py index 2b4a04907e..b699455516 100644 --- a/astrbot/core/astr_main_agent.py +++ b/astrbot/core/astr_main_agent.py @@ -386,6 +386,23 @@ async def _ensure_persona_and_skills( assigned_tools: set[str] = set() agents = orch_cfg.get("agents", []) + + # 1. 提取白名单(归一化 subagents 名称) + sub_agents_cfg = (persona or {}).get("subagents") + normalized_subagents = ( + {str(name).strip() for name in sub_agents_cfg if str(name).strip()} + if sub_agents_cfg is not None + else None + ) + + # 2. 过滤 agents(使用归一化后的名称) + if normalized_subagents is not None: + agents = [ + agent + for agent in agents + if isinstance(agent, dict) + and str(agent.get("name", "")).strip() in normalized_subagents + ] if isinstance(agents, list): for a in agents: if not isinstance(a, dict): @@ -421,8 +438,20 @@ async def _ensure_persona_and_skills( req.func_tool = ToolSet() # add subagent handoff tools - for tool in so.handoffs: - req.func_tool.add_tool(tool) + # 如果 normalized_subagents 为 None 则默认放行所有 handoffs,空集合禁用所有handoffs + if normalized_subagents is None: + # 不配置 subagents 时,默认放行所有 handoffs + for tool in so.handoffs: + req.func_tool.add_tool(tool) + else: + # 只允许指向归一化白名单中的 subagents 的 handoff + for tool in so.handoffs: + agent = getattr(tool, "agent", None) + agent_name = getattr(agent, "name", None) if agent else None + if agent_name is not None: + name_norm = str(agent_name).strip() + if name_norm and name_norm in normalized_subagents: + req.func_tool.add_tool(tool) # check duplicates if remove_dup: diff --git a/astrbot/core/db/__init__.py b/astrbot/core/db/__init__.py index a18c127ebf..73d72e8fda 100644 --- a/astrbot/core/db/__init__.py +++ b/astrbot/core/db/__init__.py @@ -314,6 +314,7 @@ async def insert_persona( begin_dialogs: list[str] | None = None, tools: list[str] | None = None, skills: list[str] | None = None, + subagents: list[str] | None = None, custom_error_message: str | None = None, folder_id: str | None = None, sort_order: int = 0, @@ -326,6 +327,7 @@ async def insert_persona( begin_dialogs: Optional list of initial dialog strings tools: Optional list of tool names (None means all tools, [] means no tools) skills: Optional list of skill names (None means all skills, [] means no skills) + subagents: Optional list of subagent names (None means all subagents, [] means no subagents) custom_error_message: Optional persona-level fallback error message folder_id: Optional folder ID to place the persona in (None means root) sort_order: Sort order within the folder (default 0) @@ -350,6 +352,7 @@ async def update_persona( begin_dialogs: list[str] | None = None, tools: list[str] | None = None, skills: list[str] | None = None, + subagents: list[str] | None = None, custom_error_message: str | None = None, ) -> Persona | None: """Update a persona's system prompt or begin dialogs.""" diff --git a/astrbot/core/db/po.py b/astrbot/core/db/po.py index 451f054f62..67ea185bf3 100644 --- a/astrbot/core/db/po.py +++ b/astrbot/core/db/po.py @@ -126,6 +126,8 @@ class Persona(TimestampMixin, SQLModel, table=True): """None means use ALL tools for default, empty list means no tools, otherwise a list of tool names.""" skills: list | None = Field(default=None, sa_type=JSON) """None means use ALL skills for default, empty list means no skills, otherwise a list of skill names.""" + subagents: list | None = Field(default=None, sa_type=JSON) + """None means use ALL subagents for default, empty list means no subagents, otherwise a list of subagents names.""" custom_error_message: str | None = Field(default=None, sa_type=Text) """Optional custom error message sent to end users when the agent request fails.""" folder_id: str | None = Field(default=None, max_length=36) @@ -474,6 +476,8 @@ class Personality(TypedDict): """工具列表。None 表示使用所有工具,空列表表示不使用任何工具""" skills: list[str] | None """Skills 列表。None 表示使用所有 Skills,空列表表示不使用任何 Skills""" + subagents: list[str] | None + """Subagents 列表。None 表示使用所有 Subagents,空列表表示不使用任何 Subagents""" custom_error_message: str | None """可选的人格自定义报错回复信息。配置后将优先发送给最终用户。""" diff --git a/astrbot/core/db/sqlite.py b/astrbot/core/db/sqlite.py index c8e50909d5..3ac1ed8e7d 100644 --- a/astrbot/core/db/sqlite.py +++ b/astrbot/core/db/sqlite.py @@ -55,9 +55,10 @@ async def initialize(self) -> None: await conn.execute(text("PRAGMA temp_store=MEMORY")) await conn.execute(text("PRAGMA mmap_size=134217728")) await conn.execute(text("PRAGMA optimize")) - # 确保 personas 表有 folder_id、sort_order、skills 列(前向兼容) + # 确保 personas 表有 folder_id、sort_order、skills、subagents 列(前向兼容) await self._ensure_persona_folder_columns(conn) await self._ensure_persona_skills_column(conn) + await self._ensure_persona_subagents_column(conn) await self._ensure_persona_custom_error_message_column(conn) await conn.commit() @@ -93,6 +94,18 @@ async def _ensure_persona_skills_column(self, conn) -> None: if "skills" not in columns: await conn.execute(text("ALTER TABLE personas ADD COLUMN skills JSON")) + async def _ensure_persona_subagents_column(self, conn) -> None: + """确保 personas 表有 subagents 列。 + + 这是为了支持旧版数据库的平滑升级。新版数据库通过 SQLModel + 的 metadata.create_all 自动创建这些列。 + """ + result = await conn.execute(text("PRAGMA table_info(personas)")) + columns = {row[1] for row in result.fetchall()} + + if "subagents" not in columns: + await conn.execute(text("ALTER TABLE personas ADD COLUMN subagents JSON")) + async def _ensure_persona_custom_error_message_column(self, conn) -> None: """确保 personas 表有 custom_error_message 列。""" result = await conn.execute(text("PRAGMA table_info(personas)")) @@ -686,6 +699,7 @@ async def insert_persona( begin_dialogs=None, tools=None, skills=None, + subagents=None, custom_error_message=None, folder_id=None, sort_order=0, @@ -700,6 +714,7 @@ async def insert_persona( begin_dialogs=begin_dialogs or [], tools=tools, skills=skills, + subagents=subagents, custom_error_message=custom_error_message, folder_id=folder_id, sort_order=sort_order, @@ -732,6 +747,7 @@ async def update_persona( begin_dialogs=None, tools=NOT_GIVEN, skills=NOT_GIVEN, + subagents=NOT_GIVEN, custom_error_message=NOT_GIVEN, ): """Update a persona's system prompt or begin dialogs.""" @@ -748,6 +764,8 @@ async def update_persona( values["tools"] = tools if skills is not NOT_GIVEN: values["skills"] = skills + if subagents is not NOT_GIVEN: + values["subagents"] = subagents if custom_error_message is not NOT_GIVEN: values["custom_error_message"] = custom_error_message if not values: diff --git a/astrbot/core/persona_mgr.py b/astrbot/core/persona_mgr.py index 6320ac3bbc..118a77b7de 100644 --- a/astrbot/core/persona_mgr.py +++ b/astrbot/core/persona_mgr.py @@ -13,6 +13,7 @@ mood_imitation_dialogs=[], tools=None, skills=None, + subagents=None, custom_error_message=None, _begin_dialogs_processed=[], _mood_imitation_dialogs_processed="", @@ -141,6 +142,7 @@ async def update_persona( begin_dialogs: list[str] | None = None, tools: list[str] | None | object = NOT_GIVEN, skills: list[str] | None | object = NOT_GIVEN, + subagents: list[str] | None | object = NOT_GIVEN, custom_error_message: str | None | object = NOT_GIVEN, ): """更新指定 persona 的信息。tools 参数为 None 时表示使用所有工具,空列表表示不使用任何工具""" @@ -152,6 +154,8 @@ async def update_persona( update_kwargs["tools"] = tools if skills is not NOT_GIVEN: update_kwargs["skills"] = skills + if subagents is not NOT_GIVEN: + update_kwargs["subagents"] = subagents if custom_error_message is not NOT_GIVEN: update_kwargs["custom_error_message"] = custom_error_message @@ -319,6 +323,7 @@ async def create_persona( begin_dialogs: list[str] | None = None, tools: list[str] | None = None, skills: list[str] | None = None, + subagents: list[str] | None = None, custom_error_message: str | None = None, folder_id: str | None = None, sort_order: int = 0, @@ -331,6 +336,7 @@ async def create_persona( begin_dialogs: 预设对话列表 tools: 工具列表,None 表示使用所有工具,空列表表示不使用任何工具 skills: Skills 列表,None 表示使用所有 Skills,空列表表示不使用任何 Skills + subagents: Subagents 列表,None 表示使用所有 Subagents,空列表表示不使用任何 Subagents folder_id: 所属文件夹 ID,None 表示根目录 sort_order: 排序顺序 """ @@ -342,6 +348,7 @@ async def create_persona( begin_dialogs, tools=tools, skills=skills, + subagents=subagents, custom_error_message=custom_error_message, folder_id=folder_id, sort_order=sort_order, @@ -369,6 +376,7 @@ def get_v3_persona_data( "mood_imitation_dialogs": [], # deprecated "tools": persona.tools, "skills": persona.skills, + "subagents": persona.subagents, "custom_error_message": persona.custom_error_message, } for persona in self.personas @@ -426,6 +434,7 @@ def get_v3_persona_data( begin_dialogs=selected_default_persona["begin_dialogs"], tools=selected_default_persona["tools"] or None, skills=selected_default_persona["skills"] or None, + subagents=selected_default_persona["subagents"] or None, custom_error_message=selected_default_persona["custom_error_message"], ) diff --git a/astrbot/dashboard/routes/persona.py b/astrbot/dashboard/routes/persona.py index 56c14fe617..e265536248 100644 --- a/astrbot/dashboard/routes/persona.py +++ b/astrbot/dashboard/routes/persona.py @@ -58,6 +58,7 @@ async def list_personas(self): "begin_dialogs": persona.begin_dialogs or [], "tools": persona.tools, "skills": persona.skills, + "subagents": persona.subagents, "custom_error_message": persona.custom_error_message, "folder_id": persona.folder_id, "sort_order": persona.sort_order, @@ -99,6 +100,7 @@ async def get_persona_detail(self): "begin_dialogs": persona.begin_dialogs or [], "tools": persona.tools, "skills": persona.skills, + "subagents": persona.subagents, "custom_error_message": persona.custom_error_message, "folder_id": persona.folder_id, "sort_order": persona.sort_order, @@ -125,6 +127,7 @@ async def create_persona(self): begin_dialogs = data.get("begin_dialogs", []) tools = data.get("tools") skills = data.get("skills") + subagents = data.get("subagents") custom_error_message = data.get("custom_error_message") folder_id = data.get("folder_id") # None 表示根目录 sort_order = data.get("sort_order", 0) @@ -154,6 +157,7 @@ async def create_persona(self): begin_dialogs=begin_dialogs if begin_dialogs else None, tools=tools if tools else None, skills=skills if skills else None, + subagents=subagents if subagents else None, custom_error_message=custom_error_message, folder_id=folder_id, sort_order=sort_order, @@ -170,6 +174,7 @@ async def create_persona(self): "begin_dialogs": persona.begin_dialogs or [], "tools": persona.tools or [], "skills": persona.skills or [], + "subagents": persona.subagents or [], "custom_error_message": persona.custom_error_message, "folder_id": persona.folder_id, "sort_order": persona.sort_order, @@ -201,6 +206,8 @@ async def update_persona(self): tools = data.get("tools") has_skills = "skills" in data skills = data.get("skills") + has_subagents = "subagents" in data + subagents = data.get("subagents") has_custom_error_message = "custom_error_message" in data custom_error_message = data.get("custom_error_message") @@ -232,6 +239,8 @@ async def update_persona(self): update_kwargs["tools"] = tools if has_skills: update_kwargs["skills"] = skills + if has_subagents: + update_kwargs["subagents"] = subagents if has_custom_error_message: update_kwargs["custom_error_message"] = custom_error_message diff --git a/dashboard/src/components/shared/PersonaForm.vue b/dashboard/src/components/shared/PersonaForm.vue index 19865feb6f..0a81e80972 100644 --- a/dashboard/src/components/shared/PersonaForm.vue +++ b/dashboard/src/components/shared/PersonaForm.vue @@ -265,6 +265,101 @@ + + + + mdi-vector-link + {{ tm('form.subagents') }} + + {{ personaForm.subagents.length }} + + + + +
+

+ {{ tm('form.subagentsHelp') }} +

+
+ + + + + + +
+ + +
+ + + +
+ +
+ mdi-lightning-bolt +

{{ tm('form.noSubagentsAvailable') }} +

+
+ +
+ mdi-magnify +

{{ tm('form.noSubagentsFound') }} +

+
+ +
+ +

{{ tm('form.loadingSubagents') }} +

+
+ +
+

+ {{ tm('form.selectedSubagents') }} + + ({{ tm('form.allSelected') }}) + + + ({{ personaForm.subagents.length }}) + +

+
+ + {{ subagentName }} + +
+
+ {{ tm('form.noSubagentsSelected') }} +
+
+
+
+
+ @@ -367,6 +462,8 @@ export default { loadingTools: false, availableSkills: [], loadingSkills: false, + availableSubagents: [], + loadingSubagents: false, existingPersonaIds: [], // 已存在的人格ID列表 personaForm: { persona_id: '', @@ -375,6 +472,7 @@ export default { begin_dialogs: [], tools: [], skills: [], + subagents: [], folder_id: null }, personaIdRules: [ @@ -388,7 +486,9 @@ export default { ], toolSearch: '', skillSearch: '', - skillSelectValue: '0' + skillSelectValue: '0', + subagentSearch: '', + subagentSelectValue: '0' } }, @@ -422,6 +522,16 @@ export default { (skill.description && skill.description.toLowerCase().includes(search)) ); }, + filteredSubagents() { + if (!this.subagentSearch) { + return this.availableSubagents; + } + const search = this.subagentSearch.toLowerCase(); + return this.availableSubagents.filter(subagent => + subagent.name.toLowerCase().includes(search) || + (subagent.public_description && subagent.public_description.toLowerCase().includes(search)) + ); + }, folderDisplayName() { // 优先使用传入的文件夹名称 if (this.currentFolderName) { @@ -450,6 +560,7 @@ export default { this.loadMcpServers(); this.loadTools(); this.loadSkills(); + this.loadSubagents(); } }, editingPersona: { @@ -484,6 +595,15 @@ export default { this.personaForm.skills = []; } } + }, + subagentSelectValue(newValue) { + if (newValue === '0') { + this.personaForm.subagents = null; + } else if (newValue === '1') { + if (this.personaForm.subagents === null) { + this.personaForm.subagents = []; + } + } } }, @@ -496,10 +616,12 @@ export default { begin_dialogs: [], tools: [], skills: [], + subagents: [], folder_id: this.currentFolderId }; this.toolSelectValue = '0'; this.skillSelectValue = '0'; + this.subagentSelectValue = '0' this.expandedPanels = this.getDefaultExpandedPanels(); }, @@ -511,11 +633,13 @@ export default { begin_dialogs: [...(persona.begin_dialogs || [])], tools: persona.tools === null ? null : [...(persona.tools || [])], skills: persona.skills === null ? null : [...(persona.skills || [])], + subagents: persona.subagents === null ? null : [...(persona.subagents || [])], folder_id: persona.folder_id }; // 根据 tools 的值设置 toolSelectValue this.toolSelectValue = persona.tools === null ? '0' : '1'; this.skillSelectValue = persona.skills === null ? '0' : '1'; + this.subagentSelectValue = persona.subagents === null ? '0' : '1'; this.expandedPanels = this.getDefaultExpandedPanels(); }, @@ -581,6 +705,25 @@ export default { } }, + async loadSubagents(){ + this.loadingSubagents = true; + try { + const response = await axios.get('/api/subagent/config'); + if (response.data.status === 'ok') { + const payload = response.data.data || []; + const subagents = payload.agents || []; + this.availableSubagents = subagents.filter(subagent => subagent.enabled !== false); + } else { + this.$emit('error', response.data.message || 'Failed to load subagents'); + } + } catch (error) { + this.$emit('error', error.response?.data?.message || 'Failed to load subagents'); + this.availableSubagents = []; + } finally { + this.loadingSubagents = false; + } + }, + async loadExistingPersonaIds() { try { const response = await axios.get('/api/persona/list'); @@ -779,6 +922,38 @@ export default { } }, + toggleSubagent(subagentName) { + if (this.personaForm.subagents === null) { + this.personaForm.subagents = this.availableSubagents.map(subagent => subagent.name) + .filter(name => name !== subagentName); + this.subagentSelectValue = '1'; + } else if (Array.isArray(this.personaForm.subagents)) { + const index = this.personaForm.subagents.indexOf(subagentName); + if (index !== -1) { + this.personaForm.subagents.splice(index, 1); + } else { + this.personaForm.subagents.push(subagentName); + } + } else { + this.personaForm.subagents = [subagentName]; + this.subagentSelectValue = '1'; + } + }, + + removeSubagent(subagentName) { + if (this.personaForm.subagents === null) { + this.personaForm.subagents = this.availableSubagents.map(subagent => subagent.name) + .filter(name => name !== subagentName); + this.subagentSelectValue = '1'; + } else if (Array.isArray(this.personaForm.subagents)) { + const index = this.personaForm.subagents.indexOf(subagentName); + if (index !== -1) { + this.personaForm.subagents.splice(index, 1); + } + } + }, + + truncateText(text, maxLength) { if (!text) return ''; return text.length > maxLength ? text.substring(0, maxLength) + '...' : text; @@ -807,6 +982,13 @@ export default { return Array.isArray(this.personaForm.skills) && this.personaForm.skills.includes(skillName); }, + isSubagentSelected(subagentName) { + if (this.personaForm.subagents === null) { + return true; + } + return Array.isArray(this.personaForm.subagents) && this.personaForm.subagents.includes(subagentName); + }, + isServerSelected(server) { if (!server.tools || server.tools.length === 0) return false; @@ -864,6 +1046,11 @@ export default { overflow-y: auto; } +.subagents-selection { + max-height: 300px; + overflow-y: auto; +} + .v-virtual-scroll { padding-bottom: 16px; } @@ -893,7 +1080,8 @@ export default { } .tools-selection, - .skills-selection { + .skills-selection, + .subagents-selection{ max-height: 38vh; } diff --git a/dashboard/src/i18n/locales/en-US/features/persona.json b/dashboard/src/i18n/locales/en-US/features/persona.json index 84aaef52c6..aa03b7f4ab 100644 --- a/dashboard/src/i18n/locales/en-US/features/persona.json +++ b/dashboard/src/i18n/locales/en-US/features/persona.json @@ -52,6 +52,17 @@ "allSkillsAvailable": "Use all available Skills", "noSkillsSelected": "No skills selected", "skillsRuntimeNoneWarning": "Computer Use runtime is set to None; Skills may not run correctly because no runtime is enabled.", + "subagents": "Subagents Selection", + "subagentsHelp": "Select available Subagents for this persona. Subagents can participate as auxiliary agents in task execution.", + "subagentsAllAvailable": "Use all Subagents by default", + "subagentsSelectSpecific": "Select specific Subagents", + "searchSubagents": "Search Subagents", + "selectedSubagents": "Selected Subagents", + "noSubagentsAvailable": "No Subagents available", + "noSubagentsFound": "No matching Subagents found", + "loadingSubagents": "Loading Subagents...", + "allSubagentsAvailable": "Use all available Subagents", + "noSubagentsSelected": "No Subagents selected", "createInFolder": "Will be created in \"{folder}\"", "rootFolder": "All Personas" }, @@ -88,6 +99,7 @@ "personasTitle": "Personas", "toolsCount": "tools", "skillsCount": "skills", + "subagentsCount": "subagents", "contextMenu": { "moveTo": "Move to..." }, diff --git a/dashboard/src/i18n/locales/ru-RU/features/persona.json b/dashboard/src/i18n/locales/ru-RU/features/persona.json index e6e58ad7fa..ca3a1dcea2 100644 --- a/dashboard/src/i18n/locales/ru-RU/features/persona.json +++ b/dashboard/src/i18n/locales/ru-RU/features/persona.json @@ -52,6 +52,17 @@ "allSkillsAvailable": "Использовать все доступные навыки", "noSkillsSelected": "Навыки не выбраны", "skillsRuntimeNoneWarning": "Среда выполнения Computer Use не задана. Навыки могут не работать, так как нет активного окружения.", + "subagents": "Выбор Subagents", + "subagentsHelp": "Выберите доступных Subagents для этого персонажа. Subagents могут участвовать в выполнении задач в качестве вспомогательных агентов.", + "subagentsAllAvailable": "По умолчанию использовать всех Subagents", + "subagentsSelectSpecific": "Выбрать конкретных Subagents", + "searchSubagents": "Поиск Subagents", + "selectedSubagents": "Выбранные Subagents", + "noSubagentsAvailable": "Нет доступных Subagents", + "noSubagentsFound": "Совпадающие Subagents не найдены", + "loadingSubagents": "Загрузка Subagents...", + "allSubagentsAvailable": "Использовать всех доступных Subagents", + "noSubagentsSelected": "Не выбрано ни одного Subagents", "createInFolder": "Будет создан в папке «{folder}»", "rootFolder": "Все персонажи" }, @@ -88,6 +99,7 @@ "personasTitle": "Персонаж", "toolsCount": "инстр.", "skillsCount": "навыков", + "subagentsCount": "subagents", "contextMenu": { "moveTo": "Переместить в..." }, diff --git a/dashboard/src/i18n/locales/zh-CN/features/persona.json b/dashboard/src/i18n/locales/zh-CN/features/persona.json index d3eec49a57..a92f9dbd25 100644 --- a/dashboard/src/i18n/locales/zh-CN/features/persona.json +++ b/dashboard/src/i18n/locales/zh-CN/features/persona.json @@ -52,6 +52,17 @@ "allSkillsAvailable": "使用所有可用 Skills", "noSkillsSelected": "未选择任何 Skills", "skillsRuntimeNoneWarning": "Computer Use 运行环境为无,Skills 可能无法正确被 Agent 运行,因为没有启用运行环境。", + "subagents": "Subagents 选择", + "subagentsHelp": "为这个人格选择可用的 Subagents。Subagents 可以作为辅助代理参与任务执行。", + "subagentsAllAvailable": "默认使用全部 Subagents", + "subagentsSelectSpecific": "选择指定 Subagents", + "searchSubagents": "搜索 Subagents", + "selectedSubagents": "已选择的 Subagents", + "noSubagentsAvailable": "暂无可用 Subagents", + "noSubagentsFound": "未找到匹配的 Subagents", + "loadingSubagents": "正在加载 Subagents...", + "allSubagentsAvailable": "使用所有可用 Subagents", + "noSubagentsSelected": "未选择任何 Subagents", "createInFolder": "将在「{folder}」中创建", "rootFolder": "全部人格" }, @@ -88,6 +99,7 @@ "personasTitle": "人格", "toolsCount": "个工具", "skillsCount": "个 Skills", + "subagentsCount": "个 Subagents", "contextMenu": { "moveTo": "移动到..." }, diff --git a/dashboard/src/views/persona/PersonaCard.vue b/dashboard/src/views/persona/PersonaCard.vue index 37f523c678..66ea15d876 100644 --- a/dashboard/src/views/persona/PersonaCard.vue +++ b/dashboard/src/views/persona/PersonaCard.vue @@ -57,6 +57,14 @@ variant="tonal" prepend-icon="mdi-lightning-bolt"> {{ persona.skills.length }} {{ tm('persona.skillsCount') }} + + {{ tm('form.allSubagentsAvailable') }} + + + {{ persona.subagents.length }} {{ tm('persona.subagentsCount') }} +
@@ -83,6 +91,7 @@ interface Persona { begin_dialogs?: string[] | null; tools?: string[] | null; skills?: string[] | null; + subagents?: string[] | null; created_at?: string; updated_at?: string; folder_id?: string | null; diff --git a/dashboard/src/views/persona/PersonaManager.vue b/dashboard/src/views/persona/PersonaManager.vue index 8ad581779f..621cef7ac7 100644 --- a/dashboard/src/views/persona/PersonaManager.vue +++ b/dashboard/src/views/persona/PersonaManager.vue @@ -191,6 +191,25 @@
+
+

{{ tm('form.subagents') }}

+
+ + {{ tm('form.allSubagentsAvailable') }} + +
+
+ + {{ subagentName }} + +
+
+ {{ tm('form.noSubagentsSelected') }} +
+
+
{{ tm('labels.createdAt') }}: {{ formatDate(viewingPersona.created_at) }}
{{ tm('labels.updatedAt') }}: @@ -290,6 +309,7 @@ interface Persona { begin_dialogs?: string[] | null; tools?: string[] | null; skills?: string[] | null; + subagents?: string[] | null; created_at?: string; updated_at?: string; folder_id?: string | null;