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 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) => createElement('option', { key: s.sessionId, value: s.sessionId }, s.sessionId)),
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) apiJson('/observability/api/resources').then(setResource).catch(() => {})
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, { label: strings.resourceRate(), value: `${fmtResourceBytes(resource.writeRateBytesPerHour)}/h` }),
1089
- createElement(ResourceMetric, { label: strings.resourceCpu(), value: `${Math.round(resource.cpuPercent ?? 0)}%` }),
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) apiJson('/observability/api/resources').then(setResource).catch(() => {})
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'),
@@ -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) => createElement('option', { key: s.sessionId, value: s.sessionId }, s.sessionId)),
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.5",
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",