@modesto_ma/dsh-guanxiang 0.2.0-beta.1 → 0.2.0-beta.3

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
@@ -64,7 +64,7 @@
64
64
  - **代码快照不可变。** 子 Agent 在 `<reposDir>/.snapshots/<仓库名>/` 下按提交创建的 `git worktree` 快照里工作。快照创建后不再改动,同一提交的派发共用一份;没有分析在用、且已不是最新提交的快照会被删除。
65
65
  - **运行目录。** 每次派发在父会话目录下创建 `.guanxiang/runs/<runId>/<序号>-<应用 id>/`,下载的日志放在这里,子 Agent 也只在这里写文件;应用仓库对子 Agent 只读。
66
66
  - **等待式派发。** 工具等待所有子 Agent 结束后一次返回,一条消息对应一张完整回复;超过派发超时或调用被取消时停止仍在运行的子 Agent。子会话中不提供派发工具。
67
- - **SKILL 改进(可选,默认关闭)。** 子 Agent 发现应用的日志分析 SKILL 有欠缺时,可以在运行目录写出修改提议;经用户授权后,观象把它推送到应用仓库的新分支并创建合并请求,见 [SKILL 改进](#skill-改进)。
67
+ - **SKILL 改进(可选,默认关闭)。** 子 Agent 发现应用的日志分析 SKILL 有欠缺时,可以在运行目录写出修改提议;经用户授权后,观象经 [dsh-repo](https://github.com/modestoma/dsh-repo) 把它提交到应用仓库的新分支并创建合并请求(GitLab MR 或 GitHub PR),见 [SKILL 改进](#skill-改进)。
68
68
 
69
69
  ## 安装与配置
70
70
 
@@ -114,16 +114,19 @@ npm 发行包已包含构建产物;本地 `link:` 安装前必须先执行 `pn
114
114
  enabled: false # 默认关闭
115
115
  maxFiles: 16 # 一次提议最多新增或修改的文件数
116
116
  maxFileBytes: 262144 # 提议中单个文件的大小上限
117
- skillUpdateAuthor: # 可选:SKILL 改进提交的作者;省略时使用本机 git 的 user.name / user.email
117
+ skillUpdateAuthor: # 可选:SKILL 改进提交的作者;省略时是 dsh-repo 中该主机令牌的主人
118
118
  name: 观象
119
119
  email: guanxiang@example.com
120
+ analyze: # 日志平台分析接口,见下文
121
+ enabled: false # 默认关闭
122
+ analyzeTokenRef: GUANXIANG_ANALYZE_TOKEN # 接口访问令牌在 DSH 凭据库中的名称
120
123
  ```
121
124
 
122
125
  配置在加载时校验,不合法时插件加载失败。git 地址支持 https/http/ssh/git/file URL、`user@host:path` 和绝对路径,拉取时使用本机已有的 git 凭据(SSH key 或 credential helper),不会弹出交互式输入。
123
126
 
124
127
  ### 访问令牌
125
128
 
126
- 用 https 地址拉取 GitLab 等私有仓库时,可以配置个人访问令牌(GitLab 需要 `read_repository` 权限;开启 [SKILL 改进](#skill-改进) 时需要 `write_repository`):
129
+ 用 https 地址拉取 GitLab 等私有仓库时,可以配置个人访问令牌(GitLab 需要 `read_repository` 权限;[SKILL 改进](#skill-改进) 的提交改由 dsh-repo 的令牌完成,这里不需要写权限):
127
130
 
128
131
  - **配置只写名称。** `gitCredentials` 的每一项把一个地址前缀(`https://主机[:端口][/路径]`)对应到一个令牌名称 `tokenEnv`;地址在前缀下的 https 仓库使用该令牌,多个前缀匹配时取路径最长的。ssh、`user@host:path` 和本地路径不受影响。
129
132
  - **令牌值在 DSH 凭据库。** 令牌通过 DSH 的 `ctx.credentials` 读取(默认组合加载的 `@deepseek-ai/dsh-credentials-local`,文件 `$DSH_HOME/.credentials.yaml`,仅本机用户可读),可以在设置面板的「访问令牌」分页保存,也可以设置同名环境变量或写进 `.env`。每次拉取都重新读取,更换令牌不用重启。面板接口只接受已保存的配置引用的名称,也从不返回令牌值。
@@ -145,6 +148,7 @@ DSH Web 左侧栏的「观象」入口打开整页设置面板:
145
148
  | 应用仓库 | git 地址列表(分支可选)、每个仓库的拉取状态、提交号、应用描述与不可用原因,单个或全部重新拉取 |
146
149
  | 访问令牌 | 填 GitLab 地址、令牌别名和令牌三项即可添加;用户名固定为 oauth2,凭据库键名自动分配(`GUANXIANG_GITLAB_TOKEN`、`GUANXIANG_GITLAB_TOKEN_2`……)。每行显示令牌是否已设置及来源,可替换或清除 |
147
150
  | 日志下载 | 允许下载的域名、单文件大小上限、下载超时 |
151
+ | 分析接口 | [日志平台分析接口](#日志平台分析接口)的开关、访问令牌(只显示是否已设置)、飞书渠道是否就绪,以及调用地址与字段摘要 |
148
152
  | 高级 | 拉取目录、更新间隔、单次应用数上限、派发超时、SKILL 改进开关与提议上限,恢复 cordis.yml 配置 |
149
153
 
150
154
  面板保存的值写入 `$DSH_HOME/guanxiang/settings.json`(只保存与 cordis.yml 不同的字段),按与配置相同的规则校验;令牌值不在其中,存入 DSH 凭据库。面板接口 `/guanxiang/*` 使用 DSH 自己的登录与来源校验,写操作另做同源校验。
@@ -170,24 +174,81 @@ skill: log-analysis # 对应 .dsh/skills/log-analysis/SKILL.md
170
174
 
171
175
  ## SKILL 改进
172
176
 
173
- 开启 `skillUpdates.enabled`(设置面板「高级」→「SKILL 改进」)后,分析中发现的 SKILL 欠缺可以经用户授权后提交为合并请求:
177
+ 开启 `skillUpdates.enabled`(设置面板「高级」→「SKILL 改进」)后,分析中发现的 SKILL 欠缺可以经用户授权后提交为合并请求。提交和创建合并请求经 [dsh-repo](https://github.com/modestoma/dsh-repo) 的 `dshRepos` 服务完成(需要 dsh-repo 0.1.0-beta.2 及以上):需要同时安装 dsh-repo,并在它的 `hosts` 中配置应用仓库所在的主机和令牌;没有该服务或版本不符时工具报错。
174
178
 
175
179
  1. **子 Agent 写提议。** 子 Agent 的任务消息多出一节说明:把要改的文件从 `.dsh/skills/<skill>/` 复制到运行目录的 `skill-proposal/files/`(保持相对路径)后修改,理由写进 `skill-proposal/reason.md`。仓库对子 Agent 仍然只读。
176
180
  2. **派发结果标出提议。** 派发结束后,观象检查已完成任务的提议:只接受普通文件和受限的文件名、UTF-8 文本、文件数与大小上限,`SKILL.md` 不能设置 `user-invocable: false`。合格的提议在结果中给出改动行数,并在运行目录写出 `skill-proposal.diff`;不合格的给出原因。
177
181
  3. **父 Agent 展示并提交。** 入口 SKILL 让父 Agent 在汇总回复后附上改动和 diff,再调用 `guanxiang_propose_skill`(参数为任务的运行目录和一句话摘要)。只接受本次 DSH 运行中由同一会话派发过的任务;应用、仓库和分析时的提交取自派发记录。
178
- 4. **请求授权。** 工具通过 DSH 的审批服务(`ctx.approval`,`@deepseek-ai/dsh-user-approval`)请求一次性授权,说明将推送的仓库、分支、目标分支、改动文件和理由。经 dsh_channels 使用时显示为飞书决策卡片,在 DSH Web 中显示为审批提示;观象与 dsh_channels 之间没有代码依赖。只有「允许」才推送;拒绝、撤回或没有可回答的界面时都不推送。会话的审批策略为 `never`(例如 DSH 的「完全访问」权限模式)时请求一律被拒绝,所以要在「询问」类权限模式下使用;DSH 组合没有审批服务时工具报错。
179
- 5. **推送新分支并创建合并请求。** 观象进程(不是 Agent)在拉取仓库中以分析时的提交为父提交生成提交,只改 `.dsh/skills/<skill>/` 下的文件,不动任何检出和快照;然后推送到新分支 `guanxiang/skill-<应用 id>-<树对象前缀>`,用 GitLab push options(`merge_request.create`、`merge_request.target`、`merge_request.title`、`merge_request.description`)创建合入跟踪分支的合并请求,返回服务器给出的合并请求链接。同一改动的分支已存在时不再请求授权,也不推送。从不推送已有分支或默认分支,合并由仓库的评审流程决定;合并后下次 `/guanxiang` 拉取即生效。
182
+ 4. **请求授权。** 工具通过 DSH 的审批服务(`ctx.approval`,`@deepseek-ai/dsh-user-approval`)请求一次性授权,说明将提交的仓库、分支、目标分支、改动文件和理由。经 dsh_channels 使用时显示为飞书决策卡片,在 DSH Web 中显示为审批提示;观象与 dsh_channels 之间没有代码依赖。只有「允许」才提交;拒绝、撤回或没有可回答的界面时都不提交。会话的审批策略为 `never`(例如 DSH 的「完全访问」权限模式)时请求一律被拒绝,所以要在「询问」类权限模式下使用;DSH 组合没有审批服务时工具报错。
183
+ 5. **提交新分支并创建合并请求。** 观象进程(不是 Agent)经 `dshRepos` 以分析时的提交为起点建新分支 `guanxiang/skill-<应用 id>-<树对象前缀>`,提交 `.dsh/skills/<skill>/` 下有改动的文件,再创建合入跟踪分支的合并请求,返回它的链接。同一改动的分支已存在时不再请求授权,也不提交;等待授权期间分支被别人建出时同样不提交。从不提交到已有分支或默认分支,合并由仓库的评审流程决定;合并后下次 `/guanxiang` 拉取即生效。提交成功但合并请求创建失败时,工具返回分支和失败原因,可以在代码托管平台上手动创建。
180
184
 
181
185
  注意:
182
186
 
183
- - 只支持 GitLab:不支持 push options 的服务器会拒绝这次推送。https 仓库的令牌需要 `write_repository`(不需要 `api`);ssh 地址使用本机的 SSH key。
184
- - 个人访问令牌加了写权限后,可以推送你有权限的所有项目。建议改用只覆盖应用仓库的项目或组访问令牌,设置过期时间,并保护应用仓库的默认分支。
185
- - 合并请求以令牌主人的身份创建,提交作者是 `skillUpdateAuthor` 或本机 git 身份。飞书群里点「允许」的人可能不是令牌主人;合并请求描述写明了来源会话与分析提交。
187
+ - 支持 GitLab(包括自建实例)和 GitHub。应用仓库的 git 地址(https、ssh 或 `user@host:path`)要能被 dsh-repo 识别为已配置主机上的仓库;dsh-repo 的令牌在 GitLab 上需要 `api` 权限,在 GitHub 上需要 Contents 和 Pull requests 的读写权限。
188
+ - 有写权限的令牌可以改动它能访问的所有项目。建议用只覆盖应用仓库的项目或组访问令牌,设置过期时间,用 dsh-repo 的 `repos` 限定范围,并保护应用仓库的默认分支。
189
+ - 合并请求以 dsh-repo 令牌主人的身份创建,提交作者是 `skillUpdateAuthor`,省略时也是令牌主人。飞书群里点「允许」的人可能不是令牌主人;合并请求描述写明了来源会话与分析提交。
186
190
  - 日志内容不可信,可能诱导模型写出不当的 SKILL。观象只限制改动范围,内容需要授权的人和合并请求的评审者把关。
187
191
 
192
+ ## 日志平台分析接口
193
+
194
+ 日志平台调用观象的分析接口:观象经 [dsh_channels](https://github.com/modestoma/dsh_channels) 的 `feishuTopics` 服务在指定群里发一张分析卡(发起人、触发时间、应用、错误码和日志详情页表格),在卡片下开话题,把平台给的 prompt 原样交给一个新 Session(prompt 开头的斜杠命令由 DSH 展开成对应 SKILL,例如 `/guanxiang`),分析过程在话题内流式输出。分析结束后分析卡的状态标签随之更新,群里通过准入的成员都可以在该话题内追问。日志包地址写在 prompt 里,下载、解压、超时都由 prompt 调用的 SKILL 处理(Session 的沙箱需要能访问日志存储地址)。
195
+
196
+ 接口原由 dsh_channels 0.7 提供,从 dsh_channels 0.8 起迁到观象,请求、响应、错误文本和去重记录都不变。
197
+
198
+ **前提**:
199
+
200
+ - dsh_channels 0.8 及以上已加载并提供 `feishuTopics` 服务(版本 1)。没有这个服务时观象不注册接口:dsh_channels 0.7 自带同名接口,升级前由它提供;服务版本不兼容时观象记一条警告,也不注册。设置面板「分析接口」分页显示飞书渠道是否就绪。
201
+ - 只有带 Web 的 profile(web、desktop)有这个接口,headless 没有。
202
+
203
+ **开启**:在设置面板「分析接口」打开开关(`analyze.enabled`,默认关,保存后立即生效),并在同一页保存访问令牌(建议 32 位以上随机串)。令牌存入 DSH 凭据库的 `GUANXIANG_ANALYZE_TOKEN`(名称可用 `analyzeTokenRef` 修改),也可以设置同名环境变量或写进 `.env`。接口挂在 DSH Web 服务上(与 Web 界面同一端口),路径 `/open-api/logalyze` 放在 DSH 的 `/api` 之外,网关可以把它与需要登录的 DSH 接口分开放行;DSH 通过反向代理挂在前缀下时,地址随之带上前缀(如 `https://<域名>/dsh/open-api/logalyze`)。该路由不走 DSH 登录 cookie,访问令牌是唯一凭据。开关关闭时所有请求返回 404;令牌每次请求都重新读取,轮换后下一次请求生效;令牌未设置时所有请求返回 503。
204
+
205
+ **从 dsh_channels 0.7 迁移**:把观象和 dsh_channels 一起升级;在观象面板「分析接口」打开开关并保存令牌(新名称 `GUANXIANG_ANALYZE_TOKEN`,可以填原来的令牌值,日志平台无需改动);然后从 cordis.yml 删掉 dsh_channels 的 `analyzeEnabled` 和 `analyzeToken`(新版 dsh_channels 会对这两项打印警告)。升级前接受过的 `Idempotency-Key` 升级后照常重放。
206
+
207
+ **请求**:
208
+
209
+ ```http
210
+ POST /open-api/logalyze
211
+ Authorization: Bearer <令牌>
212
+ Idempotency-Key: <平台生成的唯一值,1-200 个可见 ASCII 字符>
213
+ Content-Type: application/json
214
+
215
+ {
216
+ "requesterUserId": "a1b2c3d4",
217
+ "triggeredAt": "2026-10-22 19:30",
218
+ "app": "camera",
219
+ "errorCode": "E1001",
220
+ "logDetails": [
221
+ { "name": "设备 A 崩溃日志", "url": "https://logs.example.com/detail/1" },
222
+ { "name": "设备 B 崩溃日志", "url": "https://logs.example.com/detail/2" }
223
+ ],
224
+ "chatId": "oc_xxx",
225
+ "prompt": "/guanxiang https://logs.example.com/1.zip https://logs.example.com/2.zip 请分析这些日志"
226
+ }
227
+ ```
228
+
229
+ - `requesterUserId`:平台发起人的飞书 user_id,分析卡 @ 此人;路由记录里也以此人作为发起人。
230
+ - `triggeredAt`:平台上发起分析的时间,1–64 字符,原样显示在分析卡上(时区和格式由平台决定)。
231
+ - `app` / `errorCode`:只允许 `A-Z a-z 0-9 . _ -`(1–128 字符),决定工作区 `<dsh_channels baseDir>/analyze/<app>/<errorCode>/`;同一应用同一错误码的所有分析都在这个工作区里,每次分析各有一个话题目录。
232
+ - `logDetails`:触发分析的日志在日志平台上的详情页,1–50 条,每条 `name`(1–200 字符)+ `url`(http/https)。只在分析卡的表格里列出,观象不会访问这些地址。
233
+ - `chatId`:目标群,必须是 dsh_channels 已授权的群(`/auth group add` 或其面板「访问与授权」),机器人需在群内;dsh_channels `accessMode: open` 时不校验。
234
+ - `prompt`:1–100000 字符,原样作为首条用户消息,不追加任何内容。
235
+ - 只接受以上 7 个字段,多余字段返回 400。
236
+
237
+ **响应**:校验通过、分析卡发出后立即返回 `202`:
238
+
239
+ ```json
240
+ { "ok": true, "chatId": "oc_xxx", "messageId": "om_xxx" }
241
+ ```
242
+
243
+ `messageId` 是分析卡的消息 id,分析话题挂在它下面。同步错误都带中文 `error`:`401` 令牌无效、`404` 接口开关未打开(或接口未注册)、`405` 不是 POST、`400` 参数或 `Idempotency-Key` 不合法、`403` 目标群未授权、`409` 同一个 `Idempotency-Key` 已被内容不同的请求使用、`413` 请求体超过 1 MiB、`415` 不是 JSON、`502` 分析卡发送失败(如机器人不在群里)、`503` 飞书未连接或未设置令牌。
244
+
245
+ **去重**:同一个 `Idempotency-Key` 再次请求(包括并发重试)原样返回第一次的结果,不会再发卡、再分析。记录由 dsh_channels 永久保存。分析卡发出后的失败(话题创建、Session 创建)不会回滚这条记录:分析卡的状态变成「失败」并写明原因,需要重试时请换一个新的 `Idempotency-Key`。
246
+
247
+ **群里看到的**:先是分析卡「@发起人 于 <triggeredAt> 发起了对 X 应用 Y 错误码不良日志的分析」,下面是日志详情页表格,标题栏标签为「分析中」;紧接着话题内一张流式卡片,首轮分析在这里流式输出,带停止按钮。首轮结束后分析卡的标签变成「已完成」「已停止」或「失败」;DSH 在首轮进行中重启时,重新连上飞书后仍是「分析中」的分析卡会改成「已中断」,在话题内回复即可继续。分析话题是群内共享的:群里任何通过 dsh_channels 准入的成员都可以在话题内追问、停止或回答审批卡。卡片、话题与权限的细节见 dsh_channels README 的「群话题服务(feishuTopics)」。
248
+
188
249
  ## 通过飞书使用
189
250
 
190
- 观象与 [dsh_channels](https://github.com/modestoma/dsh_channels) 没有代码耦合。dsh_channels 把不在自己命令表中的 `/` 开头消息作为普通 prompt 交给 dsh,dsh 识别 `/guanxiang` 后展开入口 SKILL。飞书附件的宿主路径会出现在消息里,入口 SKILL 会把它作为日志路径派发。
251
+ 在飞书里使用 `/guanxiang` 不需要观象与 [dsh_channels](https://github.com/modestoma/dsh_channels) 之间的任何接口:dsh_channels 把不在自己命令表中的 `/` 开头消息作为普通 prompt 交给 dsh,dsh 识别 `/guanxiang` 后展开入口 SKILL。飞书附件的宿主路径会出现在消息里,入口 SKILL 会把它作为日志路径派发。只有[日志平台分析接口](#日志平台分析接口)经 dsh_channels 的 `feishuTopics` 服务发卡开话题;观象只在开发时依赖 dsh_channels 的类型,运行时按服务名查找,没有加载 dsh_channels 时其余功能不受影响。
191
252
 
192
253
  ## 开发
193
254
 
@@ -196,6 +257,8 @@ pnpm install
196
257
  pnpm check # 类型检查(Host 与 Client)+ 单元测试 + 构建 + 两个加载自检
197
258
  ```
198
259
 
260
+ `@modesto_ma/dsh-channels` 是开发依赖,只用来引入 `feishuTopics` 服务的类型(`@modesto_ma/dsh-channels/topics`,`import type`),构建产物不包含它。
261
+
199
262
  ## 发布
200
263
 
201
264
  版本号由 semantic-release 按 Conventional Commits 计算。推送到 `dev` 后,检查通过就自动发布 beta;正式版**只在手动触发时发布**,推送到 `main` 只跑检查。只有 `feat`、`fix` 或破坏性变更会产生新版本,其余类型的提交不发布。PR 标题需符合 `<type>(<scope>): <summary>`,type 取 `feat | fix | docs | test | refactor | chore | ci`。
@@ -215,6 +278,7 @@ pnpm check # 类型检查(Host 与 Client)+ 单元测试 + 构建 +
215
278
  | `src/skill/entry.ts` | `/guanxiang` 入口 SKILL |
216
279
  | `src/dispatch/` | 派发工具、日志下载、子 Agent 首条消息 |
217
280
  | `src/skill-update/` | SKILL 修改提议的校验、提交、授权与推送 |
281
+ | `src/analyze/` | 日志平台分析接口:请求校验、令牌、到 `feishuTopics` 的转换 |
218
282
  | `src/settings/` | 面板契约、覆盖文件、HTTP 路由 |
219
283
  | `src/client/` | 左侧栏入口与设置页面 |
220
284
  | `examples/order-service/` | 示例应用仓库 |
package/lib/client.js CHANGED
@@ -48,7 +48,7 @@ window.__ModuleLoader__.load({
48
48
  const SETTINGS_ROUTE = "/guanxiang/settings";
49
49
  /** POST:立即重新拉取全部仓库,或 body `{ url }` 指定的一个仓库。 */
50
50
  const SYNC_ROUTE = "/guanxiang/sync";
51
- /** PUT:body `{ tokenEnv, value }` 把令牌存入 DSH 凭据库;DELETE:body `{ tokenEnv }` 删除已存的值。只接受已保存的访问令牌配置引用的名称。 */
51
+ /** PUT:body `{ tokenEnv, value }` 把令牌存入 DSH 凭据库;DELETE:body `{ tokenEnv }` 删除已存的值。只接受已保存的访问令牌配置引用的名称和分析接口令牌的名称。 */
52
52
  const TOKEN_ROUTE = "/guanxiang/token";
53
53
  //#endregion
54
54
  //#region src/client/api.ts
@@ -157,9 +157,9 @@ window.__ModuleLoader__.load({
157
157
  //#endregion
158
158
  //#region src/client/page.tsx
159
159
  /**
160
- * 左侧栏「观象」主区页面:应用仓库、访问令牌、日志下载、高级(含 SKILL 改进)四个分页,共用
161
- * 一份草稿和底部保存条;令牌值不进草稿,单独存入 DSH 凭据库。仓库拉取中时
162
- * 每 2 秒刷新状态。
160
+ * 左侧栏「观象」主区页面:应用仓库、访问令牌、日志下载、分析接口、高级(含 SKILL 改进)五个
161
+ * 分页,共用一份草稿和底部保存条;令牌值不进草稿,单独存入 DSH 凭据库。
162
+ * 仓库拉取中时每 2 秒刷新状态。
163
163
  *
164
164
  * @module dsh-guanxiang/client/page
165
165
  */
@@ -181,6 +181,10 @@ window.__ModuleLoader__.load({
181
181
  id: "download",
182
182
  label: "日志下载"
183
183
  },
184
+ {
185
+ id: "analyze",
186
+ label: "分析接口"
187
+ },
184
188
  {
185
189
  id: "advanced",
186
190
  label: "高级"
@@ -202,7 +206,8 @@ window.__ModuleLoader__.load({
202
206
  timeoutSec: trimNumber(settings.download.timeoutMs / 1e3),
203
207
  skillUpdatesEnabled: settings.skillUpdates.enabled,
204
208
  skillMaxFiles: String(settings.skillUpdates.maxFiles),
205
- skillMaxKb: trimNumber(settings.skillUpdates.maxFileBytes / KB)
209
+ skillMaxKb: trimNumber(settings.skillUpdates.maxFileBytes / KB),
210
+ analyzeEnabled: settings.analyze.enabled
206
211
  };
207
212
  }
208
213
  function positive(text, label, allowZero = false) {
@@ -239,7 +244,8 @@ window.__ModuleLoader__.load({
239
244
  enabled: draft.skillUpdatesEnabled,
240
245
  maxFiles,
241
246
  maxFileBytes: Math.round(positive(draft.skillMaxKb, "提议单文件大小上限") * KB)
242
- }
247
+ },
248
+ analyze: { enabled: draft.analyzeEnabled }
243
249
  };
244
250
  }
245
251
  function sameSettings(draft, settings) {
@@ -485,6 +491,18 @@ window.__ModuleLoader__.load({
485
491
  setDraft,
486
492
  overridden: state.overridden
487
493
  }) : null,
494
+ tab === "analyze" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AnalyzeTab, {
495
+ draft,
496
+ setDraft,
497
+ analyze: state.analyze,
498
+ credentialsAvailable: state.credentials.available,
499
+ overridden: state.overridden,
500
+ busy,
501
+ onSave: async (value) => await run(() => saveToken(state.analyze.tokenEnv, value), "已保存分析接口令牌,下一个请求起生效", false) !== void 0,
502
+ onClear: () => {
503
+ run(() => clearToken(state.analyze.tokenEnv), "已清除面板保存的分析接口令牌", false);
504
+ }
505
+ }) : null,
488
506
  tab === "advanced" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AdvancedTab, {
489
507
  draft,
490
508
  setDraft,
@@ -1077,6 +1095,224 @@ window.__ModuleLoader__.load({
1077
1095
  })]
1078
1096
  })] });
1079
1097
  }
1098
+ const TOPICS_STATUS = {
1099
+ ready: {
1100
+ pill: "gx__pill--ok",
1101
+ text: "飞书渠道就绪"
1102
+ },
1103
+ missing: {
1104
+ pill: "gx__pill--bad",
1105
+ text: "未找到飞书渠道"
1106
+ },
1107
+ incompatible: {
1108
+ pill: "gx__pill--bad",
1109
+ text: "飞书渠道版本不兼容"
1110
+ }
1111
+ };
1112
+ function AnalyzeTab({ draft, setDraft, analyze, credentialsAvailable, overridden, busy, onSave, onClear }) {
1113
+ const [value, setValue] = (0, react.useState)("");
1114
+ const token = analyze.token;
1115
+ const save = () => {
1116
+ const trimmed = value.trim();
1117
+ if (trimmed === "" || busy) return;
1118
+ onSave(trimmed).then((ok) => {
1119
+ if (ok) setValue("");
1120
+ });
1121
+ };
1122
+ const topics = TOPICS_STATUS[analyze.topics];
1123
+ const endpoint = new URL(analyze.route.slice(1), document.baseURI).href;
1124
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1125
+ className: "gx__sec",
1126
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
1127
+ className: "gx__secTitle",
1128
+ children: "日志平台分析接口"
1129
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1130
+ className: "gx__list",
1131
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1132
+ className: "gx__row",
1133
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1134
+ className: "gx__rowText",
1135
+ children: [
1136
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1137
+ className: "gx__rowLabel",
1138
+ children: [
1139
+ "开启接口 ",
1140
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Overridden, {
1141
+ keys: overridden,
1142
+ name: "analyze"
1143
+ }),
1144
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1145
+ className: `gx__pill ${topics.pill}`,
1146
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "gx__dot" }), topics.text]
1147
+ })
1148
+ ]
1149
+ }),
1150
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1151
+ className: "gx__rowSummary",
1152
+ children: "日志平台调用接口后,观象经飞书渠道(dsh-channels 0.8 及以上)在目标群发出分析卡片,并在卡片下的话题里开始分析;群内成员都可以在话题里继续追问。关闭时接口回复 404。"
1153
+ }),
1154
+ analyze.topics === "ready" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1155
+ className: "gx__rowError",
1156
+ children: analyze.topics === "missing" ? "接口需要 dsh-channels 0.8 及以上版本并已加载;dsh-channels 0.7 自带同名接口,升级前由它提供。" : "请把观象与 dsh-channels 升级到相互兼容的版本。"
1157
+ })
1158
+ ]
1159
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1160
+ className: "gx__rowCtl",
1161
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1162
+ type: "checkbox",
1163
+ className: "gx__check",
1164
+ "aria-label": "开启分析接口",
1165
+ checked: draft.analyzeEnabled,
1166
+ onChange: (event) => {
1167
+ setDraft({
1168
+ ...draft,
1169
+ analyzeEnabled: event.target.checked
1170
+ });
1171
+ }
1172
+ })
1173
+ })]
1174
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1175
+ className: "gx__row gx__row--top",
1176
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1177
+ className: "gx__rowText",
1178
+ children: [
1179
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1180
+ className: "gx__rowLabel",
1181
+ children: [
1182
+ "访问令牌 ",
1183
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1184
+ className: "gx__tag gx__mono",
1185
+ children: analyze.tokenEnv
1186
+ }),
1187
+ credentialsAvailable ? tokenPill(token) : null
1188
+ ]
1189
+ }),
1190
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1191
+ className: "gx__rowSummary",
1192
+ children: [
1193
+ "日志平台以 ",
1194
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1195
+ className: "gx__mono",
1196
+ children: "Authorization: Bearer <令牌>"
1197
+ }),
1198
+ " 调用接口。令牌未设置时接口回复 503。"
1199
+ ]
1200
+ }),
1201
+ credentialsAvailable ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1202
+ className: "gx__rowError",
1203
+ children: "当前 DSH 组合没有加载凭据服务(@deepseek-ai/dsh-credentials-local),无法保存或读取令牌。"
1204
+ }),
1205
+ token?.error === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1206
+ className: "gx__rowError",
1207
+ children: token.error
1208
+ }),
1209
+ token !== void 0 && token.writable ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1210
+ className: "gx__addRow gx__addRow--flush",
1211
+ children: [
1212
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1213
+ className: "gx__input gx__mono",
1214
+ type: "password",
1215
+ autoComplete: "new-password",
1216
+ spellCheck: false,
1217
+ placeholder: token.configured ? "输入新令牌以替换" : "粘贴令牌",
1218
+ value,
1219
+ onChange: (event) => {
1220
+ setValue(event.target.value);
1221
+ },
1222
+ onKeyDown: (event) => {
1223
+ if (event.key === "Enter") save();
1224
+ }
1225
+ }),
1226
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1227
+ type: "button",
1228
+ className: "gx__btn",
1229
+ disabled: busy || value.trim() === "",
1230
+ onClick: save,
1231
+ children: "保存令牌"
1232
+ }),
1233
+ token.configured && token.source === "file" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1234
+ type: "button",
1235
+ className: "gx__btn gx__btn--ghost",
1236
+ disabled: busy,
1237
+ onClick: onClear,
1238
+ children: "清除"
1239
+ }) : null
1240
+ ]
1241
+ }) : null
1242
+ ]
1243
+ })
1244
+ })]
1245
+ })]
1246
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1247
+ className: "gx__sec",
1248
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
1249
+ className: "gx__secTitle",
1250
+ children: "调用方式"
1251
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1252
+ className: "gx__list",
1253
+ children: [
1254
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1255
+ className: "gx__row",
1256
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1257
+ className: "gx__rowText",
1258
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1259
+ className: "gx__rowLabel",
1260
+ children: "地址"
1261
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1262
+ className: "gx__rowSummary gx__mono",
1263
+ children: ["POST ", endpoint]
1264
+ })]
1265
+ })
1266
+ }),
1267
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1268
+ className: "gx__row",
1269
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1270
+ className: "gx__rowText",
1271
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1272
+ className: "gx__rowLabel",
1273
+ children: "请求头"
1274
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1275
+ className: "gx__rowSummary gx__mono",
1276
+ children: "Authorization: Bearer <令牌> · Idempotency-Key: <1-200 个可见 ASCII 字符> · Content-Type: application/json"
1277
+ })]
1278
+ })
1279
+ }),
1280
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1281
+ className: "gx__row",
1282
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1283
+ className: "gx__rowText",
1284
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1285
+ className: "gx__rowLabel",
1286
+ children: "请求体"
1287
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1288
+ className: "gx__rowSummary gx__mono",
1289
+ children: [
1290
+ "requesterUserId, triggeredAt, app, errorCode, logDetails[",
1291
+ "{",
1292
+ "name, url",
1293
+ "}",
1294
+ "], chatId, prompt"
1295
+ ]
1296
+ })]
1297
+ })
1298
+ }),
1299
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1300
+ className: "gx__row",
1301
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1302
+ className: "gx__rowText",
1303
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1304
+ className: "gx__rowLabel",
1305
+ children: "响应"
1306
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1307
+ className: "gx__rowSummary",
1308
+ children: "202 已发出分析卡片(同一 Idempotency-Key 重试返回同一张卡片);400/401/404/405/409/413/415 请求问题;403 目标群未授权;502 卡片发送失败;503 飞书未连接或令牌未设置。字段说明见观象 README。"
1309
+ })]
1310
+ })
1311
+ })
1312
+ ]
1313
+ })]
1314
+ })] });
1315
+ }
1080
1316
  function AdvancedTab({ draft, setDraft, overridden, busy, onReset }) {
1081
1317
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1082
1318
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
@@ -1182,7 +1418,7 @@ window.__ModuleLoader__.load({
1182
1418
  })]
1183
1419
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1184
1420
  className: "gx__rowSummary",
1185
- children: "子 Agent 发现应用的日志分析 SKILL 有欠缺时可以写出修改提议;经用户授权后,观象用访问令牌把它推送到新分支并创建合并请求(GitLab,令牌需要 write_repository 权限)。"
1421
+ children: "子 Agent 发现应用的日志分析 SKILL 有欠缺时可以写出修改提议;经用户授权后,观象经 dsh-repo 把它提交到新分支并创建合并请求(GitLab 或 GitHub,需要安装 dsh-repo 并配置该仓库主机的令牌)。"
1186
1422
  })]
1187
1423
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1188
1424
  className: "gx__rowCtl",