@modesto_ma/dsh-channels 0.7.0 → 0.8.0-beta.1
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 +31 -47
- package/lib/client.js +44 -122
- package/lib/client.js.map +1 -1
- package/lib/index.js +436 -558
- package/lib/index.js.map +1 -1
- package/package.json +4 -1
- package/src/access/controller.ts +3 -3
- package/src/cards/index.ts +1 -1
- package/src/cards/topic.ts +104 -0
- package/src/client/origin.tsx +1 -1
- package/src/client/panel/advanced-tab.tsx +5 -29
- package/src/client/panel/index.tsx +2 -39
- package/src/client/panel/shared.tsx +0 -12
- package/src/config.ts +26 -16
- package/src/domain.ts +89 -32
- package/src/host/core.ts +32 -1
- package/src/host/services.ts +5 -0
- package/src/index.ts +11 -4
- package/src/routing/inbound.ts +1 -1
- package/src/service.ts +20 -25
- package/src/settings/contract.ts +6 -21
- package/src/settings/overlay.ts +3 -1
- package/src/settings/routes.ts +2 -3
- package/src/settings/surface.ts +1 -14
- package/src/streamer.ts +91 -5
- package/src/topics/contract.ts +149 -0
- package/src/topics/launcher.ts +428 -0
- package/src/workspaces.ts +25 -18
- package/src/analyze/contract.ts +0 -126
- package/src/analyze/launcher.ts +0 -376
- package/src/analyze/server.ts +0 -207
- package/src/cards/analyze.ts +0 -116
package/README.md
CHANGED
|
@@ -53,6 +53,7 @@ DeepSeek Harness(DSH)飞书渠道插件:**一个 dsh 实例对应一个飞
|
|
|
53
53
|
- **活动状态两行**(小字,**只在当前正在流式输出的那张卡上渲染**,已冻结的续卡/主卡不再重复;**互相独立、互不顶替**),让卡片在“模型不吐字”的阶段(工具执行、subagent、workflow)**不再静默**——按 `statusRefreshMs`(默认 5s)慢节奏刷新计时:
|
|
54
54
|
- **状态行**(委托进度):`tool-workflow/run-start / agent-start / agent-end / run-end`(工作流进度:完成数/总数、阶段、活动成员)→ run 结束转为 `🧵 工作流 xxx 结束(完成)` 并**保留到最终卡片**(后续工具调用、正文输出都不会顶掉它);**子代理进度**(跨会话映射):子代理是独立 session(`header.parentSession` 指向主会话),其 `subagent/descriptor`(label)、`tool/call`(当前工具)被折叠为 `🤖 子代理执行中:检索实现方案 (read) · 已运行 45s`,并行子代理显示计数,完成后在最终卡片汇总为 `🤖 子代理完成:a、b`。**只显示状态,不显示输出**——子代理正文是父模型的中间产物,绝不进卡片正文
|
|
55
55
|
- **工具行**(父会话当前工具调用):`tool/call` → `🔧 调用工具:bash · 已运行 Ns`(subagent 工具显示其 description、workflow 工具显示 meta.name),`tool/result` 返回即清除,**回合结束后收起**(完成态只保留结算状态行:工作流结论 / 子代理完成摘要)
|
|
56
|
+
- **模型请求重试**:DSH(`dsh-llm-retry`)在限流、服务端错误、超时等可重试失败后会在**同一回合内**重跑失败的那一步,卡片照常保持流式:工具行显示 `⚠️ 模型请求失败(RATE_LIMIT),N 秒后第 k/5 次重试`,等待结束后变为 `🔁 模型请求重试中(第 k/5 次)`,重试的请求开始输出即收起。失败那次已流出的半截正文/推理不进入模型历史,卡片随即撤回,重试从头输出。只有 `turn/end` 才结束卡片;重试用尽仍失败时,失败提示后附「已自动重试 N 次」。
|
|
56
57
|
- 两者可同屏:如 `🧵 工作流 xxx 结束(完成)` + `🔧 调用工具:bash`
|
|
57
58
|
2. **正文**:富文本 markdown;超长正文**自动分段投放**——单卡片预算用满后按干净断点(段落 / 标题 / 代码块边界)切分,且**每张卡最多 5 个表格**(飞书硬限制,错误 11310);断点会避开代码围栏与表格,**单个表格/代码块小于单卡预算时不会被从中间切断**(单块本身超过单卡预算时只能硬切,内容仍不丢失)。续片以「📄 回复分段 n」卡片跟进同一话题,**全文无丢失**;续卡被飞书拒收时自动降级为纯文本消息(内容仍送达)。**续卡只留正文**:`📄 回复分段 n` → 可选活动行(仅当它是当前活动卡)→ 正文,无面板、无页脚、无分割线
|
|
58
59
|
3. **停止按钮**:回合进行中,当前正在流式输出的那张卡(主卡或最新续卡)正文下方显示「⏹ 停止」,效果同 `/stop`。首帧不带按钮;排在后面的消息的卡片在轮到它运行时才出现按钮。只有话题发起人在原聊天里点击才生效,点击后按钮变为不可点的「正在停止…」;其他人点击只会在按钮下加一行「仅发起该会话的用户可停止」。回合结束后按钮移除;经 `/stop` 或按钮停止的回合面板标题为 **已停止**,主卡页脚上方显示「⏹ 已按请求停止本轮回复」。
|
|
@@ -82,7 +83,7 @@ DeepSeek Harness(DSH)飞书渠道插件:**一个 dsh 实例对应一个飞
|
|
|
82
83
|
| 附件与工具 | 「入站附件」:接收图片和文件、使用 DSH 原生附件库(开关,关闭则存到话题目录 `attachments/`)、图片预取、首轮话题历史;「工具」:`feishu_attachment_fetch` / `feishu_topic_messages` / `feishu_send_file` 开关;「限额与超时」:预取上限、读取附件上限与超时、读取话题消息超时、引用摘录与历史超时(共 8 项,默认收起,有改动或错误时自动展开) |
|
|
83
84
|
| 访问授权 | 待处理申请(置顶,一键授权 / 忽略)、调试模式(`accessMode`,开关)、白名单用户可私聊(`dmAllowWhitelist`,开关)、管理员 user_id、申请有效期;已授权群 / 用户白名单(添加表单默认收起) |
|
|
84
85
|
| 知识库 | 知识库读 / 写开关(`feishuTools.wikiRead` / `feishuTools.wikiWrite`)、可访问范围 `wikiRoots`(每条链接一个输入框,逐条校验);「可使用的人」:知识库白名单增删(与用户白名单独立,等同 `/auth wiki add|remove`) |
|
|
85
|
-
| 高级 |
|
|
86
|
+
| 高级 | SDK 日志级别、Web 覆盖文件路径与「恢复 profile 配置」 |
|
|
86
87
|
|
|
87
88
|
**调试模式**:「访问授权 → 调试模式」打开即 `accessMode: open`,所有人都能和机器人聊天;打开期间面板顶部常驻黄色警告。默认关闭(`guarded`)。
|
|
88
89
|
|
|
@@ -124,11 +125,11 @@ pnpm verify:client # 用 __ModuleLoader__ shim 做 loader 冒烟
|
|
|
124
125
|
|
|
125
126
|
## 对话里的飞书来源
|
|
126
127
|
|
|
127
|
-
从飞书发起的会话(话题、会话级降级、`/renew`
|
|
128
|
+
从飞书发起的会话(话题、会话级降级、`/renew` 新开的会话、其他插件经 `feishuTopics` 发起的群共享话题,如观象的日志分析)在 Web/桌面端对话标题旁会出现一个来源按钮:群聊显示群名,私聊显示「私聊 · 对方姓名」(读不到姓名时为「飞书私聊」)。点击后在右侧栏打开「飞书来源」标签页,内容包括:
|
|
128
129
|
|
|
129
130
|
- **标题**:群聊为群头像和群名,私聊为对方头像和姓名;
|
|
130
131
|
- **来源**:私聊 / 群聊 / 群聊 · 话题;群名读不到时显示群 ID;
|
|
131
|
-
- **发起人**:头像、姓名 + `user_id
|
|
132
|
+
- **发起人**:头像、姓名 + `user_id`(群共享话题显示发起人,并注明群内成员都可以继续);
|
|
132
133
|
- **开始于 / 最近活跃**:飞书侧已经换到新会话(`/renew`、`/new`)或路由过期后,「最近活跃」换成「状态:飞书侧已切换到新会话或已过期」;
|
|
133
134
|
- **在飞书中打开**(飞书 applink 打开对应聊天)与「刷新」。
|
|
134
135
|
|
|
@@ -136,7 +137,7 @@ Web 里直接新建的会话不显示按钮。来源在 Host 侧按会话 id 持
|
|
|
136
137
|
|
|
137
138
|
## 发布
|
|
138
139
|
|
|
139
|
-
版本号由 semantic-release 按 Conventional Commits
|
|
140
|
+
版本号由 semantic-release 按 Conventional Commits 计算。1.0 之前,破坏性变更(`BREAKING CHANGE`)只升次版本号(如 0.7 → 0.8)。推送(含合并 PR)到 `main` 在检查通过后**自动发布**正式版;推送到 `dev` 只跑检查,beta 版**只在手动触发时发布**,可以先累积多笔提交。
|
|
140
141
|
|
|
141
142
|
| 分支 | 版本 | npm dist-tag |
|
|
142
143
|
| --- | --- | --- |
|
|
@@ -167,7 +168,7 @@ src/
|
|
|
167
168
|
│ ├── streaming.ts # 流式回复卡构成(面板/活动行/正文/停止按钮/结算页脚)、分段、字节/表格预算
|
|
168
169
|
│ ├── picker.ts # /model、/effort、/permissions 选择卡与按钮回传解析
|
|
169
170
|
│ ├── onboarding.ts # 私聊首次触达引导卡
|
|
170
|
-
│ ├──
|
|
171
|
+
│ ├── topic.ts # 群话题服务的根卡片(引导句、链接表格、状态标签)
|
|
171
172
|
│ ├── approval.ts # 审批决策卡
|
|
172
173
|
│ └── question.ts # 结构化问答卡
|
|
173
174
|
├── approvals.ts # 审批 barrel → src/interactions/
|
|
@@ -302,8 +303,6 @@ npm 发行包已包含构建产物:Host 入口是 `lib/index.js`(profile 直
|
|
|
302
303
|
dmAllowWhitelist: false # guarded 下用户白名单里的人也可私聊(默认关:私聊仅管理员)
|
|
303
304
|
adminUserId: 'a1b2c3d4' # 管理员 user_id(可留空,留空时自动使用应用所有者;都没有时 guarded 下拒绝所有对话;面板可改)
|
|
304
305
|
authRequestTtlDays: 7 # 入群/用户授权申请卡有效期(天,1–30)
|
|
305
|
-
analyzeEnabled: false # 日志平台分析接口开关(POST /open-api/logalyze,挂在 DSH Web 端口;改后立即生效)
|
|
306
|
-
analyzeToken: 'DSH_CHANNELS_ANALYZE_TOKEN' # 分析接口 Bearer 令牌的凭据引用名(值在面板填写或写进 .credentials.yaml)
|
|
307
306
|
loggerLevel: 'info'
|
|
308
307
|
```
|
|
309
308
|
|
|
@@ -364,7 +363,7 @@ FEISHU_APP_SECRET: xxxxxxxx
|
|
|
364
363
|
|---|---|---|
|
|
365
364
|
| 私聊 | `<baseDir>/p2p/<user_id>/` | `<工作区>/topics/<话题 id>/` |
|
|
366
365
|
| 群聊 | `<baseDir>/groups/<chat_id>/<user_id>/` | 同上 |
|
|
367
|
-
|
|
|
366
|
+
| 其他插件经 `feishuTopics` 发起的话题 | `<baseDir>/<调用方给的路径段>/`(观象的日志分析是 `analyze/<应用>/<错误码>/`) | 同上 |
|
|
368
367
|
|
|
369
368
|
- 群聊必须是「群 × 用户」:同一个人在不同群之间不共享文件,避免 A 群会话把 B 群内容带出来。
|
|
370
369
|
- 会话创建时以工作区为 header cwd(保证 GUI 里归属该项目、沙箱边界稳定),随后通过 `ctx.workingDirectory` 把实际工作目录切到话题目录;冷恢复会从 Session 日志还原该目录。因此话题之间的文件互不干扰,而工作区数量只随用户增长。
|
|
@@ -375,55 +374,40 @@ FEISHU_APP_SECRET: xxxxxxxx
|
|
|
375
374
|
- 旧版本「一个话题一个工作区」(`<baseDir>/topics/...`)不会自动迁移:旧话题继续用原目录;旧版「用户 × 聊天」共享工作区正好作为新的用户工作区复用。
|
|
376
375
|
- 设计取舍与约束详见 [docs/workspace-isolation-design.md](docs/workspace-isolation-design.md)。
|
|
377
376
|
|
|
378
|
-
##
|
|
377
|
+
## 群话题服务(feishuTopics)
|
|
379
378
|
|
|
380
|
-
|
|
379
|
+
插件向其他 DSH 插件提供 Cordis 服务 `feishuTopics`:调用方给出目标群、发起人、根卡片内容、工作区和 prompt,插件在群里发一张根卡片,在卡片下开话题,把 prompt 原样交给一个新 Session(prompt 开头的斜杠命令由 DSH 展开成对应 skill),过程在话题内流式输出,和用户自己发起的会话一样。首轮结束后根卡片的状态标签随之更新。群里通过准入的成员都可以在该话题内追问。观象([@modesto_ma/dsh-guanxiang](https://github.com/modestoma/dsh-guanxiang))的日志平台分析接口 `POST /open-api/logalyze` 就建在这个服务上。
|
|
381
380
|
|
|
382
|
-
|
|
381
|
+
**日志平台分析接口已迁到观象**:接口的路径、请求体和响应都没有变,但开关和令牌改在观象面板「分析接口」中设置,令牌的凭据引用名改为 `GUANXIANG_ANALYZE_TOKEN`。本插件不再注册这个路由,也不再读取 `analyzeEnabled` / `analyzeToken`;`cordis.yml` 或 Web 覆盖文件里还留着这两项时,启动日志会提示一次。升级步骤:
|
|
383
382
|
|
|
384
|
-
|
|
383
|
+
1. 把 dsh-channels 和观象一起升级(观象需要支持 `feishuTopics` 的版本)。只升级 dsh-channels 时没有插件提供这个接口,请求会得到 404。
|
|
384
|
+
2. 在观象面板「分析接口」中打开开关,保存令牌。平台继续用原来的令牌值时,把同一个值存进去即可。
|
|
385
|
+
3. 从 `cordis.yml` 中删掉 `analyzeEnabled` / `analyzeToken`。
|
|
385
386
|
|
|
386
|
-
|
|
387
|
-
POST /open-api/logalyze
|
|
388
|
-
Authorization: Bearer <令牌>
|
|
389
|
-
Idempotency-Key: <平台生成的唯一值,1-200 个可见 ASCII 字符>
|
|
390
|
-
Content-Type: application/json
|
|
387
|
+
升级前已发出的分析卡、话题和追问权限都不受影响;升级时仍在「分析中」的分析卡会在重新连上飞书后改成「已中断」。
|
|
391
388
|
|
|
392
|
-
|
|
393
|
-
"requesterUserId": "a1b2c3d4",
|
|
394
|
-
"triggeredAt": "2026-10-22 19:30",
|
|
395
|
-
"app": "camera",
|
|
396
|
-
"errorCode": "E1001",
|
|
397
|
-
"logDetails": [
|
|
398
|
-
{ "name": "设备 A 崩溃日志", "url": "https://logs.example.com/detail/1" },
|
|
399
|
-
{ "name": "设备 B 崩溃日志", "url": "https://logs.example.com/detail/2" }
|
|
400
|
-
],
|
|
401
|
-
"chatId": "oc_xxx",
|
|
402
|
-
"prompt": "/log-analyze https://logs.example.com/1.zip https://logs.example.com/2.zip 请分析这些日志"
|
|
403
|
-
}
|
|
404
|
-
```
|
|
405
|
-
|
|
406
|
-
- `requesterUserId`:平台发起人的飞书 user_id,分析卡 @ 此人;路由记录里也以此人作为发起人。
|
|
407
|
-
- `triggeredAt`:平台上发起分析的时间,1–64 字符,原样显示在分析卡上(时区和格式由平台决定)。
|
|
408
|
-
- `app` / `errorCode`:只允许 `A-Z a-z 0-9 . _ -`(1–128 字符),决定工作区 `<baseDir>/analyze/<app>/<errorCode>/`;同一应用同一错误码的所有分析都在这个工作区里,每次分析各有一个话题目录。
|
|
409
|
-
- `logDetails`:触发分析的日志在日志平台上的详情页,1–50 条,每条 `name`(1–200 字符)+ `url`(http/https)。只在分析卡的表格里列出(每页 10 行),插件不会访问这些地址。
|
|
410
|
-
- `chatId`:目标群,必须是已授权群(`/auth group add` 或面板「访问与授权」),机器人需在群内;`accessMode: open` 时不校验。
|
|
411
|
-
- `prompt`:1–100000 字符,原样作为首条用户消息,插件不追加任何内容。
|
|
412
|
-
- 只接受以上 7 个字段,多余字段返回 400。
|
|
389
|
+
**调用方式**:类型在包子路径 `@modesto_ma/dsh-channels/topics`(只有类型,没有运行时导出),调用方用 `import type` 引用,在每次调用时用 `ctx.get('feishuTopics')` 取服务(本插件未加载或正在重载时取不到),并检查 `version`(当前为 `1`)。不要把 `feishuTopics` 写进调用方插件的主 `inject`:那样本插件每次重载(例如改配置后重连)都会连带重载调用方。需要随服务上下线挂载路由时,在子作用域里 `ctx.inject(['feishuTopics'], …)`。
|
|
413
390
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
391
|
+
```ts
|
|
392
|
+
const topics = ctx.get('feishuTopics') as FeishuTopicService | undefined
|
|
393
|
+
const result = await topics?.launch({
|
|
394
|
+
key, fingerprint, chatId, requesterUserId,
|
|
395
|
+
card: { title: '日志分析', taskName: '分析', lead: [{ text: '于 … 发起了对 ' }, { text: 'camera', bold: true }, …], links },
|
|
396
|
+
workspace: { segments: ['analyze', 'camera', 'E1001'], title: '日志分析 · camera · E1001' },
|
|
397
|
+
prompt,
|
|
398
|
+
})
|
|
418
399
|
```
|
|
419
400
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
401
|
+
- `key` / `fingerprint`:幂等键(1–200 个可见 ASCII 字符)与调用方定义的请求指纹。同一个键再次发起(包括并发)返回第一次的根卡片(`replayed: true`),指纹不同则拒绝(`key-conflict`)。记录永久保存在 `channels_routes_v2` 域的 `analyze_requests` 表(沿用旧接口的表名);根卡片发出后的失败不会回滚记录,根卡片显示「失败」和原因,需要重试时换一个新键。
|
|
402
|
+
- `chatId`:目标群,必须是已授权群(`/auth group add` 或面板「访问与授权」),机器人需在群内;`accessMode: open` 时不校验。
|
|
403
|
+
- `card`:标题、任务名(状态文字里用,如「分析中」「分析已完成」)、跟在 @发起人 后的引导句(可加粗片段)、可选的链接表格(最多 50 行,每页 10 行)。
|
|
404
|
+
- `workspace`:`<baseDir>` 下的 1–8 个路径段(只允许 `A-Z a-z 0-9 . _ -`,第一段不能是本插件自用的 `p2p`、`groups`、`topics`、`_staging`)和它在 DSH 工作区列表里的标题。路径段相同的发起共用一个工作区,各有一个话题目录。
|
|
405
|
+
- `prompt`:1–100000 字符,原样作为首条用户消息。
|
|
406
|
+
- 结果:成功时返回根卡片 `messageId`;失败时返回 `code` 和中文 `message`:`invalid`(参数不合法)、`not-connected`(飞书未连接或正在关闭)、`forbidden-chat`(目标群未授权)、`key-conflict`、`send-failed`(根卡片发送失败,如机器人不在群里)。
|
|
423
407
|
|
|
424
|
-
|
|
408
|
+
**群里看到的**:先是根卡片「@发起人 <引导句>」,下面是链接表格,标题栏标签为「<任务名>中」;紧接着话题内一张流式卡片(它同时创建话题),首轮在这里流式输出,带停止按钮。首轮结束后根卡片的标签变成「已完成」「已停止」或「失败」,并加上 DONE / No 表情;之后的追问不再改动根卡片。启动失败时话题里的流式卡片和根卡片都会写明原因。过程中出现的审批卡、提问卡发在话题内。DSH 在首轮进行中重启或重连飞书时,话题里的流式卡片照常由中断修复处理;重新连上后,插件把仍是「<任务名>中」的根卡片改成「已中断」(橙色),提示在话题内回复即可继续,原来的 Session 会接着追问。根卡片状态同时记在发起记录里(连同重画卡片所需的内容)。
|
|
425
409
|
|
|
426
|
-
|
|
410
|
+
**追问与权限**:这类话题的路由是**群内共享**的(路由记录 `shared: true`):群里任何通过准入的成员(管理员,或该群已授权且本人在用户白名单)都可以在话题内直接回复追问(无需 @),也可以点停止按钮、回答审批卡与提问卡、在话题里执行会话命令;未授权的人仍被静默忽略。追问的回复照常流式输出。话题之间互不读取,和普通话题一样。结果怎么回传给外部系统由调用方插件负责,本插件不参与。
|
|
427
411
|
|
|
428
412
|
## 行为与降级
|
|
429
413
|
|
package/lib/client.js
CHANGED
|
@@ -120,7 +120,7 @@ window.__ModuleLoader__.load({
|
|
|
120
120
|
],
|
|
121
121
|
access: ["access"],
|
|
122
122
|
wiki: ["wiki"],
|
|
123
|
-
advanced: ["
|
|
123
|
+
advanced: ["logging"]
|
|
124
124
|
};
|
|
125
125
|
/** Section heading for one field group inside its tab. */
|
|
126
126
|
const FIELD_GROUP_LABELS = {
|
|
@@ -134,7 +134,6 @@ window.__ModuleLoader__.load({
|
|
|
134
134
|
limits: "限额与超时",
|
|
135
135
|
access: "准入",
|
|
136
136
|
wiki: "知识库工具",
|
|
137
|
-
analyze: "日志分析接口",
|
|
138
137
|
logging: "日志"
|
|
139
138
|
};
|
|
140
139
|
/** Groups rendered collapsed behind one summary row (rarely changed values). */
|
|
@@ -250,11 +249,6 @@ window.__ModuleLoader__.load({
|
|
|
250
249
|
reconnectDone: "已重启飞书连接",
|
|
251
250
|
loading: "加载中…",
|
|
252
251
|
loadFailed: "设置面板加载失败",
|
|
253
|
-
analyzeTokenLabel: "访问令牌",
|
|
254
|
-
analyzeTokenSummary: "请求头 Authorization: Bearer 的值,保存后立即生效;未配置时接口拒绝所有请求",
|
|
255
|
-
analyzeTokenPlaceholder: "填写新令牌(建议 32 位以上随机串)",
|
|
256
|
-
analyzeTokenSaved: "令牌已保存,下一次请求生效",
|
|
257
|
-
analyzeTokenRemoved: "令牌已移除,接口将拒绝所有请求",
|
|
258
252
|
overlayTitle: "Web 覆盖",
|
|
259
253
|
overlayPath: "覆盖文件",
|
|
260
254
|
overlayCount: "{n} 项配置由面板覆盖",
|
|
@@ -855,7 +849,7 @@ window.__ModuleLoader__.load({
|
|
|
855
849
|
]
|
|
856
850
|
})]);
|
|
857
851
|
const heading = originHeading(origin);
|
|
858
|
-
if (origin.shared) rows.push(["说明", "
|
|
852
|
+
if (origin.shared) rows.push(["说明", "群共享话题,群内成员都可以继续"]);
|
|
859
853
|
rows.push(["开始于", formatTime(origin.createdAt)]);
|
|
860
854
|
rows.push(origin.bound && origin.lastActivityAt !== void 0 ? ["最近活跃", formatTime(origin.lastActivityAt)] : ["状态", "飞书侧已切换到新会话或已过期"]);
|
|
861
855
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
@@ -2087,6 +2081,47 @@ window.__ModuleLoader__.load({
|
|
|
2087
2081
|
});
|
|
2088
2082
|
}
|
|
2089
2083
|
//#endregion
|
|
2084
|
+
//#region src/client/panel/advanced-tab.tsx
|
|
2085
|
+
/**
|
|
2086
|
+
* 「高级」tab:SDK 日志级别与 Web 覆盖文件(「恢复 profile 配置」删除整个
|
|
2087
|
+
* 覆盖层,不走 draft)。
|
|
2088
|
+
*
|
|
2089
|
+
* @module dsh-channels/client/panel/advanced-tab
|
|
2090
|
+
*/
|
|
2091
|
+
function AdvancedTab(props) {
|
|
2092
|
+
const { form, onClear } = props;
|
|
2093
|
+
const { panel, busy, locked } = form;
|
|
2094
|
+
const overridden = panel.config.overriddenKeys.length;
|
|
2095
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2096
|
+
className: "dsh-channels-cfg__tabBody",
|
|
2097
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldSections, {
|
|
2098
|
+
form,
|
|
2099
|
+
groups: TAB_FIELD_GROUPS.advanced
|
|
2100
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Section, {
|
|
2101
|
+
title: PANEL_COPY.overlayTitle,
|
|
2102
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
2103
|
+
label: PANEL_COPY.overlayPath,
|
|
2104
|
+
summary: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2105
|
+
className: "dsh-channels-cfg__mono",
|
|
2106
|
+
children: panel.status.overlayPath
|
|
2107
|
+
}),
|
|
2108
|
+
details: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
2109
|
+
PANEL_COPY.overlayPathHelp,
|
|
2110
|
+
" ",
|
|
2111
|
+
overridden === 0 ? PANEL_COPY.noOverrides : fillCopy(PANEL_COPY.overlayCount, { n: overridden })
|
|
2112
|
+
] }),
|
|
2113
|
+
control: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2114
|
+
type: "button",
|
|
2115
|
+
className: "dsh-channels-cfg__btn dsh-channels-cfg__btn--danger",
|
|
2116
|
+
onClick: onClear,
|
|
2117
|
+
disabled: overridden === 0 || busy !== null || locked,
|
|
2118
|
+
children: busy === "clear" ? PANEL_COPY.clearing : PANEL_COPY.clear
|
|
2119
|
+
})
|
|
2120
|
+
})
|
|
2121
|
+
})]
|
|
2122
|
+
});
|
|
2123
|
+
}
|
|
2124
|
+
//#endregion
|
|
2090
2125
|
//#region src/client/panel/credentials-card.tsx
|
|
2091
2126
|
/**
|
|
2092
2127
|
* 凭据行:值经 Host 的 DSH 凭据服务写入本机凭据库,面板只显示「已配置 /
|
|
@@ -2203,62 +2238,6 @@ window.__ModuleLoader__.load({
|
|
|
2203
2238
|
});
|
|
2204
2239
|
}
|
|
2205
2240
|
//#endregion
|
|
2206
|
-
//#region src/client/panel/advanced-tab.tsx
|
|
2207
|
-
/**
|
|
2208
|
-
* 「高级」tab:日志分析接口(开关 + 访问令牌凭据行)、SDK 日志级别
|
|
2209
|
-
* 与 Web 覆盖文件(「恢复 profile 配置」删除整个覆盖层,不走 draft)。
|
|
2210
|
-
*
|
|
2211
|
-
* @module dsh-channels/client/panel/advanced-tab
|
|
2212
|
-
*/
|
|
2213
|
-
function AdvancedTab(props) {
|
|
2214
|
-
const { form, analyzeToken, onClear } = props;
|
|
2215
|
-
const { panel, busy, locked } = form;
|
|
2216
|
-
const tokenState = panel.config.credentials.analyzeToken;
|
|
2217
|
-
const overridden = panel.config.overriddenKeys.length;
|
|
2218
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2219
|
-
className: "dsh-channels-cfg__tabBody",
|
|
2220
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldSections, {
|
|
2221
|
-
form,
|
|
2222
|
-
groups: TAB_FIELD_GROUPS.advanced,
|
|
2223
|
-
extra: { analyze: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CredentialRow, {
|
|
2224
|
-
label: PANEL_COPY.analyzeTokenLabel,
|
|
2225
|
-
summary: PANEL_COPY.analyzeTokenSummary,
|
|
2226
|
-
state: tokenState,
|
|
2227
|
-
value: analyzeToken.value,
|
|
2228
|
-
secret: true,
|
|
2229
|
-
placeholder: PANEL_COPY.analyzeTokenPlaceholder,
|
|
2230
|
-
issue: analyzeToken.issues.find((item) => item.key === "analyzeToken"),
|
|
2231
|
-
busy: analyzeToken.busy,
|
|
2232
|
-
disabled: analyzeToken.busy !== null || analyzeToken.locked || !tokenState.writable,
|
|
2233
|
-
onChange: analyzeToken.onChange,
|
|
2234
|
-
onSave: analyzeToken.onSave,
|
|
2235
|
-
onClear: analyzeToken.onClear
|
|
2236
|
-
}) }
|
|
2237
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Section, {
|
|
2238
|
-
title: PANEL_COPY.overlayTitle,
|
|
2239
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
2240
|
-
label: PANEL_COPY.overlayPath,
|
|
2241
|
-
summary: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2242
|
-
className: "dsh-channels-cfg__mono",
|
|
2243
|
-
children: panel.status.overlayPath
|
|
2244
|
-
}),
|
|
2245
|
-
details: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
2246
|
-
PANEL_COPY.overlayPathHelp,
|
|
2247
|
-
" ",
|
|
2248
|
-
overridden === 0 ? PANEL_COPY.noOverrides : fillCopy(PANEL_COPY.overlayCount, { n: overridden })
|
|
2249
|
-
] }),
|
|
2250
|
-
control: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2251
|
-
type: "button",
|
|
2252
|
-
className: "dsh-channels-cfg__btn dsh-channels-cfg__btn--danger",
|
|
2253
|
-
onClick: onClear,
|
|
2254
|
-
disabled: overridden === 0 || busy !== null || locked,
|
|
2255
|
-
children: busy === "clear" ? PANEL_COPY.clearing : PANEL_COPY.clear
|
|
2256
|
-
})
|
|
2257
|
-
})
|
|
2258
|
-
})]
|
|
2259
|
-
});
|
|
2260
|
-
}
|
|
2261
|
-
//#endregion
|
|
2262
2241
|
//#region src/client/panel/connection-tab.tsx
|
|
2263
2242
|
/**
|
|
2264
2243
|
* 「连接」tab:按 Host 派生的生命周期阶段渲染。
|
|
@@ -2731,7 +2710,6 @@ window.__ModuleLoader__.load({
|
|
|
2731
2710
|
const [pollError, setPollError] = (0, react.useState)(null);
|
|
2732
2711
|
const [appIdInput, setAppIdInput] = (0, react.useState)("");
|
|
2733
2712
|
const [appSecretInput, setAppSecretInput] = (0, react.useState)("");
|
|
2734
|
-
const [analyzeTokenInput, setAnalyzeTokenInput] = (0, react.useState)("");
|
|
2735
2713
|
const [catalog, setCatalog] = (0, react.useState)(null);
|
|
2736
2714
|
const [catalogBusy, setCatalogBusy] = (0, react.useState)(false);
|
|
2737
2715
|
const [accessNotice, setAccessNotice] = (0, react.useState)(null);
|
|
@@ -2760,7 +2738,6 @@ window.__ModuleLoader__.load({
|
|
|
2760
2738
|
setDraft({});
|
|
2761
2739
|
setAppIdInput("");
|
|
2762
2740
|
setAppSecretInput("");
|
|
2763
|
-
setAnalyzeTokenInput("");
|
|
2764
2741
|
} catch (error) {
|
|
2765
2742
|
setLoad({
|
|
2766
2743
|
phase: "error",
|
|
@@ -3236,40 +3213,6 @@ window.__ModuleLoader__.load({
|
|
|
3236
3213
|
setBusy(null);
|
|
3237
3214
|
}
|
|
3238
3215
|
}, [appIdInput, appSecretInput]);
|
|
3239
|
-
/** Save the analyze API token; it applies to the next request, no reconnect. */
|
|
3240
|
-
const onSaveAnalyzeToken = (0, react.useCallback)(async () => {
|
|
3241
|
-
if (analyzeTokenInput.trim() === "") return;
|
|
3242
|
-
setBusy("credentials");
|
|
3243
|
-
setNotice(null);
|
|
3244
|
-
try {
|
|
3245
|
-
const result = await saveCredentials({ analyzeToken: analyzeTokenInput });
|
|
3246
|
-
if (!result.ok) {
|
|
3247
|
-
setNotice({
|
|
3248
|
-
kind: "error",
|
|
3249
|
-
text: result.error,
|
|
3250
|
-
issues: result.issues
|
|
3251
|
-
});
|
|
3252
|
-
return;
|
|
3253
|
-
}
|
|
3254
|
-
setLoad({
|
|
3255
|
-
phase: "ready",
|
|
3256
|
-
panel: result.panel
|
|
3257
|
-
});
|
|
3258
|
-
setStatus(result.panel.status);
|
|
3259
|
-
setAnalyzeTokenInput("");
|
|
3260
|
-
setNotice({
|
|
3261
|
-
kind: "ok",
|
|
3262
|
-
text: PANEL_COPY.analyzeTokenSaved
|
|
3263
|
-
});
|
|
3264
|
-
} catch (error) {
|
|
3265
|
-
setNotice({
|
|
3266
|
-
kind: "error",
|
|
3267
|
-
text: messageOf(error)
|
|
3268
|
-
});
|
|
3269
|
-
} finally {
|
|
3270
|
-
setBusy(null);
|
|
3271
|
-
}
|
|
3272
|
-
}, [analyzeTokenInput]);
|
|
3273
3216
|
const onClearCredential = (0, react.useCallback)(async (field) => {
|
|
3274
3217
|
if (!(typeof window.confirm === "function" ? window.confirm(PANEL_COPY.credentialRemoveConfirm) : true)) return;
|
|
3275
3218
|
setBusy("credentials-clear");
|
|
@@ -3291,7 +3234,7 @@ window.__ModuleLoader__.load({
|
|
|
3291
3234
|
setStatus(result.panel.status);
|
|
3292
3235
|
setNotice({
|
|
3293
3236
|
kind: "ok",
|
|
3294
|
-
text:
|
|
3237
|
+
text: result.reconnect === "pending" ? PANEL_COPY.credentialRemovedPending : PANEL_COPY.credentialRemoved
|
|
3295
3238
|
});
|
|
3296
3239
|
} catch (error) {
|
|
3297
3240
|
setNotice({
|
|
@@ -3367,26 +3310,6 @@ window.__ModuleLoader__.load({
|
|
|
3367
3310
|
onClearCredential,
|
|
3368
3311
|
onSaveCredentials
|
|
3369
3312
|
]);
|
|
3370
|
-
const analyzeToken = (0, react.useMemo)(() => ({
|
|
3371
|
-
value: analyzeTokenInput,
|
|
3372
|
-
issues,
|
|
3373
|
-
busy,
|
|
3374
|
-
locked: hostBusy,
|
|
3375
|
-
onChange: setAnalyzeTokenInput,
|
|
3376
|
-
onSave: () => {
|
|
3377
|
-
onSaveAnalyzeToken();
|
|
3378
|
-
},
|
|
3379
|
-
onClear: () => {
|
|
3380
|
-
onClearCredential("analyzeToken");
|
|
3381
|
-
}
|
|
3382
|
-
}), [
|
|
3383
|
-
analyzeTokenInput,
|
|
3384
|
-
busy,
|
|
3385
|
-
hostBusy,
|
|
3386
|
-
issues,
|
|
3387
|
-
onClearCredential,
|
|
3388
|
-
onSaveAnalyzeToken
|
|
3389
|
-
]);
|
|
3390
3313
|
if (load.phase === "loading") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3391
3314
|
className: "dsh-channels-cfg",
|
|
3392
3315
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
@@ -3496,7 +3419,6 @@ window.__ModuleLoader__.load({
|
|
|
3496
3419
|
}) : null,
|
|
3497
3420
|
activeTab === "advanced" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AdvancedTab, {
|
|
3498
3421
|
form,
|
|
3499
|
-
analyzeToken,
|
|
3500
3422
|
onClear: () => {
|
|
3501
3423
|
onClear();
|
|
3502
3424
|
}
|