@modesto_ma/dsh-channels 0.5.0-beta.3 → 0.5.0-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,12 +8,12 @@ DeepSeek Harness(DSH)飞书渠道插件:**一个 dsh 实例对应一个飞
8
8
  - 工作区归属:**每用户一个工作区**(私聊 = 用户;群聊 = 群 × 用户,同群不同用户互不影响);每个话题在工作区内拥有独立工作目录(默认 `topics/<话题 id>/`,Git 仓库工作区可自动改用 worktree),因此话题之间的文件互不干扰,而工作区数量只随用户增长
9
9
  - **入站附件(v2,零等待)**:收到消息时**会话、话题、流式卡片立即建立,不等附件下载**。触发消息中的图片自动预取为首轮原生 `ImageBlock`,模型直接可见;文件默认不下载,prompt 用 `<feishu_context>` 清单声明“附件未加载”,模型需要时调用 `feishu_attachment_fetch` 按 `message_id` 懒取;引用消息与话题历史以 `<feishu_quote>` / `<feishu_history>` 摘录,更早的消息用 `feishu_topic_messages` 翻页(发现的资源同时获得 fetch 授权)。**只发附件、不带说明的消息完全静默**:不回复、不建会话、不下载、不加表情,本地也不留任何状态;要让模型看到它,请引用那条消息再提问(多个文件可合并转发后引用),话题内的附件也可由模型用 `feishu_topic_messages` 读到。所有入站图片/文件统一存入 **DSH 原生附件库**(不可变、内容寻址去重、读取校验、随 Session 回放;插件不承担入站附件清理)。显式 `attachmentStore: 'archive'`、宿主未挂载原生附件服务或关闭 fetch 工具时退回旧版工作区 `attachments/` 落盘。完整设计见 [docs/attachment-pipeline-v2.md](docs/attachment-pipeline-v2.md)。
10
10
  - **把生成的文件发给用户**:模型在话题工作目录里生成图片、报告、表格等文件后,可调用 `feishu_send_file` 直接发回当前话题/会话(可识别图片 ≤10 MB 以图片消息发送,其余以文件消息发送,文件上限 30 MB)。只能发送**当前话题工作目录内**的普通文件(`..` 与符号链接都按真实路径校验,不能读到其他话题或系统文件);需开通 `im:resource` 权限,缺失时工具返回可读的 `PERMISSION_DENIED`,回合不受影响。开关:`feishuTools.sendFile`(默认开)
11
- - **知识库读写**:开启 `feishuTools.wikiRead` / `feishuTools.wikiWrite`(默认都关)并在 `wikiRoots` 里配置知识空间链接 `https://<租户>.feishu.cn/wiki/space/<space_id>` 或节点链接 `https://<租户>.feishu.cn/wiki/<node_token>` 后,模型可用 `feishu_wiki_list` 浏览、`feishu_wiki_read` 读取飞书文档(Markdown,或带 block id 的飞书文档 XML,支持目录/章节/区间/关键词局部读取)、`feishu_wiki_create` 在范围内新建飞书文档、电子表格或多维表格(新节点也可作为子目录继续往下建)、`feishu_wiki_edit` 修改**本应用创建的**文档(追加、在块后插入、替换/删除块、文本替换、整篇重写;必须带上读取时的 `revision_id`,文档被他人改过会被拒绝)。电子表格用 `feishu_wiki_sheet_read` 按子表和 A1 范围读成 CSV(每行带 `[row=N]` 真实行号),`feishu_wiki_sheet_edit` 向**本应用创建的**表格写入单元格值或公式、在数据末尾追加行(追加位置会越过空行找到真正的最后一行,目标单元格非空时失败、不会覆盖;同样校验 `revision_id`;不改样式、图表和行列结构)。多维表格用 `feishu_wiki_base_read` 列数据表、读字段和记录(支持筛选、排序、关键词搜索、分页),`feishu_wiki_base_edit` 增改删记录:**本应用创建的**多维表格可增改删,其他多维表格**只能新增记录**(不改表和字段结构)。所有调用都用应用身份,并且:只有**管理员或知识库白名单**(`/auth wiki add @用户`)里的人发起的话题能用;`wikiRoots` 之外一律拒绝;打开某个文档或表格、每次新建、每次修改都会在话题里弹出**授权卡**,只有话题发起人能点(读取授权在本话题内对该文档持续有效,`/renew` 或重启后重新询问),超时(`approvalTimeoutMs`)自动拒绝;浏览目录不弹卡。需开通 `wiki:wiki`、`docx:document`、`sheets:spreadsheet`、`bitable:app`(只读可用 `wiki:wiki:readonly` / `docx:document:readonly` / `sheets:spreadsheet:readonly` / `bitable:app:readonly`),且应用要是目标知识空间的成员(可把包含机器人的群加为空间成员/管理员)
11
+ - **知识库读写**:开启 `feishuTools.wikiRead` / `feishuTools.wikiWrite`(默认都关)并在 `wikiRoots` 里配置知识空间链接 `https://<租户>.feishu.cn/wiki/space/<space_id>` 或节点链接 `https://<租户>.feishu.cn/wiki/<node_token>` 后,模型可用 `feishu_wiki_list` 浏览、`feishu_wiki_read` 读取飞书文档(Markdown,或带 block id 的飞书文档 XML,支持目录/章节/区间/关键词局部读取)、`feishu_wiki_create` 在范围内新建飞书文档、电子表格或多维表格(新节点也可作为子目录继续往下建)、`feishu_wiki_edit` 修改**本应用创建的**文档(追加、在块后插入、替换/删除块、文本替换、整篇重写;必须带上读取时的 `revision_id`,文档被他人改过会被拒绝)。电子表格用 `feishu_wiki_sheet_read` 按子表和 A1 范围读成 CSV(每行带 `[row=N]` 真实行号),`feishu_wiki_sheet_edit` 向**本应用创建的**表格写入单元格值或公式、在数据末尾追加行(追加位置会越过空行找到真正的最后一行,目标单元格非空时失败、不会覆盖;同样校验 `revision_id`;不改样式、图表和行列结构)。多维表格用 `feishu_wiki_base_read` 列数据表、读字段和记录(支持筛选、排序、关键词搜索、分页),`feishu_wiki_base_edit` 增改删记录:**本应用创建的**多维表格可增改删,其他多维表格**只能新增记录**(不改表和字段结构)。所有调用都用应用身份,并且:只有**管理员或知识库白名单**(`/auth wiki add @用户`)里的人发起的话题能用;`wikiRoots` 之外一律拒绝;打开某个文档或表格、每次新建、每次修改都会在话题里弹出**授权卡**,只有话题发起人能点(读取授权在本话题内对该文档持续有效,`/renew` 或重启后重新询问),超时(`approvalTimeoutMs`)自动拒绝;浏览目录不弹卡。需开通 `wiki:wiki`、`docx:document`(只读可用 `wiki:wiki:readonly` / `docx:document:readonly`),电子表格和多维表格另需细分权限 `sheets:spreadsheet:read` / `sheets:spreadsheet:write_only`、`base:table:read` / `base:field:read` / `base:record:read` / `base:record:create|update|delete`(旧版 `sheets:spreadsheet` / `bitable:app` 不生效),且应用要是目标知识空间的成员(可把包含机器人的群加为空间成员/管理员)
12
12
  - **HITL 审批**:当模型请求需要授权的动作(如 bash 沙箱升级 `sandbox_permissions`)时,插件监听主机 `approval/request` 事件,在同一话题里**新发一张独立审批决策卡**:卡上直接显示将执行的命令(或工具参数)和提权目标,提供 ✅ 允许 / ❌ 拒绝 / ♾️ 本话题不再询问 三个按钮;也可以在话题里直接回复「允许」「拒绝」「全部允许」,回复其他内容视为拒绝并作为下一条指令。等待期间流式主卡显示「⏳ 等待你审批」。超时(`approvalTimeoutMs`,默认 5 分钟)自动拒绝(fail-closed),非会话发起人点击或回复无效
