dsh-my-observability 0.1.0 → 0.1.2
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 +17 -0
- package/README.md +23 -23
- package/lib/ai.js +13 -24
- package/lib/audit.js +11 -5
- package/lib/client.js +1091 -535
- package/lib/client.src.js +26 -16
- package/lib/diff.js +1 -2
- package/lib/git.js +17 -12
- package/lib/parts/git.js +281 -223
- package/lib/parts/i18n.js +72 -66
- package/lib/parts/replay.js +299 -172
- package/lib/parts/styles.js +111 -59
- package/lib/review.js +6 -2
- package/lib/routes.js +22 -33
- package/lib/store.js +17 -23
- package/package.json +3 -2
- package/lib/fence.js +0 -60
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
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
|
+
## [0.1.2] - 2026-09-01
|
|
6
|
+
|
|
7
|
+
### 变更
|
|
8
|
+
|
|
9
|
+
- fix(ui): 9 个插件未定义 token danger-primary 改用 error-primary(DSH 主题仅定义 business/error/success/warn)
|
|
10
|
+
|
|
11
|
+
## [0.1.1] - 2026-08-28
|
|
12
|
+
|
|
13
|
+
### 变更
|
|
14
|
+
|
|
15
|
+
- feat(ui): dsh-my-observability 时间轴翻新——类型图标/连线节点/前缀(issue #54)
|
|
16
|
+
- fix(security): 修复 CodeQL 告警——正则 ReDoS/随机数/命令注入(issue #5/#6/#7/#9/#10)
|
|
17
|
+
- refactor(shared): 抽取 dsh-shared 共享工具包,10 个插件迁移消除重复实现(issue #45)
|
|
18
|
+
- chore(deps): 升级 react 19 兼容性——13 个插件 peer 声明 ^18.2.0 || ^19.2.0(issue #49)
|
|
19
|
+
- style(format): 全仓 prettier 格式化(issue #44)
|
|
20
|
+
|
|
5
21
|
## [0.1.0] - 2026-08-28
|
|
6
22
|
|
|
7
23
|
### 新增
|
|
@@ -15,6 +31,7 @@
|
|
|
15
31
|
- **测试**:`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%。
|
|
16
32
|
|
|
17
33
|
**真实环境验证**(独立端口 3081 隔离实例):
|
|
34
|
+
|
|
18
35
|
- 侧边栏「轨迹回放」页签出现,时间轴展示真实 agent 行为(状态/模型流/工具调用);
|
|
19
36
|
- 「Git 工具」页签:真实仓库状态/差异/类型化提交/提交前审查全流程可用;
|
|
20
37
|
- 重启验证实例后审计事件恢复。
|
package/README.md
CHANGED
|
@@ -15,12 +15,12 @@
|
|
|
15
15
|
|
|
16
16
|
Server 端只读观察 DSH 生命周期事件并记录审计日志:
|
|
17
17
|
|
|
18
|
-
| 事件
|
|
19
|
-
|
|
20
|
-
| `agent/status`
|
|
21
|
-
| `llm/stream`
|
|
22
|
-
| `tools/pre-execute` | `tool_call`
|
|
23
|
-
| `tools/execute`
|
|
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
24
|
|
|
25
25
|
- **会话隔离**:事件按会话分桶,切换会话互不串扰;
|
|
26
26
|
- **重启恢复**:持久化到 `$DSH_HOME/observability/audit.json`(防抖 + 原子写),重启后完整恢复;
|
|
@@ -43,16 +43,16 @@ Server 端只读观察 DSH 生命周期事件并记录审计日志:
|
|
|
43
43
|
|
|
44
44
|
「提交前审查」按钮对增量 diff 运行规则引擎(确定性、可测试):
|
|
45
45
|
|
|
46
|
-
| 规则
|
|
47
|
-
|
|
48
|
-
| `secret-leak`
|
|
49
|
-
| `conflict-marker` | error
|
|
50
|
-
| `debug-statement` | warning
|
|
51
|
-
| `large-diff`
|
|
52
|
-
| `binary-file`
|
|
53
|
-
| `todo-marker`
|
|
54
|
-
| `trailing-space`
|
|
55
|
-
| `no-test-change`
|
|
46
|
+
| 规则 | 严重级别 | 检查内容 |
|
|
47
|
+
| ----------------- | -------- | ----------------------------------------------------- |
|
|
48
|
+
| `secret-leak` | error | 密钥/凭据硬编码(password/api_key/secret/token) |
|
|
49
|
+
| `conflict-marker` | error | 合并冲突标记残留(`<<<<<<<` / `=======` / `>>>>>>>`) |
|
|
50
|
+
| `debug-statement` | warning | 调试残留(console.* / debugger / print 族) |
|
|
51
|
+
| `large-diff` | warning | 单文件变更 > 300 行 |
|
|
52
|
+
| `binary-file` | warning | 二进制文件变更 |
|
|
53
|
+
| `todo-marker` | info | TODO/FIXME/HACK 标记 |
|
|
54
|
+
| `trailing-space` | info | 行尾多余空格 |
|
|
55
|
+
| `no-test-change` | info | 有源码变更但无测试变更 |
|
|
56
56
|
|
|
57
57
|
- **可选 AI 审查**:配置 `aiReview`(默认开)且 agents 服务可用时,创建独立审查 agent 阅读 diff 输出总评(verdict/summary/topIssues);超时/失败/解析失败自动降级,规则引擎结果不受影响(AI 是增强,不是门禁)。
|
|
58
58
|
|
|
@@ -82,17 +82,17 @@ dsh plugin --profile web add link:<仓库路径>/plugins/dsh-my-observability
|
|
|
82
82
|
- insert:
|
|
83
83
|
- id: observability
|
|
84
84
|
name: 'dsh-my-observability'
|
|
85
|
-
- config:
|
|
86
|
-
aiReview: true
|
|
87
|
-
aiTimeoutMs: 60000
|
|
85
|
+
- config: # 传给 apply(ctx, config)
|
|
86
|
+
aiReview: true # 增量 diff 审查启用 AI 增强(默认 true;agents 服务不可用时自动降级)
|
|
87
|
+
aiTimeoutMs: 60000 # AI 审查超时(毫秒,默认 60000)
|
|
88
88
|
```
|
|
89
89
|
|
|
90
90
|
## 依赖
|
|
91
91
|
|
|
92
|
-
| 依赖
|
|
93
|
-
|
|
94
|
-
| `cordis` | 插件运行时
|
|
95
|
-
| `react`
|
|
92
|
+
| 依赖 | 用途 | 可选 |
|
|
93
|
+
| -------- | ------------- | -------------- |
|
|
94
|
+
| `cordis` | 插件运行时 | 是(宿主提供) |
|
|
95
|
+
| `react` | client 端组件 | 是(宿主提供) |
|
|
96
96
|
|
|
97
97
|
## 限制与说明
|
|
98
98
|
|
package/lib/ai.js
CHANGED
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
* 参考 dsh-task-reliability 的校验 agent 模式(agents.create + whenIdle
|
|
11
11
|
* 超时 + dispose)。
|
|
12
12
|
*/
|
|
13
|
+
import { randomUUID } from 'node:crypto'
|
|
14
|
+
import { withTimeout, userMessage } from 'dsh-shared'
|
|
13
15
|
import { REVIEW_TIMEOUT_MS } from './constants.js'
|
|
14
16
|
|
|
15
17
|
/** diff 送入 prompt 的最大长度(截断防 token 爆炸)。 */
|
|
@@ -19,7 +21,9 @@ const AI_DIFF_MAX = 8000
|
|
|
19
21
|
export async function runAiReview(ctx, diffText, report, timeoutMs = REVIEW_TIMEOUT_MS) {
|
|
20
22
|
const agents = agentsServiceOf(ctx)
|
|
21
23
|
if (agents === undefined) return { enabled: true, failed: true, note: 'agents service unavailable' }
|
|
22
|
-
|
|
24
|
+
// CodeQL js/insecure-randomness 修复:sessionId 随机段改用 crypto.randomUUID()
|
|
25
|
+
// (密码学安全随机,无需种子),保持 `obs-review-<ts>-<6位hex>` 格式兼容。
|
|
26
|
+
const sessionId = `obs-review-${Date.now()}-${randomUUID().slice(0, 6)}`
|
|
23
27
|
let handle
|
|
24
28
|
try {
|
|
25
29
|
handle = await agents.create({
|
|
@@ -33,7 +37,11 @@ export async function runAiReview(ctx, diffText, report, timeoutMs = REVIEW_TIME
|
|
|
33
37
|
if (parsed === undefined) return { enabled: true, failed: true, note: 'AI 结论解析失败' }
|
|
34
38
|
return { enabled: true, ...parsed }
|
|
35
39
|
} catch (error) {
|
|
36
|
-
return {
|
|
40
|
+
return {
|
|
41
|
+
enabled: true,
|
|
42
|
+
failed: true,
|
|
43
|
+
note: error instanceof Error ? error.message : String(error),
|
|
44
|
+
}
|
|
37
45
|
} finally {
|
|
38
46
|
await disposeHandle(handle)
|
|
39
47
|
}
|
|
@@ -119,27 +127,6 @@ function extractJsonBlock(text) {
|
|
|
119
127
|
return match !== null ? match[1].trim() : undefined
|
|
120
128
|
}
|
|
121
129
|
|
|
122
|
-
// ── helpers(与 dsh-task-reliability 同契约的轻量实现)────────────────────
|
|
123
|
-
|
|
124
|
-
function withTimeout(promise, ms) {
|
|
125
|
-
return new Promise((resolve) => {
|
|
126
|
-
const timer = setTimeout(() => resolve(undefined), ms)
|
|
127
|
-
Promise.resolve(promise).then(
|
|
128
|
-
(value) => { clearTimeout(timer); resolve(value) },
|
|
129
|
-
() => { clearTimeout(timer); resolve(undefined) },
|
|
130
|
-
)
|
|
131
|
-
})
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function userMessage(text) {
|
|
135
|
-
return {
|
|
136
|
-
id: `msg-${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`,
|
|
137
|
-
role: 'user',
|
|
138
|
-
content: [{ type: 'text', text }],
|
|
139
|
-
source: { kind: 'user' },
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
130
|
function lastAssistantText(session) {
|
|
144
131
|
try {
|
|
145
132
|
const events = session?.events
|
|
@@ -159,7 +146,9 @@ function assistantTextOf(event) {
|
|
|
159
146
|
const blocks = event.data?.message?.content
|
|
160
147
|
if (!Array.isArray(blocks)) return ''
|
|
161
148
|
return blocks
|
|
162
|
-
.filter(
|
|
149
|
+
.filter(
|
|
150
|
+
(block) => block !== null && typeof block === 'object' && block.type === 'text' && typeof block.text === 'string',
|
|
151
|
+
)
|
|
163
152
|
.map((block) => block.text)
|
|
164
153
|
.join('\n')
|
|
165
154
|
}
|
package/lib/audit.js
CHANGED
|
@@ -80,9 +80,7 @@ function agentTypeOf(agent) {
|
|
|
80
80
|
if (header === undefined || header === null) return 'unknown'
|
|
81
81
|
if (header.origin === 'subagent') return 'subagent'
|
|
82
82
|
if (typeof header.delegationDepth === 'number' && header.delegationDepth > 0) return 'subagent'
|
|
83
|
-
return typeof agent.options?.subagentDepth === 'number' && agent.options.subagentDepth > 0
|
|
84
|
-
? 'subagent'
|
|
85
|
-
: 'top'
|
|
83
|
+
return typeof agent.options?.subagentDepth === 'number' && agent.options.subagentDepth > 0 ? 'subagent' : 'top'
|
|
86
84
|
}
|
|
87
85
|
|
|
88
86
|
/** 工具结果 ok 判定:非对象视为成功;error 字段非空视为失败。 */
|
|
@@ -131,9 +129,17 @@ function wrapStream(sessionId, stream, record) {
|
|
|
131
129
|
}
|
|
132
130
|
yield chunk
|
|
133
131
|
}
|
|
134
|
-
record({
|
|
132
|
+
record({
|
|
133
|
+
type: 'llm_stream',
|
|
134
|
+
sessionId,
|
|
135
|
+
data: streamSummary('end', startedAt, chunks, chars),
|
|
136
|
+
})
|
|
135
137
|
} catch (error) {
|
|
136
|
-
record({
|
|
138
|
+
record({
|
|
139
|
+
type: 'llm_stream',
|
|
140
|
+
sessionId,
|
|
141
|
+
data: streamSummary('error', startedAt, chunks, chars, error),
|
|
142
|
+
})
|
|
137
143
|
throw error
|
|
138
144
|
}
|
|
139
145
|
})()
|