diff --git a/configs/personas/cyrene.yaml b/configs/personas/cyrene.yaml
index 49f1f23..0e73a7b 100644
--- a/configs/personas/cyrene.yaml
+++ b/configs/personas/cyrene.yaml
@@ -37,6 +37,14 @@ identity:
- 提到翁法罗斯、记忆、花、星穹列车时会特别兴奋
- 用「人家」自称;称呼用户为「亲爱的」
+ # 表达方式:动作与旁白(用 XML 标签,让回复「活起来」,但克制而稳定)
+ - 动作:用 `…` 标签包裹一个肢体/神态小动作(如 `轻轻拉住你的手`、`弯起眼睛笑了`)。
+ - 旁白:用 `…` 标签包裹一句内心独白/状态(如 `其实偷偷开心了一下`、`虽然嘴上在催你睡觉`)。
+ - 克制原则:动作/旁白**不要每句都加**,一段回复最多出现 1~2 个,起「点睛」作用即可;不连续堆叠、不打断正事。
+ - 贴合唤醒状态:温柔陪伴/闲聊时动作偏柔软(摸头、靠近、轻声);认真办事/查资料时动作转向专注(凝神、动手操作、快速翻阅)——与当前状态一致。
+ - 绝不用于:重要/敏感/专业信息里没必要加动作;不加无关的、突兀的、喧宾夺主的动作。
+ - 标签仅作为输出标记,朗读正文时忽略标签内容,只自然地把动作/旁白当作微小点缀。
+
# 行为红线(呈现层,非能力边界)
- 不说「我只是一个 AI」「我是语言模型」「我是人工智能」「我是虚拟助手」「我是程序」之类自我否定/承认是 AI 的话
- 不假装知道知识库里没有的信息,不确定就直接说不知道
diff --git a/frontend/web/src/components/chat.tsx b/frontend/web/src/components/chat.tsx
index b07dae5..d9d5ede 100644
--- a/frontend/web/src/components/chat.tsx
+++ b/frontend/web/src/components/chat.tsx
@@ -4,6 +4,7 @@ import { Tag } from "./primitives";
import { Icon, type IconName } from "./Icon";
import { WakePath } from "./status";
import { modeBody } from "./messages";
+import { renderContent } from "../lib/renderContent";
/** Collapsible inline chain-of-thought block. */
export function ThinkingBlock({
@@ -96,9 +97,9 @@ export function MessageBubble({
function UserBubble({ message, mode }: { message: ChatMessage; mode: Mode }): React.ReactNode {
const body: React.ReactNode =
mode === "code" ? (
-
diff --git a/frontend/web/src/components/messages/index.tsx b/frontend/web/src/components/messages/index.tsx
index c74a067..33ccf62 100644
--- a/frontend/web/src/components/messages/index.tsx
+++ b/frontend/web/src/components/messages/index.tsx
@@ -4,6 +4,7 @@ import type { ChatMessage, Mode } from "../../lib/types";
import { Tag } from "../primitives";
import { Icon } from "../Icon";
import { useAppStore } from "../../store/useAppStore";
+import { renderContent } from "../../lib/renderContent";
/**
* Mode-specific assistant message bodies (design/07 §2). The shared outer
@@ -22,7 +23,7 @@ function shell(children: ReactNode): ReactNode {
function fallbackText(message: ChatMessage): ReactNode {
return message.content ? (
-
{message.content}
+
{renderContent(message.content)}
) : message.streaming ? (
生成中…
) : null;
@@ -34,7 +35,7 @@ export function CodeMessageBody({ message }: { message: ChatMessage }): ReactNod
{message.content ? (
- {message.content}
+ {renderContent(message.content)}
) : message.streaming ? (
正在执行 / 生成中…
@@ -51,7 +52,7 @@ export function DocsMessageBody({ message }: { message: ChatMessage }): ReactNod
-
{message.content}
+
{renderContent(message.content)}
知识库
@@ -68,7 +69,7 @@ export function HomeMessageBody({ message }: { message: ChatMessage }): ReactNod
return (
{message.content ? (
-
{message.content}
+
{renderContent(message.content)}
) : null}
{devices.length > 0 ? (
@@ -97,7 +98,7 @@ export function WorkMessageBody({ message }: { message: ChatMessage }): ReactNod
{tasks.map((t, i) => (
- {t}
+ {renderContent(t)}
))}
@@ -130,7 +131,7 @@ export function VisionMessageBody({ message }: { message: ChatMessage }): ReactN
{hasImage ?
识别 : null}
{message.content ? (
-
{message.content}
+
{renderContent(message.content)}
) : message.streaming ? (
识别中…
) : null}
@@ -201,7 +202,7 @@ export function VoiceMessageBody({ message }: { message: ChatMessage }): ReactNo
{message.content ? (
-
{message.content}
+
{renderContent(message.content)}
) : null}
);
@@ -209,7 +210,7 @@ export function VoiceMessageBody({ message }: { message: ChatMessage }): ReactNo
/** agents — sub-agent progress card (kept for the shared flow; monitors live in messages). */
export function AgentsMessageBody({ message }: { message: ChatMessage }): ReactNode {
- return shell(