Files
gochat/docs/architecture/message-lifecycle-and-ai-flow.md
Rogeeandrogee 4455f8938d HH-574: enforce stale job claim limit (#168)
* fix(HH-574): enforce stale job claim limit

* fix(HH-574): guard exhausted retry claims

---------

Co-authored-by: Rogee <rogee@ipao.vip>
2026-08-24 16:46:35 +08:00

30 KiB
Raw Permalink Blame History

GoChat 消息生命周期与 AI 自动回复流程

本文档描述从渠道消息入站、人工/AI 回复、消息打标签到 AI 自动触发的完整运行流程。 所有文件路径相对于仓库根目录,行号截至 2026-07-30。


一、整体架构概览

┌─────────────────────────────────────────────────────────────────────┐
│                        外部渠道                                      │
│  Facebook │ Instagram │ TikTok │ Telegram │ LINE │ Email │ Web Widget │ API
└─────┬──────────┬──────────┬──────────┬─────────┬───────┬──────┬──────┘
      │          │          │          │         │       │      │
      ▼          ▼          ▼          ▼         ▼       ▼      ▼
┌─────────────────────────────────────────────────────────────────────┐
│                     Webhook 路由层 (router.go)                        │
│  /webhooks/facebook  /webhooks/instagram  /webhooks/tiktok  ...     │
└──────────────────────────────┬──────────────────────────────────────┘
                               │
                               ▼
┌─────────────────────────────────────────────────────────────────────┐
│                   Channel Provider 层 (插件接口)                      │
│  FacebookProvider  InstagramProvider  TikTokProvider  TelegramProvider│
│  IncomingMessage() 解析渠道原始 payload → channel.IncomingMessage     │
└──────────────────────────────┬──────────────────────────────────────┘
                               │
                               ▼
┌─────────────────────────────────────────────────────────────────────┐
│              IncomingPersister (持久化层)                             │
│  1. resolveOrCreateContactInbox — 查找/创建 Contact + ContactInbox   │
│  2. resolveOrCreateConversation — 查找/创建 Conversation             │
│  3. createMessage — 持久化 Message                                    │
│  4. dispatchIncomingEvents — 广播 channel events                     │
└──────────────────────────────┬──────────────────────────────────────┘
                               │ DispatchAsync
                               ▼
┌─────────────────────────────────────────────────────────────────────┐
│                   Dispatcher (事件分发总线)                           │
│  channel.Dispatcher → 广播 ChannelEvent 给所有注册的 EventListener    │
│                                                                       │
│  注册的 Listener:                                                     │
│  ├── AutoReplyListener     — AI 自动回复                              │
│  ├── AutomationRuleListener— 自动化规则(标签/分配/状态变更)           │
│  ├── ActionCableListener   — WebSocket 推送到前端                     │
│  ├── NotificationListener  — 创建通知                                 │
│  ├── AgentBotRuleListener  — Agent Bot 规则                           │
│  └── AutoAssignmentListener— 自动分配                                 │
└─────────────────────────────────────────────────────────────────────┘

二、入站消息流(Inbound

2.1 Webhook 路由注册

路由在 backend/internal/router/router.go 中注册:

  • Facebook: POST /webhooks/facebookFacebookWebhookHandler.HandleWebhook
  • Instagram: POST /webhooks/instagramInstagramWebhookHandler
  • TikTok: POST /webhooks/tiktokTikTokWebhookHandler
  • Telegram: POST /webhooks/telegram/:inbox_idTelegramWebhookHandler
  • LINE: POST /webhooks/line/:inbox_idLineWebhookHandler
  • Email: IMAP 轮询 + ActionMailbox 端点
  • Web Widget: WebSocket 实时消息(不走 webhook
  • API Channel: POST /webhooks/api/:inbox_id → 外部系统主动推送

2.2 Webhook Handler → Provider → IncomingMessage

以 Facebook 为例:

FacebookWebhookHandler.HandleWebhook (handler/webhook/facebook_webhook.go)
  → 解析 Facebook webhook payload (entry[].messaging[])
  → 遍历每条 messaging event
  → 调用 FacebookProvider.IncomingMessage() (channel/facebook/provider.go)
      → 解析 sender_id, recipient_id, message text, attachments
      → 返回 channel.IncomingMessage{
            SourceID:      "facebook_msg_xxx",
            ConversationID: senderPSID,
            Content:       messageText,
            ContentType:   channel.ContentText,
            Attachments:   []Attachment{...},
            ChannelType:   channel.ChannelFacebook,
        }

其他渠道同理,每个 Provider 实现 IncomingMessage() 方法将渠道原始数据归一化为 channel.IncomingMessage 结构。

2.3 IncomingPersister 持久化

backend/internal/handler/webhook/incoming_persister.go:74

IncomingPersister.PersistIncoming(ctx, inbox, msg)
  │
  ├── 若有 WorkerPool → enqueueIncomingMessagePersist (异步队列)
  │     └── providerIncomingMessagePersistJob (incoming_persister_jobs.go:24)
  │         └── performPersistIncoming (异步执行)
  │
  └── performPersistIncoming(ctx, inbox, msg) — 同步路径
        │
        ├── 1. resolveOrCreateContactInbox (L451)
        │     ├── 按 inbox_id + source_id 查找 ContactInbox
        │     ├── 找到 → 返回关联的 Contact(更新 name 等)
        │     └── 未找到 → 创建 Contact + ContactInbox
        │
        ├── 2. resolveOrCreateConversation (L504)
        │     ├── 查找 account_id+inbox_id+contact_id+status=open 的最近会话
        │     ├── 找到 → 更新 last_activity_at, last_message_at
        │     └── 未找到 → 创建新 Conversation (status=open)
        │
        ├── 3. createMessage (L545)
        │     ├── 构建 model.Message{SenderType:"Contact", MessageType:"incoming"}
        │     ├── 处理 in_reply_to(按 source_id 查找被回复的消息)
        │     ├── 处理 attachments
        │     └── messageRepo.Create() 持久化到 messages 表
        │
        └── 4. dispatchIncomingEvents (L383)
              ├── EventContactCreated (仅新联系人)
              ├── EventConversationCreated + EventConversationOpened (仅新会话)
              ├── EventConversationUpdated (已有会话)
              ├── EventMessageCreated
              └── EventMessageIncoming
                  └── dispatcher.DispatchAsync() → 广播给所有 Listener

2.4 WebSocket 实时推送

ActionCableListener.OnEvent (internal/wsevent/bridge_listener.go:51)
  ├── 接收 EventMessageCreated / EventMessageIncoming
  ├── 构建 WebSocket payload (message JSON + conversation meta)
  └── wsHub.Broadcast() → 推送到该 account 所有在线 agent 前端
      └── 前端 Vue SPA WebSocket 连接接收 → Vuex/Pinia store 更新 → UI 刷新

Durable realtime 投递语义

配置 WorkerPool 后,EventPublisher 先把 account 与 pubsub token 目标分别写入 background_jobs。在当前 Redis relay 配置下,account target job 发布到 account room,由 BroadcastRelay 转发到 Hub,并由执行 job 的实例写 account SSEpubsub token target job 只发布到 token room,经 relay 转发到 Hub,不写 SSE。并非每个 job 都同时直发 Redis、Hub 和 SSE。 幂等键避免同一条 message.created/目标重复入队,job claim 避免并发执行同一条 记录;它们不覆盖发布副作用与 job 完成状态之间的崩溃窗口:

  1. job 已完成 Redis 发布,并可能已完成本地 SSE 投递;
  2. 进程在将 background_jobs.status 更新为 completed 前退出;
  3. stale-job 恢复将该记录重新置为可执行并再次发布。

因此,对外契约是 durable at-least-once publication,不是 exactly-once delivery。进程恢复后,在线 WebSocket/SSE 消费者可能再次收到同一事件;account 与 pubsub token 是独立 job,重试和到达顺序也彼此独立。Redis Pub/Sub、Hub 和 SSE 不保存离线或慢消费者的确认状态,所以该契约保证发布尝试可恢复,不保证每个 客户端至少接收一次。

该保证从 job 成功写入 background_jobs 开始。事务内入队失败会使业务事务回滚; 非事务入队按 account、pubsub token 顺序逐个执行,失败 target 没有 durable job 同一事件的其他 target 可能已成功入队。每个 target job 的 MaxAttempts=3 是绝对 claim 次数上限(进程在 claim 后崩溃也消耗一次);达到上限(或遇到 permanent error)进入 dead 后不会再被自动重试,也不再保证发布,消费者仍需 通过 REST reconciliation 恢复持久化状态。

消费者必须把 realtime 事件作为可重放通知处理:

  • message.created 使用订阅作用域、事件名和 payload 的稳定 message id 去重或 upsert;不得使用未出现在 wire payload 中的 background job ID。
  • *.updated 按资源 id 幂等 upsert,并在 payload 提供 updated_at/版本时拒绝 旧更新;不要仅按资源 ID 永久丢弃后续合法更新。
  • 重连或发现序列缺口时,以 REST API 返回的持久化资源为准。会触发声音、通知、 SDK callback 或其他非幂等副作用的外部消费者必须在执行副作用前自行去重。

当前风险可接受:复用的 dashboard 会按 message ID 替换重复消息,并按 conversation updated_at 忽略旧更新;widget 消息仓库也按 message ID upsert。 这与上游 Chatwoot 的异步 ActionCableBroadcastJob 行为一致。只有当业务要求 跨进程崩溃的 exactly-once 副作用时,才应另行引入稳定 transport event ID 与 消费端 inbox/ack;当前协议不承诺该能力。

2.5 通知创建

NotificationListener.OnEvent
  ├── 接收 EventMessageIncoming
  ├── 检查 inbox 的通知设置
  ├── 创建 Notification 记录 (notification 表)
  ├── 推送 Push notification (PushDeliveryService)
  │     └── SendPushNotification (push_delivery_service.go:63)
  └── 发送邮件通知(如配置)

三、出站回复流(Outbound

3.1 前端发起

Agent 在 ReplyBox 输入消息 → 点击发送
  │
  ├── frontend/app/javascript/dashboard/api/inbox/message.js:56
  │     MessageApi.create({ conversationId, message, ... })
  │     → POST /api/v1/accounts/:id/conversations/:conversation_id/messages
  │
  └── 前端 Vuex store: conversations/actions.js
        createPendingMessageAndSend → MessageApi.create()

3.2 后端处理

MessageHandler.Create (handler/api/v1/message_handler.go)
  → 解析请求 body (content, private, files, echo_id, ...)
  → 调用 MessageService.Create()
      │
      │  backend/internal/service/message_service.go
      │
      ├── 构建 model.Message{MessageType:"outgoing", SenderType:"User"}
      ├── messageRepo.Create() — 持久化消息
      ├── 更新 conversation.last_activity_at, last_message_at
      ├── dispatchMessageEvent(EventMessageCreated + EventMessageOutgoing)
      │     └── dispatcher.Dispatch() → 广播给 Listener
      │           ├── ActionCableListener → WebSocket 推送 (agent 端即时看到)
      │           ├── AutomationRuleListener → 触发自动化规则
      │           └── 其他 Listener
      │
      └── 投递到渠道 (MessageDeliveryWorker)
            │
            ├── 若有 WorkerPool → EnqueueSendReply (异步队列)
            │     └── message_delivery_worker.go: CreateOutgoing → SendReply
            │
            └── SendReply(messageID)
                  ├── 加载 message + conversation + inbox + contact
                  ├── 根据 inbox.ChannelType 获取对应 Provider
                  ├── 调用 Provider.SendMessage(ctx, inbox, message, contact)
                  │     ├── Facebook:  POST graph.facebook.com/me/messages
                  │     ├── Instagram: POST graph.facebook.com/me/messages (IGID)
                  │     ├── TikTok:    POST business-api.tiktok.com/...
                  │     ├── Telegram:  POST api.telegram.org/bot.../sendMessage
                  │     ├── Email:     SMTP 发送
                  │     ├── Web Widget: WebSocket 推送到 widget 端
                  │     └── API:       POST 到 inbox.webhook_url
                  ├── 更新 message.Status = "sent" / "delivered" / "failed"
                  └── dispatchMessageEvent(EventMessageStatusUpdated)

3.3 消息状态回执

渠道 webhook 回调消息已读/已送达状态:

FacebookWebhookHandler → 解析 delivery/read event
  → IncomingPersister.dispatchMessageStatusEvent()
  → 更新 message.Status
  → dispatcher.Dispatch(EventMessageStatusUpdated)
  → ActionCableListener → WebSocket 推送状态更新到前端

四、标签系统

4.1 数据模型(双路径并存)

GoChat 标签系统存在两条并行路径:

路径 A — Tag 关联表(结构化)

  • Tag (backend/internal/model/tag.go:12): 表名 tags,字段 ID, AccountID, Name(账号内唯一), Color, Description, ShowOnSidebar
    • 唯一索引:idx_tag_account_nameAccountID + Name
  • ConversationLabel (backend/internal/model/conversation_label.go:11): 表名 conversation_labels,字段 ID, ConversationID, TagID, AccountID
    • 唯一索引:idx_conv_label_tagConversationID + TagID),防止重复关联
    • Repository: TagRepo (repository/tag_repo.go) — DeleteWithAssociations 删除 Tag 时同步清理关联行和 conversations.labels 文本字段

路径 B — Conversation.Labels 文本字段(遗留)

  • Conversation.Labels string (model/conversation.go:30) — 逗号分隔字符串,如 "billing,urgent"
  • 辅助函数: mergeConversationLabels() / splitConversationLabels() (conversation_maintenance_worker.go:726,755)
  • TagRepo.RenameConversationLabelText (repository/tag_repo.go:70) — Tag 重命名时同步更新 conversations.labels 中的文本

两条路径在自动化规则和手动操作中同时维护。

4.2 标签 CRUD API

LabelHandler (handler/api/v1/label_handler.go)
  ├── GET    /api/v1/accounts/:id/labels          — 列出标签
  ├── POST   /api/v1/accounts/:id/labels          — 创建标签
  ├── PATCH  /api/v1/accounts/:id/labels/:id      — 更新标签
  └── DELETE /api/v1/accounts/:id/labels/:id      — 删除标签
      │
      └── LabelService (service/label_service.go)
            └── LabelRepo (repository/label_repo.go)

4.3 会话标签操作

ConversationHandler (handler/api/v1/conversation_handler.go)
  ├── POST /conversations/:id/labels   — 给会话打标签
  └── POST /conversations/:id/labels   — 移除标签
      │
      └── ConversationService.ToggleLabels()
            ├── 更新 conversation.labels 字段 (mergeConversationLabels)
            ├── dispatchConversationEventWithData(EventConversationLabelsUpdated)
            └── ActionCableListener → WebSocket 推送标签变更到前端

4.4 自动化规则中的标签

AutomationRule (model/automation_rule.go)
  └── AutomationAction.ActionType = "add_label"
      └── ActionParams = {"labels": ["billing", "urgent"]}

AutomationRuleListener.OnEvent (automation/listener.go)
  ├── 接收 EventMessageCreated / EventConversationCreated
  ├── ConditionsFilterService 检查规则条件是否匹配
  ├── 匹配 → ActionService 执行动作
  │     └── add_label → 更新 conversation.labels
  └── dispatch EventConversationLabelsUpdated

4.5 前端标签管理

LabelBox.vue (dashboard/routes/dashboard/conversation/labels/LabelBox.vue)
  ├── 显示当前会话所有标签
  ├── 添加/移除标签 → API 调用
  └── useConversationLabels composable

五、AI 自动回复

5.1 架构概览

                    入站消息 EventMessageCreated
                               │
                               ▼
              ┌────────────────────────────────┐
              │      AutoReplyListener          │
              │  (service/auto_reply_listener.go)│
              └───────────────┬────────────────┘
                              │
                    ┌─────────▼──────────┐
                    │ EvaluateRules()    │
                    │ (auto_reply_rule   │
                    │  _service.go)      │
                    └─────────┬──────────┘
                              │
              ┌───────────────┼───────────────┐
              │               │               │
              ▼               ▼               ▼
        Static Mode      LLM Mode        Mixed Mode
        (固定文本回复)    (LLM 生成回复)   (LLM + 固定文本)
              │               │               │
              │        ┌──────▼──────┐        │
              │        │ LLM Provider │        │
              │        │ (RAG 知识库)  │        │
              │        └──────┬──────┘        │
              │               │               │
              └───────────────┼───────────────┘
                              ▼
                    MessageService.Create()
                    (outgoing message)
                              │
                              ▼
                    MessageDeliveryWorker
                    → 渠道发送

5.2 AutoReplyListener 触发链路

backend/internal/service/auto_reply_listener.go

AutoReplyListener.OnEvent(ctx, event) (L62)
  │
  ├── 过滤:仅处理 EventMessageCreated / EventMessageIncoming
  ├── 过滤:仅处理 sender_type = "Contact" 的入站消息
  ├── 加载 Conversation(检查是否已 resolved → 跳过)
  ├── fetchRecentMessages — 取最近 10 条消息作为上下文 (L205)
  │
  ├── 构建 AutoReplyEvaluationContext {
  │     AccountID, InboxID, ConversationID,
  │     MessageContent, PreviousMessages,
  │     ConversationStatus, Language
  │   }
  │
  ├── AutoReplyRuleService.EvaluateRules(ctx, evalCtx) (auto_reply_rule_service.go)
  │     ├── 按 inbox_id 查找 active 状态的 auto-reply rules
  │     ├── 逐条评估 conditions(关键词匹配 / 正则 / 消息长度 / 时间窗口)
  │     ├── 按 priority 排序,返回第一个匹配的 rule
  │     └── 根据 rule.Mode 生成回复内容:
  │           ├── Static: 直接返回 rule.ResponseText
  │           ├── LLM: 调用 LLM Provider 生成回复(可带 RAG 检索)
  │           └── Mixed: LLM 生成 + 固定文本拼接
  │
  ├── 检查 OneTimeOnly — 该规则是否已对此会话触发过 (L225)
  │
  ├── 若有 DelaySeconds → 异步 goroutine 延迟发送 (L137)
  │
  └── sendAutoReply(ctx, event, conversation, result) (L151)
        ├── 解析 inbox 关联的 AgentBot 作为 sender
        ├── 构建 CreateMessageRequest{
        │     Content: replyContent,
        │     MessageType: "outgoing",
        │     SenderType: "agent_bot",
        │   }
        └── MessageService.Create() → 消息持久化 + 事件分发
              └── → MessageDeliveryWorker → 渠道发送

5.3 Auto-Reply Rule 数据模型

backend/internal/model/captain_auto_reply_rule.go

CaptainAutoReplyRule {
  AccountID
  AssistantID        — 关联 Captain Assistant
  InboxID            — 绑定到特定 inbox
  Name, Description
  Status             — draft / active / archived
  Mode               — static / llm / mixed
  Priority           — 数字越小优先级越高
  Conditions         — JSON: 关键词/正则/消息属性条件
  ResponseText       — static/mixed 模式的固定回复文本
  LLMPromptOverride  — LLM 模式的自定义 prompt
  DelaySeconds       — 延迟发送秒数
  OneTimeOnly        — 是否只触发一次
}

5.4 Captain Assistant

Captain 是 GoChat 的 AI 助手框架,提供 LLM 能力:

CaptainAssistant (model/captain_assistant.go)
  ├── 关联一个 LLM Provider (OpenAI / DeepSeek / 自定义)
  ├── 关联 RAG 文档库 (CaptainDocument)
  ├── 关联 Scenario(场景化 prompt 模板)
  └── 关联 Auto-Reply Rules

CaptainAssistantHandler (handler/api/v1/captain_assistant_handler.go)
  ├── CRUD assistant
  ├── 测试 assistant (发送测试消息)
  └── 生成回复 (直接调用 LLM)

CaptainConversationHandler (handler/api/v1/captain_conversation_handler.go)
  ├── AI 参与会话 — Captain 读取会话上下文 + RAG 检索 → 生成建议回复
  └── 也可直接发送 AI 回复到会话

5.5 CopilotAgent 辅助)

Copilot 是给人工 agent 用的 AI 辅助工具,不会自动发送回复:

CopilotHandler (handler/api/v1/copilot_handler.go)
  ├── POST /copilot/suggest — 根据会话上下文生成建议回复
  ├── POST /copilot/rephrase — 改写 agent 输入的文本
  └── POST /copilot/summarize — 总结会话

CopilotContainer.vue (前端侧边栏)
  ├── Agent 在侧边栏与 Copilot 交互
  ├── Copilot 调用 LLM + RAG 生成建议
  └── Agent 确认后手动发送(不自动发送)

5.6 RAG 知识检索

RAGHandler (handler/api/v1/rag_handler.go)
  ├── 上传文档 → 向量化 → 存入 pgvector
  ├── 检索:query 向量化 → pgvector 相似度搜索 → 返回 top-k 文档片段
  └── 在 LLM 调用时注入检索到的上下文

流程:
  用户消息 → 向量化 → pgvector 检索相关文档 → 注入 LLM prompt → 生成回复

六、完整消息生命周期

    客户                    GoChat 后端                     Agent 前端
     │                         │                              │
     │  1. 发送消息              │                              │
     │ ──────────────────────► │                              │
     │                    2. Webhook 接收                       │
     │                    3. Provider 解析                      │
     │                    4. IncomingPersister 持久化            │
     │                    5. Dispatcher 广播事件                 │
     │                         │  6. WebSocket 推送             │
     │                         │ ────────────────────────────► │
     │                         │                    7. Agent 看到消息 │
     │                         │                              │
     │                    8. AutoReplyListener 检查              │
     │                    9. 匹配规则 → AI 生成回复               │
     │                   10. MessageService.Create()             │
     │                   11. MessageDeliveryWorker               │
     │  12. 渠道发送回复          │                              │
     │ ◄────────────────────── │                              │
     │                         │  13. WebSocket 推送 AI 回复     │
     │                         │ ────────────────────────────► │
     │                         │              14. Agent 看到 AI 回复 │
     │                         │                              │
     │                         │  15. (可选) Agent 手动回复      │
     │                         │ ◄──────────────────────────── │
     │                   16. MessageService.Create()             │
     │                   17. Provider.SendMessage()              │
     │  18. 渠道发送回复          │                              │
     │ ◄────────────────────── │                              │
     │                         │                              │
     │                         │  19. AutomationRuleListener    │
     │                         │      → 匹配规则 → 自动打标签     │
     │                         │      → 自动分配 agent          │
     │                         │      → 自动变更状态            │

七、关键文件索引

组件 文件路径 关键函数/行号
Webhook 路由 backend/internal/router/router.go webhook 路由注册
Facebook Webhook backend/internal/handler/webhook/facebook_webhook.go HandleWebhook
TikTok Webhook backend/internal/handler/webhook/tiktok_webhook.go HandleWebhook
LINE Webhook backend/internal/handler/webhook/line_webhook.go HandleWebhook
IncomingPersister backend/internal/handler/webhook/incoming_persister.go PersistIncoming L74
持久化 Job backend/internal/handler/webhook/incoming_persister_jobs.go providerIncomingMessagePersistJob L24
Dispatcher backend/internal/channel/dispatcher.go Dispatch L85, DispatchAsync L110
Channel Event backend/internal/channel/event.go ChannelEvent 结构体, EventType 常量
Facebook Provider backend/internal/channel/facebook/provider.go IncomingMessage, SendMessage L352
Instagram Provider backend/internal/channel/facebook/instagram_provider.go IncomingMessage, SendMessage L364
Message Service backend/internal/service/message_service.go Create, dispatchMessageEvent L68
Message Handler backend/internal/handler/api/v1/message_handler.go Create
Message Delivery Worker backend/internal/service/message_delivery_worker.go EnqueueSendReply, SendReply
AutoReply Listener backend/internal/service/auto_reply_listener.go OnEvent L62, sendAutoReply L151
AutoReply Rule Service backend/internal/service/auto_reply_rule_service.go EvaluateRules
AutoReply Rule Model backend/internal/model/captain_auto_reply_rule.go CaptainAutoReplyRule
Captain Assistant Handler backend/internal/handler/api/v1/captain_assistant_handler.go CRUD + 测试
Captain Conversation Handler backend/internal/handler/api/v1/captain_conversation_handler.go AI 会话参与
Copilot Handler backend/internal/handler/api/v1/copilot_handler.go suggest/rephrase/summarize
RAG Handler backend/internal/handler/api/v1/rag_handler.go 文档上传/检索
Automation Rule Listener backend/internal/automation/listener.go OnEvent
Automation Action backend/internal/model/automation_action.go AutomationAction L10
Tag Model backend/internal/model/tag.go Tag L12
ConversationLabel Model backend/internal/model/conversation_label.go ConversationLabel L11
Tag Repo backend/internal/repository/tag_repo.go DeleteWithAssociations L54, RenameConversationLabelText L70
ConversationLabel Repo backend/internal/repository/conversation_label_repo.go CRUD
WebSocket Hub backend/internal/ws/hub.go Broadcast
WS Event Bridge backend/internal/wsevent/bridge_listener.go ActionCableListener.OnEvent L51
Inbox Serializer backend/internal/handler/api/v1/inbox_serializer.go serializeInbox L21
Notification Service backend/internal/service/notification_delivery_service.go 通知投递
Push Delivery backend/internal/service/push_delivery_service.go SendPushNotification L63
前端 Message API frontend/app/javascript/dashboard/api/inbox/message.js create L56
前端 ReplyBox frontend/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue 消息输入框
前端 Copilot frontend/app/javascript/dashboard/components/copilot/CopilotContainer.vue Copilot 侧边栏