dsh-my-observability 0.1.5 → 0.1.6
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 +6 -0
- package/lib/audit.js +38 -0
- package/lib/client.js +34 -5
- package/lib/parts/i18n.js +3 -0
- package/lib/parts/replay.js +15 -1
- package/lib/store.js +12 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
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.6] - 2026-09-04
|
|
6
|
+
|
|
7
|
+
### 变更
|
|
8
|
+
|
|
9
|
+
- fix(observability): 轨迹回放会话下拉可读标题(首条用户消息)——此前只展示 UUID 清单
|
|
10
|
+
|
|
5
11
|
## [0.1.5] - 2026-09-04
|
|
6
12
|
|
|
7
13
|
### 变更
|
package/lib/audit.js
CHANGED
|
@@ -17,6 +17,7 @@ import { MAX_ARG_KEYS, MAX_TEXT_LEN } from './constants.js'
|
|
|
17
17
|
/** 注册全部审计监听;返回 disposer 数组(全部经 ctx.on 注册)。 */
|
|
18
18
|
export function attachAuditListeners(ctx, record) {
|
|
19
19
|
return [
|
|
20
|
+
ctx.on('session/event', (session, event) => handleSessionEvent(session, event, record)),
|
|
20
21
|
ctx.on('agent/status', (payload) => handleStatus(payload, record)),
|
|
21
22
|
ctx.on('llm/stream', (options, next) => handleStream(options, next, record)),
|
|
22
23
|
ctx.on('tools/pre-execute', (exec, next) => handlePreExecute(exec, next, record)),
|
|
@@ -24,6 +25,43 @@ export function attachAuditListeners(ctx, record) {
|
|
|
24
25
|
]
|
|
25
26
|
}
|
|
26
27
|
|
|
28
|
+
/**
|
|
29
|
+
* session/event → user_message 事件(会话标题来源)。
|
|
30
|
+
* 轨迹回放面板需要"对话可读标题"而非 UUID:从每个会话真实用户的首条
|
|
31
|
+
* 消息截断生成(跳过插件注入消息),面板 sessionsOf 取最早一条作为
|
|
32
|
+
* title。不作为独立存储字段,走现有事件通路,重启后自然恢复。
|
|
33
|
+
*/
|
|
34
|
+
function handleSessionEvent(session, event, record) {
|
|
35
|
+
if (event === null || typeof event !== 'object' || event.type !== 'user/message') return
|
|
36
|
+
const message = event.data
|
|
37
|
+
if (isPluginMessage(message)) return
|
|
38
|
+
const sessionId = session?.id
|
|
39
|
+
if (typeof sessionId !== 'string' || sessionId === '') return
|
|
40
|
+
const text = userTextOf(message)
|
|
41
|
+
if (text === '') return
|
|
42
|
+
record({ type: 'user_message', sessionId, data: { text: truncate(text) } })
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** 是否为插件注入的消息(非真实用户输入,不作为标题)。 */
|
|
46
|
+
function isPluginMessage(message) {
|
|
47
|
+
const source = message?.source
|
|
48
|
+
return source !== null && typeof source === 'object' && source.kind === 'plugin'
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** 从 user message 提取文本(content 中全部 text block 拼接)。 */
|
|
52
|
+
function userTextOf(message) {
|
|
53
|
+
if (message === null || typeof message !== 'object') return ''
|
|
54
|
+
const content = message.content
|
|
55
|
+
if (!Array.isArray(content)) return ''
|
|
56
|
+
const parts = []
|
|
57
|
+
for (const block of content) {
|
|
58
|
+
if (block !== null && typeof block === 'object' && block.type === 'text' && typeof block.text === 'string') {
|
|
59
|
+
parts.push(block.text)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return parts.join(' ').trim()
|
|
63
|
+
}
|
|
64
|
+
|
|
27
65
|
/** agent/status → agent_status 事件(含顶层/子代理标记)。 */
|
|
28
66
|
function handleStatus(payload, record) {
|
|
29
67
|
const agent = payload?.agent
|
package/lib/client.js
CHANGED
|
@@ -50,6 +50,9 @@ const strings = {
|
|
|
50
50
|
resourceMem: () => (isZh() ? '内存' : 'Memory'),
|
|
51
51
|
gitTitle: () => (isZh() ? 'Git 工具' : 'Git Tools'),
|
|
52
52
|
allSessions: () => (isZh() ? '全部会话' : 'All sessions'),
|
|
53
|
+
// ── 会话下拉可读性(issue #1xx:只能看到 UUID)────────────────────────
|
|
54
|
+
eventCount: (n) => (isZh() ? `${n} 事件` : `${n} events`),
|
|
55
|
+
sessionFallback: (shortId) => (isZh() ? `会话 ${shortId}` : `session ${shortId}`),
|
|
53
56
|
filterAll: () => (isZh() ? '全部' : 'All'),
|
|
54
57
|
filterStatus: () => (isZh() ? '状态' : 'Status'),
|
|
55
58
|
filterLlm: () => (isZh() ? '模型流' : 'LLM'),
|
|
@@ -907,6 +910,18 @@ async function loadReplayData(selected, currentSession, setters) {
|
|
|
907
910
|
}
|
|
908
911
|
}
|
|
909
912
|
|
|
913
|
+
/** 下拉选项文案:可读标题(首条用户消息)优先,无标题回退 UUID 短显;
|
|
914
|
+
* 附加事件数与时间,用户一眼看出"哪个对话"。 */
|
|
915
|
+
function sessionOptionLabel(s) {
|
|
916
|
+
const title = typeof s.title === 'string' && s.title !== '' ? s.title : strings.sessionFallback(shortId(s.sessionId))
|
|
917
|
+
return `${title} · ${strings.eventCount(s.count)}`
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
/** 会话 id 短显示(UUID 取前 8 位)。 */
|
|
921
|
+
function shortId(sessionId) {
|
|
922
|
+
return typeof sessionId === 'string' && sessionId.length > 8 ? `${sessionId.slice(0, 8)}…` : sessionId || ''
|
|
923
|
+
}
|
|
924
|
+
|
|
910
925
|
/** 工具栏:会话选择 + 手动刷新 + 类型过滤。 */
|
|
911
926
|
function ReplayToolbar({ sessions, selected, onSelect, filter, onFilter, onRefresh }) {
|
|
912
927
|
return createElement(
|
|
@@ -925,7 +940,9 @@ function ReplayToolbar({ sessions, selected, onSelect, filter, onFilter, onRefre
|
|
|
925
940
|
},
|
|
926
941
|
sessions.length === 0
|
|
927
942
|
? createElement('option', { value: '' }, strings.allSessions())
|
|
928
|
-
: sessions.map((s) =>
|
|
943
|
+
: sessions.map((s) =>
|
|
944
|
+
createElement('option', { key: s.sessionId, value: s.sessionId }, sessionOptionLabel(s)),
|
|
945
|
+
),
|
|
929
946
|
),
|
|
930
947
|
createElement(
|
|
931
948
|
'button',
|
|
@@ -1050,7 +1067,10 @@ function useResourceState(visible) {
|
|
|
1050
1067
|
if (!visible) return undefined
|
|
1051
1068
|
let alive = true
|
|
1052
1069
|
const tick = () => {
|
|
1053
|
-
if (alive)
|
|
1070
|
+
if (alive)
|
|
1071
|
+
apiJson('/observability/api/resources')
|
|
1072
|
+
.then(setResource)
|
|
1073
|
+
.catch(() => {})
|
|
1054
1074
|
}
|
|
1055
1075
|
tick()
|
|
1056
1076
|
const timer = setInterval(tick, RESOURCE_POLL_MS)
|
|
@@ -1085,8 +1105,14 @@ function ResourcePanel({ resource }) {
|
|
|
1085
1105
|
'div',
|
|
1086
1106
|
{ className: 'dsh-my-observability-resource-grid' },
|
|
1087
1107
|
createElement(ResourceMetric, { label: strings.resourceFile(), value: fmtResourceBytes(resource.fileBytes) }),
|
|
1088
|
-
createElement(ResourceMetric, {
|
|
1089
|
-
|
|
1108
|
+
createElement(ResourceMetric, {
|
|
1109
|
+
label: strings.resourceRate(),
|
|
1110
|
+
value: `${fmtResourceBytes(resource.writeRateBytesPerHour)}/h`,
|
|
1111
|
+
}),
|
|
1112
|
+
createElement(ResourceMetric, {
|
|
1113
|
+
label: strings.resourceCpu(),
|
|
1114
|
+
value: `${Math.round(resource.cpuPercent ?? 0)}%`,
|
|
1115
|
+
}),
|
|
1090
1116
|
createElement(ResourceMetric, { label: strings.resourceMem(), value: fmtResourceBytes(resource.memoryBytes) }),
|
|
1091
1117
|
),
|
|
1092
1118
|
alerts.length > 0
|
|
@@ -1386,7 +1412,10 @@ function useReplayDataState(props) {
|
|
|
1386
1412
|
if (!visible) return undefined
|
|
1387
1413
|
let alive = true
|
|
1388
1414
|
const tick = () => {
|
|
1389
|
-
if (alive)
|
|
1415
|
+
if (alive)
|
|
1416
|
+
apiJson('/observability/api/resources')
|
|
1417
|
+
.then(setResource)
|
|
1418
|
+
.catch(() => {})
|
|
1390
1419
|
}
|
|
1391
1420
|
tick()
|
|
1392
1421
|
const timer = setInterval(tick, RESOURCE_POLL_MS)
|
package/lib/parts/i18n.js
CHANGED
|
@@ -18,6 +18,9 @@ const strings = {
|
|
|
18
18
|
resourceMem: () => (isZh() ? '内存' : 'Memory'),
|
|
19
19
|
gitTitle: () => (isZh() ? 'Git 工具' : 'Git Tools'),
|
|
20
20
|
allSessions: () => (isZh() ? '全部会话' : 'All sessions'),
|
|
21
|
+
// ── 会话下拉可读性(issue #1xx:只能看到 UUID)────────────────────────
|
|
22
|
+
eventCount: (n) => (isZh() ? `${n} 事件` : `${n} events`),
|
|
23
|
+
sessionFallback: (shortId) => (isZh() ? `会话 ${shortId}` : `session ${shortId}`),
|
|
21
24
|
filterAll: () => (isZh() ? '全部' : 'All'),
|
|
22
25
|
filterStatus: () => (isZh() ? '状态' : 'Status'),
|
|
23
26
|
filterLlm: () => (isZh() ? '模型流' : 'LLM'),
|
package/lib/parts/replay.js
CHANGED
|
@@ -194,6 +194,18 @@ async function loadReplayData(selected, currentSession, setters) {
|
|
|
194
194
|
}
|
|
195
195
|
}
|
|
196
196
|
|
|
197
|
+
/** 下拉选项文案:可读标题(首条用户消息)优先,无标题回退 UUID 短显;
|
|
198
|
+
* 附加事件数与时间,用户一眼看出"哪个对话"。 */
|
|
199
|
+
function sessionOptionLabel(s) {
|
|
200
|
+
const title = typeof s.title === 'string' && s.title !== '' ? s.title : strings.sessionFallback(shortId(s.sessionId))
|
|
201
|
+
return `${title} · ${strings.eventCount(s.count)}`
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** 会话 id 短显示(UUID 取前 8 位)。 */
|
|
205
|
+
function shortId(sessionId) {
|
|
206
|
+
return typeof sessionId === 'string' && sessionId.length > 8 ? `${sessionId.slice(0, 8)}…` : sessionId || ''
|
|
207
|
+
}
|
|
208
|
+
|
|
197
209
|
/** 工具栏:会话选择 + 手动刷新 + 类型过滤。 */
|
|
198
210
|
function ReplayToolbar({ sessions, selected, onSelect, filter, onFilter, onRefresh }) {
|
|
199
211
|
return createElement(
|
|
@@ -212,7 +224,9 @@ function ReplayToolbar({ sessions, selected, onSelect, filter, onFilter, onRefre
|
|
|
212
224
|
},
|
|
213
225
|
sessions.length === 0
|
|
214
226
|
? createElement('option', { value: '' }, strings.allSessions())
|
|
215
|
-
: sessions.map((s) =>
|
|
227
|
+
: sessions.map((s) =>
|
|
228
|
+
createElement('option', { key: s.sessionId, value: s.sessionId }, sessionOptionLabel(s)),
|
|
229
|
+
),
|
|
216
230
|
),
|
|
217
231
|
createElement(
|
|
218
232
|
'button',
|
package/lib/store.js
CHANGED
|
@@ -134,7 +134,7 @@ function eventsOf(handle, sessionId, type, limit) {
|
|
|
134
134
|
return capped.map((event) => ({ ...event }))
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
-
/**
|
|
137
|
+
/** 有审计事件的会话列表(按最后活动时间倒序,含事件数与可读标题)。 */
|
|
138
138
|
function sessionsOf(handle) {
|
|
139
139
|
const entries = Object.entries(handle.store.state.bySession)
|
|
140
140
|
const list = entries
|
|
@@ -142,12 +142,23 @@ function sessionsOf(handle) {
|
|
|
142
142
|
sessionId,
|
|
143
143
|
count: bucket.events.length,
|
|
144
144
|
lastTime: bucket.events.length > 0 ? bucket.events[bucket.events.length - 1].time : 0,
|
|
145
|
+
title: sessionTitleOf(bucket),
|
|
145
146
|
}))
|
|
146
147
|
.filter((entry) => entry.count > 0)
|
|
147
148
|
list.sort((a, b) => b.lastTime - a.lastTime)
|
|
148
149
|
return list
|
|
149
150
|
}
|
|
150
151
|
|
|
152
|
+
/** 会话标题:最早一条 user_message 事件的文本(无则空串,面板回退 UUID 短显)。 */
|
|
153
|
+
function sessionTitleOf(bucket) {
|
|
154
|
+
for (const event of bucket.events) {
|
|
155
|
+
if (event.type === 'user_message' && typeof event.data?.text === 'string' && event.data.text !== '') {
|
|
156
|
+
return event.data.text
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return ''
|
|
160
|
+
}
|
|
161
|
+
|
|
151
162
|
/** 全部会话事件总数(O(1) 计数)。 */
|
|
152
163
|
function countOf(handle) {
|
|
153
164
|
return handle.total
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-my-observability",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
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",
|