@modesto_ma/dsh-channels 0.5.0 → 0.7.0
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 +10 -10
- package/lib/client.js +53 -37
- package/lib/client.js.map +1 -1
- package/lib/index.js +446 -665
- package/lib/index.js.map +1 -1
- package/package.json +2 -2
- package/src/analyze/contract.ts +8 -3
- package/src/analyze/launcher.ts +28 -5
- package/src/analyze/server.ts +60 -54
- package/src/client/api.ts +28 -30
- package/src/client/catalog.ts +1 -1
- package/src/client/panel/advanced-tab.tsx +1 -1
- package/src/config.ts +4 -13
- package/src/health.ts +0 -12
- package/src/host/agent.ts +24 -1
- package/src/index.ts +6 -4
- package/src/service.ts +18 -38
- package/src/session/manager.ts +4 -4
- package/src/session/request-policy.ts +64 -5
- package/src/settings/contract.ts +49 -34
- package/src/settings/routes.ts +159 -443
- package/src/settings/surface.ts +5 -6
- package/src/streamer.ts +3 -2
package/README.md
CHANGED
|
@@ -82,7 +82,7 @@ DeepSeek Harness(DSH)飞书渠道插件:**一个 dsh 实例对应一个飞
|
|
|
82
82
|
| 附件与工具 | 「入站附件」:接收图片和文件、使用 DSH 原生附件库(开关,关闭则存到话题目录 `attachments/`)、图片预取、首轮话题历史;「工具」:`feishu_attachment_fetch` / `feishu_topic_messages` / `feishu_send_file` 开关;「限额与超时」:预取上限、读取附件上限与超时、读取话题消息超时、引用摘录与历史超时(共 8 项,默认收起,有改动或错误时自动展开) |
|
|
83
83
|
| 访问授权 | 待处理申请(置顶,一键授权 / 忽略)、调试模式(`accessMode`,开关)、白名单用户可私聊(`dmAllowWhitelist`,开关)、管理员 user_id、申请有效期;已授权群 / 用户白名单(添加表单默认收起) |
|
|
84
84
|
| 知识库 | 知识库读 / 写开关(`feishuTools.wikiRead` / `feishuTools.wikiWrite`)、可访问范围 `wikiRoots`(每条链接一个输入框,逐条校验);「可使用的人」:知识库白名单增删(与用户白名单独立,等同 `/auth wiki add|remove`) |
|
|
85
|
-
| 高级 |
|
|
85
|
+
| 高级 | 日志分析接口(开关 `analyzeEnabled` 与访问令牌)、SDK 日志级别、Web 覆盖文件路径与「恢复 profile 配置」 |
|
|
86
86
|
|
|
87
87
|
**调试模式**:「访问授权 → 调试模式」打开即 `accessMode: open`,所有人都能和机器人聊天;打开期间面板顶部常驻黄色警告。默认关闭(`guarded`)。
|
|
88
88
|
|
|
@@ -100,19 +100,19 @@ DeepSeek Harness(DSH)飞书渠道插件:**一个 dsh 实例对应一个飞
|
|
|
100
100
|
|
|
101
101
|
**统一保存**:除连接类操作外,可编辑字段共用一个跨标签页的草稿;有未保存改动时页面底部浮出保存栏(「N 项未保存」、其中需重连的项数、「放弃 / 保存」),没有改动时不显示。只提交改动过的字段,标注「需重连」的项在下次连接时生效;保存后「连接」标签页顶部横幅列出待生效项并提供一键重连。
|
|
102
102
|
|
|
103
|
-
**模型选择器**:模型与推理在面板里是**独立的「模型」标签页**:宿主 LLM 服务可用时,`provider` / `model` / `reasoningEffort` 与 `agentPreset` 渲染为**下拉选择**而不是手填:provider 来自 `ctx.llm.listProviders()`,model 来自所选 provider 的 `listModels()`,推理档位来自 `resolveModelInfo(provider, model).reasoning.efforts`,Agent 预设来自 `agentPresets.list()`。每个选择器的首项是「留空 = 部署默认 / 适配器默认 / roster 默认」,有部署默认模型(`agentDefaultModel`)或 roster 默认预设时会在标签里带上具体值。选择按依赖级联:先选 provider 才会加载 model,选了 model 才会加载推理档位;宿主暂时无法枚举的项(服务未挂载、适配器查询失败/超时)自动退回原来的自由文本框,已保存但当前列表里没有的值会作为「当前配置,不在宿主机列表中」选项保留,不会在打开面板时被静默改掉。级联查询走只读的 `GET /dsh-channels
|
|
103
|
+
**模型选择器**:模型与推理在面板里是**独立的「模型」标签页**:宿主 LLM 服务可用时,`provider` / `model` / `reasoningEffort` 与 `agentPreset` 渲染为**下拉选择**而不是手填:provider 来自 `ctx.llm.listProviders()`,model 来自所选 provider 的 `listModels()`,推理档位来自 `resolveModelInfo(provider, model).reasoning.efforts`,Agent 预设来自 `agentPresets.list()`。每个选择器的首项是「留空 = 部署默认 / 适配器默认 / roster 默认」,有部署默认模型(`agentDefaultModel`)或 roster 默认预设时会在标签里带上具体值。选择按依赖级联:先选 provider 才会加载 model,选了 model 才会加载推理档位;宿主暂时无法枚举的项(服务未挂载、适配器查询失败/超时)自动退回原来的自由文本框,已保存但当前列表里没有的值会作为「当前配置,不在宿主机列表中」选项保留,不会在打开面板时被静默改掉。级联查询走只读的 `GET /api/dsh-channels.catalog?provider=…&model=…`(Host 侧短 TTL 缓存,单次查询 4 秒上限)。`provider` 与 `model` 在宿主侧是一条完整的请求路由,因此面板保存时要求**两者同时设置或同时留空**,不允许只选一半。
|
|
104
104
|
|
|
105
105
|
`baseDir` 是**模型/工具产生文件的落点**(入站附件默认进 DSH 原生附件库、不落这里;仅 archive 后端才写话题目录):**每个用户拥有一个独立工作区**(私聊 `<baseDir>/p2p/<user_id>/`,群聊 `<baseDir>/groups/<chat_id>/<user_id>/`,同一群里不同用户互不影响;旧版本的「用户 × 聊天」共享工作区正好复用为这个新工作区)。每个话题在工作区内拥有独立工作目录 `<工作区>/topics/<话题 id>/`(无话题的降级会话用 session id),所以话题之间的文件互不干扰,而工作区数量只随用户增长。面板里只接受**绝对路径**(`~` 开头按家目录展开,拒绝文件系统根目录),保存时会创建并校验该目录,不可用时当场报错,不会等到飞书里提示「会话创建失败」。已有 Session / 目录不会被迁移,因此切到新目录后旧话题继续用旧目录(话题内 `/renew` 也一样),只有之后新开的会话使用新目录。
|
|
106
106
|
|
|
107
107
|
**可视化选择工作区根目录**:输入框旁的「选择目录…」按钮会调用**宿主机的系统目录选择框**(macOS 的 NSOpenPanel / Windows 的文件夹对话框),选中后把绝对路径填进草稿,仍需点「保存更改」才落盘。该能力转发自宿主可选的 `ctx.directoryPicker` 服务:桌面端/本机 GUI 会话(native 后端)提供按钮;headless、远程或 SSH 会话没有可服务的显示会话(browse 后端),面板不显示按钮、保留手写输入,插件在缺少该服务的旧宿主上照常加载。选择框打开期间请求保持挂起;取消是正常结果,不会报错。
|
|
108
108
|
|
|
109
|
-
**访问授权**:「访问授权」标签页集中管理准入:管理员 user_id(私聊 `/userid` 获取)、调试模式、白名单用户可私聊、授权申请有效期;「已授权群」「已授权用户」列表支持增删(群 ID 可从入群群信息卡或 `/groupid` 获得,用户 user_id 可用 `/userid` 查看);「待处理申请」可一键授权/忽略。白名单只约束非管理员:管理员在任何私聊/群聊都直接放行。管理员留空时自动使用飞书应用所有者(连接时通过「获取应用信息」接口查询),管理员设置下方的「当前管理员」一行会注明“应用所有者”;`guarded` 且既未配置管理员、也查不到应用所有者时,标签页顶部会红字提示“当前会拒绝所有对话”并附上查询失败原因。管理员、群和用户白名单、待处理申请、知识库白名单在每次进入页面时实时读取飞书头像和名字(`GET /dsh-channels
|
|
109
|
+
**访问授权**:「访问授权」标签页集中管理准入:管理员 user_id(私聊 `/userid` 获取)、调试模式、白名单用户可私聊、授权申请有效期;「已授权群」「已授权用户」列表支持增删(群 ID 可从入群群信息卡或 `/groupid` 获得,用户 user_id 可用 `/userid` 查看);「待处理申请」可一键授权/忽略。白名单只约束非管理员:管理员在任何私聊/群聊都直接放行。管理员留空时自动使用飞书应用所有者(连接时通过「获取应用信息」接口查询),管理员设置下方的「当前管理员」一行会注明“应用所有者”;`guarded` 且既未配置管理员、也查不到应用所有者时,标签页顶部会红字提示“当前会拒绝所有对话”并附上查询失败原因。管理员、群和用户白名单、待处理申请、知识库白名单在每次进入页面时实时读取飞书头像和名字(`GET /api/dsh-channels.profiles?users=…&chats=…`,每类最多 200 个,Host 侧缓存 30 秒):用户需要 `contact:user.base:readonly` 且在应用的通讯录权限范围内,群需要 `im:chat:readonly`;读不到时显示授权时记下的名字或 ID,并在标签页顶部注明失败原因。授权白名单与申请记录保存在插件 `storageDomain`(`channels_routes_v2` 域),重启后保留;未建立飞书连接时白名单尚未加载,标签页会提示「连接建立后加载授权列表」并禁用写操作。
|
|
110
110
|
|
|
111
111
|
**配置层级**:`configSchema` 默认值 < profile `cordis.patch.yml` 的 `config` < Web 面板覆盖。面板只把可编辑字段写入 `$DSH_HOME/dsh-channels/settings.json`;「高级 → 恢复 profile 配置」确认后删除该文件并回落到 profile 配置。`baseDir` 与 `topicIsolation` 在「会话 → 工作区」编辑(只影响之后新建的话题);话题过期、审批 / 提问卡片等待与 `maxTokens` 保存后立即生效,不需要重连。凭据**值**在「连接 → 飞书应用凭据」编辑,凭据**引用名**(`appId` / `appSecret` 配置项)仍由 profile 配置决定。已移除的目录清理配置 `workspaceRetention` / `workspaceRetentionAuto` 与覆盖文件路径配置 `settingsFile` 若仍留在 profile 或覆盖文件中会被静默忽略,面板下次保存时从覆盖文件中去掉。
|
|
112
112
|
|
|
113
113
|
**为什么落在插件自己的文件而不是 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()` 并接受保存触发插件重载。
|
|
114
114
|
|
|
115
|
-
|
|
115
|
+
**安全边界**:面板路由都通过宿主 `connection.fetch` 注册为 DSH `/api` 下的精确路由 `/api/dsh-channels.<方法>`(读用 GET,写一律 POST),由 DSH 的 `/api` 统一做 Host/Origin 校验和登录 cookie 鉴权:未登录返回 401,Host/Origin 不受信任返回 403,只有通过的请求才会到达插件;宿主没有 `connection` 服务时面板路由不挂载。浏览器按相对路径请求(DSH 页面带 `<base href="./">`),DSH 挂在反向代理前缀下(如 `/dsh/`)时请求自动带上前缀。**授权白名单与待处理申请同样面板可写,因此“已登录 DSH Web = 可授权”**;非回环绑定时登录 cookie 走明文 HTTP,建议给 DSH Web 服务配置 TLS。凭据值只走写路径、响应永不回显,也不会写进 Web 覆盖文件。`baseDir` 可写意味着同源页面能改变模型文件的落点(面板已拒绝文件系统根目录)。
|
|
116
116
|
|
|
117
117
|
**构建**:DSH 只加载已构建的浏览器 bundle,改了 `src/client/` 或 `src/settings/contract.ts` 后需要重新构建并重启应用:
|
|
118
118
|
|
|
@@ -132,7 +132,7 @@ pnpm verify:client # 用 __ModuleLoader__ shim 做 loader 冒烟
|
|
|
132
132
|
- **开始于 / 最近活跃**:飞书侧已经换到新会话(`/renew`、`/new`)或路由过期后,「最近活跃」换成「状态:飞书侧已切换到新会话或已过期」;
|
|
133
133
|
- **在飞书中打开**(飞书 applink 打开对应聊天)与「刷新」。
|
|
134
134
|
|
|
135
|
-
Web 里直接新建的会话不显示按钮。来源在 Host 侧按会话 id 持久保存(存储表 `session_origins`,路由过期后仍可查;升级前已有的路由在启动时补写)。群名、群头像、姓名与头像在每次打开「飞书来源」标签页时实时读取(Host 侧缓存 30 秒,标题旁按钮与标签页同时打开只查一次):群用 `im:chat:readonly`,用户优先用通讯录 `contact:user.base:readonly`,缺失时群聊从群成员列表查姓名(没有头像),私聊只显示 `user_id`。接口为 `GET /dsh-channels
|
|
135
|
+
Web 里直接新建的会话不显示按钮。来源在 Host 侧按会话 id 持久保存(存储表 `session_origins`,路由过期后仍可查;升级前已有的路由在启动时补写)。群名、群头像、姓名与头像在每次打开「飞书来源」标签页时实时读取(Host 侧缓存 30 秒,标题旁按钮与标签页同时打开只查一次):群用 `im:chat:readonly`,用户优先用通讯录 `contact:user.base:readonly`,缺失时群聊从群成员列表查姓名(没有头像),私聊只显示 `user_id`。接口为 `GET /api/dsh-channels.sessionOrigin?sessionId=…`,与设置面板一样需要 DSH 登录。右侧栏不可用的宿主(如 headless)不注册这部分。
|
|
136
136
|
|
|
137
137
|
## 发布
|
|
138
138
|
|
|
@@ -302,12 +302,12 @@ npm 发行包已包含构建产物:Host 入口是 `lib/index.js`(profile 直
|
|
|
302
302
|
dmAllowWhitelist: false # guarded 下用户白名单里的人也可私聊(默认关:私聊仅管理员)
|
|
303
303
|
adminUserId: 'a1b2c3d4' # 管理员 user_id(可留空,留空时自动使用应用所有者;都没有时 guarded 下拒绝所有对话;面板可改)
|
|
304
304
|
authRequestTtlDays: 7 # 入群/用户授权申请卡有效期(天,1–30)
|
|
305
|
-
|
|
305
|
+
analyzeEnabled: false # 日志平台分析接口开关(POST /open-api/logalyze,挂在 DSH Web 端口;改后立即生效)
|
|
306
306
|
analyzeToken: 'DSH_CHANNELS_ANALYZE_TOKEN' # 分析接口 Bearer 令牌的凭据引用名(值在面板填写或写进 .credentials.yaml)
|
|
307
307
|
loggerLevel: 'info'
|
|
308
308
|
```
|
|
309
309
|
|
|
310
|
-
`reasoningEffort` 是每个 Session
|
|
310
|
+
`reasoningEffort` 是每个 Session 的初始/重置默认值。DSH 0.2.2 起模型选择归 Agent 所有(`agent.model`),插件在新建、恢复、借用 Agent 以及 `/model`、`/effort` 成功后用 `agent.model.select()` 记录生效的 provider/model/推理强度(`/model reset` 回到 Agent 的部署默认路由);DSH 0.2.1 没有 `agent.model`,插件改为在该 Agent 作用域内以 `prepend` 外层覆盖 `agent/request`,因此胜过已存在的模型选择与 provider 默认。两种宿主自动识别,无需配置;借用外部 Agent 时,插件关闭只移除这条频道策略,不销毁外部 Agent。话题所有者可用 `/effort <档位-id>` 覆盖当前 Session,`/effort` 查看生效来源,`/effort reset` 恢复该配置值(未配置时恢复模型适配器/provider 默认)。成功选择由 Session 自带的 `command/run` / `command/done` 日志恢复,重启后仍生效;新建 Session 不继承旧 Session 的覆盖。
|
|
311
311
|
|
|
312
312
|
推理档位是适配器拥有的不透明 id(常见如 `off`、`low`、`high`、`max`),命令不会猜测或预校验 provider 的能力;不受支持的值会在下一次模型请求前被 Harness 拒绝,并按失败终态显示错误。
|
|
313
313
|
|
|
@@ -379,12 +379,12 @@ FEISHU_APP_SECRET: xxxxxxxx
|
|
|
379
379
|
|
|
380
380
|
日志平台调用插件的分析接口:插件在指定群里发一张分析卡(发起人、触发时间、应用、错误码和日志详情页表格),在卡片下开话题,把平台给的 prompt 原样交给一个新 Session(prompt 开头的斜杠命令由 DSH 展开成对应 skill),分析过程在话题内流式输出,和用户自己发起的会话一样。分析结束后分析卡的状态标签随之更新。群里通过准入的成员都可以在该话题内追问。插件只负责接收 prompt 和开会话:日志包地址写在 prompt 里,下载、解压、超时都由日志分析 skill 自己处理(Session 的沙箱需要能访问日志存储地址)。
|
|
381
381
|
|
|
382
|
-
|
|
382
|
+
**开启**:在面板「高级 → 日志分析接口」打开开关(`analyzeEnabled`,默认关,改后立即生效、不需要重连),并在同一处的「访问令牌」卡片填写令牌(建议 32 位以上随机串;写入 DSH 凭据库的 `DSH_CHANNELS_ANALYZE_TOKEN`,也可直接写进 `$DSH_HOME/.credentials.yaml`,引用名可用 `analyzeToken` 修改)。接口挂在 DSH Web 服务上(与 Web 界面同一端口),路径 `/open-api/logalyze` 刻意放在 DSH 的 `/api` 之外,网关可以把它与需要登录的 DSH API 分开放行;只有带 Web 的 profile(web、desktop)有这个接口,headless 没有。DSH 通过反向代理挂在前缀下时,地址随之带上前缀(如 `https://<域名>/dsh/open-api/logalyze`)。该路由不走 DSH 登录 cookie,访问令牌是唯一凭据。开关关闭时所有请求返回 404;令牌每次请求都重新解析,轮换后下一次请求生效;令牌缺失时所有请求返回 503。
|
|
383
383
|
|
|
384
384
|
**请求**:
|
|
385
385
|
|
|
386
386
|
```http
|
|
387
|
-
POST /
|
|
387
|
+
POST /open-api/logalyze
|
|
388
388
|
Authorization: Bearer <令牌>
|
|
389
389
|
Idempotency-Key: <平台生成的唯一值,1-200 个可见 ASCII 字符>
|
|
390
390
|
Content-Type: application/json
|
|
@@ -417,7 +417,7 @@ Content-Type: application/json
|
|
|
417
417
|
{ "ok": true, "chatId": "oc_xxx", "messageId": "om_xxx" }
|
|
418
418
|
```
|
|
419
419
|
|
|
420
|
-
`messageId` 是分析卡的消息 id,分析话题挂在它下面。同步错误都带中文 `error`:`401` 令牌无效、`400` 参数或 `Idempotency-Key` 不合法、`403` 目标群未授权、`409` 同一个 `Idempotency-Key` 已被内容不同的请求使用、`413` 请求体超过 1 MiB、`415` 不是 JSON、`502` 分析卡发送失败(如机器人不在群里)、`503` 飞书未连接或未配置令牌。
|
|
420
|
+
`messageId` 是分析卡的消息 id,分析话题挂在它下面。同步错误都带中文 `error`:`401` 令牌无效、`404` 接口开关未打开、`400` 参数或 `Idempotency-Key` 不合法、`403` 目标群未授权、`409` 同一个 `Idempotency-Key` 已被内容不同的请求使用、`413` 请求体超过 1 MiB、`415` 不是 JSON、`502` 分析卡发送失败(如机器人不在群里)、`503` 飞书未连接或未配置令牌。
|
|
421
421
|
|
|
422
422
|
**去重**:同一个 `Idempotency-Key` 再次请求(包括并发重试)原样返回第一次的结果,不会再发卡、再分析。记录永久保存在 `channels_routes_v2` 域的 `analyze_requests` 表。分析卡发出后的失败(话题创建、Session 创建)不会回滚这条记录:分析卡的状态变成「失败」并写明原因,需要重试时请换一个新的 `Idempotency-Key`。
|
|
423
423
|
|
package/lib/client.js
CHANGED
|
@@ -15,27 +15,43 @@ window.__ModuleLoader__.load({
|
|
|
15
15
|
*
|
|
16
16
|
* @module dsh-channels/settings/contract
|
|
17
17
|
*/
|
|
18
|
-
/**
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Settings panel routes: exact DSH `/api` Fetch routes named
|
|
20
|
+
* `/api/dsh-channels.<method>`. Reads are GET; every write is POST, because
|
|
21
|
+
* DSH exact `/api` routes accept only GET, HEAD, and POST.
|
|
22
|
+
*/
|
|
23
|
+
const STATUS_ROUTE = "/api/dsh-channels.status";
|
|
24
|
+
const PANEL_ROUTE = "/api/dsh-channels.panel";
|
|
25
|
+
/** Persist a partial config edit (POST, body `{ values }`). */
|
|
26
|
+
const SAVE_CONFIG_ROUTE = "/api/dsh-channels.saveConfig";
|
|
27
|
+
/** Drop every Web override (POST, no body). */
|
|
28
|
+
const CLEAR_CONFIG_ROUTE = "/api/dsh-channels.clearConfig";
|
|
29
|
+
/** Store credential values (POST, body `SaveCredentialsRequest`). */
|
|
30
|
+
const SAVE_CREDENTIALS_ROUTE = "/api/dsh-channels.saveCredentials";
|
|
31
|
+
/** Remove one stored credential (POST, body `{ field }`). */
|
|
32
|
+
const CLEAR_CREDENTIAL_ROUTE = "/api/dsh-channels.clearCredential";
|
|
23
33
|
/**
|
|
24
34
|
* Connection lifecycle action (POST). While the channel runs it restarts the
|
|
25
35
|
* long connection; while it is stopped it opens one — so the panel has exactly
|
|
26
36
|
* one "connect / reconnect" button. Credentials are resolved again either way.
|
|
27
37
|
*/
|
|
28
|
-
const RECONNECT_ROUTE = "/dsh-channels
|
|
38
|
+
const RECONNECT_ROUTE = "/api/dsh-channels.reconnect";
|
|
29
39
|
/** 模型/推理档位/预设选择器的宿主目录(provider → model → effort 级联刷新)。 */
|
|
30
|
-
const CATALOG_ROUTE = "/dsh-channels
|
|
31
|
-
/**
|
|
32
|
-
const
|
|
33
|
-
/**
|
|
34
|
-
const
|
|
35
|
-
/**
|
|
36
|
-
const
|
|
40
|
+
const CATALOG_ROUTE = "/api/dsh-channels.catalog";
|
|
41
|
+
/** 授权一个群(POST,body `{ chatId, name? }`)。 */
|
|
42
|
+
const ADD_ACCESS_GROUP_ROUTE = "/api/dsh-channels.addAccessGroup";
|
|
43
|
+
/** 撤销一个群(POST,body `{ chatId }`)。 */
|
|
44
|
+
const REMOVE_ACCESS_GROUP_ROUTE = "/api/dsh-channels.removeAccessGroup";
|
|
45
|
+
/** 用户白名单添加(POST,body `{ userId, name? }`)。 */
|
|
46
|
+
const ADD_ACCESS_USER_ROUTE = "/api/dsh-channels.addAccessUser";
|
|
47
|
+
/** 用户白名单移除(POST,body `{ userId }`)。 */
|
|
48
|
+
const REMOVE_ACCESS_USER_ROUTE = "/api/dsh-channels.removeAccessUser";
|
|
49
|
+
/** 知识库白名单添加(POST,body `{ userId, name? }`)。 */
|
|
50
|
+
const ADD_WIKI_USER_ROUTE = "/api/dsh-channels.addWikiUser";
|
|
51
|
+
/** 知识库白名单移除(POST,body `{ userId }`)。 */
|
|
52
|
+
const REMOVE_WIKI_USER_ROUTE = "/api/dsh-channels.removeWikiUser";
|
|
37
53
|
/** 结算一条待处理授权申请(POST,body `{ token, decision }`)。 */
|
|
38
|
-
const
|
|
54
|
+
const RESOLVE_ACCESS_REQUEST_ROUTE = "/api/dsh-channels.resolveAccessRequest";
|
|
39
55
|
/**
|
|
40
56
|
* 打开宿主机的系统目录选择框(POST,无请求体),返回绝对路径。
|
|
41
57
|
*
|
|
@@ -43,15 +59,15 @@ window.__ModuleLoader__.load({
|
|
|
43
59
|
* 会注册实际选择逻辑;远程/无头会话的 browse 后端不可用,面板不下发
|
|
44
60
|
* [`EditableFieldWire.browse`],保留手写输入。
|
|
45
61
|
*/
|
|
46
|
-
const PICK_DIRECTORY_ROUTE = "/dsh-channels
|
|
47
|
-
/** 会话的飞书来源(GET
|
|
48
|
-
const SESSION_ORIGIN_ROUTE = "/dsh-channels
|
|
62
|
+
const PICK_DIRECTORY_ROUTE = "/api/dsh-channels.pickDirectory";
|
|
63
|
+
/** 会话的飞书来源(GET,`?sessionId=`):对话右侧栏「飞书来源」标签与会话头部入口使用。 */
|
|
64
|
+
const SESSION_ORIGIN_ROUTE = "/api/dsh-channels.sessionOrigin";
|
|
49
65
|
/**
|
|
50
66
|
* `GET PROFILES_ROUTE?users=<user_id,...>&chats=<chat_id,...>`: live Feishu
|
|
51
67
|
* names and avatars for the users and chats the panel shows. At most
|
|
52
68
|
* {@link PROFILES_MAX_IDS} ids per list.
|
|
53
69
|
*/
|
|
54
|
-
const PROFILES_ROUTE = "/dsh-channels
|
|
70
|
+
const PROFILES_ROUTE = "/api/dsh-channels.profiles";
|
|
55
71
|
/**
|
|
56
72
|
* Top-level panel tabs, in render order. The panel shows the connection state
|
|
57
73
|
* header above every tab, so switching tabs never hides whether the bridge is
|
|
@@ -371,7 +387,7 @@ window.__ModuleLoader__.load({
|
|
|
371
387
|
//#region src/client/api.ts
|
|
372
388
|
/**
|
|
373
389
|
* Same-origin HTTP client for the settings panel. No typert contract: the
|
|
374
|
-
* Host half registers
|
|
390
|
+
* Host half registers exact DSH `/api` Fetch routes and this module fetches them.
|
|
375
391
|
*
|
|
376
392
|
* @module dsh-channels/client/api
|
|
377
393
|
*/
|
|
@@ -469,8 +485,8 @@ window.__ModuleLoader__.load({
|
|
|
469
485
|
}
|
|
470
486
|
/** Persist a partial edit. Validation failures come back as `{ok:false}`. */
|
|
471
487
|
async function saveConfig(values) {
|
|
472
|
-
const response = await request(
|
|
473
|
-
method: "
|
|
488
|
+
const response = await request(SAVE_CONFIG_ROUTE, {
|
|
489
|
+
method: "POST",
|
|
474
490
|
headers: { "content-type": "application/json" },
|
|
475
491
|
body: JSON.stringify({ values })
|
|
476
492
|
});
|
|
@@ -488,7 +504,7 @@ window.__ModuleLoader__.load({
|
|
|
488
504
|
}
|
|
489
505
|
/** Drop every Web override and return the refreshed panel. */
|
|
490
506
|
async function clearConfig() {
|
|
491
|
-
const response = await request(
|
|
507
|
+
const response = await request(CLEAR_CONFIG_ROUTE, { method: "POST" });
|
|
492
508
|
const text = await response.text();
|
|
493
509
|
if (response.status === 400) try {
|
|
494
510
|
return JSON.parse(text);
|
|
@@ -535,8 +551,8 @@ window.__ModuleLoader__.load({
|
|
|
535
551
|
}
|
|
536
552
|
/** Store provided credential values (the request/response never carry stored values). */
|
|
537
553
|
async function saveCredentials(values) {
|
|
538
|
-
const response = await request(
|
|
539
|
-
method: "
|
|
554
|
+
const response = await request(SAVE_CREDENTIALS_ROUTE, {
|
|
555
|
+
method: "POST",
|
|
540
556
|
headers: { "content-type": "application/json" },
|
|
541
557
|
body: JSON.stringify(values)
|
|
542
558
|
});
|
|
@@ -554,8 +570,8 @@ window.__ModuleLoader__.load({
|
|
|
554
570
|
}
|
|
555
571
|
/** Remove the stored value behind one credential reference. */
|
|
556
572
|
async function clearCredential(field) {
|
|
557
|
-
const response = await request(
|
|
558
|
-
method: "
|
|
573
|
+
const response = await request(CLEAR_CREDENTIAL_ROUTE, {
|
|
574
|
+
method: "POST",
|
|
559
575
|
headers: { "content-type": "application/json" },
|
|
560
576
|
body: JSON.stringify({ field })
|
|
561
577
|
});
|
|
@@ -572,9 +588,9 @@ window.__ModuleLoader__.load({
|
|
|
572
588
|
return JSON.parse(text);
|
|
573
589
|
}
|
|
574
590
|
/** One access write: a 400 body already is the wire union; others throw. */
|
|
575
|
-
async function mutateAccess(path,
|
|
591
|
+
async function mutateAccess(path, body) {
|
|
576
592
|
const response = await request(path, {
|
|
577
|
-
method,
|
|
593
|
+
method: "POST",
|
|
578
594
|
headers: { "content-type": "application/json" },
|
|
579
595
|
body: JSON.stringify(body)
|
|
580
596
|
});
|
|
@@ -592,40 +608,40 @@ window.__ModuleLoader__.load({
|
|
|
592
608
|
}
|
|
593
609
|
/** Authorize one group; resolves with the refreshed access snapshot. */
|
|
594
610
|
async function addAccessGroup(chatId, name) {
|
|
595
|
-
return mutateAccess(
|
|
611
|
+
return mutateAccess(ADD_ACCESS_GROUP_ROUTE, name === void 0 || name === "" ? { chatId } : {
|
|
596
612
|
chatId,
|
|
597
613
|
name
|
|
598
614
|
});
|
|
599
615
|
}
|
|
600
616
|
/** Revoke one group authorization (takes effect on the next message). */
|
|
601
617
|
async function removeAccessGroup(chatId) {
|
|
602
|
-
return mutateAccess(
|
|
618
|
+
return mutateAccess(REMOVE_ACCESS_GROUP_ROUTE, { chatId });
|
|
603
619
|
}
|
|
604
620
|
/** Authorize one user `user_id` (global across authorized groups). */
|
|
605
621
|
async function addAccessUser(userId, name) {
|
|
606
|
-
return mutateAccess(
|
|
622
|
+
return mutateAccess(ADD_ACCESS_USER_ROUTE, name === void 0 || name === "" ? { userId } : {
|
|
607
623
|
userId,
|
|
608
624
|
name
|
|
609
625
|
});
|
|
610
626
|
}
|
|
611
627
|
/** Revoke one user authorization (takes effect on the next message). */
|
|
612
628
|
async function removeAccessUser(userId) {
|
|
613
|
-
return mutateAccess(
|
|
629
|
+
return mutateAccess(REMOVE_ACCESS_USER_ROUTE, { userId });
|
|
614
630
|
}
|
|
615
631
|
/** Add one user `user_id` to the wiki whitelist. */
|
|
616
632
|
async function addWikiUser(userId, name) {
|
|
617
|
-
return mutateAccess(
|
|
633
|
+
return mutateAccess(ADD_WIKI_USER_ROUTE, name === void 0 || name === "" ? { userId } : {
|
|
618
634
|
userId,
|
|
619
635
|
name
|
|
620
636
|
});
|
|
621
637
|
}
|
|
622
638
|
/** Remove one user from the wiki whitelist (takes effect on the next wiki tool call). */
|
|
623
639
|
async function removeWikiUser(userId) {
|
|
624
|
-
return mutateAccess(
|
|
640
|
+
return mutateAccess(REMOVE_WIKI_USER_ROUTE, { userId });
|
|
625
641
|
}
|
|
626
642
|
/** Settle one pending authorization request (same resolver as the Feishu card). */
|
|
627
643
|
async function resolveAccessRequest(token, decision) {
|
|
628
|
-
return mutateAccess(
|
|
644
|
+
return mutateAccess(RESOLVE_ACCESS_REQUEST_ROUTE, {
|
|
629
645
|
token,
|
|
630
646
|
decision
|
|
631
647
|
});
|
|
@@ -885,7 +901,7 @@ window.__ModuleLoader__.load({
|
|
|
885
901
|
//#region src/client/catalog.ts
|
|
886
902
|
/**
|
|
887
903
|
* 浏览器半区的设置面板选择器目录折叠:把 Host 下发的字段描述与实时目录
|
|
888
|
-
* (`/dsh-channels
|
|
904
|
+
* (`/api/dsh-channels.catalog`)合成通用表单需要的 select 描述。
|
|
889
905
|
*
|
|
890
906
|
* 拆成独立模块(不含 JSX/React)以便单测;`panel/index.tsx` 只负责串起状态与渲染。
|
|
891
907
|
*
|
|
@@ -2189,7 +2205,7 @@ window.__ModuleLoader__.load({
|
|
|
2189
2205
|
//#endregion
|
|
2190
2206
|
//#region src/client/panel/advanced-tab.tsx
|
|
2191
2207
|
/**
|
|
2192
|
-
* 「高级」tab
|
|
2208
|
+
* 「高级」tab:日志分析接口(开关 + 访问令牌凭据行)、SDK 日志级别
|
|
2193
2209
|
* 与 Web 覆盖文件(「恢复 profile 配置」删除整个覆盖层,不走 draft)。
|
|
2194
2210
|
*
|
|
2195
2211
|
* @module dsh-channels/client/panel/advanced-tab
|