dsh-my-observability 0.3.1 → 0.3.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/CHANGELOG.md +19 -83
- package/README.md +21 -75
- package/assets/settings-panel.png +0 -0
- package/lib/ai.js +22 -3
- package/lib/client.js +210 -4
- package/lib/client.src.js +6 -4
- package/lib/config-routes.js +88 -0
- package/lib/index.js +33 -8
- package/lib/routes.js +78 -43
- package/package.json +9 -6
package/CHANGELOG.md
CHANGED
|
@@ -2,101 +2,37 @@
|
|
|
2
2
|
|
|
3
3
|
本文件记录 dsh-my-observability 的所有版本变更。格式遵循 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),版本号遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
|
|
4
4
|
|
|
5
|
-
## [
|
|
5
|
+
## [0.3.4] - 2026-09-18
|
|
6
6
|
|
|
7
7
|
### 变更
|
|
8
8
|
|
|
9
|
-
-
|
|
9
|
+
- fix(my-observability): 去掉仅内部使用的 4 个导出(knip 死代码门禁)
|
|
10
|
+
- docs(guard,observability,mermaid-render): 设置页面板真实截图 + README 效果图
|
|
11
|
+
- fix(my-observability): 拆分设置视图至尺寸门禁内
|
|
12
|
+
- feat(my-observability): 设置页配置面板(aiReview / aiTimeoutMs 可视化编辑)
|
|
10
13
|
|
|
11
|
-
## [0.3.
|
|
12
|
-
|
|
13
|
-
### 变更
|
|
14
|
-
|
|
15
|
-
- feat(ts): 第四轮 JS→TS 迁移(5 个插件,server + client 全量)
|
|
16
|
-
|
|
17
|
-
## [0.3.0] - 2026-09-07
|
|
18
|
-
|
|
19
|
-
### 变更
|
|
20
|
-
|
|
21
|
-
- feat(observability): #155 错误上报统一——plugin_error 事件类型 + /errors API (#177)
|
|
22
|
-
- feat(observability): #155 资源监控扩展——新增 $DSH_HOME 目录总大小监控 (#176)
|
|
23
|
-
- feat(observability): #155 插件状态查询聚合——统一 status-query 事件 + /plugin-status API (#170)
|
|
24
|
-
- chore(plugins): #165 清理失效的 dsh.client.inject 声明(13 插件) (#167)
|
|
25
|
-
|
|
26
|
-
## [0.2.0] - 2026-09-07
|
|
14
|
+
## [0.3.3] - 2026-09-18
|
|
27
15
|
|
|
28
16
|
### 变更
|
|
29
17
|
|
|
30
|
-
-
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
18
|
+
- fix(my-observability): 去掉仅内部使用的 4 个导出(knip 死代码门禁)
|
|
19
|
+
- docs(guard,observability,mermaid-render): 设置页面板真实截图 + README 效果图
|
|
20
|
+
- fix(my-observability): 拆分设置视图至尺寸门禁内
|
|
21
|
+
- feat(my-observability): 设置页配置面板(aiReview / aiTimeoutMs 可视化编辑)
|
|
34
22
|
|
|
35
|
-
## [0.
|
|
23
|
+
## [0.3.2] - 2026-09-17
|
|
36
24
|
|
|
37
25
|
### 变更
|
|
38
26
|
|
|
39
|
-
-
|
|
40
|
-
-
|
|
41
|
-
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
### 变更
|
|
46
|
-
|
|
47
|
-
- fix(observability): 轨迹回放会话下拉可读标题(首条用户消息)——此前只展示 UUID 清单
|
|
48
|
-
|
|
49
|
-
## [0.1.5] - 2026-09-04
|
|
50
|
-
|
|
51
|
-
### 变更
|
|
52
|
-
|
|
53
|
-
- feat(observability): #127 资源看门狗自动降级——写入速率/文件大小连续 ≥3 次采样超限自动暂停落盘(事件仍入内存,FIFO 有界不丢)+ 日志告警;连续 ≥3 次正常自动恢复(全量快照补齐降级窗口);API 暴露 degraded 标记(#136)
|
|
54
|
-
- 发版前功能级验证清单(issue #67):verification/dsh-my-observability-0.1.5.md
|
|
55
|
-
|
|
56
|
-
## [0.1.4] - 2026-09-03
|
|
27
|
+
- docs: skill 合并 12→10 并拆分超限文件,修 observability 聚合端点缺陷
|
|
28
|
+
- docs(清理): #341 文档瘦身 23765 → 8309 行并固化精简规范 (#348)
|
|
29
|
+
- chore(artifacts): #318 同步 8 处共享件产物漂移 + 新增产物一致性门禁(fail-closed) (#325)
|
|
30
|
+
- fix(test): cucumber-js --import glob 改用双引号,兼容 Windows
|
|
31
|
+
- fix(security): 批量修复 'Insecure temporary file' 安全漏洞
|
|
32
|
+
- chore: 项目全面优化和完善
|
|
57
33
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
- feat(observability): #R17 资源监控看板——写放大/资源超限提前发现
|
|
61
|
-
|
|
62
|
-
## [0.1.3] - 2026-09-03
|
|
63
|
-
|
|
64
|
-
### 变更
|
|
65
|
-
|
|
66
|
-
- fix(observability): 审计持久化改增量追加,根治写放大磁盘风暴
|
|
67
|
-
- feat(observability): #89 审计日志搜索/过滤 + JSON/CSV 导出 (#120)
|
|
68
|
-
- docs: #106 安装命令统一加 --trust-lockfile (#113)
|
|
69
|
-
|
|
70
|
-
## [0.1.2] - 2026-09-01
|
|
71
|
-
|
|
72
|
-
### 变更
|
|
73
|
-
|
|
74
|
-
- fix(ui): 9 个插件未定义 token danger-primary 改用 error-primary(DSH 主题仅定义 business/error/success/warn)
|
|
75
|
-
|
|
76
|
-
## [0.1.1] - 2026-08-28
|
|
34
|
+
## [0.3.1] - 2026-09-10
|
|
77
35
|
|
|
78
36
|
### 变更
|
|
79
37
|
|
|
80
|
-
- feat(
|
|
81
|
-
- fix(security): 修复 CodeQL 告警——正则 ReDoS/随机数/命令注入(issue #5/#6/#7/#9/#10)
|
|
82
|
-
- refactor(shared): 抽取 dsh-shared 共享工具包,10 个插件迁移消除重复实现(issue #45)
|
|
83
|
-
- chore(deps): 升级 react 19 兼容性——13 个插件 peer 声明 ^18.2.0 || ^19.2.0(issue #49)
|
|
84
|
-
- style(format): 全仓 prettier 格式化(issue #44)
|
|
85
|
-
|
|
86
|
-
## [0.1.0] - 2026-08-28
|
|
87
|
-
|
|
88
|
-
### 新增
|
|
89
|
-
|
|
90
|
-
- **事件审计(issue #53)**:监听 `agent/status`(agent_status,含顶层/子代理标记)、`llm/stream`(llm_stream,同步包装流透传全部 chunk,记录开始/结束/错误 + chunk/字符/耗时统计)、`tools/pre-execute`(tool_call,工具名 + 参数键列表 + 文本摘要截断)、`tools/execute`(tool_result,成功/失败 + 耗时);waterfall 一律透传 `next()`,只读观察不改变流程。
|
|
91
|
-
- **审计持久化**:`$DSH_HOME/observability/audit.json`(防抖 500ms + 原子写 tmp+rename + teardown flush),启动异步加载(加载前事件缓冲不丢),重启后完整恢复;按会话隔离;每会话 2000 条 / 全局 20000 条上限防膨胀。
|
|
92
|
-
- **轨迹回放面板**:侧边栏页签 `dsh-my-observability:replay`(dsh-better-sidebar 服务),会话选择 + 类型过滤(全部/状态/模型流/工具)+ 时间轴事件列表(徽标 + 时间 + 摘要);可见时 5s 轮询、隐藏时暂停。
|
|
93
|
-
- **结构化 Git 工具**:侧边栏页签 `dsh-my-observability:git`——仓库状态(分支 + 暂存/未暂存计数)、差异查看(工作区/暂存区)、类型化提交(Conventional Commits:type 枚举 feat/fix/docs/style/refactor/test/chore + 可选 scope/body,服务端生成消息 + execFile 执行 git 不经 shell)。
|
|
94
|
-
- **增量 diff 审查**:`POST /observability/api/review`——unified diff 解析 + 8 条规则引擎(secret-leak/conflict-marker 为 error,debug-statement/large-diff/binary-file 为 warning,todo-marker/trailing-space/no-test-change 为 info);可选 AI 审查增强(agents.create,超时/失败/解析失败降级,规则结果不受影响)。
|
|
95
|
-
- **查询接口**:`GET /observability/api/events`(会话事件,type 过滤 + limit)、`GET /observability/api/sessions`(会话列表)、`GET /observability/api/status`(审计统计 + 开关);全部经 loopback 信任围栏。
|
|
96
|
-
- **测试**:`test/host-audit.mjs`(事件监听/会话隔离/重启恢复/上限/fence)、`test/host-git.mjs`(真实临时 git 仓库 status/diff/commit)、`test/host-review.mjs`(规则引擎 + AI 各路径)、`test/host-mutation.mjs`(变异补充)、Gherkin 验收(observability/git/review 3 个 feature 16 场景);覆盖率 行 95% / 分支 85%;变异测试 ≥70%。
|
|
97
|
-
|
|
98
|
-
**真实环境验证**(独立端口 3081 隔离实例):
|
|
99
|
-
|
|
100
|
-
- 侧边栏「轨迹回放」页签出现,时间轴展示真实 agent 行为(状态/模型流/工具调用);
|
|
101
|
-
- 「Git 工具」页签:真实仓库状态/差异/类型化提交/提交前审查全流程可用;
|
|
102
|
-
- 重启验证实例后审计事件恢复。
|
|
38
|
+
- feat(ts): 第四轮 JS→TS 迁移(5 个插件,server + client 全量)
|
package/README.md
CHANGED
|
@@ -5,103 +5,49 @@
|
|
|
5
5
|
<div align="center">
|
|
6
6
|
<img alt="轨迹回放面板:侧边栏时间轴展示 agent 行为(状态/模型流/工具调用)" src="https://unpkg.com/dsh-my-observability/assets/replay-panel.png" width="640" />
|
|
7
7
|
<img alt="Git 工具面板:仓库状态 / 差异 / 提交前审查 / 类型化提交" src="https://unpkg.com/dsh-my-observability/assets/git-panel.png" width="640" />
|
|
8
|
+
<br/>
|
|
9
|
+
<img alt="设置页配置面板(设置 → 插件 → 可观测性):AI 审查增强开关 + AI 审查超时" src="https://unpkg.com/dsh-my-observability/assets/settings-panel.png" width="640" />
|
|
8
10
|
</div>
|
|
9
11
|
|
|
10
|
-
**DSH 可观测性 + Git
|
|
12
|
+
**dsh-my-observability**:DSH 可观测性 + Git 工程工具插件——**事件审计**记录 agent 的每一次行为(状态变化 / 模型流 / 工具调用与结果),**轨迹回放面板**按时间轴回放会话轨迹;**结构化 Git 工具**提供 Conventional Commits 类型化提交;**增量 diff 审查**在提交前用规则引擎(可选 AI 增强)检查调试残留、密钥泄露等问题。
|
|
11
13
|
|
|
12
14
|
## 功能
|
|
13
15
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
| `agent/status` | `agent_status` | 状态变化 + 顶层/子代理标记 |
|
|
21
|
-
| `llm/stream` | `llm_stream` | 流开始/结束/错误 + chunk/字符/耗时统计(同步包装流,透传全部 chunk) |
|
|
22
|
-
| `tools/pre-execute` | `tool_call` | 工具名 + 参数键列表 + 主要文本参数截断摘要 |
|
|
23
|
-
| `tools/execute` | `tool_result` | 工具名 + 成功/失败 + 耗时 |
|
|
24
|
-
|
|
25
|
-
- **会话隔离**:事件按会话分桶,切换会话互不串扰;
|
|
26
|
-
- **重启恢复**:持久化到 `$DSH_HOME/observability/audit.jsonl`(**增量追加** + 防抖批量 flush + 周期 compact 原子快照),重启后完整恢复;升级前旧格式 `audit.json` 自动迁移;
|
|
27
|
-
- **防膨胀**:每会话最多 2000 条(FIFO 淘汰)、全局 20000 条(轮转淘汰);
|
|
28
|
-
- **零写放大**:落盘只写新增事件(≈事件本体字节),不会因事件流持续而反复全量重写审计文件;
|
|
29
|
-
- **资源监控 + 自动降级(资源看门狗)**:面板「资源」区块展示本进程 CPU/内存 + 审计文件大小与写入速率(15s 采样),写放大/超限自动告警(告警列表展示在「资源」区块,阈值见 `resource-budget-review`);写入速率/文件大小连续超限自动**暂停落盘**(事件仍入内存,有界不丢),回落自动恢复(全量快照补齐降级窗口),全程日志告警 + API `degraded` 标记(issue #127)。
|
|
30
|
-
- **只读观察**:waterfall 事件一律透传 `next()`,绝不改变工具/模型流程。
|
|
31
|
-
|
|
32
|
-
### 2. 轨迹回放面板(时间轴)
|
|
33
|
-
|
|
34
|
-
侧边栏「轨迹回放」页签:选择会话 + 类型过滤(全部 / 状态 / 模型流 / 工具),按时间轴展示 agent 行为(类型徽标 + 时间 + 摘要);面板可见时 5s 轮询、隐藏时暂停。
|
|
35
|
-
|
|
36
|
-
### 3. 结构化 Git 工具(类型化提交)
|
|
37
|
-
|
|
38
|
-
侧边栏「Git 工具」页签:输入仓库路径 → 查看状态(分支 + 暂存/未暂存计数)与差异(工作区/暂存区)→ **类型化提交**:
|
|
39
|
-
|
|
40
|
-
- 类型枚举:`feat` / `fix` / `docs` / `style` / `refactor` / `test` / `chore`;
|
|
41
|
-
- 消息格式:`<type>(<scope>): <description>`(scope 可选,body 可选);
|
|
42
|
-
- 服务端生成消息 + `execFile` 执行 git(不经 shell),提交历史清晰可追溯。
|
|
43
|
-
|
|
44
|
-
### 4. 增量 diff 审查(提交前审查)
|
|
45
|
-
|
|
46
|
-
「提交前审查」按钮对增量 diff 运行规则引擎(确定性、可测试):
|
|
47
|
-
|
|
48
|
-
| 规则 | 严重级别 | 检查内容 |
|
|
49
|
-
| ----------------- | -------- | ----------------------------------------------------- |
|
|
50
|
-
| `secret-leak` | error | 密钥/凭据硬编码(password/api_key/secret/token) |
|
|
51
|
-
| `conflict-marker` | error | 合并冲突标记残留(`<<<<<<<` / `=======` / `>>>>>>>`) |
|
|
52
|
-
| `debug-statement` | warning | 调试残留(console.* / debugger / print 族) |
|
|
53
|
-
| `large-diff` | warning | 单文件变更 > 300 行 |
|
|
54
|
-
| `binary-file` | warning | 二进制文件变更 |
|
|
55
|
-
| `todo-marker` | info | TODO/FIXME/HACK 标记 |
|
|
56
|
-
| `trailing-space` | info | 行尾多余空格 |
|
|
57
|
-
| `no-test-change` | info | 有源码变更但无测试变更 |
|
|
58
|
-
|
|
59
|
-
- **可选 AI 审查**:配置 `aiReview`(默认开)且 agents 服务可用时,创建独立审查 agent 阅读 diff 输出总评(verdict/summary/topIssues);超时/失败/解析失败自动降级,规则引擎结果不受影响(AI 是增强,不是门禁)。
|
|
60
|
-
|
|
61
|
-
## 工作原理
|
|
62
|
-
|
|
63
|
-
- **Server 端**(`lib/index.js`):`audit.js` 监听四类事件 → `store.js` 按会话分桶持久化;`git.js` 类型化提交(execFile);`review.js` + `diff.js` 规则引擎;`ai.js` 可选 AI 增强;`routes.js` 提供 `/observability/api` 路由(全部经 loopback 信任围栏)。
|
|
64
|
-
- **Client 端**(`lib/client.js`):两个侧边栏页签(`dsh-my-observability:replay` 轨迹回放、`dsh-my-observability:git` Git 工具),每个面板一个页签类型,经**宿主原生侧边栏扩展点**注册(`ctx.sidebarRightTabs` 页签类型 + `slots` 的 `sidebar.right.pane.tab` / `.title` 席位,issue #187 批 1),样式走 DSH 语义 token,随 fiber 卸载无残留。
|
|
16
|
+
- **事件审计**:只读观察并记录 `agent/status`(状态变化 + 顶层/子代理标记)、`llm/stream`(流开始/结束/错误 + chunk 与耗时统计,透传全部 chunk)、`tools/pre-execute`(工具名 + 参数摘要)、`tools/execute`(成功/失败 + 耗时);waterfall 事件一律透传 `next()`,绝不改变工具/模型流程。
|
|
17
|
+
- **持久化与上限**:按会话分桶增量追加到 `$DSH_HOME/observability/audit.jsonl`(防抖批量 flush + 周期原子 compact),重启后完整恢复;每会话上限 2000 条、全局 20000 条,超出淘汰最旧事件。
|
|
18
|
+
- **轨迹回放面板**:侧边栏「轨迹回放」页签,选会话 + 类型过滤(全部 / 状态 / 模型流 / 工具),时间轴展示类型徽标 + 时间 + 摘要;可见时 5s 轮询、隐藏时暂停。
|
|
19
|
+
- **结构化 Git 工具**:侧边栏「Git 工具」页签,输入仓库路径查看分支与暂存/未暂存计数、工作区/暂存区差异,按 `<type>(<scope>): <description>` 生成类型化提交(`feat` / `fix` / `docs` / `style` / `refactor` / `test` / `chore`);服务端 `execFile` 执行 git,不经 shell。
|
|
20
|
+
- **增量 diff 审查**:「提交前审查」对增量 diff 跑确定性规则引擎——error:密钥/凭据硬编码、合并冲突标记;warning:调试残留(console/debugger/print)、单文件变更 > 300 行、二进制文件变更;info:TODO/FIXME/HACK 标记、行尾空格、有源码变更但无测试变更。
|
|
21
|
+
- **资源看门狗**:面板「资源」区块展示本进程 CPU/内存 + 审计文件大小与写入速率(15s 采样),写放大或连续超限时自动暂停落盘(事件仍入内存,有界不丢),回落自动恢复,附日志告警与 API `degraded` 标记。
|
|
65
22
|
|
|
66
23
|
## 安装
|
|
67
24
|
|
|
68
|
-
> 💡 **npm 安装(普通用户推荐)**:`dsh plugin --profile web add dsh-my-observability --trust-lockfile
|
|
25
|
+
> 💡 **npm 安装(普通用户推荐)**:`dsh plugin --profile web add dsh-my-observability --trust-lockfile`——无需克隆本仓库;link 方式供本仓库开发者使用。
|
|
69
26
|
|
|
70
27
|
```bash
|
|
71
|
-
# 1) 克隆本仓库(任意目录)
|
|
72
28
|
git clone https://github.com/baosfeng/my-dsh-plugins.git
|
|
73
|
-
# 2) 以本地 link 方式安装(将 <仓库路径> 替换为上面的克隆目录)
|
|
74
29
|
dsh plugin --profile web add link:<仓库路径>/plugins/dsh-my-observability
|
|
75
30
|
```
|
|
76
31
|
|
|
77
|
-
|
|
32
|
+
server 端改动需重启 `dsh web`;client 端改动硬刷新浏览器(Cmd/Ctrl+Shift+R)即可。
|
|
78
33
|
|
|
79
34
|
## 配置
|
|
80
35
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
```yaml
|
|
84
|
-
- insert:
|
|
85
|
-
- id: observability
|
|
86
|
-
name: 'dsh-my-observability'
|
|
87
|
-
- config: # 传给 apply(ctx, config)
|
|
88
|
-
aiReview: true # 增量 diff 审查启用 AI 增强(默认 true;agents 服务不可用时自动降级)
|
|
89
|
-
aiTimeoutMs: 60000 # AI 审查超时(毫秒,默认 60000)
|
|
90
|
-
```
|
|
36
|
+
插件行 `config` 字段,均可选:
|
|
91
37
|
|
|
92
|
-
|
|
38
|
+
| 配置键 | 作用 |
|
|
39
|
+
| ------------- | ---------------------------------------------------------------------- |
|
|
40
|
+
| `aiReview` | 增量 diff 审查启用 AI 增强(默认 `true`;agents 服务不可用时自动降级) |
|
|
41
|
+
| `aiTimeoutMs` | AI 审查超时(毫秒,默认 60000) |
|
|
93
42
|
|
|
94
|
-
|
|
95
|
-
| -------- | ------------- | -------------- |
|
|
96
|
-
| `cordis` | 插件运行时 | 是(宿主提供) |
|
|
97
|
-
| `react` | client 端组件 | 是(宿主提供) |
|
|
43
|
+
`aiReview` 与 `aiTimeoutMs` 可在 **设置 → 插件 → 可观测性** 里可视化编辑:保存即写回 profile 层 `cordis.patch.yml` 的插件行(行 id `observability`,其余手写键原样保留),DSH 热重载后立即生效,无需重启。其余字段(`aiProvider` / `aiModel` / `aiCwd` / `resourceIntervalMs` 等)仍按上表在 `cordis.patch.yml` 手写;非法值不会写坏配置(`aiReview` 非布尔忽略,`aiTimeoutMs` 非正/非有限回退 60000)。
|
|
98
44
|
|
|
99
45
|
## 限制与说明
|
|
100
46
|
|
|
101
|
-
- **Git
|
|
102
|
-
- **AI
|
|
103
|
-
-
|
|
47
|
+
- **Git 工具作用于本机仓库**:仓库路径由你在面板输入(localStorage 记住),仅本机可访问(loopback 围栏)。
|
|
48
|
+
- **AI 审查是增强不是门禁**:agents 服务不可用或超时/解析失败时自动降级为纯规则引擎结果。
|
|
49
|
+
- **审计有上限**:每会话 2000 条 / 全局 20000 条,超出自动淘汰最旧事件。
|
|
104
50
|
|
|
105
51
|
## 相关文档
|
|
106
52
|
|
|
107
|
-
→ [可观测性模块文档](../../docs/可观测性/概述.md) · [
|
|
53
|
+
→ [可观测性模块文档](../../docs/可观测性/概述.md) · [CHANGELOG](CHANGELOG.md)
|
|
Binary file
|
package/lib/ai.js
CHANGED
|
@@ -15,8 +15,21 @@ import { withTimeout, userMessage } from 'dsh-shared';
|
|
|
15
15
|
import { REVIEW_TIMEOUT_MS } from './constants.js';
|
|
16
16
|
/** diff 送入 prompt 的最大长度(截断防 token 爆炸)。 */
|
|
17
17
|
const AI_DIFF_MAX = 8000;
|
|
18
|
+
/** 从 AiAgentOptions 构建 agents.create 所需的 agentOptions 对象。 */
|
|
19
|
+
function buildAgentOptions(opts) {
|
|
20
|
+
if (opts === undefined)
|
|
21
|
+
return {};
|
|
22
|
+
const result = {};
|
|
23
|
+
if (opts.provider !== undefined)
|
|
24
|
+
result.provider = opts.provider;
|
|
25
|
+
if (opts.model !== undefined)
|
|
26
|
+
result.model = opts.model;
|
|
27
|
+
if (opts.cwd !== undefined)
|
|
28
|
+
result.cwd = opts.cwd;
|
|
29
|
+
return result;
|
|
30
|
+
}
|
|
18
31
|
/** 运行 AI 审查(尽力而为;任何失败都降级为 failed 标记)。 */
|
|
19
|
-
export async function runAiReview(ctx, diffText, report, timeoutMs = REVIEW_TIMEOUT_MS) {
|
|
32
|
+
export async function runAiReview(ctx, diffText, report, timeoutMs = REVIEW_TIMEOUT_MS, agentOptions) {
|
|
20
33
|
const agents = agentsServiceOf(ctx);
|
|
21
34
|
if (agents === undefined)
|
|
22
35
|
return { enabled: true, failed: true, note: 'agents service unavailable' };
|
|
@@ -28,11 +41,17 @@ export async function runAiReview(ctx, diffText, report, timeoutMs = REVIEW_TIME
|
|
|
28
41
|
handle = await agents.create({
|
|
29
42
|
sessionId,
|
|
30
43
|
meta: { origin: 'subagent', delegationDepth: 1 },
|
|
31
|
-
agentOptions:
|
|
44
|
+
agentOptions: buildAgentOptions(agentOptions),
|
|
32
45
|
});
|
|
33
46
|
handle.agent.followup(userMessage(aiReviewPrompt(diffText, report)));
|
|
34
47
|
await withTimeout(handle.agent.whenIdle(), timeoutMs);
|
|
35
|
-
const
|
|
48
|
+
const text = lastAssistantText(handle.agent.session);
|
|
49
|
+
// 区分「模型未调用」与「结论解析失败」:
|
|
50
|
+
// - text 为空 → agent 未产出 assistant 消息(模型从未被调用或调用失败)
|
|
51
|
+
// - text 非空但解析失败 → 模型回复了,但结论无法解析
|
|
52
|
+
if (text === '')
|
|
53
|
+
return { enabled: true, failed: true, note: '模型未产出回复(agent 可能未调用模型或模型请求失败)' };
|
|
54
|
+
const parsed = parseAiConclusion(text);
|
|
36
55
|
if (parsed === undefined)
|
|
37
56
|
return { enabled: true, failed: true, note: 'AI 结论解析失败' };
|
|
38
57
|
return { enabled: true, ...parsed };
|
package/lib/client.js
CHANGED
|
@@ -37,8 +37,7 @@ window.__ModuleLoader__.load({
|
|
|
37
37
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' })
|
|
38
38
|
const { createElement, useEffect, useState } = require('react')
|
|
39
39
|
|
|
40
|
-
// 原生页签身份(issue #187 批 1):id/kind
|
|
41
|
-
// (每个面板一个类型,id 全局唯一且是席位的 key),order = 迁移前的指南页顺序。
|
|
40
|
+
// 原生页签身份(issue #187 批 1):id/kind 沿用迁移前 better-sidebar 的 tab id(每面板一个类型,id 全局唯一且是席位 key),order 为指南页顺序。
|
|
42
41
|
const REPLAY_TAB_ID = 'dsh-my-observability:replay'
|
|
43
42
|
const GIT_TAB_ID = 'dsh-my-observability:git'
|
|
44
43
|
const REPLAY_TAB_ORDER = 40
|
|
@@ -194,6 +193,23 @@ const strings = {
|
|
|
194
193
|
loading: () => (isZh() ? '加载中…' : 'Loading…'),
|
|
195
194
|
emptyDiff: () => (isZh() ? '(空)' : '(empty)'),
|
|
196
195
|
noRepo: () => (isZh() ? '请输入仓库路径' : 'Enter a repo path'),
|
|
196
|
+
// 设置页(设置 → 插件 → 可观测性,issue #383)
|
|
197
|
+
settingsTitle: () => (isZh() ? '可观测性' : 'Observability'),
|
|
198
|
+
settingsSectionTitle: () => (isZh() ? 'AI 审查' : 'AI review'),
|
|
199
|
+
settingsAiReviewLabel: () => (isZh() ? 'AI 审查增强' : 'AI review enhancement'),
|
|
200
|
+
settingsAiReviewHint: () => isZh()
|
|
201
|
+
? '增量 diff 审查调用 AI agent 补充规则引擎结论(agents 服务不可用或超时自动降级为纯规则)'
|
|
202
|
+
: 'Let an AI agent augment the rule-engine review of the incremental diff (degrades to rules only when the agents service is unavailable)',
|
|
203
|
+
settingsAiTimeoutLabel: () => (isZh() ? 'AI 审查超时(ms)' : 'AI review timeout (ms)'),
|
|
204
|
+
settingsAiTimeoutHint: () => isZh()
|
|
205
|
+
? '单次 AI 审查的最长等待;非正数或非有限值在保存时回退为 60000'
|
|
206
|
+
: 'Max wait per AI review; non-positive or non-finite values fall back to 60000 on save',
|
|
207
|
+
settingsSave: () => (isZh() ? '保存' : 'Save'),
|
|
208
|
+
settingsSaved: () => (isZh() ? '已保存并生效' : 'Saved and applied'),
|
|
209
|
+
settingsSaveFailed: () => (isZh() ? '保存失败' : 'Save failed'),
|
|
210
|
+
settingsLoadFailedHint: () => isZh()
|
|
211
|
+
? '无法读取 /observability/api/config:请确认服务端插件已加载(改过 server 端后需重启 dsh web)'
|
|
212
|
+
: 'Cannot read /observability/api/config: make sure the host half is loaded (restart dsh web after host-side changes)',
|
|
197
213
|
};
|
|
198
214
|
|
|
199
215
|
// ── shared icons (inline, stroke=currentColor, matching better-sidebar) ──
|
|
@@ -365,6 +381,33 @@ const icon = {
|
|
|
365
381
|
],
|
|
366
382
|
size,
|
|
367
383
|
),
|
|
384
|
+
// 箭头向上(更新图标):向上的箭头,表示更新操作
|
|
385
|
+
arrowUp: (size = 16) =>
|
|
386
|
+
iconSvg(
|
|
387
|
+
[
|
|
388
|
+
createElement('line', { x1: 12, y1: 19, x2: 12, y2: 5 }),
|
|
389
|
+
createElement('polyline', { points: '5 12 12 5 19 12' }),
|
|
390
|
+
],
|
|
391
|
+
size,
|
|
392
|
+
),
|
|
393
|
+
// 电源关(禁用图标):圆形电源按钮,表示禁用操作
|
|
394
|
+
powerOff: (size = 16) =>
|
|
395
|
+
iconSvg(
|
|
396
|
+
[
|
|
397
|
+
createElement('path', { d: 'M18.36 6.64a9 9 0 1 1-12.73 0' }),
|
|
398
|
+
createElement('line', { x1: 12, y1: 2, x2: 12, y2: 12 }),
|
|
399
|
+
],
|
|
400
|
+
size,
|
|
401
|
+
),
|
|
402
|
+
// 电源开(启用图标):圆形电源按钮,表示启用操作
|
|
403
|
+
powerOn: (size = 16) =>
|
|
404
|
+
iconSvg(
|
|
405
|
+
[
|
|
406
|
+
createElement('path', { d: 'M18.36 6.64a9 9 0 1 1-12.73 0' }),
|
|
407
|
+
createElement('line', { x1: 12, y1: 2, x2: 12, y2: 12 }),
|
|
408
|
+
],
|
|
409
|
+
size,
|
|
410
|
+
),
|
|
368
411
|
}
|
|
369
412
|
|
|
370
413
|
// Common-language / file-type badges (issue #24): brand fill + contrast
|
|
@@ -1605,6 +1648,167 @@ function GitPanel() {
|
|
|
1605
1648
|
}), createElement(DiffView, { diff }), createElement(ReviewReport, { report }), createElement(CommitForm, { repo, onCommitted }));
|
|
1606
1649
|
}
|
|
1607
1650
|
|
|
1651
|
+
"use strict";
|
|
1652
|
+
// ── 设置页签(issue #383):设置 → 插件 → 可观测性 ────────────────────
|
|
1653
|
+
// 只暴露 README 已记录的 aiReview / aiTimeoutMs 两项;其余键(aiProvider /
|
|
1654
|
+
// aiModel / aiCwd / resourceIntervalMs / resourceLimits)继续由用户在
|
|
1655
|
+
// cordis.patch.yml 手写(服务端写回时会合并保留原条目已存在的键)。
|
|
1656
|
+
//
|
|
1657
|
+
// 保存 → PUT /observability/api/config → 写回 profile patch 文件;DSH 的
|
|
1658
|
+
// watchUserPatches 热重载 patch 后重新 apply(保存即生效)。保存成功后面板
|
|
1659
|
+
// 只更新本地状态:这两项在服务端生效,前端无缓存需要同步。
|
|
1660
|
+
//
|
|
1661
|
+
// 片段文件:无 import/export,共享 factory 作用域(apiJson / strings /
|
|
1662
|
+
// createElement 由 replay.js / i18n.js / 模板提供)。
|
|
1663
|
+
const SETTINGS_API = '/observability/api/config';
|
|
1664
|
+
/** 设置页样式:只用自己的类名前缀 + 宿主语义 token(随 effect 注入/卸载)。 */
|
|
1665
|
+
const SETTINGS_STYLES = `
|
|
1666
|
+
.dsh-my-observability-settings{display:flex;flex-direction:column;gap:10px;padding:12px}
|
|
1667
|
+
.dsh-my-observability-settings-section{display:flex;flex-direction:column;gap:8px}
|
|
1668
|
+
.dsh-my-observability-settings-section-title{font:var(--dsw-font-xs-strong-13);color:var(--dsw-alias-label-secondary)}
|
|
1669
|
+
.dsh-my-observability-settings-row{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:8px 10px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-2)}
|
|
1670
|
+
.dsh-my-observability-settings-info{display:flex;flex-direction:column;gap:2px;min-width:0}
|
|
1671
|
+
.dsh-my-observability-settings-label{font:var(--dsw-font-xs-strong-13);color:var(--dsw-alias-label-primary)}
|
|
1672
|
+
.dsh-my-observability-settings-hint{font:var(--dsw-font-xxs-12);color:var(--dsw-alias-label-tertiary);line-height:1.5}
|
|
1673
|
+
.dsh-my-observability-settings-toggle{flex:none;width:34px;height:20px;border-radius:10px;border:1px solid var(--dsw-alias-border-l2);background:color-mix(in srgb, var(--dsw-alias-label-tertiary) 30%, transparent);position:relative;cursor:pointer;transition:background var(--ds-transition-duration-slow) var(--ds-ease-in-out),border-color var(--ds-transition-duration-slow) var(--ds-ease-in-out)}
|
|
1674
|
+
.dsh-my-observability-settings-toggle[data-on="true"]{background:var(--dsw-alias-state-success-primary);border-color:transparent}
|
|
1675
|
+
.dsh-my-observability-settings-toggle::after{content:"";position:absolute;top:2px;left:2px;width:14px;height:14px;border-radius:50%;background:var(--dsw-alias-label-primary);transition:transform var(--ds-transition-duration-slow) var(--ds-ease-in-out),background var(--ds-transition-duration-slow) var(--ds-ease-in-out)}
|
|
1676
|
+
.dsh-my-observability-settings-toggle[data-on="true"]::after{transform:translateX(12px);background:var(--dsw-alias-label-primary-foreground)}
|
|
1677
|
+
.dsh-my-observability-settings-input{flex:none;width:120px;height:28px;padding:0 8px;border-radius:6px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font:var(--dsw-font-xxs-12)}
|
|
1678
|
+
.dsh-my-observability-settings-input:focus{outline:none;border-color:var(--dsw-alias-interactive-primary)}
|
|
1679
|
+
.dsh-my-observability-settings-actions{display:flex;align-items:center;gap:8px}
|
|
1680
|
+
.dsh-my-observability-settings-btn{height:28px;padding:0 14px;border-radius:6px;cursor:pointer;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-interactive-bg);color:var(--dsw-alias-label-primary);font:var(--dsw-font-xxs-12)}
|
|
1681
|
+
.dsh-my-observability-settings-btn:hover{background:var(--dsw-alias-interactive-bg-hover)}
|
|
1682
|
+
.dsh-my-observability-settings-status{font:var(--dsw-font-xxs-12);color:var(--dsw-alias-label-tertiary)}
|
|
1683
|
+
.dsh-my-observability-settings-saved{font:var(--dsw-font-xxs-12);color:var(--dsw-alias-state-success-primary)}
|
|
1684
|
+
.dsh-my-observability-settings-error{font:var(--dsw-font-xxs-12);color:var(--dsw-alias-state-error-primary)}
|
|
1685
|
+
`;
|
|
1686
|
+
/** 开关行(布尔配置项)。 */
|
|
1687
|
+
function SettingsToggleRow({ label, hint, on, onChange, }) {
|
|
1688
|
+
return createElement('div', { className: 'dsh-my-observability-settings-row' }, createElement('div', { className: 'dsh-my-observability-settings-info' }, createElement('div', { className: 'dsh-my-observability-settings-label' }, label), createElement('div', { className: 'dsh-my-observability-settings-hint' }, hint)), createElement('div', {
|
|
1689
|
+
className: 'dsh-my-observability-settings-toggle',
|
|
1690
|
+
'data-on': String(on),
|
|
1691
|
+
role: 'switch',
|
|
1692
|
+
'aria-checked': String(on),
|
|
1693
|
+
onClick: () => onChange(!on),
|
|
1694
|
+
}));
|
|
1695
|
+
}
|
|
1696
|
+
/** 数字输入行(AI 审查超时 ms)。 */
|
|
1697
|
+
function SettingsNumberRow({ label, hint, value, onChange, }) {
|
|
1698
|
+
return createElement('div', { className: 'dsh-my-observability-settings-row' }, createElement('div', { className: 'dsh-my-observability-settings-info' }, createElement('div', { className: 'dsh-my-observability-settings-label' }, label), createElement('div', { className: 'dsh-my-observability-settings-hint' }, hint)), createElement('input', {
|
|
1699
|
+
className: 'dsh-my-observability-settings-input',
|
|
1700
|
+
type: 'number',
|
|
1701
|
+
min: '1',
|
|
1702
|
+
step: '1000',
|
|
1703
|
+
value: String(value),
|
|
1704
|
+
onChange: (e) => onChange(Number(e.target.value)),
|
|
1705
|
+
}));
|
|
1706
|
+
}
|
|
1707
|
+
/** 保存配置:PUT 配置端点;失败只显示失败提示(绝不误报已保存)。 */
|
|
1708
|
+
function saveSettings(draft, setSaved, setSaveError) {
|
|
1709
|
+
setSaved(false);
|
|
1710
|
+
setSaveError(false);
|
|
1711
|
+
apiJson(SETTINGS_API, {
|
|
1712
|
+
method: 'PUT',
|
|
1713
|
+
headers: { 'content-type': 'application/json' },
|
|
1714
|
+
body: JSON.stringify(draft),
|
|
1715
|
+
})
|
|
1716
|
+
.then(() => setSaved(true))
|
|
1717
|
+
.catch(() => setSaveError(true));
|
|
1718
|
+
}
|
|
1719
|
+
/** 配置加载失败视图(读不到配置时不显示表单,避免用默认值覆盖真实配置)。 */
|
|
1720
|
+
function SettingsLoadError({ onRetry }) {
|
|
1721
|
+
return createElement('div', { className: 'dsh-my-observability-settings' }, createElement('div', { className: 'dsh-my-observability-settings-error' }, strings.loadError()), createElement('div', { className: 'dsh-my-observability-settings-status' }, strings.settingsLoadFailedHint()), createElement('div', { className: 'dsh-my-observability-settings-actions' }, createElement('button', { className: 'dsh-my-observability-settings-btn', onClick: onRetry }, strings.retry())));
|
|
1722
|
+
}
|
|
1723
|
+
/** 加载态(配置未返回前的占位,避免先渲染出默认值表单再被回填覆盖)。 */
|
|
1724
|
+
function SettingsLoading() {
|
|
1725
|
+
return createElement('div', { className: 'dsh-my-observability-settings' }, createElement('div', { className: 'dsh-my-observability-settings-status' }, strings.loading()));
|
|
1726
|
+
}
|
|
1727
|
+
/** 配置区块:区块标题 + 两个配置行(AI 审查开关 / 超时)。 */
|
|
1728
|
+
function SettingsConfigSection({ draft, patch, }) {
|
|
1729
|
+
return createElement('div', { className: 'dsh-my-observability-settings-section' }, createElement('div', { className: 'dsh-my-observability-settings-section-title' }, strings.settingsSectionTitle()), createElement(SettingsToggleRow, {
|
|
1730
|
+
label: strings.settingsAiReviewLabel(),
|
|
1731
|
+
hint: strings.settingsAiReviewHint(),
|
|
1732
|
+
on: draft.aiReview !== false,
|
|
1733
|
+
onChange: (value) => patch('aiReview', value),
|
|
1734
|
+
}), createElement(SettingsNumberRow, {
|
|
1735
|
+
label: strings.settingsAiTimeoutLabel(),
|
|
1736
|
+
hint: strings.settingsAiTimeoutHint(),
|
|
1737
|
+
value: draft.aiTimeoutMs,
|
|
1738
|
+
onChange: (value) => patch('aiTimeoutMs', value),
|
|
1739
|
+
}));
|
|
1740
|
+
}
|
|
1741
|
+
/** 保存区块:保存按钮 + 成功/失败提示(互斥,保存失败绝不显示"已保存")。 */
|
|
1742
|
+
function SettingsActions({ save, saved, saveError, }) {
|
|
1743
|
+
return createElement('div', { className: 'dsh-my-observability-settings-actions' }, createElement('button', { className: 'dsh-my-observability-settings-btn', onClick: save }, strings.settingsSave()), saved ? createElement('span', { className: 'dsh-my-observability-settings-saved' }, strings.settingsSaved()) : null, saveError
|
|
1744
|
+
? createElement('span', { className: 'dsh-my-observability-settings-error' }, strings.settingsSaveFailed())
|
|
1745
|
+
: null);
|
|
1746
|
+
}
|
|
1747
|
+
/** 设置页视图:GET 回填 → 编辑 → 保存(各区块拆成子组件,控制单函数长度)。 */
|
|
1748
|
+
function ObservabilitySettingsView() {
|
|
1749
|
+
const [draft, setDraft] = useState(null);
|
|
1750
|
+
const [loading, setLoading] = useState(true);
|
|
1751
|
+
const [failed, setFailed] = useState(false);
|
|
1752
|
+
const [saved, setSaved] = useState(false);
|
|
1753
|
+
const [saveError, setSaveError] = useState(false);
|
|
1754
|
+
const load = () => {
|
|
1755
|
+
setLoading(true);
|
|
1756
|
+
setSaved(false);
|
|
1757
|
+
setSaveError(false);
|
|
1758
|
+
apiJson(SETTINGS_API)
|
|
1759
|
+
.then((value) => {
|
|
1760
|
+
setDraft(value);
|
|
1761
|
+
setLoading(false);
|
|
1762
|
+
})
|
|
1763
|
+
.catch(() => {
|
|
1764
|
+
setLoading(false);
|
|
1765
|
+
setFailed(true);
|
|
1766
|
+
});
|
|
1767
|
+
};
|
|
1768
|
+
useEffect(() => {
|
|
1769
|
+
load();
|
|
1770
|
+
}, []);
|
|
1771
|
+
if (loading)
|
|
1772
|
+
return createElement(SettingsLoading, null);
|
|
1773
|
+
if (draft === null)
|
|
1774
|
+
return createElement(SettingsLoadError, { onRetry: load });
|
|
1775
|
+
const patch = (key, value) => setDraft({ ...draft, [key]: value });
|
|
1776
|
+
return createElement('div', { className: 'dsh-my-observability-settings' }, createElement(SettingsConfigSection, { draft, patch }), createElement(SettingsActions, {
|
|
1777
|
+
save: () => saveSettings(draft, setSaved, setSaveError),
|
|
1778
|
+
saved,
|
|
1779
|
+
saveError,
|
|
1780
|
+
}));
|
|
1781
|
+
}
|
|
1782
|
+
/** 附加设置页签:slots 服务缺失(精简上下文 / 宿主未提供)时静默跳过。 */
|
|
1783
|
+
function attachObservabilitySettingsTab(ctx) {
|
|
1784
|
+
// strict=false:首屏时 slots 提供者 fiber 可能尚未 active,strict 模式的
|
|
1785
|
+
// ctx.get 返回 undefined → 页签静默消失(要等 HMR 才出现)。取到实例即可,
|
|
1786
|
+
// 注册本身由 slots.inject 等待槽位声明。
|
|
1787
|
+
const slots = typeof ctx.get === 'function' ? ctx.get('slots', false) : undefined;
|
|
1788
|
+
if (slots === undefined || slots === null)
|
|
1789
|
+
return;
|
|
1790
|
+
ctx.effect(() => {
|
|
1791
|
+
if (typeof document === 'undefined' || typeof document.head === 'undefined')
|
|
1792
|
+
return () => { };
|
|
1793
|
+
const style = document.createElement('style');
|
|
1794
|
+
style.setAttribute('data-dsh-my-observability-settings', 'styles');
|
|
1795
|
+
style.textContent = SETTINGS_STYLES;
|
|
1796
|
+
document.head.appendChild(style);
|
|
1797
|
+
return () => {
|
|
1798
|
+
if (style.parentNode !== null)
|
|
1799
|
+
style.parentNode.removeChild(style);
|
|
1800
|
+
};
|
|
1801
|
+
}, 'dsh-my-observability: settings styles');
|
|
1802
|
+
ctx.effect(() => slots.inject('settings.plugins.tab', () => slots.register({
|
|
1803
|
+
name: 'settings.plugins.tab',
|
|
1804
|
+
// id 必须全局唯一(list 型槽位的 tab key):复用别人的 id 会顶掉
|
|
1805
|
+
// 对方那一格(宿主契约:fresh id 追加在已有条目旁)。
|
|
1806
|
+
id: 'dsh-my-observability-settings',
|
|
1807
|
+
order: 92,
|
|
1808
|
+
label: () => strings.settingsTitle(),
|
|
1809
|
+
}, ObservabilitySettingsView)), 'dsh-my-observability: settings tab registration');
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1608
1812
|
"use strict";
|
|
1609
1813
|
// ── 样式(DSH 语义 token,随 activation 注入 / teardown 卸载)──────
|
|
1610
1814
|
// 前缀 dsh-my-observability-(issue #54:与 dsh-my-guard 前缀分离,消除跨插件类名冲突)。
|
|
@@ -1783,8 +1987,10 @@ function injectStyles() {
|
|
|
1783
1987
|
// 样式先注入:不依赖任何服务(服务缺失/时序未就绪也不影响面板配色)。
|
|
1784
1988
|
ctx.effect(() => injectStyles(), 'dsh-my-observability: styles')
|
|
1785
1989
|
|
|
1786
|
-
|
|
1787
|
-
|
|
1990
|
+
// 设置页签(issue #383):只依赖 slots(strict=false 读取),与侧边栏面板所需的 sidebarRightTabs 无关,故先于判空注册。
|
|
1991
|
+
attachObservabilitySettingsTab(ctx)
|
|
1992
|
+
|
|
1993
|
+
const { sidebarRightTabs: tabs, slots } = ctx
|
|
1788
1994
|
// 判空必须同时覆盖 null 与 undefined(typeof null 是 object,会骗过 === undefined)。
|
|
1789
1995
|
if (tabs == null || slots == null) return
|
|
1790
1996
|
|
package/lib/client.src.js
CHANGED
|
@@ -37,8 +37,7 @@ window.__ModuleLoader__.load({
|
|
|
37
37
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' })
|
|
38
38
|
const { createElement, useEffect, useState } = require('react')
|
|
39
39
|
|
|
40
|
-
// 原生页签身份(issue #187 批 1):id/kind
|
|
41
|
-
// (每个面板一个类型,id 全局唯一且是席位的 key),order = 迁移前的指南页顺序。
|
|
40
|
+
// 原生页签身份(issue #187 批 1):id/kind 沿用迁移前 better-sidebar 的 tab id(每面板一个类型,id 全局唯一且是席位 key),order 为指南页顺序。
|
|
42
41
|
const REPLAY_TAB_ID = 'dsh-my-observability:replay'
|
|
43
42
|
const GIT_TAB_ID = 'dsh-my-observability:git'
|
|
44
43
|
const REPLAY_TAB_ORDER = 40
|
|
@@ -53,6 +52,7 @@ window.__ModuleLoader__.load({
|
|
|
53
52
|
/*__PART_RESOURCE__*/
|
|
54
53
|
/*__PART_REPLAY_EXT__*/
|
|
55
54
|
/*__PART_GIT__*/
|
|
55
|
+
/*__PART_SETTINGS__*/
|
|
56
56
|
/*__PART_STYLES__*/
|
|
57
57
|
|
|
58
58
|
// ── 插件体:样式注入 + 两个原生页签注册 ─────────────────────────────
|
|
@@ -73,8 +73,10 @@ window.__ModuleLoader__.load({
|
|
|
73
73
|
// 样式先注入:不依赖任何服务(服务缺失/时序未就绪也不影响面板配色)。
|
|
74
74
|
ctx.effect(() => injectStyles(), 'dsh-my-observability: styles')
|
|
75
75
|
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
// 设置页签(issue #383):只依赖 slots(strict=false 读取),与侧边栏面板所需的 sidebarRightTabs 无关,故先于判空注册。
|
|
77
|
+
attachObservabilitySettingsTab(ctx)
|
|
78
|
+
|
|
79
|
+
const { sidebarRightTabs: tabs, slots } = ctx
|
|
78
80
|
// 判空必须同时覆盖 null 与 undefined(typeof null 是 object,会骗过 === undefined)。
|
|
79
81
|
if (tabs == null || slots == null) return
|
|
80
82
|
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-my-observability — 设置页配置端点(GET/PUT /observability/api/config,issue #383)。
|
|
3
|
+
*
|
|
4
|
+
* 设置 → 插件 → 可观测性 面板只暴露 README 已记录的 `aiReview` /
|
|
5
|
+
* `aiTimeoutMs` 两项;其余字段(aiProvider / aiModel / aiCwd /
|
|
6
|
+
* resourceIntervalMs / resourceLimits)继续由用户在 cordis.patch.yml 手写,
|
|
7
|
+
* 设置页保存不得连带覆盖它们。
|
|
8
|
+
*
|
|
9
|
+
* 写回复用 dsh-shared 的配置持久化原语(currentProfile / patchFileOf /
|
|
10
|
+
* writePatchConfig),并**先合并该条目已有键**:writePatchConfig 的语义是
|
|
11
|
+
* 「删除同 id 旧条目 → 追加新条目」,只写两项会把用户手写的其余配置一起删掉
|
|
12
|
+
* (数据破坏,issue #383 验收项「不写坏配置」)。
|
|
13
|
+
*/
|
|
14
|
+
import { readFile } from 'node:fs/promises';
|
|
15
|
+
import { currentProfile, extractConfig, patchFileOf, readJsonBody, writeJson, writePatchConfig } from 'dsh-shared';
|
|
16
|
+
/** aiTimeoutMs 默认值(与 src/index.ts buildOptions 的兜底共用同一口径)。 */
|
|
17
|
+
const DEFAULT_AI_TIMEOUT_MS = 60000;
|
|
18
|
+
/**
|
|
19
|
+
* 写回行 id:必须与 plugins/dsh-my-observability/cordis.patch.yml 里的插件行
|
|
20
|
+
* id 一致——loader 按行 id 匹配配置,id 不符会新增孤儿行、原行配置不变。
|
|
21
|
+
*/
|
|
22
|
+
const CONFIG_ROW_ID = 'observability';
|
|
23
|
+
/** aiTimeoutMs 规整:非数字 / 非有限 / 非正 → 回退默认(绝不把 0/NaN 写进 patch)。
|
|
24
|
+
* 导出供 src/index.ts 的 buildOptions 复用,保证「默认值口径」只有一处定义。
|
|
25
|
+
* (其余仅本文件内部使用的符号不导出:knip 会把无外部引用的导出判为 dead code。) */
|
|
26
|
+
export function normalizeTimeout(value) {
|
|
27
|
+
return typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : DEFAULT_AI_TIMEOUT_MS;
|
|
28
|
+
}
|
|
29
|
+
/** 当前生效配置(GET 的 value,也是设置页表单的回填源)。 */
|
|
30
|
+
function configValueOf(options) {
|
|
31
|
+
return { aiReview: options.aiReview !== false, aiTimeoutMs: normalizeTimeout(options.aiTimeoutMs) };
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* 校验 + 规整 PUT payload:非对象 → undefined(调用方回 400)。
|
|
35
|
+
*
|
|
36
|
+
* 非法**字段**只忽略该字段、不整单拒绝:设置页一次提交两项,因一个字段非法
|
|
37
|
+
* 而丢掉另一项合法修改,用户会误以为保存无效(issue #383 验收项「非法值回退
|
|
38
|
+
* 默认」);aiTimeoutMs 非法时回退默认而非保留旧值,与 README 记录的默认口径
|
|
39
|
+
* 一致。
|
|
40
|
+
*/
|
|
41
|
+
function normalizeConfigPatch(payload, current) {
|
|
42
|
+
if (payload === null || typeof payload !== 'object' || Array.isArray(payload))
|
|
43
|
+
return undefined;
|
|
44
|
+
const raw = payload;
|
|
45
|
+
const next = { aiReview: current.aiReview, aiTimeoutMs: current.aiTimeoutMs };
|
|
46
|
+
if (typeof raw.aiReview === 'boolean')
|
|
47
|
+
next.aiReview = raw.aiReview;
|
|
48
|
+
if (raw.aiTimeoutMs !== undefined)
|
|
49
|
+
next.aiTimeoutMs = normalizeTimeout(raw.aiTimeoutMs);
|
|
50
|
+
return next;
|
|
51
|
+
}
|
|
52
|
+
/** 读取 patch 文件中该行已有的 config 块(文件不存在 / 解析失败 → 空对象)。 */
|
|
53
|
+
async function readExistingConfig(file) {
|
|
54
|
+
try {
|
|
55
|
+
return extractConfig(await readFile(file, 'utf8'), CONFIG_ROW_ID) ?? {};
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return {};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/** 写回 profile 层 patch 文件:合并已有键后整体重写(保留用户手写的其他配置)。 */
|
|
62
|
+
export async function persistSettingsConfig(config) {
|
|
63
|
+
const file = patchFileOf(currentProfile());
|
|
64
|
+
const existing = await readExistingConfig(file);
|
|
65
|
+
await writePatchConfig(file, CONFIG_ROW_ID, { ...existing, ...config });
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* 分派配置端点:GET 返回当前生效值;PUT 校验 → 写回 + 即时生效。
|
|
69
|
+
* 未匹配(含其他动词)返回 false,交由调用方回 404(保持既有路由契约)。
|
|
70
|
+
*/
|
|
71
|
+
export async function dispatchConfigRoutes(method, request, response, options, onChange) {
|
|
72
|
+
if (method !== 'config')
|
|
73
|
+
return false;
|
|
74
|
+
if (request.method === 'GET') {
|
|
75
|
+
writeJson(response, 200, { ok: true, value: configValueOf(options) });
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
if (request.method !== 'PUT')
|
|
79
|
+
return false;
|
|
80
|
+
const next = normalizeConfigPatch(await readJsonBody(request), configValueOf(options));
|
|
81
|
+
if (next === undefined) {
|
|
82
|
+
writeJson(response, 400, { ok: false, error: { message: 'invalid config' } });
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
await onChange(next);
|
|
86
|
+
writeJson(response, 200, { ok: true, value: next });
|
|
87
|
+
return true;
|
|
88
|
+
}
|
package/lib/index.js
CHANGED
|
@@ -22,17 +22,42 @@
|
|
|
22
22
|
import { createStore } from './store.js';
|
|
23
23
|
import { attachAuditListeners } from './audit.js';
|
|
24
24
|
import { registerObservabilityRoutes } from './routes.js';
|
|
25
|
+
import { persistSettingsConfig, normalizeTimeout } from './config-routes.js';
|
|
25
26
|
import { createResourceMonitor } from './resource-monitor.js';
|
|
26
27
|
export const name = 'dsh-my-observability';
|
|
27
28
|
export const inject = ['webServer'];
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
/** 从配置构建 ObservabilityOptions。 */
|
|
30
|
+
function buildOptions(config) {
|
|
31
|
+
return {
|
|
32
|
+
aiProvider: config?.aiProvider,
|
|
33
|
+
aiModel: config?.aiModel,
|
|
34
|
+
aiCwd: config?.aiCwd,
|
|
31
35
|
aiReview: config?.aiReview !== false,
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
: 60000,
|
|
36
|
+
// 与设置页保存共用同一默认值口径(config-routes.normalizeTimeout)。
|
|
37
|
+
aiTimeoutMs: normalizeTimeout(config?.aiTimeoutMs),
|
|
35
38
|
};
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* 设置页保存回调(issue #383):**先写回 profile patch 文件、再更新内存**——
|
|
42
|
+
* 写盘失败时保持内存旧值(内存与磁盘不劈叉),并把失败如实抛给路由层;
|
|
43
|
+
* DSH 的 watchUserPatches 会热重载 patch 文件,保存即生效。
|
|
44
|
+
*/
|
|
45
|
+
function settingsChangeHandler(ctx, options) {
|
|
46
|
+
return async (next) => {
|
|
47
|
+
try {
|
|
48
|
+
await persistSettingsConfig(next);
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
ctx.logger.warn(`[dsh-my-observability] 配置保存失败(操作=config/save,原因=${error instanceof Error ? error.message : String(error)})`);
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
Object.assign(options, next);
|
|
55
|
+
ctx.logger.info?.(`[dsh-my-observability] 设置已保存(aiReview=${next.aiReview}, aiTimeoutMs=${next.aiTimeoutMs})`);
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
export function apply(ctx, config) {
|
|
59
|
+
// ── 配置(应用层 config 覆盖,默认全部开启)─────────────────────────
|
|
60
|
+
const options = buildOptions(config);
|
|
36
61
|
// ── 审计存储:会话隔离 + 持久化 + 重启恢复 ──────────────────────────
|
|
37
62
|
const store = createStore(ctx);
|
|
38
63
|
// ── 事件监听(只读观察;waterfall 一律透传 next())──────────────────
|
|
@@ -52,8 +77,8 @@ export function apply(ctx, config) {
|
|
|
52
77
|
},
|
|
53
78
|
});
|
|
54
79
|
monitor.start();
|
|
55
|
-
// ── 路由(查询 / git 工具 / diff 审查 /
|
|
56
|
-
registerObservabilityRoutes(ctx, store, monitor, options);
|
|
80
|
+
// ── 路由(查询 / git 工具 / diff 审查 / 资源 / 设置页配置)──────────
|
|
81
|
+
registerObservabilityRoutes(ctx, store, monitor, options, settingsChangeHandler(ctx, options));
|
|
57
82
|
// ── 卸载冲刷:清防抖定时器 + 立即落盘 + 停采样 ──────────────────────
|
|
58
83
|
ctx.effect(() => {
|
|
59
84
|
monitor.stop();
|
package/lib/routes.js
CHANGED
|
@@ -9,14 +9,16 @@
|
|
|
9
9
|
* - GET /git/diff?repo=&staged= — 差异文本
|
|
10
10
|
* - POST /git/commit — 类型化提交(Conventional Commits)
|
|
11
11
|
* - POST /review — 增量 diff 审查(规则引擎 + 可选 AI)
|
|
12
|
+
* - GET/PUT /config — 设置页配置读写(aiReview / aiTimeoutMs)
|
|
12
13
|
*/
|
|
13
14
|
import { isTrustedApiRequest, readJsonBody, writeJson, writeError } from 'dsh-shared';
|
|
15
|
+
import { dispatchConfigRoutes } from './config-routes.js';
|
|
14
16
|
import { gitStatus, gitDiff, gitCommit } from './git.js';
|
|
15
17
|
import { parseDiff } from './diff.js';
|
|
16
18
|
import { reviewRules } from './review.js';
|
|
17
19
|
import { runAiReview } from './ai.js';
|
|
18
20
|
/** 注册 /observability/api 路由(effect 持有 disposer)。 */
|
|
19
|
-
export function registerObservabilityRoutes(ctx, store, monitor, options) {
|
|
21
|
+
export function registerObservabilityRoutes(ctx, store, monitor, options, onConfigChange) {
|
|
20
22
|
const webRuntime = ctx.get ? ctx.get('webRuntime') : undefined;
|
|
21
23
|
const trustedHosts = webRuntime !== undefined && webRuntime !== null && Array.isArray(webRuntime.trustedHosts)
|
|
22
24
|
? webRuntime.trustedHosts
|
|
@@ -25,11 +27,11 @@ export function registerObservabilityRoutes(ctx, store, monitor, options) {
|
|
|
25
27
|
ctx.effect(() => ctx.webServer.register({
|
|
26
28
|
kind: 'prefix',
|
|
27
29
|
path: '/observability/api',
|
|
28
|
-
handler: apiHandler(ctx, fence, store, monitor, options),
|
|
30
|
+
handler: apiHandler(ctx, fence, store, monitor, options, onConfigChange),
|
|
29
31
|
}), 'dsh-my-observability: /observability/api routes');
|
|
30
32
|
}
|
|
31
33
|
/** 统一 handler:fence → 方法分派 → 404/错误兜底。 */
|
|
32
|
-
function apiHandler(ctx, fence, store, monitor, options) {
|
|
34
|
+
function apiHandler(ctx, fence, store, monitor, options, onConfigChange) {
|
|
33
35
|
return async (request, response) => {
|
|
34
36
|
if (!fence(request)) {
|
|
35
37
|
writeJson(response, 403, { ok: false, error: { code: 'forbidden', message: 'forbidden' } });
|
|
@@ -43,7 +45,7 @@ function apiHandler(ctx, fence, store, monitor, options) {
|
|
|
43
45
|
// 状态(曾经的 CI flaky:固定 40ms sleep 赌 readFile 跑完,慢机器上
|
|
44
46
|
// 查询读到 0 条事件 → 0 !== 1)。whenReady 就绪后立即 resolve,无延迟。
|
|
45
47
|
await store.whenReady();
|
|
46
|
-
const handled = await dispatchMethod(method, request, response, url, ctx, store, monitor, options);
|
|
48
|
+
const handled = await dispatchMethod(method, request, response, url, ctx, store, monitor, options, onConfigChange);
|
|
47
49
|
if (!handled) {
|
|
48
50
|
writeJson(response, 404, {
|
|
49
51
|
ok: false,
|
|
@@ -61,11 +63,12 @@ function isMethod(method, request, name, verb) {
|
|
|
61
63
|
return method === name && request.method === verb;
|
|
62
64
|
}
|
|
63
65
|
/** 按 method 分派到具体 handler;未识别返回 false(调用方回 404)。 */
|
|
64
|
-
async function dispatchMethod(method, request, response, url, ctx, store, monitor, options) {
|
|
65
|
-
|
|
66
|
-
if (handled)
|
|
66
|
+
async function dispatchMethod(method, request, response, url, ctx, store, monitor, options, onConfigChange) {
|
|
67
|
+
if (await dispatchCore(method, request, response, url, ctx, store, monitor, options))
|
|
67
68
|
return true;
|
|
68
|
-
|
|
69
|
+
if (dispatchExtended(method, request, response, url, ctx, store))
|
|
70
|
+
return true;
|
|
71
|
+
return dispatchConfigRoutes(method, request, response, options, onConfigChange);
|
|
69
72
|
}
|
|
70
73
|
/** 核心路由(复杂度 ≤10)。 */
|
|
71
74
|
async function dispatchCore(method, request, response, url, ctx, store, monitor, options) {
|
|
@@ -128,39 +131,72 @@ function statusValue(store, options) {
|
|
|
128
131
|
gitEnabled: true,
|
|
129
132
|
};
|
|
130
133
|
}
|
|
134
|
+
/** 聚合查询默认超时(3s):超时未响应的插件标记 { running: false, error: 'timeout' }。 */
|
|
135
|
+
export const PLUGIN_STATUS_TIMEOUT_MS = 3000;
|
|
136
|
+
/** 超时哨兵(区分「超时」与「监听方返回值恰好是 undefined」)。 */
|
|
137
|
+
const STATUS_TIMEOUT = Symbol('plugin-status-timeout');
|
|
131
138
|
/**
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
139
|
+
* 读取插件清单(机会性:服务未激活时 ctx.get 返回 undefined → 空清单)。
|
|
140
|
+
*
|
|
141
|
+
* 官方服务是 `ctx.pluginInventory`(host 半边),`list()` 是 **async**——
|
|
142
|
+
* 同步解引用 `entries` 会抛错。清单名取 `entry.moduleName`(**完整包名**),
|
|
143
|
+
* 因为监听方正是按完整包名比对自己。
|
|
135
144
|
*/
|
|
136
|
-
async function
|
|
137
|
-
const
|
|
138
|
-
|
|
145
|
+
async function readPluginNames(ctx) {
|
|
146
|
+
const service = ctx.get('pluginInventory');
|
|
147
|
+
if (service === undefined || service === null)
|
|
148
|
+
return [];
|
|
139
149
|
try {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
p.constructor?.name ??
|
|
145
|
-
'unknown';
|
|
146
|
-
try {
|
|
147
|
-
const result = await Promise.race([
|
|
148
|
-
ctx.emit('plugin:status-query', { plugin: name }),
|
|
149
|
-
new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), TIMEOUT_MS)),
|
|
150
|
-
]);
|
|
151
|
-
return result ?? { plugin: name, running: true, config: {}, lastActions: [] };
|
|
152
|
-
}
|
|
153
|
-
catch {
|
|
154
|
-
return { plugin: name, running: false, error: 'timeout' };
|
|
155
|
-
}
|
|
156
|
-
}));
|
|
157
|
-
for (const s of statuses) {
|
|
158
|
-
results.push(s.status === 'fulfilled' ? s.value : { plugin: 'unknown', running: false, error: 'rejected' });
|
|
159
|
-
}
|
|
150
|
+
const snapshot = await service.list();
|
|
151
|
+
return (snapshot?.entries ?? [])
|
|
152
|
+
.map((entry) => entry.moduleName)
|
|
153
|
+
.filter((name) => typeof name === 'string' && name !== '');
|
|
160
154
|
}
|
|
161
155
|
catch {
|
|
162
|
-
|
|
156
|
+
return [];
|
|
163
157
|
}
|
|
158
|
+
}
|
|
159
|
+
/** 从分发结果挑出匹配本插件的 value(`ok === true` 且 `value.plugin` 同名)。 */
|
|
160
|
+
function pickStatusValue(collected, name) {
|
|
161
|
+
for (const item of Array.isArray(collected) ? collected : [collected]) {
|
|
162
|
+
const response = item;
|
|
163
|
+
if (response?.ok === true && response.value?.plugin === name)
|
|
164
|
+
return response.value;
|
|
165
|
+
}
|
|
166
|
+
return undefined;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* 单插件状态查询:用 `ctx.serial` 收集监听方返回值。
|
|
170
|
+
*
|
|
171
|
+
* cordis 三种派发对返回值的处理各不相同(本机 @deepseek-ai/cordis@4.0.2 实测):
|
|
172
|
+
* `emit` 同步派发**不收集**;`parallel` 只处理异常、**不返回结果**(Promise<void>);
|
|
173
|
+
* 只有 `serial` 顺序 await 并返回首个非 null/false/undefined 的返回值。逐插件
|
|
174
|
+
* 查询时非匹配监听方一律 `return undefined`,因此 serial 恰好命中唯一那一条。
|
|
175
|
+
* 无匹配响应一律如实标记(`no-response`),**绝不编造 running: true**。
|
|
176
|
+
*/
|
|
177
|
+
async function queryPluginStatus(ctx, name, timeoutMs) {
|
|
178
|
+
// 期望态包装成永不 reject:超时后已丢弃的分发不得变成 unhandled rejection。
|
|
179
|
+
const dispatch = Promise.resolve()
|
|
180
|
+
.then(() => ctx.serial('plugin:status-query', { plugin: name }))
|
|
181
|
+
.then((value) => value, () => undefined);
|
|
182
|
+
const timer = new Promise((resolve) => {
|
|
183
|
+
const handle = setTimeout(() => resolve(STATUS_TIMEOUT), timeoutMs);
|
|
184
|
+
handle.unref?.();
|
|
185
|
+
});
|
|
186
|
+
const outcome = await Promise.race([dispatch, timer]);
|
|
187
|
+
if (outcome === STATUS_TIMEOUT)
|
|
188
|
+
return { plugin: name, running: false, error: 'timeout' };
|
|
189
|
+
return pickStatusValue(outcome, name) ?? { plugin: name, running: false, error: 'no-response' };
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* 插件状态聚合:按插件清单逐个查询 `plugin:status-query`,汇总监听方真实状态。
|
|
193
|
+
* 每条为监听方返回的 { plugin, config, running, stats?, lastActions? };超时 3s
|
|
194
|
+
* 未响应标 { running: false, error: 'timeout' },无匹配响应标 no-response。
|
|
195
|
+
* 清单不可用(服务未激活)时返回空列表 `{ ok: true, value: [] }`。
|
|
196
|
+
*/
|
|
197
|
+
export async function handlePluginStatus(ctx, response, timeoutMs = PLUGIN_STATUS_TIMEOUT_MS) {
|
|
198
|
+
const names = await readPluginNames(ctx);
|
|
199
|
+
const results = await Promise.all(names.map((name) => queryPluginStatus(ctx, name, timeoutMs)));
|
|
164
200
|
writeJson(response, 200, { ok: true, value: results });
|
|
165
201
|
}
|
|
166
202
|
/** 错误事件查询(#155 错误上报统一):返回 plugin_error 类型事件。 */
|
|
@@ -235,16 +271,15 @@ async function aiOutcome(ctx, payload, options, diffText, report) {
|
|
|
235
271
|
return { enabled: false };
|
|
236
272
|
if (payload.aiReview === false)
|
|
237
273
|
return { enabled: false };
|
|
238
|
-
return runAiReview(ctx, diffText, report, options.aiTimeoutMs
|
|
274
|
+
return runAiReview(ctx, diffText, report, options.aiTimeoutMs, {
|
|
275
|
+
provider: options.aiProvider,
|
|
276
|
+
model: options.aiModel,
|
|
277
|
+
cwd: options.aiCwd,
|
|
278
|
+
});
|
|
239
279
|
}
|
|
240
280
|
// ── HTTP helpers ───────────────────────────────────────────────────────────
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
}
|
|
244
|
-
function repoOf(url) {
|
|
245
|
-
const repo = url.searchParams.get('repo');
|
|
246
|
-
return typeof repo === 'string' ? repo : '';
|
|
247
|
-
}
|
|
281
|
+
const queryOf = (url, name) => url.searchParams.get(name) ?? '';
|
|
282
|
+
const repoOf = (url) => url.searchParams.get('repo') ?? '';
|
|
248
283
|
function limitOf(url) {
|
|
249
284
|
const raw = url.searchParams.get('limit');
|
|
250
285
|
const parsed = raw === null ? 0 : Number(raw);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-my-observability",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
4
4
|
"description": "DSH 可观测性 + Git 工程工具插件:会话轨迹回放(时间轴)、事件审计(agent 行为记录)、结构化 Git 提交(Conventional Commits)、增量 diff 审查(提交前审查)。DSH web plugin: trajectory replay timeline, event audit log, structured git commits, pre-commit incremental diff review.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"scripts": {
|
|
15
15
|
"build": "npx tsc -p tsconfig.json && node scripts/build.mjs",
|
|
16
16
|
"typecheck": "npx tsc --noEmit -p tsconfig.json && npx tsc --noEmit -p tsconfig.client.json",
|
|
17
|
-
"test": "npx vitest run --coverage && cucumber-js test/features --import
|
|
17
|
+
"test": "npx vitest run --coverage && cucumber-js test/features --import \"test/features/steps/*.mjs\""
|
|
18
18
|
},
|
|
19
19
|
"exports": {
|
|
20
20
|
".": {
|
|
@@ -42,9 +42,9 @@
|
|
|
42
42
|
}
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
|
45
|
-
"cordis": "^4.0.0-rc.
|
|
46
|
-
"
|
|
47
|
-
"
|
|
45
|
+
"cordis": "^4.0.0-rc.10",
|
|
46
|
+
"dsh-shared": "^0.1.4",
|
|
47
|
+
"react": "^18.2.0 || ^19.3.0"
|
|
48
48
|
},
|
|
49
49
|
"peerDependenciesMeta": {
|
|
50
50
|
"cordis": {
|
|
@@ -58,5 +58,8 @@
|
|
|
58
58
|
"@types/node": "^26.5.1",
|
|
59
59
|
"typescript": "^7.0.2"
|
|
60
60
|
},
|
|
61
|
-
"license": "MIT"
|
|
61
|
+
"license": "MIT",
|
|
62
|
+
"dependencies": {
|
|
63
|
+
"tmp": "^0.2.7"
|
|
64
|
+
}
|
|
62
65
|
}
|