13
13
  - **结构化提问**:模型调用 `ask_user_question` 时,插件监听主机 `user-questions/request` 瀑布并发一张问答卡(每个问题一组选项按钮,多选先勾选再「提交本题选择」),答案回填给模型;主机自带的应答者只有 Web 客户端,没有本插件时纯飞书会话会以 `NO_PROVIDER` 失败。超时(`questionTimeoutMs`,默认 5 分钟)或回合取消时本轮提问失败(fail-closed);没有可选项的问题直接提示用文字作答,**阻塞期间普通文本回复即作为该问题的自由文本答案**(同时支持多选后的自定义文本)
14
14
  - **准入与授权(默认开启)**:**管理员不受白名单限制**(私聊、任意群聊——包括尚未授权的群——都能直接发起会话);其他人私聊只收到一条固定提示(`/userid` 例外),群聊需要「群已授权 + 用户在全局白名单」两层同时命中。机器人被拉进群后私聊管理员发**群信息卡**(未授权群附授权按钮,用于放行白名单用户);已授权群里未授权用户 @ 会触发用户授权卡。授权入口:飞书 `/auth` 命令、授权卡、Web 设置面板;管理员 user_id 在面板配置(私聊 `/userid` 获取),**留空时自动使用飞书应用所有者**
15
15
  - 全部基于飞书官方 SDK [`@larksuiteoapi/node-sdk`](https://www.npmjs.com/package/@larksuiteoapi/node-sdk)(WebSocket 长连接,免公网)
16
- - **Web 设置面板**:Web/桌面端 **设置 → 飞书渠道**,顶部状态头 + 6 个标签页(连接 / 会话与行为 / 模型 / 工作区 / 访问授权 / 高级);凭据不完整时插件不连接、面板显示三步引导,填写保存后自动连接(保存写入本机覆盖文件,部分字段需重连生效);详见下文「Web 设置面板」章节
16
+ - **Web 设置面板**:Web/桌面端 **设置 → 飞书渠道**,顶部状态头 + 7 个标签页(连接 / 会话与行为 / 模型 / 工作区 / 访问授权 / 知识库 / 高级);凭据不完整时插件不连接、面板显示三步引导,填写保存后自动连接(保存写入本机覆盖文件,部分字段需重连生效);详见下文「Web 设置面板」章节
17
17
 
18
18
  设计与可行性论证见 [docs/feasibility-and-design.md](docs/feasibility-and-design.md);工作区粒度与会话隔离的取舍见 [docs/workspace-isolation-design.md](docs/workspace-isolation-design.md);入站附件管线(存储 / 清单 / 工具 / 引用与历史 / 降级 / 隐私边界)见 [docs/attachment-pipeline-v2.md](docs/attachment-pipeline-v2.md)。
19
19
 
@@ -53,6 +53,7 @@ DeepSeek Harness(DSH)飞书渠道插件:**一个 dsh 实例对应一个飞
53
53
  - 两者可同屏:如 `🧵 工作流 xxx 结束(完成)` + `🔧 调用工具:bash`
54
54
  2. **正文**:富文本 markdown;超长正文**自动分段投放**——单卡片预算用满后按干净断点(段落 / 标题 / 代码块边界)切分,且**每张卡最多 5 个表格**(飞书硬限制,错误 11310);断点会避开代码围栏与表格,**单个表格/代码块小于单卡预算时不会被从中间切断**(单块本身超过单卡预算时只能硬切,内容仍不丢失)。续片以「📄 回复分段 n」卡片跟进同一话题,**全文无丢失**;续卡被飞书拒收时自动降级为纯文本消息(内容仍送达)。**续卡只留正文**:`📄 回复分段 n` → 可选活动行(仅当它是当前活动卡)→ 正文,无面板、无页脚、无分割线
55
55
  3. **停止按钮**:回合进行中,当前正在流式输出的那张卡(主卡或最新续卡)正文下方显示「⏹ 停止」,效果同 `/stop`。首帧不带按钮;排在后面的消息的卡片在轮到它运行时才出现按钮。只有话题发起人在原聊天里点击才生效,点击后按钮变为不可点的「正在停止…」;其他人点击只会在按钮下加一行「仅发起该会话的用户可停止」。回合结束后按钮移除;经 `/stop` 或按钮停止的回合面板标题为 **已停止**,主卡页脚上方显示「⏹ 已按请求停止本轮回复」。
56
+ - **排队卡片**:同一话题里上一轮还在运行时再发消息,新消息排到下一轮,回复卡片只显示「⏳ 排队中:上一轮回复结束后再处理这条消息」和按钮「⚡ 立即补充到当前回复」。点击后这条消息从下一轮队列撤回,改为在当前回合的下一步(当前这次输出或这批工具调用结束后)交给模型,卡片变为「↪️ 已补充到当前回复」,回复继续在上一张卡片中输出,两条消息的表情一起随该回合结束切换为 DONE/No。只有话题发起人在原聊天里点击才生效,其他人点击只会加一行「仅发起该会话的用户可补充」;当前回合正在停止,或消息已经开始自己的回合时,点击不生效。不点击时,上一轮结束后卡片自动转为正常的流式卡片。
56
57
  4. **结算页脚**(分割线 + 两行小字):**只在主卡、且只在回合结束(`done` / `stopped` / `error`)后渲染一次**——首帧、流式中、续卡都没有,因此不会出现 0/— 的空统计。
57
58
  - 第一行:`🧠 模型 {model}({effort}) · 🔑 会话 {sessionId} · ⏱ 推理 {推理时长}`。推理时长为**本轮所有推理阶段的累计墙钟时间**(每个 step 从首个推理增量开始,到该 step 输出结束——首个正文增量或 step 结束——为止;工具执行时间不计入;回合在推理中结束时计到 turn 结束),无推理时为 `—`
58
59
  - 第二行:`📊 上下文 {tokens}/{window} ({占比}) · 📩 输入 {n} · 📨 输出 {n} · ⚡ 缓存命中 {pct}`。占比 = 上下文 tokens / 窗口(<10% 保留一位小数,≥10% 取整);没有窗口时只显示 tokens
@@ -73,22 +74,23 @@ DeepSeek Harness(DSH)飞书渠道插件:**一个 dsh 实例对应一个飞
73
74
  | 标签页 | 内容 |
74
75
  | --- | --- |
75
76
  | 连接 | 连接状态与连接信息(长连接状态、Bot open_id、运行时长、话题路由 / 活动会话 / 在途回复)、权限自检、斜杠指令面板同步结果、未配置时的三步引导、飞书应用凭据、最近一次连接失败原因、需重连的待生效提醒 |
76
- | 会话与行为 | 「行为」:私聊模式、普通群 @ 要求、话题兜底、首次私聊引导、原生斜杠指令面板同步、权限切换;「内容与附件」:附件接收、存储后端、图片预取与上限、首轮历史;「模型工具」:`feishu_attachment_fetch` / `feishu_topic_messages` / `feishu_send_file` / 知识库工具开关与上限、`wikiRoots`、被动读取预算 |
77
+ | 会话与行为 | 「行为」:私聊模式、普通群 @ 要求、话题兜底、首次私聊引导、原生斜杠指令面板同步、权限切换;「内容与附件」:附件接收、存储后端、图片预取与上限、首轮历史;「模型工具」:`feishu_attachment_fetch` / `feishu_topic_messages` / `feishu_send_file` 开关与上限、被动读取预算 |
77
78
  | 模型 | 模型服务商、模型 id、推理档位、Agent 预设(宿主目录驱动的级联下拉) |
78
79
  | 工作区 | 工作区根目录(可点「选择目录…」调用系统目录选择框);话题目录隔离方式(`topicIsolation`)在 profile 配置中设置 |
79
80
  | 访问授权 | 管理员 user_id、准入模式、申请有效期;已授权群 / 用户白名单与待处理申请 |
81
+ | 知识库 | 知识库读/写开关(`feishuTools.wikiRead` / `feishuTools.wikiWrite`)、知识库范围 `wikiRoots`(每条链接一个输入框,逐条校验);知识库白名单增删(与用户白名单独立,等同 `/auth wiki add|remove`) |
80
82
  | 高级 | 流式刷新 / 状态行 / SDK 日志级别、部署级只读参数、Web 覆盖文件路径与「清除 Web 覆盖」 |
81
83
 
82
84
  **入站附件与模型工具(v2)**:「会话与行为」标签页新增两组可编辑字段,与 profile 配置同名、写入同一 Web 覆盖文件(`$DSH_HOME/dsh-channels/settings.json`):
83
85
 
84
86
  - **内容与附件**:附件接收(`acceptAttachments`)、入站附件存储后端(`attachmentStore`:DSH 原生附件库 / 工作区 `attachments/`)、触发消息图片预取(`prefetchImages`)及单图 / 总量 / 整段超时上限(`prefetchImageMaxBytes` / `prefetchTotalMaxBytes` / `prefetchTimeoutMs`)、首轮话题历史条数(`historyBootstrapLimit`)。
85
- - **模型工具**:`feishu_attachment_fetch` 开关与单次资源数 / 字节预算 / 超时(`feishuTools.attachmentFetch*`)、`feishu_topic_messages` 开关与超时(`feishuTools.topicMessages*`)、`feishu_send_file` 开关(`feishuTools.sendFile`)、知识库读/写开关(`feishuTools.wikiRead` / `feishuTools.wikiWrite`)与知识库范围 `wikiRoots`(每条链接一个输入框,逐条校验)、被动引用/历史读取预算(`promptContextTimeoutMs`)。
87
+ - **模型工具**:`feishu_attachment_fetch` 开关与单次资源数 / 字节预算 / 超时(`feishuTools.attachmentFetch*`)、`feishu_topic_messages` 开关与超时(`feishuTools.topicMessages*`)、`feishu_send_file` 开关(`feishuTools.sendFile`)、被动引用/历史读取预算(`promptContextTimeoutMs`)。
86
88
 
87
89
  `feishuTools.*` 是嵌套配置:面板只把改动过的子键写进覆盖层,其余子键保持原值(Web 覆盖或 profile 配置),不会因部分保存变成未定义。字段默认值、边界与降级规则见下文配置示例和 [docs/attachment-pipeline-v2.md](docs/attachment-pipeline-v2.md)。
88
90
 
89
91
  **连接状态**:阶段徽标(待配置 / 未连接 / 连接中 / 已连接 / 连接失败)、飞书长连接状态、Bot open_id 尾号、运行时长、话题路由数 / 活动会话 / 在途回复、最近一次连接失败原因;每 5 秒自动刷新,也可手动刷新或**重启飞书连接**(会断开当前长连接并中断进行中的回复)。徽标不是健康时会额外显示「健康判定」原因——健康等级由宿主**所有 Cordis fiber** 折叠而来,因此「降级」也可能来自其它插件入口仍在等待依赖/加载(与飞书通道无关),原因行会列出对应插件名;「连接失败」只针对飞书连接本身,插件入口保持加载。
90
92
 
91
- **飞书应用凭据**:在「连接 → 飞书应用凭据」卡片里填写 **App ID / App Secret** 并保存,值经宿主 `ctx.credentials` 写入 DSH 凭据库(默认 `$DSH_HOME/.credentials.yaml` 的 `refs:` 段,仍遵循凭据层的解析优先级),面板只显示「已配置 / 未配置」与来源,**永不回显已存值**。两项齐全且通道未运行时保存后自动建立连接;已连接时保存只提示重连;只填了一项时保存成功但**不会连接**(状态保持「待配置」),补齐另一项后自动连接。已存值可以单独「移除已存值」;卡片下方显示的是凭据**引用名**(profile 配置里的 `appId` / `appSecret`),换引用名仍需改 profile 配置。
93
+ **飞书应用凭据**:在「连接 → 飞书应用凭据」卡片里填写 **App ID / App Secret** 并保存,值经宿主 `ctx.credentials` 写入 DSH 凭据库(默认 `$DSH_HOME/.credentials.yaml` 的 `refs:` 段,仍遵循凭据层的解析优先级),面板只显示「已配置 / 未配置」与来源,**永不回显已存值**。两项齐全且通道未运行时保存后自动建立连接;已连接时保存只提示重连;只填了一项时保存成功但**不会连接**(状态保持「待配置」),补齐另一项后自动连接。已存值可以单独「移除已存值」;卡片下方显示的是凭据**引用名**(profile 配置里的 `appId` / `appSecret`),换引用名仍需改 profile 配置。「会话与行为」页底部的「日志分析接口令牌」卡片用同样方式写入令牌,保存或移除后下一次接口请求即生效,不会重连飞书。
92
94
 
93
95
  **统一保存**:除连接类操作外,可编辑字段共用一个草稿;每个表单标签页底部是「保存更改 / 放弃更改」,只提交改动过的字段,标注「需重连」的项在下次连接时生效;保存后「连接」标签页顶部横幅列出待生效项并提供一键重连。
94
96
 
@@ -104,7 +106,7 @@ DeepSeek Harness(DSH)飞书渠道插件:**一个 dsh 实例对应一个飞
104
106
 
105
107
  **为什么落在插件自己的文件而不是 profile 的 `cordis.patch.yml`?** DSH 提供官方 `ctx.configEditor.edit()`,可以安全写 profile patch(完整 Config 校验、保留注释与 `!!js`、原子替换、失败回滚、与 HMR 串行);但它是「完整配置编辑」通道——本插件的 Config 是 zod,没有 schemastery `.volatile()` 标记,普通字段写入会触发 Loader 重载插件,等于每次保存都断开并重连飞书;而且该服务只在有 `profileContext` 的启动器(桌面端 / `dsh web`)存在。`$DSH_HOME/<plugin>/` 是 DSH 插件保存运行时数据的惯例(如 dsh-pet 的 `$DSH_HOME/dsh-pet/`),因此面板偏好写入 `$DSH_HOME/dsh-channels/settings.json`:保存即生效(需重连字段除外)、可一键清除回退、不依赖 `profileContext`;profile 的 `cordis.patch.yml` 继续作为部署级基础配置层。若将来要求「所有配置统一进 profile」,迁移路径是改用 `ctx.configEditor.edit()` 并接受保存触发插件重载。
106
108
 
107
- **安全边界**:面板路由与其他本机 DSH 路由一样**不带鉴权**;写操作(PUT / DELETE / POST)额外校验 `Origin` 与 `Host` 同源,拒绝跨站页面改写配置或凭据。**授权白名单与待处理申请同样面板可写,因此“本机访问面板 = 可授权”**;不要把 DSH Web 服务绑定到不可信网络。凭据值只走写路径、响应永不回显,也不会写进 Web 覆盖文件。`baseDir` 可写意味着同源页面能改变模型文件的落点(面板已拒绝文件系统根目录)。
109
+ **安全边界**:每个面板路由(读和写)都复用 DSH 宿主 `connection` 服务的 `requestRejection`,与 DSH 自己的 `/api` 走同一套 Host/Origin 校验和登录 cookie 鉴权:未登录返回 401,Host/Origin 不受信任返回 403,请求体不会被读取;宿主没有 `connection` 服务时面板路由不挂载。写操作(PUT / DELETE / POST)另保留 `Origin` 与 `Host` 同源校验。**授权白名单与待处理申请同样面板可写,因此“已登录 DSH Web = 可授权”**;非回环绑定时登录 cookie 走明文 HTTP,建议给 DSH Web 服务配置 TLS。凭据值只走写路径、响应永不回显,也不会写进 Web 覆盖文件。`baseDir` 可写意味着同源页面能改变模型文件的落点(面板已拒绝文件系统根目录)。
108
110
 
109
111
  **构建**:DSH 只加载已构建的浏览器 bundle,改了 `src/client/` 或 `src/settings/contract.ts` 后需要重新构建并重启应用:
110
112
 
@@ -147,6 +149,7 @@ src/
147
149
  │ ├── streaming.ts # 流式回复卡构成(面板/活动行/正文/停止按钮/结算页脚)、分段、字节/表格预算
148
150
  │ ├── picker.ts # /model、/effort、/permissions 选择卡与按钮回传解析
149
151
  │ ├── onboarding.ts # 私聊首次触达引导卡
152
+ │ ├── analyze.ts # 日志分析卡(详情页表格、状态标签)
150
153
  │ ├── approval.ts # 审批决策卡
151
154
  │ └── question.ts # 结构化问答卡
152
155
  ├── approvals.ts # 审批 barrel → src/interactions/
@@ -184,12 +187,13 @@ src/
184
187
  ├── feishu-channel.ts # 官方 SDK Channel 适配(可注入 stub)
185
188
  ├── client/ # 浏览器半区:设置 →「飞书渠道」页面(构建到 lib/client.js)
186
189
  │ ├── index.ts # client 插件入口:settings.section 注册 + 样式
187
- │ ├── panel/ # 页面外壳 + 六个标签页
190
+ │ ├── panel/ # 页面外壳 + 七个标签页
188
191
  │ │ ├── index.tsx # 加载 / 5s 状态轮询 / 全局草稿 / catalog 级联 / 顶部状态头 / Tab
189
192
  │ │ ├── shared.tsx # 字段渲染原语、保存栏、阶段徽标、草稿校验
190
193
  │ │ ├── connection-tab.tsx # 连接:三步引导 / 连接信息 / 待生效提醒 / 凭据卡
191
194
  │ │ ├── credentials-card.tsx # App ID / App Secret 编辑(不回显)
192
- │ │ ├── sessions-tab.tsx / model-tab.tsx / workspace-tab.tsx / access-tab.tsx / advanced-tab.tsx
195
+ │ │ ├── analyze-token-card.tsx # 日志分析接口令牌编辑(不回显,不触发重连)
196
+ │ │ ├── sessions-tab.tsx / model-tab.tsx / workspace-tab.tsx / access-tab.tsx / wiki-tab.tsx / advanced-tab.tsx
193
197
  │ │ └── access-card.tsx
194
198
  │ ├── catalog.ts # 选择器目录折叠:Host 字段 + 实时目录 → 表单字段
195
199
  │ ├── api.ts # 同源 /dsh-channels/* 请求封装
@@ -200,7 +204,7 @@ src/
200
204
  │ ├── catalog.ts # 宿主选择目录:provider/model/推理档位/preset
201
205
  │ ├── directory-picker.ts # 系统目录选择框适配(ctx.directoryPicker native 后端)
202
206
  │ ├── surface.ts # 状态 / 面板视图模型 + 保存 / 清除 / 重连 / 选目录
203
- │ └── routes.ts # HTTP 路由 + 同源写保护
207
+ │ └── routes.ts # HTTP 路由 + DSH 登录鉴权 + 同源写保护
204
208
  └── workspaces.ts # 用户工作区 / 话题目录 / 旧版 _staging 路径解析
205
209
  ```
206
210
 
@@ -276,6 +280,8 @@ npm 发行包已包含构建产物:Host 入口是 `lib/index.js`(profile 直
276
280
  accessMode: 'guarded' # 准入模式:guarded(默认,仅管理员+白名单)| open(旧行为,不安全)
277
281
  adminUserId: 'a1b2c3d4' # 管理员 user_id(可留空,留空时自动使用应用所有者;都没有时 guarded 下拒绝所有对话;面板可改)
278
282
  authRequestTtlDays: 7 # 入群/用户授权申请卡有效期(天,1–30)
283
+ analyzeListen: '0.0.0.0:3090' # 日志平台分析接口的独立监听地址(不填 = 关闭;改后需重连)
284
+ analyzeToken: 'DSH_CHANNELS_ANALYZE_TOKEN' # 分析接口 Bearer 令牌的凭据引用名(值在面板填写或写进 .credentials.yaml)
279
285
  loggerLevel: 'info'
280
286
  ```
281
287
 
@@ -319,12 +325,12 @@ FEISHU_APP_SECRET: xxxxxxxx
319
325
  - 「获取应用信息」权限(**可选**,如 `admin:app.info:readonly` / `application:application:self_manage`,以控制台要求为准):未配置 `adminUserId` 时,插件在每次连接时调用 `application/v6/applications/:app_id` 查询应用所有者并让其成为管理员;调不通(企业内不便申请该权限等)时日志与面板会给出飞书返回的错误码,并提示在「访问授权」手动填写管理员 user_id,填写后不再依赖它
320
326
  - 回调:**卡片回传交互 card.action.trigger**(流式卡停止按钮、`/model` 等选择卡、HITL 审批 / `ask_user_question` 问答按钮的回传事件,同样走长连接)
321
327
  - `im:resource`(获取与上传图片或文件资源,**可选**;`im:resource:upload` 亦可):`feishu_send_file` 上传图片/文件后发给用户。未开通时该工具返回可读的 `PERMISSION_DENIED`;关闭 `feishuTools.sendFile` 后自检不再检查此项
322
- - `wiki:wiki`、`docx:document`、`sheets:spreadsheet`、`bitable:app`(**可选**,仅开启知识库工具时需要;只读可用 `wiki:wiki:readonly` / `docx:document:readonly` / `sheets:spreadsheet:readonly` / `bitable:app:readonly`,也接受新版细分权限 `sheets:spreadsheet:read` / `sheets:spreadsheet:write_only` / `base:record:read` / `base:record:create` / `base:record:update` / `base:record:delete`):浏览知识库节点,读取/新建/修改飞书文档、电子表格、多维表格。应用还必须是目标知识空间的成员,否则调用返回 `NO_ACCESS`
328
+ - `wiki:wiki`、`docx:document`(**可选**,仅开启知识库工具时需要;只读可用 `wiki:wiki:readonly` / `docx:document:readonly`):浏览知识库节点,读取/新建/修改飞书文档。电子表格和多维表格走飞书新版接口,只认细分权限,旧版 `sheets:spreadsheet` / `bitable:app` 不算:电子表格读 `sheets:spreadsheet:read`、写 `sheets:spreadsheet:write_only`;多维表格读 `base:table:read` + `base:field:read` + `base:record:read`,写 `base:record:create` / `base:record:update` / `base:record:delete`。应用还必须是目标知识空间的成员,否则调用返回 `NO_ACCESS`
323
329
  - `im:message.reactions:write_only`(发送、删除消息表情回复)
324
330
  - `im:message:update`(更新卡片)
325
331
 
326
332
  > 消息读取权限缺失时不会报死错:引用摘录与首轮话题历史静默省略(插件生命周期内首次降级输出一条 warn,之后 debug 级),显式调用 `feishu_topic_messages` 返回可读的 `PERMISSION_DENIED`。附件下载仍依赖 `im:message` 一类的消息资源权限;下载被拒时工具与 archive 路径都会返回可读原因。
327
- > **连接后权限自检**:每次连上飞书后,插件调用 `application/v6/scopes` 读取应用的租户权限授予状态,与上面的清单对比(私聊关闭时不检查 `im:message.p2p_msg:readonly`,`requireMention: false` 时 `im:message.group_msg` 升为必需;`feishuTools.sendFile` 关闭时不检查 `im:resource`;知识库工具未挂载时不检查 `wiki:wiki` / `docx:document` / `sheets:spreadsheet` / `bitable:app`;未配置管理员且应用所有者查询被飞书拒绝时追加可选项「获取应用信息」)。缺失项在 Web 设置「连接」页以红字列出(标注必需/可选与用途,每项附开发者后台权限页深链,另有一键打开全部缺失项的链接),并在日志打印一条 warn;读取失败时显示飞书返回的原因。自检只做提示,不阻断连接;开通权限后需发布新版本并重新连接才会刷新。
333
+ > **连接后权限自检**:每次连上飞书后,插件调用 `application/v6/scopes` 读取应用的租户权限授予状态,与上面的清单对比(私聊关闭时不检查 `im:message.p2p_msg:readonly`,`requireMention: false` 时 `im:message.group_msg` 升为必需;`feishuTools.sendFile` 关闭时不检查 `im:resource`;知识库工具未挂载时不检查知识库、文档、电子表格和多维表格权限;未配置管理员且应用所有者查询被飞书拒绝时追加可选项「获取应用信息」)。缺失项在 Web 设置「连接」页以红字列出(标注必需/可选与用途,每项附开发者后台权限页深链,另有一键打开全部缺失项的链接),并在日志打印一条 warn;读取失败时显示飞书返回的原因。自检只做提示,不阻断连接;开通权限后需发布新版本并重新连接才会刷新。
328
334
  3. 事件订阅方式选**长连接**(WebSocket,免公网)。
329
335
  4. 发布版本并把用户加入可用范围;将机器人拉入测试群。
330
336
 
@@ -336,6 +342,7 @@ FEISHU_APP_SECRET: xxxxxxxx
336
342
  |---|---|---|
337
343
  | 私聊 | `<baseDir>/p2p/<user_id>/` | `<工作区>/topics/<话题 id>/` |
338
344
  | 群聊 | `<baseDir>/groups/<chat_id>/<user_id>/` | 同上 |
345
+ | 日志平台分析 | `<baseDir>/analyze/<应用>/<错误码>/` | 同上 |
339
346
 
340
347
  - 群聊必须是「群 × 用户」:同一个人在不同群之间不共享文件,避免 A 群会话把 B 群内容带出来。
341
348
  - 会话创建时以工作区为 header cwd(保证 GUI 里归属该项目、沙箱边界稳定),随后通过 `ctx.workingDirectory` 把实际工作目录切到话题目录;冷恢复会从 Session 日志还原该目录。因此话题之间的文件互不干扰,而工作区数量只随用户增长。
@@ -346,6 +353,56 @@ FEISHU_APP_SECRET: xxxxxxxx
346
353
  - 旧版本「一个话题一个工作区」(`<baseDir>/topics/...`)不会自动迁移:旧话题继续用原目录;旧版「用户 × 聊天」共享工作区正好作为新的用户工作区复用。
347
354
  - 设计取舍与约束详见 [docs/workspace-isolation-design.md](docs/workspace-isolation-design.md)。
348
355
 
356
+ ## 日志平台分析接口
357
+
358
+ 日志平台调用插件的分析接口:插件在指定群里发一张分析卡(发起人、触发时间、应用、错误码和日志详情页表格),在卡片下开话题,把平台给的 prompt 原样交给一个新 Session(prompt 开头的斜杠命令由 DSH 展开成对应 skill),分析过程在话题内流式输出,和用户自己发起的会话一样。分析结束后分析卡的状态标签随之更新。群里通过准入的成员都可以在该话题内追问。插件只负责接收 prompt 和开会话:日志包地址写在 prompt 里,下载、解压、超时都由日志分析 skill 自己处理(Session 的沙箱需要能访问日志存储地址)。
359
+
360
+ **开启**:配置 `analyzeListen`(如 `0.0.0.0:3090`,面板「行为」页可改,需重连),并在同一页的「日志分析接口令牌」卡片填写访问令牌(建议 32 位以上随机串;写入 DSH 凭据库的 `DSH_CHANNELS_ANALYZE_TOKEN`,也可直接写进 `$DSH_HOME/.credentials.yaml`,引用名可用 `analyzeToken` 修改)。接口跑在独立端口上,与 DSH Web 端口无关;令牌每次请求都重新解析,轮换后下一次请求生效;令牌缺失时所有请求返回 503。端口绑定失败不影响飞书连接,健康等级降为「降级」,面板「健康判定」与 `/health` 卡片会显示失败原因。
361
+
362
+ **请求**:
363
+
364
+ ```http
365
+ POST /dsh-channels/analyze
366
+ Authorization: Bearer <令牌>
367
+ Idempotency-Key: <平台生成的唯一值,1-200 个可见 ASCII 字符>
368
+ Content-Type: application/json
369
+
370
+ {
371
+ "requesterUserId": "a1b2c3d4",
372
+ "triggeredAt": "2026-10-22 19:30",
373
+ "app": "camera",
374
+ "errorCode": "E1001",
375
+ "logDetails": [
376
+ { "name": "设备 A 崩溃日志", "url": "https://logs.example.com/detail/1" },
377
+ { "name": "设备 B 崩溃日志", "url": "https://logs.example.com/detail/2" }
378
+ ],
379
+ "chatId": "oc_xxx",
380
+ "prompt": "/log-analyze https://logs.example.com/1.zip https://logs.example.com/2.zip 请分析这些日志"
381
+ }
382
+ ```
383
+
384
+ - `requesterUserId`:平台发起人的飞书 user_id,分析卡 @ 此人;路由记录里也以此人作为发起人。
385
+ - `triggeredAt`:平台上发起分析的时间,1–64 字符,原样显示在分析卡上(时区和格式由平台决定)。
386
+ - `app` / `errorCode`:只允许 `A-Z a-z 0-9 . _ -`(1–128 字符),决定工作区 `<baseDir>/analyze/<app>/<errorCode>/`;同一应用同一错误码的所有分析都在这个工作区里,每次分析各有一个话题目录。
387
+ - `logDetails`:触发分析的日志在日志平台上的详情页,1–50 条,每条 `name`(1–200 字符)+ `url`(http/https)。只在分析卡的表格里列出(每页 10 行),插件不会访问这些地址。
388
+ - `chatId`:目标群,必须是已授权群(`/auth group add` 或面板「访问与授权」),机器人需在群内;`accessMode: open` 时不校验。
389
+ - `prompt`:1–100000 字符,原样作为首条用户消息,插件不追加任何内容。
390
+ - 只接受以上 7 个字段,多余字段返回 400。
391
+
392
+ **响应**:校验通过、分析卡发出后立即返回 `202`:
393
+
394
+ ```json
395
+ { "ok": true, "chatId": "oc_xxx", "messageId": "om_xxx" }
396
+ ```
397
+
398
+ `messageId` 是分析卡的消息 id,分析话题挂在它下面。同步错误都带中文 `error`:`401` 令牌无效、`400` 参数或 `Idempotency-Key` 不合法、`403` 目标群未授权、`409` 同一个 `Idempotency-Key` 已被内容不同的请求使用、`413` 请求体超过 1 MiB、`415` 不是 JSON、`502` 分析卡发送失败(如机器人不在群里)、`503` 飞书未连接或未配置令牌。
399
+
400
+ **去重**:同一个 `Idempotency-Key` 再次请求(包括并发重试)原样返回第一次的结果,不会再发卡、再分析。记录永久保存在 `channels_routes_v2` 域的 `analyze_requests` 表。分析卡发出后的失败(话题创建、Session 创建)不会回滚这条记录:分析卡的状态变成「失败」并写明原因,需要重试时请换一个新的 `Idempotency-Key`。
401
+
402
+ **群里看到的**:先是分析卡「@发起人 于 <triggeredAt> 发起了对 X 应用 Y 错误码不良日志的分析」,下面是日志详情页表格,标题栏标签为「分析中」;紧接着话题内一张流式卡片(它同时创建话题),首轮分析在这里流式输出,带停止按钮。首轮结束后分析卡的标签变成「已完成」「已停止」或「失败」,并加上 DONE / No 表情;之后的追问不再改动分析卡。启动失败时话题里的流式卡片和分析卡都会写明原因。分析过程中出现的审批卡、提问卡发在话题内。DSH 在首轮进行中重启或重连飞书时,话题里的流式卡片照常由中断修复处理;重新连上后,插件把仍是「分析中」的分析卡改成「已中断」(橙色),提示在话题内回复即可继续,原来的 Session 会接着追问。分析卡状态同时记在 `analyze_requests` 记录里(连同重画卡片所需的发起人、时间、应用、错误码和详情页)。
403
+
404
+ **追问与权限**:分析话题的路由是**群内共享**的(路由记录 `shared: true`):群里任何通过准入的成员(管理员,或该群已授权且本人在用户白名单)都可以在话题内直接回复追问(无需 @),也可以点停止按钮、回答审批卡与提问卡、在话题里执行会话命令;未授权的人仍被静默忽略。追问的回复照常流式输出。话题之间互不读取,和普通话题一样。分析结果 markdown 怎么回传给平台由日志分析插件自己负责,本插件不参与。
405
+
349
406
  ## 行为与降级
350
407
 
351
408
  - **入站附件(v2:零等待 + 清单 + 图片预取 + 文件懒取)**:收到触发消息时先建会话 / 话题 / 卡片第一帧并持久化路由,**不在建立前下载任何附件字节**。随后组装的 prompt 依次为:说明文字 → `<feishu_quote>` 引用摘录 → 预取成功的 `ImageBlock` → `<feishu_context>` 未加载附件清单 → (首轮)`<feishu_history>` 话题历史;空块不产生 token。触发消息中的图片在路由建立后、`followup` 前**预取**进首轮(单图 `prefetchImageMaxBytes`、总量 `prefetchTotalMaxBytes`、整段 `prefetchTimeoutMs`,失败/超限/超时降级为清单项);文件**默认不下载**,清单只给 `message_id / file_key / 类型 / 文件名`,模型需要时调用 `feishu_attachment_fetch` 懒取(图片回 `ImageBlock`,文件流式入库 + `FileBlock` + 只读路径兜底)。富文本(post)消息 `files[]` 里 SDK 归一化会丢的文件也会被恢复并进入清单/预取。**只发附件、不带说明的消息完全静默**:不回复、不建会话、不下载、不加表情,本地不写任何记录。插件不扫描历史去找这类消息:用户需要引用那条消息再提问(多个文件先合并转发再引用),被引用消息的附件进入清单与 fetch 白名单;话题内的附件也可由模型调用 `feishu_topic_messages` 读到并获得授权。
@@ -412,4 +469,4 @@ pnpm verify:client # client loader 冒烟
412
469
  pnpm check # 以上全部
413
470
  ```
414
471
 
415
- 测试以 stub 替换官方 SDK 与 DSH 宿主服务,无需真实凭据。附件管线相关测试:`tests/native-attachments.test.ts`(存储层与 archive 降级)、`tests/feishu-tools.test.ts`(两个工具的白名单 / 上限 / 取消)、`tests/manifest.test.ts`(清单格式与转义)、`tests/inbound-image-prefetch.test.ts`(预取降级)、`tests/message-read.test.ts`(引用 / 历史读取);接入链路由 `tests/service.test.ts` / `tests/inbound-attachments.test.ts` 覆盖。`tests/settings.test.ts` 覆盖:覆盖层 schema/层级、面板视图模型、连接阶段(待配置/未连接/已连接/失败)、凭据保存按就绪度连接、HTTP 路由(含同源写保护)、`apply()` 的 webServer 接线、模型选择器的宿主目录与降级行为;`tests/client-catalog.test.ts` 覆盖浏览器半区的目录折叠(含依赖项解禁回归);以及 `FeishuService` 的覆盖加载 / 热更新 / 重启 / 连接失败记录 / 监听器释放。
472
+ 测试以 stub 替换官方 SDK 与 DSH 宿主服务,无需真实凭据。附件管线相关测试:`tests/native-attachments.test.ts`(存储层与 archive 降级)、`tests/feishu-tools.test.ts`(两个工具的白名单 / 上限 / 取消)、`tests/manifest.test.ts`(清单格式与转义)、`tests/inbound-image-prefetch.test.ts`(预取降级)、`tests/message-read.test.ts`(引用 / 历史读取);接入链路由 `tests/service.test.ts` / `tests/inbound-attachments.test.ts` 覆盖。`tests/settings.test.ts` 覆盖:覆盖层 schema/层级、面板视图模型、连接阶段(待配置/未连接/已连接/失败)、凭据保存按就绪度连接、HTTP 路由(含 DSH 登录鉴权与同源写保护)、`apply()` 的 webServer + connection 接线、模型选择器的宿主目录与降级行为;`tests/client-catalog.test.ts` 覆盖浏览器半区的目录折叠(含依赖项解禁回归);以及 `FeishuService` 的覆盖加载 / 热更新 / 重启 / 连接失败记录 / 监听器释放。
@@ -25,7 +25,7 @@
25
25
  | `im:message.reactions:write_only`(发送、删除消息表情回复) | Typing / DONE / No 表态 |
26
26
  | `im:resource`(获取与上传图片或文件资源,**可选**;`im:resource:upload` 亦可) | 模型用 `feishu_send_file` 把生成的图片/文件发给用户;不开通时该工具返回可读错误 |
27
27
  | `im:message:update`(更新消息卡片) | 流式卡片刷新(patch) |
28
- | `wiki:wiki`、`docx:document`、`sheets:spreadsheet`、`bitable:app`(**可选**,只读可用对应的 `:readonly` 权限) | 知识库工具(`feishuTools.wikiRead` / `wikiWrite` + `wikiRoots`):浏览知识库,读取/新建/修改飞书文档、电子表格、多维表格;应用还需是目标知识空间的成员 |
28
+ | `wiki:wiki`、`docx:document`(只读可用对应的 `:readonly` 权限),电子表格 `sheets:spreadsheet:read` / `sheets:spreadsheet:write_only`,多维表格 `base:table:read`、`base:field:read`、`base:record:read` / `base:record:create`、`base:record:update`、`base:record:delete`(**可选**;旧版 `sheets:spreadsheet` / `bitable:app` 对新版接口不生效) | 知识库工具(`feishuTools.wikiRead` / `wikiWrite` + `wikiRoots`):浏览知识库,读取/新建/修改飞书文档、电子表格、多维表格;应用还需是目标知识空间的成员 |
29
29
  | `im:chat:readonly` 一类(获取群信息) | 入群通知卡展示群名/成员数(具体 scope 以控制台要求为准) |
30
30
  | 「获取应用信息」一类(如 `admin:app.info:readonly` / `application:application:self_manage`,**可选**,以控制台要求为准) | 未配置 `adminUserId` 时连接后自动把应用所有者设为管理员;不便申请时跳过即可,按 3.3 手动设置管理员 |
31
31
  | `application:app_slash_command:read` 与 `application:app_slash_command:write`(读取/管理应用斜杠指令,可选,仅 `slashCommandPanel: true` 时需要) | 连接后把插件命令同步到飞书原生斜杠指令面板(输入 `/` 时列出,客户端约 5 分钟后刷新);缺失时只是面板没有列表,直接输入命令不受影响 |