dsh-my-observability 0.1.6 → 0.1.7
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 +8 -0
- package/lib/ai.js +6 -1
- package/lib/audit-view.js +18 -1
- package/lib/audit.js +59 -0
- package/lib/client.js +85 -5
- package/lib/parts/i18n.js +3 -0
- package/lib/parts/replay.js +55 -4
- package/lib/parts/styles.js +9 -0
- package/lib/store.js +4 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
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.7] - 2026-09-06
|
|
6
|
+
|
|
7
|
+
### 变更
|
|
8
|
+
|
|
9
|
+
- fix(task-reliability,observability): #165 Session.events 迁移——snapshotEvents 优先 + 旧 API 兜底 (#166)
|
|
10
|
+
- feat(observability,task-reliability): #154 插件关键行为纳入事件审计(干预/ask 决策/救场/verify/恢复)+ 文件体积控制 (#158)
|
|
11
|
+
- fix(observability): #142 dispose 落盘链可等待,修复 release 门禁 flaky 测试 (#148)
|
|
12
|
+
|
|
5
13
|
## [0.1.6] - 2026-09-04
|
|
6
14
|
|
|
7
15
|
### 变更
|
package/lib/ai.js
CHANGED
|
@@ -127,9 +127,14 @@ function extractJsonBlock(text) {
|
|
|
127
127
|
return match !== null ? match[1].trim() : undefined
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
+
/** 会话事件快照:新 API snapshotEvents() 优先,旧 API events 兜底(issue #165)。 */
|
|
131
|
+
function sessionEvents(session) {
|
|
132
|
+
return session?.snapshotEvents?.() ?? session?.events
|
|
133
|
+
}
|
|
134
|
+
|
|
130
135
|
function lastAssistantText(session) {
|
|
131
136
|
try {
|
|
132
|
-
const events = session
|
|
137
|
+
const events = sessionEvents(session)
|
|
133
138
|
if (!Array.isArray(events)) return ''
|
|
134
139
|
for (let i = events.length - 1; i >= 0; i--) {
|
|
135
140
|
const text = assistantTextOf(events[i])
|
package/lib/audit-view.js
CHANGED
|
@@ -75,12 +75,28 @@ function toolResultParts(data) {
|
|
|
75
75
|
return parts
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
/** 插件事件(issue #154)的搜索片段(插件名/事件名/动作/原因/参数值)。 */
|
|
79
|
+
function pluginEventParts(data) {
|
|
80
|
+
const parts = []
|
|
81
|
+
if (typeof data.plugin === 'string') parts.push(data.plugin)
|
|
82
|
+
if (typeof data.event === 'string') parts.push(data.event)
|
|
83
|
+
if (typeof data.action === 'string') parts.push(data.action)
|
|
84
|
+
if (typeof data.reason === 'string') parts.push(data.reason)
|
|
85
|
+
if (data.params !== null && typeof data.params === 'object') {
|
|
86
|
+
for (const value of Object.values(data.params)) {
|
|
87
|
+
if (typeof value === 'string' && value !== '') parts.push(value)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return parts
|
|
91
|
+
}
|
|
92
|
+
|
|
78
93
|
/** 事件类型 → 搜索片段收集函数(查表消分支)。 */
|
|
79
94
|
const PARTS_COLLECTORS = {
|
|
80
95
|
agent_status: agentStatusParts,
|
|
81
96
|
llm_stream: llmParts,
|
|
82
97
|
tool_call: toolCallParts,
|
|
83
98
|
tool_result: toolResultParts,
|
|
99
|
+
plugin_event: pluginEventParts,
|
|
84
100
|
}
|
|
85
101
|
|
|
86
102
|
/** 提取事件可用于关键词匹配的文本(工具名/参数摘要/错误信息/状态/阶段等)。 */
|
|
@@ -119,10 +135,11 @@ function normalizeCriteria(criteria) {
|
|
|
119
135
|
return { type: criteria.type ?? '', keyword: criteria.keyword ?? '', result: criteria.result ?? '', start, end }
|
|
120
136
|
}
|
|
121
137
|
|
|
122
|
-
/** 类型过滤('tool' 表示 tool_call + tool_result)。 */
|
|
138
|
+
/** 类型过滤('tool' 表示 tool_call + tool_result;'plugin' 表示 plugin_event)。 */
|
|
123
139
|
function passType(type, filterType) {
|
|
124
140
|
if (filterType === '') return true
|
|
125
141
|
if (filterType === 'tool') return type === 'tool_call' || type === 'tool_result'
|
|
142
|
+
if (filterType === 'plugin') return type === 'plugin_event'
|
|
126
143
|
return type === filterType
|
|
127
144
|
}
|
|
128
145
|
|
package/lib/audit.js
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
* waterfall,包装流透传全部 chunk)
|
|
8
8
|
* - `tools/pre-execute` → `tool_call`(工具调用开始 + 参数摘要;透传 next)
|
|
9
9
|
* - `tools/execute` → `tool_result`(工具结果 ok/失败 + 耗时;透传 next)
|
|
10
|
+
* - 插件事件(issue #154)→ `plugin_event`(插件关键行为:干预/ask 决策/
|
|
11
|
+
* 救场/校验/恢复,含插件名/事件名/动作/原因/参数)
|
|
10
12
|
*
|
|
11
13
|
* ⚠️ llm/stream 监听器必须保持同步函数:cordis waterfall 不 await listener
|
|
12
14
|
* 返回值,next() 同步返回流;async listener 会让消费方(vision-toolkit 等
|
|
@@ -14,6 +16,19 @@
|
|
|
14
16
|
*/
|
|
15
17
|
import { MAX_ARG_KEYS, MAX_TEXT_LEN } from './constants.js'
|
|
16
18
|
|
|
19
|
+
/**
|
|
20
|
+
* 采集的插件事件名(按插件前缀显式注册,不依赖 cordis 通配符行为)。
|
|
21
|
+
* 与 dsh-task-reliability/lib/emit.js 的 PLUGIN_EVENTS 保持一致;
|
|
22
|
+
* 未来推广到其他插件(dsh-my-guardian 等)在此追加。
|
|
23
|
+
*/
|
|
24
|
+
const PLUGIN_EVENT_NAMES = [
|
|
25
|
+
'task-reliability/intervention',
|
|
26
|
+
'task-reliability/ask-decision',
|
|
27
|
+
'task-reliability/rescue',
|
|
28
|
+
'task-reliability/verify',
|
|
29
|
+
'task-reliability/resume',
|
|
30
|
+
]
|
|
31
|
+
|
|
17
32
|
/** 注册全部审计监听;返回 disposer 数组(全部经 ctx.on 注册)。 */
|
|
18
33
|
export function attachAuditListeners(ctx, record) {
|
|
19
34
|
return [
|
|
@@ -22,9 +37,53 @@ export function attachAuditListeners(ctx, record) {
|
|
|
22
37
|
ctx.on('llm/stream', (options, next) => handleStream(options, next, record)),
|
|
23
38
|
ctx.on('tools/pre-execute', (exec, next) => handlePreExecute(exec, next, record)),
|
|
24
39
|
ctx.on('tools/execute', (exec, next) => handleExecute(exec, next, record)),
|
|
40
|
+
...PLUGIN_EVENT_NAMES.map((name) => ctx.on(name, (payload) => handlePluginEvent(name, payload, record))),
|
|
25
41
|
]
|
|
26
42
|
}
|
|
27
43
|
|
|
44
|
+
/** 插件事件名 → 插件名(前缀补 dsh-,如 task-reliability → dsh-task-reliability)。 */
|
|
45
|
+
function pluginNameOf(name) {
|
|
46
|
+
const prefix = name.split('/')[0]
|
|
47
|
+
return prefix === '' ? name : `dsh-${prefix}`
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** 插件事件名 → 事件名(去掉前缀,如 task-reliability/intervention → intervention)。 */
|
|
51
|
+
function eventNameOf(name) {
|
|
52
|
+
const parts = name.split('/')
|
|
53
|
+
return parts.length > 1 ? parts.slice(1).join('/') : name
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** 插件事件 payload → 参数摘要(除 sessionId/action/reason 外的字段;字符串截断防膨胀)。 */
|
|
57
|
+
function summarizeParams(payload) {
|
|
58
|
+
const params = {}
|
|
59
|
+
for (const [key, value] of Object.entries(payload ?? {})) {
|
|
60
|
+
if (key === 'sessionId' || key === 'action' || key === 'reason') continue
|
|
61
|
+
if (typeof value === 'string') params[key] = truncate(value)
|
|
62
|
+
else if (typeof value === 'number' || typeof value === 'boolean') params[key] = value
|
|
63
|
+
}
|
|
64
|
+
return params
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 插件事件 → plugin_event 审计事件(与现有事件同时间线/同过滤/同导出)。
|
|
69
|
+
* 无 sessionId 的插件事件不记录(与 agent_status 等一致)。
|
|
70
|
+
*/
|
|
71
|
+
function handlePluginEvent(name, payload, record) {
|
|
72
|
+
const sessionId = payload?.sessionId
|
|
73
|
+
if (typeof sessionId !== 'string' || sessionId === '') return
|
|
74
|
+
record({
|
|
75
|
+
type: 'plugin_event',
|
|
76
|
+
sessionId,
|
|
77
|
+
data: {
|
|
78
|
+
plugin: pluginNameOf(name),
|
|
79
|
+
event: eventNameOf(name),
|
|
80
|
+
action: truncate(String(payload?.action ?? '')),
|
|
81
|
+
reason: truncate(String(payload?.reason ?? '')),
|
|
82
|
+
params: summarizeParams(payload),
|
|
83
|
+
},
|
|
84
|
+
})
|
|
85
|
+
}
|
|
86
|
+
|
|
28
87
|
/**
|
|
29
88
|
* session/event → user_message 事件(会话标题来源)。
|
|
30
89
|
* 轨迹回放面板需要"对话可读标题"而非 UUID:从每个会话真实用户的首条
|
package/lib/client.js
CHANGED
|
@@ -57,6 +57,9 @@ const strings = {
|
|
|
57
57
|
filterStatus: () => (isZh() ? '状态' : 'Status'),
|
|
58
58
|
filterLlm: () => (isZh() ? '模型流' : 'LLM'),
|
|
59
59
|
filterTools: () => (isZh() ? '工具' : 'Tools'),
|
|
60
|
+
filterPlugin: () => (isZh() ? '插件' : 'Plugins'),
|
|
61
|
+
typePluginEvent: () => (isZh() ? '插件事件' : 'plugin event'),
|
|
62
|
+
detailReason: () => (isZh() ? '原因' : 'reason'),
|
|
60
63
|
emptyEvents: () => (isZh() ? '暂无审计事件' : 'No audit events yet'),
|
|
61
64
|
emptyEventsHint: () =>
|
|
62
65
|
isZh()
|
|
@@ -534,12 +537,28 @@ function toolResultParts(data) {
|
|
|
534
537
|
return parts
|
|
535
538
|
}
|
|
536
539
|
|
|
540
|
+
/** 插件事件(issue #154)的搜索片段(插件名/事件名/动作/原因/参数值)。 */
|
|
541
|
+
function pluginEventParts(data) {
|
|
542
|
+
const parts = []
|
|
543
|
+
if (typeof data.plugin === 'string') parts.push(data.plugin)
|
|
544
|
+
if (typeof data.event === 'string') parts.push(data.event)
|
|
545
|
+
if (typeof data.action === 'string') parts.push(data.action)
|
|
546
|
+
if (typeof data.reason === 'string') parts.push(data.reason)
|
|
547
|
+
if (data.params !== null && typeof data.params === 'object') {
|
|
548
|
+
for (const value of Object.values(data.params)) {
|
|
549
|
+
if (typeof value === 'string' && value !== '') parts.push(value)
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
return parts
|
|
553
|
+
}
|
|
554
|
+
|
|
537
555
|
/** 事件类型 → 搜索片段收集函数(查表消分支)。 */
|
|
538
556
|
const PARTS_COLLECTORS = {
|
|
539
557
|
agent_status: agentStatusParts,
|
|
540
558
|
llm_stream: llmParts,
|
|
541
559
|
tool_call: toolCallParts,
|
|
542
560
|
tool_result: toolResultParts,
|
|
561
|
+
plugin_event: pluginEventParts,
|
|
543
562
|
}
|
|
544
563
|
|
|
545
564
|
/** 提取事件可用于关键词匹配的文本(工具名/参数摘要/错误信息/状态/阶段等)。 */
|
|
@@ -578,10 +597,11 @@ function normalizeCriteria(criteria) {
|
|
|
578
597
|
return { type: criteria.type ?? '', keyword: criteria.keyword ?? '', result: criteria.result ?? '', start, end }
|
|
579
598
|
}
|
|
580
599
|
|
|
581
|
-
/** 类型过滤('tool' 表示 tool_call + tool_result)。 */
|
|
600
|
+
/** 类型过滤('tool' 表示 tool_call + tool_result;'plugin' 表示 plugin_event)。 */
|
|
582
601
|
function passType(type, filterType) {
|
|
583
602
|
if (filterType === '') return true
|
|
584
603
|
if (filterType === 'tool') return type === 'tool_call' || type === 'tool_result'
|
|
604
|
+
if (filterType === 'plugin') return type === 'plugin_event'
|
|
585
605
|
return type === filterType
|
|
586
606
|
}
|
|
587
607
|
|
|
@@ -738,17 +758,21 @@ function typeLabel(event) {
|
|
|
738
758
|
return strings.typeToolCall()
|
|
739
759
|
case 'tool_result':
|
|
740
760
|
return strings.typeToolResult()
|
|
761
|
+
case 'plugin_event':
|
|
762
|
+
return strings.typePluginEvent()
|
|
741
763
|
default:
|
|
742
764
|
return event.type
|
|
743
765
|
}
|
|
744
766
|
}
|
|
745
767
|
|
|
746
768
|
/** 事件类型 → 视觉类别(徽标/图标/节点共用,颜色语义一致):
|
|
747
|
-
* status=info / llm=warn / call=accent / result=success / fail=danger
|
|
769
|
+
* status=info / llm=warn / call=accent / result=success / fail=danger /
|
|
770
|
+
* plugin=info(插件事件复用 info 色,图标区分)。 */
|
|
748
771
|
function typeKind(event) {
|
|
749
772
|
if (event.type === 'agent_status') return 'status'
|
|
750
773
|
if (event.type === 'llm_stream') return 'llm'
|
|
751
774
|
if (event.type === 'tool_call') return 'call'
|
|
775
|
+
if (event.type === 'plugin_event') return 'plugin'
|
|
752
776
|
return event.data?.ok === false ? 'fail' : 'result'
|
|
753
777
|
}
|
|
754
778
|
|
|
@@ -758,6 +782,7 @@ function typeIcon(event) {
|
|
|
758
782
|
if (kind === 'status') return icon.clock(15)
|
|
759
783
|
if (kind === 'llm') return icon.file(15)
|
|
760
784
|
if (kind === 'call') return icon.external(15)
|
|
785
|
+
if (kind === 'plugin') return icon.alert(15)
|
|
761
786
|
if (kind === 'fail') return icon.close(15)
|
|
762
787
|
return icon.check(15)
|
|
763
788
|
}
|
|
@@ -812,6 +837,12 @@ function toolResultMeta(data) {
|
|
|
812
837
|
return `${data.name} · ${result} · ${data.ms}ms`
|
|
813
838
|
}
|
|
814
839
|
|
|
840
|
+
/** 插件事件摘要(插件名 · 事件名 · 动作;issue #154)。 */
|
|
841
|
+
function pluginEventMeta(data) {
|
|
842
|
+
const action = typeof data.action === 'string' && data.action !== '' ? data.action : data.event
|
|
843
|
+
return `${data.plugin} · ${data.event} · ${action}`
|
|
844
|
+
}
|
|
845
|
+
|
|
815
846
|
/** 事件 → 摘要文本(单行,尽力而为)。 */
|
|
816
847
|
function eventMeta(event) {
|
|
817
848
|
const data = event.data || {}
|
|
@@ -819,17 +850,55 @@ function eventMeta(event) {
|
|
|
819
850
|
if (event.type === 'llm_stream') return llmMeta(data)
|
|
820
851
|
if (event.type === 'tool_call') return toolCallMeta(data)
|
|
821
852
|
if (event.type === 'tool_result') return toolResultMeta(data)
|
|
853
|
+
if (event.type === 'plugin_event') return pluginEventMeta(data)
|
|
822
854
|
return ''
|
|
823
855
|
}
|
|
824
856
|
|
|
857
|
+
/** 插件事件详情行(原因 + 参数键值对;非插件事件返回 null 不可展开)。 */
|
|
858
|
+
function eventDetail(event) {
|
|
859
|
+
if (event?.type !== 'plugin_event') return null
|
|
860
|
+
const data = event.data || {}
|
|
861
|
+
const rows = []
|
|
862
|
+
if (typeof data.reason === 'string' && data.reason !== '') {
|
|
863
|
+
rows.push(
|
|
864
|
+
createElement(
|
|
865
|
+
'div',
|
|
866
|
+
{ key: 'reason', className: 'dsh-my-observability-detail-row' },
|
|
867
|
+
createElement('span', { className: 'dsh-my-observability-detail-key' }, strings.detailReason()),
|
|
868
|
+
createElement('span', { className: 'dsh-my-observability-detail-value' }, data.reason),
|
|
869
|
+
),
|
|
870
|
+
)
|
|
871
|
+
}
|
|
872
|
+
if (data.params !== null && typeof data.params === 'object') {
|
|
873
|
+
for (const [key, value] of Object.entries(data.params)) {
|
|
874
|
+
rows.push(
|
|
875
|
+
createElement(
|
|
876
|
+
'div',
|
|
877
|
+
{ key, className: 'dsh-my-observability-detail-row' },
|
|
878
|
+
createElement('span', { className: 'dsh-my-observability-detail-key' }, key),
|
|
879
|
+
createElement('span', { className: 'dsh-my-observability-detail-value' }, String(value)),
|
|
880
|
+
),
|
|
881
|
+
)
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
return rows.length > 0 ? rows : null
|
|
885
|
+
}
|
|
886
|
+
|
|
825
887
|
/** 单条事件行:节点圆点 + 类型图标 + 徽标/时间 + 摘要(hover/active 反馈)。
|
|
826
|
-
* 摘要命中关键词时以 mark
|
|
888
|
+
* 摘要命中关键词时以 mark 高亮;插件事件可点击展开详情(原因/参数)。 */
|
|
827
889
|
function EventRow({ event, keyword }) {
|
|
828
890
|
const meta = eventMeta(event)
|
|
829
891
|
const kind = typeKind(event)
|
|
892
|
+
const detail = eventDetail(event)
|
|
893
|
+
const [open, setOpen] = useState(false)
|
|
830
894
|
return createElement(
|
|
831
895
|
'button',
|
|
832
|
-
{
|
|
896
|
+
{
|
|
897
|
+
className: 'dsh-my-observability-event',
|
|
898
|
+
type: 'button',
|
|
899
|
+
'aria-expanded': detail !== null ? open : undefined,
|
|
900
|
+
onClick: detail !== null ? () => setOpen(!open) : undefined,
|
|
901
|
+
},
|
|
833
902
|
createElement('span', { className: `dsh-my-observability-node dsh-my-observability-node-${kind}` }),
|
|
834
903
|
createElement(
|
|
835
904
|
'span',
|
|
@@ -856,17 +925,19 @@ function EventRow({ event, keyword }) {
|
|
|
856
925
|
createElement(HighlightText, { text: meta, keyword }),
|
|
857
926
|
)
|
|
858
927
|
: null,
|
|
928
|
+
detail !== null && open ? createElement('div', { className: 'dsh-my-observability-event-detail' }, detail) : null,
|
|
859
929
|
),
|
|
860
930
|
)
|
|
861
931
|
}
|
|
862
932
|
|
|
863
|
-
/** 类型过滤按钮组(aria-pressed
|
|
933
|
+
/** 类型过滤按钮组(aria-pressed 选中态;plugin 过滤插件事件,issue #154)。 */
|
|
864
934
|
function TypeFilter({ filter, onFilter }) {
|
|
865
935
|
const options = [
|
|
866
936
|
['', strings.filterAll()],
|
|
867
937
|
['agent_status', strings.filterStatus()],
|
|
868
938
|
['llm_stream', strings.filterLlm()],
|
|
869
939
|
['tool', strings.filterTools()],
|
|
940
|
+
['plugin', strings.filterPlugin()],
|
|
870
941
|
]
|
|
871
942
|
return createElement(
|
|
872
943
|
'div',
|
|
@@ -1871,12 +1942,14 @@ const STYLES = `
|
|
|
1871
1942
|
.dsh-my-observability-node-status{border-color:var(--dsw-alias-state-info-primary)}
|
|
1872
1943
|
.dsh-my-observability-node-llm{border-color:var(--dsw-alias-state-warn-primary)}
|
|
1873
1944
|
.dsh-my-observability-node-call{border-color:var(--dsw-alias-accent)}
|
|
1945
|
+
.dsh-my-observability-node-plugin{border-color:var(--dsw-alias-state-info-primary)}
|
|
1874
1946
|
.dsh-my-observability-node-result{border-color:var(--dsw-alias-state-success-primary)}
|
|
1875
1947
|
.dsh-my-observability-node-fail{border-color:var(--dsw-alias-state-error-primary)}
|
|
1876
1948
|
.dsh-my-observability-event-icon{flex:none;display:flex;align-items:center;margin-top:1px}
|
|
1877
1949
|
.dsh-my-observability-icon-status{color:var(--dsw-alias-state-info-primary)}
|
|
1878
1950
|
.dsh-my-observability-icon-llm{color:var(--dsw-alias-state-warn-primary)}
|
|
1879
1951
|
.dsh-my-observability-icon-call{color:var(--dsw-alias-accent)}
|
|
1952
|
+
.dsh-my-observability-icon-plugin{color:var(--dsw-alias-state-info-primary)}
|
|
1880
1953
|
.dsh-my-observability-icon-result{color:var(--dsw-alias-state-success-primary)}
|
|
1881
1954
|
.dsh-my-observability-icon-fail{color:var(--dsw-alias-state-error-primary)}
|
|
1882
1955
|
.dsh-my-observability-event-body{flex:1;min-width:0;display:flex;flex-direction:column;gap:1px}
|
|
@@ -1885,10 +1958,17 @@ const STYLES = `
|
|
|
1885
1958
|
.dsh-my-observability-badge-status{color:var(--dsw-alias-state-info-primary);background:color-mix(in srgb, var(--dsw-alias-state-info-primary) 14%, transparent)}
|
|
1886
1959
|
.dsh-my-observability-badge-llm{color:var(--dsw-alias-state-warn-primary);background:color-mix(in srgb, var(--dsw-alias-state-warn-primary) 14%, transparent)}
|
|
1887
1960
|
.dsh-my-observability-badge-call{color:var(--dsw-alias-accent);background:color-mix(in srgb, var(--dsw-alias-accent) 12%, transparent)}
|
|
1961
|
+
.dsh-my-observability-badge-plugin{color:var(--dsw-alias-state-info-primary);background:color-mix(in srgb, var(--dsw-alias-state-info-primary) 14%, transparent)}
|
|
1888
1962
|
.dsh-my-observability-badge-result{color:var(--dsw-alias-state-success-primary);background:color-mix(in srgb, var(--dsw-alias-state-success-primary) 14%, transparent)}
|
|
1889
1963
|
.dsh-my-observability-badge-fail{color:var(--dsw-alias-state-error-primary);background:color-mix(in srgb, var(--dsw-alias-state-error-primary) 14%, transparent)}
|
|
1890
1964
|
.dsh-my-observability-time{flex:none;font:var(--dsw-font-xxxs-11);color:var(--dsw-alias-label-tertiary);white-space:nowrap}
|
|
1891
1965
|
.dsh-my-observability-event-meta{font:var(--dsw-font-xxs-12);color:var(--dsw-alias-label-secondary);line-height:1.6;word-break:break-word}
|
|
1966
|
+
/* ── 插件事件详情展开(issue #154:原因/参数可查)── */
|
|
1967
|
+
.dsh-my-observability-event-detail{display:flex;flex-direction:column;gap:2px;margin-top:4px;padding:6px 8px;
|
|
1968
|
+
border:1px solid var(--dsw-alias-border-l2);border-radius:6px;background:var(--dsw-alias-bg-layer-1)}
|
|
1969
|
+
.dsh-my-observability-detail-row{display:flex;gap:8px;font:var(--dsw-font-xxs-12);line-height:1.5;word-break:break-word}
|
|
1970
|
+
.dsh-my-observability-detail-key{flex:none;font:var(--dsw-font-xxxs-strong-11);color:var(--dsw-alias-label-tertiary);min-width:56px}
|
|
1971
|
+
.dsh-my-observability-detail-value{color:var(--dsw-alias-label-primary)}
|
|
1892
1972
|
/* ── 状态区:loading / 空 / 错误 ── */
|
|
1893
1973
|
.dsh-my-observability-state{display:flex;align-items:center;gap:6px;padding:8px 6px;font:var(--dsw-font-xxs-12);color:var(--dsw-alias-label-tertiary)}
|
|
1894
1974
|
.dsh-my-observability-state svg{flex:none;animation:dsh-my-observability-spin 1s linear infinite}
|
package/lib/parts/i18n.js
CHANGED
|
@@ -25,6 +25,9 @@ const strings = {
|
|
|
25
25
|
filterStatus: () => (isZh() ? '状态' : 'Status'),
|
|
26
26
|
filterLlm: () => (isZh() ? '模型流' : 'LLM'),
|
|
27
27
|
filterTools: () => (isZh() ? '工具' : 'Tools'),
|
|
28
|
+
filterPlugin: () => (isZh() ? '插件' : 'Plugins'),
|
|
29
|
+
typePluginEvent: () => (isZh() ? '插件事件' : 'plugin event'),
|
|
30
|
+
detailReason: () => (isZh() ? '原因' : 'reason'),
|
|
28
31
|
emptyEvents: () => (isZh() ? '暂无审计事件' : 'No audit events yet'),
|
|
29
32
|
emptyEventsHint: () =>
|
|
30
33
|
isZh()
|
package/lib/parts/replay.js
CHANGED
|
@@ -22,17 +22,21 @@ function typeLabel(event) {
|
|
|
22
22
|
return strings.typeToolCall()
|
|
23
23
|
case 'tool_result':
|
|
24
24
|
return strings.typeToolResult()
|
|
25
|
+
case 'plugin_event':
|
|
26
|
+
return strings.typePluginEvent()
|
|
25
27
|
default:
|
|
26
28
|
return event.type
|
|
27
29
|
}
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
/** 事件类型 → 视觉类别(徽标/图标/节点共用,颜色语义一致):
|
|
31
|
-
* status=info / llm=warn / call=accent / result=success / fail=danger
|
|
33
|
+
* status=info / llm=warn / call=accent / result=success / fail=danger /
|
|
34
|
+
* plugin=info(插件事件复用 info 色,图标区分)。 */
|
|
32
35
|
function typeKind(event) {
|
|
33
36
|
if (event.type === 'agent_status') return 'status'
|
|
34
37
|
if (event.type === 'llm_stream') return 'llm'
|
|
35
38
|
if (event.type === 'tool_call') return 'call'
|
|
39
|
+
if (event.type === 'plugin_event') return 'plugin'
|
|
36
40
|
return event.data?.ok === false ? 'fail' : 'result'
|
|
37
41
|
}
|
|
38
42
|
|
|
@@ -42,6 +46,7 @@ function typeIcon(event) {
|
|
|
42
46
|
if (kind === 'status') return icon.clock(15)
|
|
43
47
|
if (kind === 'llm') return icon.file(15)
|
|
44
48
|
if (kind === 'call') return icon.external(15)
|
|
49
|
+
if (kind === 'plugin') return icon.alert(15)
|
|
45
50
|
if (kind === 'fail') return icon.close(15)
|
|
46
51
|
return icon.check(15)
|
|
47
52
|
}
|
|
@@ -96,6 +101,12 @@ function toolResultMeta(data) {
|
|
|
96
101
|
return `${data.name} · ${result} · ${data.ms}ms`
|
|
97
102
|
}
|
|
98
103
|
|
|
104
|
+
/** 插件事件摘要(插件名 · 事件名 · 动作;issue #154)。 */
|
|
105
|
+
function pluginEventMeta(data) {
|
|
106
|
+
const action = typeof data.action === 'string' && data.action !== '' ? data.action : data.event
|
|
107
|
+
return `${data.plugin} · ${data.event} · ${action}`
|
|
108
|
+
}
|
|
109
|
+
|
|
99
110
|
/** 事件 → 摘要文本(单行,尽力而为)。 */
|
|
100
111
|
function eventMeta(event) {
|
|
101
112
|
const data = event.data || {}
|
|
@@ -103,17 +114,55 @@ function eventMeta(event) {
|
|
|
103
114
|
if (event.type === 'llm_stream') return llmMeta(data)
|
|
104
115
|
if (event.type === 'tool_call') return toolCallMeta(data)
|
|
105
116
|
if (event.type === 'tool_result') return toolResultMeta(data)
|
|
117
|
+
if (event.type === 'plugin_event') return pluginEventMeta(data)
|
|
106
118
|
return ''
|
|
107
119
|
}
|
|
108
120
|
|
|
121
|
+
/** 插件事件详情行(原因 + 参数键值对;非插件事件返回 null 不可展开)。 */
|
|
122
|
+
function eventDetail(event) {
|
|
123
|
+
if (event?.type !== 'plugin_event') return null
|
|
124
|
+
const data = event.data || {}
|
|
125
|
+
const rows = []
|
|
126
|
+
if (typeof data.reason === 'string' && data.reason !== '') {
|
|
127
|
+
rows.push(
|
|
128
|
+
createElement(
|
|
129
|
+
'div',
|
|
130
|
+
{ key: 'reason', className: 'dsh-my-observability-detail-row' },
|
|
131
|
+
createElement('span', { className: 'dsh-my-observability-detail-key' }, strings.detailReason()),
|
|
132
|
+
createElement('span', { className: 'dsh-my-observability-detail-value' }, data.reason),
|
|
133
|
+
),
|
|
134
|
+
)
|
|
135
|
+
}
|
|
136
|
+
if (data.params !== null && typeof data.params === 'object') {
|
|
137
|
+
for (const [key, value] of Object.entries(data.params)) {
|
|
138
|
+
rows.push(
|
|
139
|
+
createElement(
|
|
140
|
+
'div',
|
|
141
|
+
{ key, className: 'dsh-my-observability-detail-row' },
|
|
142
|
+
createElement('span', { className: 'dsh-my-observability-detail-key' }, key),
|
|
143
|
+
createElement('span', { className: 'dsh-my-observability-detail-value' }, String(value)),
|
|
144
|
+
),
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return rows.length > 0 ? rows : null
|
|
149
|
+
}
|
|
150
|
+
|
|
109
151
|
/** 单条事件行:节点圆点 + 类型图标 + 徽标/时间 + 摘要(hover/active 反馈)。
|
|
110
|
-
* 摘要命中关键词时以 mark
|
|
152
|
+
* 摘要命中关键词时以 mark 高亮;插件事件可点击展开详情(原因/参数)。 */
|
|
111
153
|
function EventRow({ event, keyword }) {
|
|
112
154
|
const meta = eventMeta(event)
|
|
113
155
|
const kind = typeKind(event)
|
|
156
|
+
const detail = eventDetail(event)
|
|
157
|
+
const [open, setOpen] = useState(false)
|
|
114
158
|
return createElement(
|
|
115
159
|
'button',
|
|
116
|
-
{
|
|
160
|
+
{
|
|
161
|
+
className: 'dsh-my-observability-event',
|
|
162
|
+
type: 'button',
|
|
163
|
+
'aria-expanded': detail !== null ? open : undefined,
|
|
164
|
+
onClick: detail !== null ? () => setOpen(!open) : undefined,
|
|
165
|
+
},
|
|
117
166
|
createElement('span', { className: `dsh-my-observability-node dsh-my-observability-node-${kind}` }),
|
|
118
167
|
createElement(
|
|
119
168
|
'span',
|
|
@@ -140,17 +189,19 @@ function EventRow({ event, keyword }) {
|
|
|
140
189
|
createElement(HighlightText, { text: meta, keyword }),
|
|
141
190
|
)
|
|
142
191
|
: null,
|
|
192
|
+
detail !== null && open ? createElement('div', { className: 'dsh-my-observability-event-detail' }, detail) : null,
|
|
143
193
|
),
|
|
144
194
|
)
|
|
145
195
|
}
|
|
146
196
|
|
|
147
|
-
/** 类型过滤按钮组(aria-pressed
|
|
197
|
+
/** 类型过滤按钮组(aria-pressed 选中态;plugin 过滤插件事件,issue #154)。 */
|
|
148
198
|
function TypeFilter({ filter, onFilter }) {
|
|
149
199
|
const options = [
|
|
150
200
|
['', strings.filterAll()],
|
|
151
201
|
['agent_status', strings.filterStatus()],
|
|
152
202
|
['llm_stream', strings.filterLlm()],
|
|
153
203
|
['tool', strings.filterTools()],
|
|
204
|
+
['plugin', strings.filterPlugin()],
|
|
154
205
|
]
|
|
155
206
|
return createElement(
|
|
156
207
|
'div',
|
package/lib/parts/styles.js
CHANGED
|
@@ -42,12 +42,14 @@ const STYLES = `
|
|
|
42
42
|
.dsh-my-observability-node-status{border-color:var(--dsw-alias-state-info-primary)}
|
|
43
43
|
.dsh-my-observability-node-llm{border-color:var(--dsw-alias-state-warn-primary)}
|
|
44
44
|
.dsh-my-observability-node-call{border-color:var(--dsw-alias-accent)}
|
|
45
|
+
.dsh-my-observability-node-plugin{border-color:var(--dsw-alias-state-info-primary)}
|
|
45
46
|
.dsh-my-observability-node-result{border-color:var(--dsw-alias-state-success-primary)}
|
|
46
47
|
.dsh-my-observability-node-fail{border-color:var(--dsw-alias-state-error-primary)}
|
|
47
48
|
.dsh-my-observability-event-icon{flex:none;display:flex;align-items:center;margin-top:1px}
|
|
48
49
|
.dsh-my-observability-icon-status{color:var(--dsw-alias-state-info-primary)}
|
|
49
50
|
.dsh-my-observability-icon-llm{color:var(--dsw-alias-state-warn-primary)}
|
|
50
51
|
.dsh-my-observability-icon-call{color:var(--dsw-alias-accent)}
|
|
52
|
+
.dsh-my-observability-icon-plugin{color:var(--dsw-alias-state-info-primary)}
|
|
51
53
|
.dsh-my-observability-icon-result{color:var(--dsw-alias-state-success-primary)}
|
|
52
54
|
.dsh-my-observability-icon-fail{color:var(--dsw-alias-state-error-primary)}
|
|
53
55
|
.dsh-my-observability-event-body{flex:1;min-width:0;display:flex;flex-direction:column;gap:1px}
|
|
@@ -56,10 +58,17 @@ const STYLES = `
|
|
|
56
58
|
.dsh-my-observability-badge-status{color:var(--dsw-alias-state-info-primary);background:color-mix(in srgb, var(--dsw-alias-state-info-primary) 14%, transparent)}
|
|
57
59
|
.dsh-my-observability-badge-llm{color:var(--dsw-alias-state-warn-primary);background:color-mix(in srgb, var(--dsw-alias-state-warn-primary) 14%, transparent)}
|
|
58
60
|
.dsh-my-observability-badge-call{color:var(--dsw-alias-accent);background:color-mix(in srgb, var(--dsw-alias-accent) 12%, transparent)}
|
|
61
|
+
.dsh-my-observability-badge-plugin{color:var(--dsw-alias-state-info-primary);background:color-mix(in srgb, var(--dsw-alias-state-info-primary) 14%, transparent)}
|
|
59
62
|
.dsh-my-observability-badge-result{color:var(--dsw-alias-state-success-primary);background:color-mix(in srgb, var(--dsw-alias-state-success-primary) 14%, transparent)}
|
|
60
63
|
.dsh-my-observability-badge-fail{color:var(--dsw-alias-state-error-primary);background:color-mix(in srgb, var(--dsw-alias-state-error-primary) 14%, transparent)}
|
|
61
64
|
.dsh-my-observability-time{flex:none;font:var(--dsw-font-xxxs-11);color:var(--dsw-alias-label-tertiary);white-space:nowrap}
|
|
62
65
|
.dsh-my-observability-event-meta{font:var(--dsw-font-xxs-12);color:var(--dsw-alias-label-secondary);line-height:1.6;word-break:break-word}
|
|
66
|
+
/* ── 插件事件详情展开(issue #154:原因/参数可查)── */
|
|
67
|
+
.dsh-my-observability-event-detail{display:flex;flex-direction:column;gap:2px;margin-top:4px;padding:6px 8px;
|
|
68
|
+
border:1px solid var(--dsw-alias-border-l2);border-radius:6px;background:var(--dsw-alias-bg-layer-1)}
|
|
69
|
+
.dsh-my-observability-detail-row{display:flex;gap:8px;font:var(--dsw-font-xxs-12);line-height:1.5;word-break:break-word}
|
|
70
|
+
.dsh-my-observability-detail-key{flex:none;font:var(--dsw-font-xxxs-strong-11);color:var(--dsw-alias-label-tertiary);min-width:56px}
|
|
71
|
+
.dsh-my-observability-detail-value{color:var(--dsw-alias-label-primary)}
|
|
63
72
|
/* ── 状态区:loading / 空 / 错误 ── */
|
|
64
73
|
.dsh-my-observability-state{display:flex;align-items:center;gap:6px;padding:8px 6px;font:var(--dsw-font-xxs-12);color:var(--dsw-alias-label-tertiary)}
|
|
65
74
|
.dsh-my-observability-state svg{flex:none;animation:dsh-my-observability-spin 1s linear infinite}
|
package/lib/store.js
CHANGED
|
@@ -281,7 +281,9 @@ function compactNow(handle) {
|
|
|
281
281
|
})
|
|
282
282
|
}
|
|
283
283
|
|
|
284
|
-
/** 卸载冲刷:清定时器 + 回放未就绪缓冲 + 立即落盘(迁移兜底)。
|
|
284
|
+
/** 卸载冲刷:清定时器 + 回放未就绪缓冲 + 立即落盘(迁移兜底)。
|
|
285
|
+
* 返回落盘链 promise:调用方 await 可保证冲刷完成(卸载/退出时事件
|
|
286
|
+
* 不丢);fire-and-forget 调用亦兼容(触发后异步落盘)。 */
|
|
285
287
|
function dispose(handle) {
|
|
286
288
|
if (handle.flushTimer !== null) {
|
|
287
289
|
clearTimeout(handle.flushTimer)
|
|
@@ -297,5 +299,5 @@ function dispose(handle) {
|
|
|
297
299
|
}
|
|
298
300
|
flushNow(handle)
|
|
299
301
|
if (handle.migrated || handle.queuedLines >= COMPACT_LINES) compactNow(handle)
|
|
300
|
-
|
|
302
|
+
return handle.dirtyChain
|
|
301
303
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-my-observability",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
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",
|