@modesto_ma/dsh-guanxiang 0.0.0-stage → 0.1.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 CHANGED
@@ -1,3 +1,189 @@
1
- # Temporary Holding Version
1
+ # 观象 GuanXiang
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ > **观其象,知其因。**
4
+
5
+ 观象是面向 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(dsh)的日志分析插件。
6
+
7
+ 名称取意于《周易》的观象思想:以日志为象,以推理为径,从复杂的系统运行痕迹中洞察异常、关联线索、追溯根因。
8
+
9
+ 中文名统一使用「观象」,英文品牌统一使用「GuanXiang」。
10
+
11
+ ## 项目状态
12
+
13
+ 初版(未发布到 npm):打通「`/guanxiang` 入口 → 识别应用 → 派发子 Agent 到应用代码仓库 → 汇总结论」这条链路,附带一个示例应用仓库。接口仍可能调整。
14
+
15
+ ## 核心能力
16
+
17
+ 观象的能力按名称拆分为三个层次,由浅入深:
18
+
19
+ | 能力 | 关注点 | 规划内容 |
20
+ |---|---|---|
21
+ | **观** | 看见 | 解析日志、提取线索、发现异常 |
22
+ | **象** | 理解 | 理解系统行为、关联事件、构建问题全貌 |
23
+ | **推演** | 求因 | 借助 AI Agent 自主分析、验证假设、定位根因 |
24
+
25
+ ### 观:解析日志,发现异常
26
+
27
+ - 解析常见的结构化与半结构化日志格式,统一为可检索的事件记录。
28
+ - 提取时间、级别、模块、请求标识、错误信息等关键字段作为线索。
29
+ - 识别错误突增、异常堆栈、频率偏离等可疑信号。
30
+
31
+ ### 象:关联事件,构建全貌
32
+
33
+ - 按时间线、请求链路和组件关系关联分散的日志事件。
34
+ - 将多个异常信号归并为同一问题,还原问题发生的上下文。
35
+ - 呈现系统在问题时段内的行为全貌,为后续推演提供依据。
36
+
37
+ ### 推演:自主分析,定位根因
38
+
39
+ - 由 AI Agent 基于「观」与「象」的结果提出根因假设。
40
+ - 主动检索更多日志与上下文,对假设逐一验证或排除。
41
+ - 输出可追溯的分析过程与结论,说明每个判断所依据的日志证据。
42
+
43
+ ## 工作方式
44
+
45
+ 观象是分体结构:插件只负责识别与派发,各应用的日志分析知识跟着应用代码放在应用自己的仓库里。
46
+
47
+ ```
48
+ 用户:/guanxiang 订单服务昨晚 21 点下单报 500,日志见附件
49
+ │
50
+ ▼
51
+ 入口 SKILL(意图识别)── 先 fetch 各仓库,应用目录由远端最新提交的 .dsh/guanxiang.yaml 生成
52
+ │ guanxiang_dispatch({ tasks: [{ app, question, logs }] })
53
+ ▼
54
+ 派发工具:校验应用 id → 下载 URL 日志 → 并行启动子 Agent,等待全部结束
55
+ │ 子 Agent 工作目录 = 应用仓库,首条消息以 /<skill> 开头加载该应用的日志分析 SKILL
56
+ ▼
57
+ 父 Agent 按应用分节汇总,涉及多个应用时给出跨应用的推演
58
+ ```
59
+
60
+ - **一个入口。** 插件只提供用户斜杠入口 `/guanxiang`(模型不会自行加载)。dsh 在 Web、TUI、ACP 以及经 dsh_channels 的飞书消息中都能识别它。
61
+ - **派发由代码完成。** 模型只给出应用 id、问题和日志位置;仓库目录、应用 SKILL、并发上限都由插件决定。应用 id 必须与注册表一一对应,否则整次派发被拒绝。
62
+ - **日志一律以绝对路径交给子 Agent。** 附件和本机文件直接传路径;消息里的对象存储下载链接由派发工具下载(只允许白名单域名,不带鉴权,限制大小与耗时)。日志包不解压,由应用的日志分析 SKILL 在运行目录里解压。
63
+ - **应用目录随时最新。** 每次加载 `/guanxiang` 都先 `git fetch` 各仓库(间隔由 `fetchIntervalMs` 控制,默认每次),再从远端分支最新提交的 git 对象读取应用描述生成应用目录。fetch 不改动任何检出,所以正在进行的分析不会挡住更新。最多等待 `refreshWaitMs`,超时就先用已有的目录,fetch 在后台继续。
64
+ - **代码快照不可变。** 子 Agent 在 `<reposDir>/.snapshots/<仓库名>/` 下按提交创建的 `git worktree` 快照里工作。快照创建后不再改动,同一提交的派发共用一份;没有分析在用、且已不是最新提交的快照会被删除。
65
+ - **运行目录。** 每次派发在父会话目录下创建 `.guanxiang/runs/<runId>/<序号>-<应用 id>/`,下载的日志放在这里,子 Agent 也只在这里写文件;应用仓库对子 Agent 只读。
66
+ - **等待式派发。** 工具等待所有子 Agent 结束后一次返回,一条消息对应一张完整回复;超过派发超时或调用被取消时停止仍在运行的子 Agent。子会话中不提供派发工具。
67
+
68
+ ## 安装与配置
69
+
70
+ 观象以 dsh bundle 交付(`package.json` 的 `dsh.bundle.patch` 指向 `cordis.patch.yml`),Host 半侧运行 `lib/index.js`,浏览器半侧运行 `lib/client.js`。
71
+
72
+ 安装已发布的 npm 版本(推荐):
73
+
74
+ ```bash
75
+ # 在 deepseek-harness 仓库内:把 npm 包安装进 web profile
76
+ DSH_HOME=$DSH_HOME pnpm dsh plugin --profile web add @modesto_ma/dsh-guanxiang
77
+ ```
78
+
79
+ 本地 checkout 开发时:
80
+
81
+ ```bash
82
+ pnpm build
83
+ DSH_HOME=$DSH_HOME pnpm dsh plugin --profile web add link:/path/to/dsh-guanxiang
84
+ ```
85
+
86
+ npm 发行包已包含构建产物;本地 `link:` 安装前必须先执行 `pnpm build`。安装或修改后重启 dsh。包内的 `cordis.patch.yml` 把 `@modesto_ma/dsh-guanxiang` 加入 profile 的 bundle 列表,并提供稳定 `id: guanxiang` 的 Loader 行。可在 profile 的 patch 中按该 id 配置默认值:
87
+
88
+ ```yaml
89
+ - id: guanxiang
90
+ config:
91
+ reposDir: ~/.dsh/guanxiang/repos # 默认 $DSH_HOME/guanxiang/repos
92
+ repos:
93
+ - url: git@github.com:example/order-service.git
94
+ - url: https://github.com/example/pay-service.git
95
+ branch: release # 可选,默认跟随远端默认分支
96
+ fetchIntervalMs: 0 # 加载 /guanxiang 时距上次拉取超过该时间的仓库先 fetch;0 = 每次
97
+ maxAppsPerRequest: 4 # 单次派发的任务上限
98
+ dispatchTimeoutMs: 1800000 # 等待全部子 Agent 的上限
99
+ download:
100
+ allowedHosts: [oss.example.com] # 同时允许子域名;为空时不下载 URL
101
+ maxBytes: 536870912
102
+ timeoutMs: 600000
103
+ subagentProvider: spawn # 子 Agent 使用的 ctx.subagents provider
104
+ gitTimeoutMs: 600000 # 单条 git 命令的超时
105
+ refreshWaitMs: 15000 # 加载 /guanxiang 时等待 fetch 的上限;0 = 不等待
106
+ ```
107
+
108
+ 配置在加载时校验,不合法时插件加载失败。git 地址支持 https/http/ssh/git/file URL、`user@host:path` 和绝对路径,拉取时使用本机已有的 git 凭据(SSH key 或 credential helper),插件不保存密码或 token,也不会弹出交互式输入。
109
+
110
+ 子 Agent 的命令在 dsh 的文件沙箱中运行:仓库只读,只有父会话的项目目录可写,运行目录就在其中。Linux 的 bubblewrap 沙箱会把 `/tmp` 换成空的临时目录,所以 `reposDir` 不要放在 `/tmp` 下,否则子 Agent 的命令看不到仓库。
111
+
112
+ 拉取目录 `<reposDir>/<仓库名>` 只用来 fetch,新克隆不检出文件(旧版本留下的检出文件不再使用)。多个同时运行的 dsh 进程不要共用同一个 `reposDir`:进程启动后首次同步时会删除快照目录里的遗留快照。
113
+
114
+ ### 设置面板
115
+
116
+ DSH Web 左侧栏的「观象」入口打开整页设置面板:
117
+
118
+ | 分页 | 内容 |
119
+ |---|---|
120
+ | 应用仓库 | git 地址列表(分支可选)、每个仓库的拉取状态、提交号、应用描述与不可用原因,单个或全部重新拉取 |
121
+ | 日志下载 | 允许下载的域名、单文件大小上限、下载超时 |
122
+ | 高级 | 拉取目录、更新间隔、单次应用数上限、派发超时,恢复 cordis.yml 配置 |
123
+
124
+ 面板保存的值写入 `$DSH_HOME/guanxiang/settings.json`(只保存与 cordis.yml 不同的字段),按与配置相同的规则校验。面板接口 `/guanxiang/*` 使用 DSH 自己的登录与来源校验,写操作另做同源校验。
125
+
126
+ ## 应用仓库约定
127
+
128
+ 每个应用仓库需要两样东西(完整示例见 [examples/order-service](examples/order-service)):
129
+
130
+ `.dsh/guanxiang.yaml` 应用描述:
131
+
132
+ ```yaml
133
+ id: order-service # 小写短横线格式,派发与分析请求按它对应应用
134
+ name: 订单服务 # 显示名,也是子 Agent 的标签
135
+ description: 下单、库存扣减与支付回调
136
+ aliases: [订单, 下单, order] # 用户对它的其他叫法
137
+ logHints: [order-service, OrderController] # 能识别本应用日志的字符串
138
+ skill: log-analysis # 对应 .dsh/skills/log-analysis/SKILL.md
139
+ ```
140
+
141
+ `.dsh/skills/<skill>/SKILL.md`:这个应用的日志分析流程(如何解压日志包、日志格式、代码结构、常见问题)。它不能设置 `user-invocable: false`,因为派发时用 `/<skill>` 加载。
142
+
143
+ 插件拉取仓库后检查这两个文件;缺失、不合法,或多个仓库声明同一个 id 时,相关应用不可用,原因显示在设置面板。应用描述和 SKILL 必须是普通文件,不能是符号链接。删除地址不会删除本地克隆,它的快照在没有分析使用后删除。
144
+
145
+ ## 通过飞书使用
146
+
147
+ 观象与 [dsh_channels](https://github.com/modestoma/dsh_channels) 没有代码耦合。dsh_channels 把不在自己命令表中的 `/` 开头消息作为普通 prompt 交给 dsh,dsh 识别 `/guanxiang` 后展开入口 SKILL。飞书附件的宿主路径会出现在消息里,入口 SKILL 会把它作为日志路径派发。
148
+
149
+ ## 开发
150
+
151
+ ```sh
152
+ pnpm install
153
+ pnpm check # 类型检查(Host 与 Client)+ 单元测试 + 构建 + 两个加载自检
154
+ ```
155
+
156
+ ## 发布
157
+
158
+ 版本号由 semantic-release 按 Conventional Commits 计算。推送到 `dev` 后,检查通过就自动发布 beta;正式版**只在手动触发时发布**,推送到 `main` 只跑检查。只有 `feat`、`fix` 或破坏性变更会产生新版本,其余类型的提交不发布。PR 标题需符合 `<type>(<scope>): <summary>`,type 取 `feat | fix | docs | test | refactor | chore | ci`。
159
+
160
+ | 分支 | 触发 | 版本 | npm dist-tag |
161
+ | --- | --- | --- | --- |
162
+ | `main` | 手动 | 正式版,如 `0.1.0` | `latest` |
163
+ | `dev` | 推送自动 | 预发布版,如 `0.1.0-beta.1` | `beta` |
164
+
165
+ 发布正式版:在 GitHub Actions 的 `pipeline` workflow 点击 **Run workflow** 并选择 `main`。`dry_run` 默认勾选,只预览下一个版本号;取消勾选才真正发布 npm 包、打 tag 并创建 GitHub Release。在 `dev` 上手动触发同样可用,用来预览或补发 beta。安装 beta 版用 `@modesto_ma/dsh-guanxiang@beta`。
166
+
167
+ | 路径 | 内容 |
168
+ |---|---|
169
+ | `src/index.ts` | Cordis 插件入口 |
170
+ | `src/service.ts` | 生效设置、注册表、入口 SKILL 与派发工具的注册 |
171
+ | `src/repos/` | git 拉取、应用描述校验、应用注册表 |
172
+ | `src/skill/entry.ts` | `/guanxiang` 入口 SKILL |
173
+ | `src/dispatch/` | 派发工具、日志下载、子 Agent 首条消息 |
174
+ | `src/settings/` | 面板契约、覆盖文件、HTTP 路由 |
175
+ | `src/client/` | 左侧栏入口与设置页面 |
176
+ | `examples/order-service/` | 示例应用仓库 |
177
+
178
+ ## 路线图
179
+
180
+ - [x] 确定插件结构与 bundle 打包方式(分体派发初版)
181
+ - [ ] 按日志中的版本号切换应用代码版本
182
+ - [ ] 观:通用日志解析与异常检测工具,供各应用 SKILL 复用
183
+ - [ ] 象:事件关联与问题全貌
184
+ - [ ] 推演:Agent 驱动的根因分析
185
+ - [ ] 使用文档与示例
186
+
187
+ ## 许可证
188
+
189
+ 待定。
@@ -0,0 +1,3 @@
1
+ - insert:
2
+ - id: guanxiang
3
+ name: '@modesto_ma/dsh-guanxiang'
@@ -0,0 +1,7 @@
1
+ # 观象应用描述:观象拉取本仓库后读取此文件,把应用加入 /guanxiang 的应用目录。
2
+ id: order-service
3
+ name: 订单服务
4
+ description: 下单、库存扣减与支付回调
5
+ aliases: [订单, 下单, order]
6
+ logHints: [order-service, OrderController, OrderService]
7
+ skill: log-analysis # 对应 .dsh/skills/log-analysis/SKILL.md
@@ -0,0 +1,30 @@
1
+ ---
2
+ name: log-analysis
3
+ description: 订单服务的日志分析流程:解压日志包、定位异常、对照本仓库代码给出根因。
4
+ ---
5
+
6
+ # 订单服务日志分析
7
+
8
+ 你在订单服务的代码仓库中工作,任务消息给出了问题、日志路径和一个可写的运行目录。仓库只读。
9
+
10
+ ## 1. 准备日志
11
+
12
+ - `.tar.gz` / `.tgz`:`tar -xzf <日志包> -C <运行目录>`
13
+ - `.zip`:`unzip -o <日志包> -d <运行目录>`
14
+ - `.gz`(单文件):`gzip -dc <文件> > <运行目录>/<文件名去掉 .gz>`
15
+ - 纯文本日志直接读取,不要复制。
16
+
17
+ ## 2. 定位异常
18
+
19
+ 1. 先按任务给出的时间范围和关键标识(订单号、用户 id、traceId)过滤。
20
+ 2. 查找 `ERROR`、`Exception`、`Caused by`,记录第一条异常和它之前同一 traceId 的请求。
21
+ 3. 本服务的日志格式:`时间 级别 [线程] [traceId] 类名 - 消息`。
22
+
23
+ ## 3. 对照代码
24
+
25
+ - 异常堆栈里的 `com.example.order.*` 类位于 `src/main/java/com/example/order/`,按文件名和行号阅读对应代码。
26
+ - 库存扣减由 `InventoryClient` 调用库存服务,`503` 或超时属于下游问题,不是本服务缺陷。
27
+
28
+ ## 4. 输出
29
+
30
+ 按任务消息要求的五部分返回结论、关键证据、时间线、假设与待验证、建议。证据中的代码位置写成 `文件:行号`。
@@ -0,0 +1,23 @@
1
+ # 示例应用仓库:订单服务
2
+
3
+ 演示观象要求的应用仓库结构:
4
+
5
+ ```
6
+ .dsh/guanxiang.yaml 应用描述(id、名称、别名、日志特征、分析 SKILL 名)
7
+ .dsh/skills/log-analysis/SKILL.md 日志分析 SKILL,观象派发时用 /log-analysis 加载
8
+ src/ 应用代码,子 Agent 对照它定位根因
9
+ sample-logs/order-service.log 一份示例日志(促销商品没有 SKU 导致 NullPointerException)
10
+ ```
11
+
12
+ 本地试用:把本目录复制出来初始化为 git 仓库,再在「观象」面板中添加它的绝对路径作为 git 地址。
13
+
14
+ ```sh
15
+ cp -r examples/order-service /tmp/order-service && cd /tmp/order-service
16
+ git init -q && git add -A && git commit -qm init
17
+ ```
18
+
19
+ 然后在对话中输入:
20
+
21
+ ```
22
+ /guanxiang 订单服务昨晚 21 点下单报 500,日志在 /tmp/order-service/sample-logs/order-service.log
23
+ ```
@@ -0,0 +1,8 @@
1
+ 2026-10-24 21:03:11.204 INFO [http-nio-8080-exec-7] [7f3a9c21] c.e.order.OrderController - POST /orders userId=u_10086 items=2
2
+ 2026-10-24 21:03:11.209 INFO [http-nio-8080-exec-7] [7f3a9c21] c.e.order.OrderService - draft order created orderId=O20261024210311
3
+ 2026-10-24 21:03:11.214 INFO [http-nio-8080-exec-7] [7f3a9c21] c.e.order.InventoryClient - deduct sku=SKU-1001 qty=1 ok
4
+ 2026-10-24 21:03:11.215 ERROR [http-nio-8080-exec-7] [7f3a9c21] c.e.order.OrderController - create order failed
5
+ java.lang.NullPointerException: Cannot invoke "String.trim()" because the return value of "com.example.order.OrderItem.getSku()" is null
6
+ at com.example.order.OrderService.create(OrderService.java:18)
7
+ at com.example.order.OrderController.createOrder(OrderController.java:13)
8
+ 2026-10-24 21:03:11.216 INFO [http-nio-8080-exec-7] [7f3a9c21] c.e.order.OrderController - response status=500 elapsed=12ms
@@ -0,0 +1,15 @@
1
+ package com.example.order;
2
+
3
+ /** HTTP entry for order creation. */
4
+ public class OrderController {
5
+ private final OrderService service;
6
+
7
+ public OrderController(OrderService service) {
8
+ this.service = service;
9
+ }
10
+
11
+ /** POST /orders */
12
+ public Order createOrder(CreateOrderRequest request) {
13
+ return service.create(request.getUserId(), request.getItems());
14
+ }
15
+ }
@@ -0,0 +1,24 @@
1
+ package com.example.order;
2
+
3
+ import java.util.List;
4
+
5
+ /** Creates orders and reserves their stock. */
6
+ public class OrderService {
7
+ private final InventoryClient inventory;
8
+
9
+ public OrderService(InventoryClient inventory) {
10
+ this.inventory = inventory;
11
+ }
12
+
13
+ /** Create an order for the given items; every item must name a SKU. */
14
+ public Order create(String userId, List<OrderItem> items) {
15
+ Order order = Order.draft(userId);
16
+ for (OrderItem item : items) {
17
+ // Items added from a promotion carry their SKU in promotionSku.
18
+ String sku = item.getSku().trim();
19
+ inventory.deduct(sku, item.getQuantity());
20
+ order.add(item);
21
+ }
22
+ return order.confirm();
23
+ }
24
+ }