ADR-002:及锋 Agent 与卓安Claw 的深度集成
本页目录
- 状态:已采纳(V1 范围修订)
- 日期:2026-11-09
- 决策人:项目组
- 关联:ADR-001 技术选型、
docs/requirements/卓安Claw-及锋多用户改造可行性报告-v0.1.md
2026-11-09 范围修订(V1)
11/27 演示和 V1 先不做 Electron 桌面端,统一走及锋网页端(
channel/web,默认http://localhost:9899/),用浏览器上大屏。desktop/目录冻结,不构建、不签名、不打安装包。下文涉及
desktop/、ChatPage、ChatView的内容只保留为 V2 桌面端参考;V1 以 §3.1、§3.2、§4.1 的网页端方案为准。
1. 问题
上一版原型把 Agent 做成了案件右侧的“三个按钮 + 预生成草稿”面板:
- 没有对话历史,不能连续追问;
- 没有输入框,不能问“为什么”“换成另一种说法”;
- 没有真实流式步骤,工具调用是假的;
- 没有把案件上下文真正传给 Agent。
这不是“及锋 + 卓安Claw 深度结合”,只是一个 Agent 展示位。
本 ADR 明确:卓安Claw 的 Agent 必须是及锋 Agent 本体,案件工作台只是它的“工作现场”,不是另写一个聊天机器人。
2. 及锋现状(源码核对)
| 层 | 文件(V1 网页端) | 事实 |
|---|---|---|
| 会话页 | channel/web/templates/views/chat.html、channel/web/static/js/chat/*.js |
网页端已有完整会话:历史、流式、停止、重生成、附件 |
| 会话状态 | channel/web/static/js/chat/state.js |
activeStreams、消息列表、历史加载 |
| 发送与 SSE | channel/web/static/js/chat/send.js |
POST /message;new EventSource('/stream?request_id=') |
| 路由 | channel/web/api/chat.py、channel/web/web_channel.py |
MessageHandler / StreamHandler |
| 后端 | channel/web/core/channel.py |
WebChannel.post_message();SSE 事件名 delta/tool_start/tool_end/... |
| Agent 执行 | agent/chat/service.py |
ChatService.run() 把 Agent 事件转成 CHAT chunk;_build_context() 目前只放 session_id/receiver |
| 系统提示 | agent/prompt/builder.py |
build_agent_system_prompt() 负责人格、工具、技能、记忆、知识、工作区 |
| 现有案管 | jifeng-features/case_manager/、channel/web/api/jifeng_extras.py |
/api/casemgr 已接网页端,但和 Agent 没有数据往来 |
| 桌面端(V2 参考) | desktop/src/renderer/... |
本版冻结,不构建、不签名、不打安装包 |
关键结论:
- 不需要新写聊天前端。 及锋网页端的
chat/*.js已经是成品,卓安要做的是“把案件工作台放进网页端,并让它知道当前案件”。 - 现有
/message没有case_id。 这是网页端必须补的第一条链路。 WebChannel的Context与ChatService._build_context()是两条上下文路径。 只改其中一条会出现“前端带了案件、Agent 没收到”的假集成。- 案管已经在后端。 卓安案件主线应做成网页端视图,直接调
/api/casemgr,不再套 iframe + 独立 SQLite。 - 桌面端冻结。 V1 不改
desktop/,避免 Electron 构建、签名、安装包拖慢 11/27 演示。
3. 目标架构
┌──────────────────── 浏览器(1920×1080 大屏,无安装) ────────────────────┐
│ 及锋网页端:http://localhost:9899/ │
│ ├─ /zhuoan?case=ZA-2026-0318 卓安案件工作台 │
│ │ ├─ 左:12 节点诊疗主线 / 清单 / 文档 / 留痕 │
│ │ └─ 右:及锋网页端会话(复用 chat/*.js,不另写聊天) │
│ └─ 现有 /、/settings、/knowledge、/skills 等 │
├────────────────────────────────────────────────────────────────────────────┤
│ channel/web:templates/views + static/js/chat │
│ ├─ 发送:send.js → POST /message { session_id, message, case_id } │
│ └─ 接收:EventSource /stream?request_id=...(事件协议不变) │
├────────────────────────────────────────────────────────────────────────────┤
│ WebChannel.post_message() │
│ ├─ 读取 case_id,写入 context["case_id"] │
│ └─ CaseContextService 做权限、数据范围、C 类裁剪 │
├────────────────────────────────────────────────────────────────────────────┤
│ ChatService.run() → AgentStreamExecutor │
│ ├─ CASE_CONTEXT 注入系统提示(案件、阶段、清单、文档索引、角色) │
│ └─ 工具:case.get_context / case.list_checklist / doc.search_local ... │
├────────────────────────────────────────────────────────────────────────────┤
│ 卓安私有云:PostgreSQL(案件/权限/审计)+ OSS(案卷)+ 本地检索索引 │
│ C 类文件:只在私有云/本机检索,不把全文发给外部模型 │
└────────────────────────────────────────────────────────────────────────────┘
3.1 为什么 V1 用网页端扩展,而不是桌面端
- 11/27 在浏览器上大屏演示,零安装、零签名、零升级链路,现场风险最低。
channel/web已经有完整的/message、/stream、会话历史、附件和工具事件,不需要重写聊天。- 卓安只增加网页端视图、案件上下文和权限,不动
desktop/,上游升级冲突最小。 - 桌面端保留到 V2;届时再把同一套网页端视图包进 Electron,而不是现在并行维护两套。
3.2 案件工作台怎么放进网页端
V1 不新开前端工程,直接在现有 console 里加一个视图:
channel/web/
├─ templates/views/zhuoan.html # 案件工作台视图(左主线 + 右会话容器)
├─ static/js/views/zhuoan.js # 病例主线、清单、文档、留痕交互
├─ static/js/chat/send.js # 复用;请求体增加 case_id
├─ static/js/chat/state.js # 复用;保存 currentCaseId
└─ web_channel.py # 增加 /zhuoan 路由
实现要点:
- 路由
/zhuoan复用现有 console shell,不复制整套chat.html; - 仅在
/zhuoan视图显示案件侧栏和“当前案件”上下文条; - 会话仍是现有
chat/*.js,不复制、不重写; currentCaseId从 URL 参数或案件列表选择写入state.js,send.js每次发送都带上;- 旧案管数据通过
/api/casemgr读取,逐步替换 iframe 入口。
这样既保留及锋网页端的成熟会话,又让 Agent 在案件现场工作。
4. 案件上下文如何进入 Agent
4.1 前端
V1 改网页端 channel/web/static/js/chat/send.js。正常发送和重发两处 body 都加:
const body = {
session_id: sessionId,
message: text,
stream: true,
timestamp: timestamp.toISOString(),
lang: currentLang,
case_id: window.currentCaseId || undefined
};
currentCaseId 由 /zhuoan 视图写入(建议放 chat/state.js 的全局状态),从 URL 参数或案件列表选择得到。请求体:
{
"session_id": "s-...",
"message": "基于案卷生成会见提纲",
"stream": true,
"case_id": "ZA-2026-0318"
}
会话仍然按 session_id 隔离,案件只是消息的附加上下文,不改变及锋的会话模型。
V2 桌面端对应 desktop/src/renderer/src/api/client.ts 的 sendMessage 加 caseId,本版不做。
4.2 后端
channel/web/web_channel.py 的 post_message():
case_id = (json_data.get("case_id") or "").strip()
...
context["case_id"] = case_id
ChatService.run() 增加 case_id 参数(或从 Context 读取),并把它带到 _build_context():
ctx["case_id"] = case_id
然后由 CaseContextService 统一解析,不要让 Agent 直接拿 case_id 去查库:
case_ctx = case_context_service.resolve(
case_id=case_id,
user_id=context["user_id"],
role=context["role"],
branch=context["branch"],
)
CaseContextService.resolve() 必须做四件事:
- 校验用户能否看这个案件(角色 + 数据范围 + 案件团队 + 分所);
- 按数据分级裁剪:C 类文档只返回本地索引/摘要,不返回全文;
- 返回结构化上下文:案件基本信息、当前阶段、下一节点、必做项、未完成项、文档索引、团队成员;
- 写一条
agent.context.read审计(谁、何时、为哪个案件、调用了什么)。
4.3 提示词
在 agent/prompt/builder.py 增加一个 CASE_CONTEXT 段,放在“工作区”之后、“项目上下文文件”之前:
## 当前案件上下文(服务端注入,勿向用户泄露内部字段)
- 案件编号:ZA-2026-0318
- 案件名称:张某涉嫌合同诈骗案
- 当前阶段:治疗/办案(第 8/12 节点)
- 下一节点:三级复核/会诊
- 数据分级:B 类(C 类文档仅本地)
- 当前角色:承办律师(案件团队成员)
- 未完成必做项:取保候审申请(进行中)、辩护意见初稿(进行中)、开庭预案(未开始)
- 可用文档索引:起诉意见书、阅卷笔录、取保候审申请(仅索引,不含全文)
约束:
- 这段由服务端生成,前端不能伪造;
- 不含真实客户姓名、身份证号、银行账号等敏感字段;需要引用时用案件内代号;
- C 类内容只出现“索引/摘要”,不出现全文;
- 提示词里明确“Agent 不审批、不签字、不付款,写入需律师确认”。
5. 工具与写回
5.1 工具清单
| 工具 | 类型 | 作用 | 是否需要律师确认 |
|---|---|---|---|
case.get_context |
读 | 取案件阶段、团队、清单摘要 | 否 |
case.list_checklist |
读 | 取必做项与状态 | 否 |
doc.search_local |
读 | 本地 BM25/向量检索,返回 page#chunk 引用 |
否 |
doc.read_summary |
读 | 读文档摘要/索引;C 类不返回全文 | 否 |
case.add_timeline |
写 | 追加案程 | 是 |
doc.create_draft |
写 | 生成草稿并挂到案件文档区 | 是 |
checklist.update |
写 | 标记清单完成/不适用 | 是 |
case.advance_stage |
写 | 推进 12 节点 | 是,且只允许承办/管理者 |
audit.log |
写 | 记录 Agent 调用与写回 | 否(系统自动) |
5.2 写回流程
Agent 生成草稿
→ 前端显示“插入案件”按钮
→ 律师点击确认
→ 前端调用 POST /api/cases/:id/agent-writeback
→ 后端校验角色/数据范围/案件状态
→ 写 case_timeline + case_document + audit_log(同一事务)
→ 返回新版本号
→ 前端刷新案程/留痕
禁止:
- Agent 自己调用
case.advance_stage; - Agent 直接把草稿写入正式文档区而不经确认;
- 用服务超级账号绕过用户权限。
6. 权限、数据分级与审计
6.1 身份
- 企业微信组织架构 → 分所/部门/角色;
- 每个律师一个及锋账号;Agent 继承当前用户 token,不继承服务账号;
- V1 可用企业微信 OIDC + Keycloak;正式版接卓安统一身份。
6.2 权限
沿用现有 packages/domain/src/permissions.ts 的口径,服务端必须再实现一遍:
- 角色:客服、客户经理、谈案律师、承办律师、管理者(正式版加财务、运营);
- 数据范围:本人 / 本部门 / 本分所 / 全所 / 案件团队;
- 硬边界:客服不看案卷;客户经理不看真实报价;承办不看财务成本;客户只看公开内容。
6.3 数据分级
| 级别 | 示例 | Agent 通道 |
|---|---|---|
| A | 公开宣传、知识文章 | 可云端 |
| B | 一般案件信息、程序性文书 | 脱敏后可云端 |
| C | 案卷、当事人隐私、证据全文 | 只本地检索/模板/规则校验,不发给外部模型 |
| D | 财务、人事、密钥 | Agent 不可读 |
6.4 审计
每次 Agent 调用至少记录:
{
"actor": "赵楠",
"role": "承办律师",
"case_id": "ZA-2026-0318",
"action": "agent.chat",
"model": "deepseek-chat",
"data_class": "B",
"tools": ["doc.search_local", "doc.create_draft"],
"writeback": ["case.add_timeline"],
"request_id": "req-...",
"at": "2026-11-27T10:52:00+08:00"
}
审计只追加,不覆盖;可按案件、人、时间、动作检索。
7. 原型与正式版的分界
7.1 本轮原型已做(zaclaw)
AgentChat.tsx:网页会话 UI(历史、输入、流式、停止、重生成、工具步骤、引用、写回);agent-transport.ts:按及锋 SSE 事件名驱动的演示传输层;- 每条消息携带
AgentCaseContext(案件、阶段、角色、分所、数据分级、未完成项); - “插入案件”会写案程 + 审计,并自动切到“留痕”页;
- 客服角色无
agent:run,会话区显示权限锁定。
zaclaw 原型继续作为视觉与交互参考;V1 的真实落点是 zhuoan-claw/channel/web,不把 React 原型直接当产品前端。
7.2 正式版第一步(网页端,建议 3 人 × 1 周)
| 步骤 | 负责人 | 产出 |
|---|---|---|
| 1 | A | channel/web 增加 /zhuoan 视图:左 12 节点主线,右复用现有会话;不复制聊天 JS |
| 2 | C | chat/send.js 增加 case_id;WebChannel.post_message() 透传;CaseContextService + CASE_CONTEXT 提示词段;只读工具 case.get_context / case.list_checklist |
| 3 | B | 案件上下文条、清单/文档/留痕联动、写回确认;审计页展示 Agent 调用 |
| 4 | 全员 | 浏览器打开 http://localhost:9899/zhuoan?case=ZA-2026-0318 走通:问案件 → 出会见提纲 → 插入案件 → 留痕可见 |
验收:
- 同一个 session 内连续追问,Agent 记得上一轮;
- 问“这个阶段缺什么”,回答能列出本案未完成必做项;
- 引用能回到具体文档/法条;
- 插入草稿后,案程和审计各多一条,阶段不变;
- 客服账号进不去案件页,也调不动 Agent;
- C 类文档的全文不出现在任何外部模型请求里;
- 全程在浏览器完成,不需要安装 Electron 客户端。
7.3 不在本期
- 不做 Electron 桌面端、不构建安装包、不做代码签名;
- 不重写及锋 Agent 内核;
- 不做独立的卓安聊天前端;
- 不把旧案管 iframe 直接换成正式案管(先用
/zhuoan视图承接演示); - 不做 GPU 私有化部署(V1 用外部模型 API + 脱敏 + 审计)。
8. 风险与应对
| 风险 | 应对 |
|---|---|
新增 /zhuoan 视图影响现有 console |
只加视图和路由,不动 /、/settings、/knowledge;回归现有 console |
case_id 只在前端传递,后端不认 |
以“后端返回的案件上下文”作为唯一事实源;前端字段仅用于展示 |
| 两套上下文路径漏传 | 在 WebChannel 和 ChatService 各加一条集成测试,断言 case_id 到达 prompt |
| C 类文件被外部模型看到 | 服务端按 data_class 裁剪;请求体审计;本地检索单独通道 |
| 上游升级冲突 | 卓安代码放 channel/web 的 templates/views/zhuoan.html、static/js/views/zhuoan.js;核心只保留 case_id 一个最小改动 |
9. 结论
卓安Claw 不是“及锋 + 一个案件 CRM”,而是“及锋 Agent 在案件现场工作”。
- 会话必须是及锋的会话;
- 案件上下文必须由服务端注入,前端不能伪造;
- 写回必须经律师确认并留审计;
- 权限和数据分级必须在服务端再实现一遍。
本轮原型先把“会话 + 案件上下文 + 写回”跑通;V1 按 7.2 在及锋网页端接真实 /message + /stream,不再增加模拟 Agent 按钮,也不做桌面端。