feat(handoff): route assignee notifications by channel - #200
Conversation
- SOP handoff nodes and channel bindings gain assignee_notify_channel: None keeps default delivery, "web" limits to the web inbox, and a bound channel (e.g. feishu) forwards the handoff via that channel - validate the assignee is an internal member and, when a channel is requested, that a non-group channel identity is bound in the binding scope; skills API rejects unreachable channel specs - surface channel-labelled assignee options in the channels page and the SOP editor (source and flow views), sharing the new handoff-assignee helpers - restrict the SOP editor: the assignee picker and handoff action now appear only on handoff-type nodes, and switching a node away from handoff strips its handoff action and assignee - resume handoff turns as the session owner so harness user fencing passes after channel-identity rebinding migrates session.user_id
…ee-notify-channel
Deleting an agent left orphan rows in channel_binding_agents, so the Feishu/DingTalk /员工 command listed a bare agent_id for a deleted employee and /切换 could route a conversation to it. delete_agent now removes the agent's channel mounts, promotes the first remaining mount to default (syncing binding.agent_id) when the default mount is deleted. mounted_agents additionally filters mounts whose agent no longer exists, so legacy orphan rows self-heal across listing, switching, autorouting, and the channels page; conversation pointers keep resetting through the existing resolve_current_agent fallback.
|
追加修复:删除数字员工后渠道挂载未清理(debff16) 问题删除员工(如 agent_508e762658ef49cd)后, 修复(两层)
测试
|
The Feishu setup page now lists all twelve permissions the integration needs: three contact-reading scopes (contact:contact.base:readonly, contact:user.base:readonly, contact:user.id:readonly), the group message scopes (group_at_msg, group_at_msg.include_bot, group_msg, group_msg.include_bot:read, im:message:readonly) alongside the existing p2p/send/reaction scopes. Directory and sensitive group-read entries move out of the "usually removable" list since they are now required, and the tooltip copy plus English translations follow suit.
|
追加:飞书接入页权限名单补全(d7ac5b1) 变更接入飞书向导的"必需权限"从 5 条扩充到 12 条,覆盖实际运行所需的完整权限集:
同时把"通讯录权限范围 / 获取用户 user ID / 读取群内全部消息的敏感权限"从"通常可删除"列表移除(与新增必需权限冲突),tooltip 提示文案与 en.json 翻译同步更新。 验证
|
The SOP call count in the admin console was stuck at zero: skill stats count skill_started/skill_resumed agent events, which only the removed legacy agent loop used to emit. Harness v2 now records the events when an SOP task frame is activated — start_new_task emits skill_started, switch_to_pending emits skill_resumed, continuing the active SOP stays uncounted — with the legacy payload shape so the stats reader, trace consumers, and Feishu traces keep working. Also fix a fallout from the mount cleanup: adopt_orphan_channel_sessions must claim sessions using raw mount rows instead of the user-facing filtered mount set, since historical sessions can belong to deleted employees and still need to migrate to the new binding.
|
追加修复:数字员工管理端 SOP 调用次数永远是 0(8424a5a) 根因SOP 调用次数统计( 修复
验证
注意历史调用因事件从未落库无法追溯统计;修复部署后新产生的对话调用会开始正常累计。 |
|
整体方案和 Feishu 场景没问题,相关测试也覆盖到了。 但目前“按渠道转接”的实现还不完整:前端和 SOP 配置允许选择 dingtalk/wecom 等渠道,后端校验也允许保存这些渠道;运行时实际通知仍只走 Feishu:
因此选择 dingtalk/wecom 后,人工转接会创建成功,但处理人不会收到对应渠道通知,只能在网页端看到。 请二选一:
|
|
采用方案 2:限制前后端只能选择飞书(11bf079) 选择钉钉/企微后配置保存成功但处理人收不到渠道通知,根因是钉钉适配器只能通过 sessionWebhook 回会话内消息、企微/微信出站同样依赖会话上下文(context_token / chatid),均无法对处理人发起主动私聊;补齐需要各渠道全新的服务端 API 集成,超出本 PR 范围,因此按评论建议收窄为仅支持飞书: 后端拒绝其他渠道配置
前端只提供飞书选项
测试
后续如需支持钉钉/企微转接(钉钉企业内部机器人单聊 API / 企微应用消息),再单独开 PR 补齐运行时通知链路并放开此限制。 |
|
追加修复 1:限制转接渠道只能选飞书(11bf079) 采用方案 2:选择钉钉/企微后配置保存成功但处理人收不到渠道通知,根因是钉钉适配器只能通过 sessionWebhook 回会话内消息、企微/微信出站同样依赖会话上下文(context_token / chatid),均无法对处理人发起主动私聊;补齐需要各渠道全新的服务端 API 集成,超出本 PR 范围,因此按评论建议收窄为仅支持飞书: 后端拒绝其他渠道配置
前端只提供飞书选项
测试:新增钉钉绑定渠道选项被 400、SOP 节点钉钉渠道被 400、前端选项过滤等回归用例;相关后端 4 个套件 229 个测试、前端 213 个测试全部通过。后续如需支持钉钉/企微转接再单独开 PR 补齐运行时通知链路并放开限制。 追加修复 2:飞书身份绑定列表显示异常(08571c0) 修复三个显示问题:
测试: |
删除团队此前只清理成员/任务等表,带 team_id 的会话残留并在对话端 全局列表可见。抽出共享清理助手 session/cleanup.py(消息/事件/反馈/ Harness 记录同事务删除,commit 后清 Harness 工作区),delete_team 与 delete_chat_session 端点统一复用。
删除员工此前会话全部残留,且发消息会由租户默认 persona 静默接管 回复。现在删除员工时级联清空其全部会话(含 Harness 工作区),同步 清理团队成员关系,active 定时任务立即 paused,pending 人工转接 标记 cancelled。
新增修复:删除团队/数字员工后对话记录残留本分支新增两个级联清理修复,与原 handoff 通知改动互不影响: 根因
改动
风险
测试
|
级联清理上线前删除团队/员工残留的孤儿会话,init_db 启动时幂等清理, 复用与删除时相同的 purge 逻辑。
|
新增启动时一次性清理:自动删除团队或数字员工已被删除但残留在库中的历史孤儿会话。 |
飞书流式卡片文案用户可读化(新增提交 08fef6e)问题飞书渠道流式卡片直接展示技术细节,用户看不懂:
方案飞卡片复用
测试
UI 验证
|
|
|
增加 一些 流程可读性后的截图 |
…e-level validation
|
@fadeoreo 两条评论均已处理,见提交 174db2c。 1. 通用 outbox 路由(去掉 Feishu 硬编码)投递链路通用化
2. SOP 节点校验改为 scope 级可达
新增测试(13 个)
测试结果
|
飞书 Trace Card 紧凑模式
|
Assignees can now answer human handoff notices by replying (quoting) the notification message directly in feishu, without the /回复反馈 prefix. Replies quoting already-answered notices or ack messages are consumed with a hint instead of starting a new agent conversation. - match quote replies against delivered handoff_notice/handoff_ack message ids with strict receive_id sender validation - persist message_id for handoff_ack deliveries so follow-up quote replies are consumed too - bound problem description to keep notices single-message for reliable quote matching - update notice and usage text to advertise direct quote reply
|
支持处理人直接在飞书里引用(回复)转人工通知消息进行答复,无需 /回复反馈 前缀,且不会触发处理人与数字员工的新对话。 行为变更
实现要点
测试
|
…ee-notify-channel # Conflicts: # backend/app/api/chat.py
Intent
人工转交的处理人现在可以指定通知渠道:SOP handoff 节点与渠道绑定默认处理人均支持
assignee_notify_channel(None=默认投递、web=仅网页收件箱、绑定渠道如feishu=按该渠道转接)。同时收紧 SOP 编辑器:处理人下拉框与转人工动作仅出现在 handoff 类型节点上,回复/收集等节点不再提供转人工能力。Changes
后端
SkillGraphNode/ChannelBindingAgentsUpdate/ChannelBindingRead新增assignee_notify_channel字段HumanHandoffService按命中配置携带通知渠道(SOP 节点 → 渠道默认 → 负责人/管理员),agent_loop按渠道偏好决定是否经当前 binding 投递前端
lib/handoff-assignee(格式化/解析user::channel值),渠道页与 SOP 编辑器共用Risk
handoff_human动作仍被运行时识别(数据不迁移、不失效),仅 UI 不再提供新增入口Tests
backend/.venv/bin/python -m pytest backend/tests— 1838 passed(本地环境另有 35 个与本次改动无关的既有失败,已在干净工作树复现确认)npm --prefix frontend-enterprise test— 212 passednpm --prefix frontend-enterprise run build— 通过npm --prefix frontend-enterprise run i18n:check— 通过test_channel_api.py、test_feishu_handoff.py、test_human_handoff_policy.py、test_graph_rules.py、test_skill_editing_and_stats.py、lib/handoff-assignee.test.ts、DistillPage.handoffRestrictions.test.tsxUI Validation
/enterprise/channels(管理员)验证默认处理人渠道变体保存与回显;/enterprise/skillsSOP 编辑器源码/流程视图验证 handoff 节点处理人、非 handoff 节点无处理人与转人工动作、类型切换自动清理