dsh-my-observability 0.1.6 → 0.3.0
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 +26 -0
- package/lib/ai.js +6 -1
- package/lib/audit-view.js +18 -1
- package/lib/audit.js +63 -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/resource-monitor.js +34 -3
- package/lib/routes.js +58 -0
- package/lib/store.js +4 -2
- package/package.json +2 -5
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,32 @@
|
|
|
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.3.0] - 2026-09-07
|
|
6
|
+
|
|
7
|
+
### 变更
|
|
8
|
+
|
|
9
|
+
- feat(observability): #155 错误上报统一——plugin_error 事件类型 + /errors API (#177)
|
|
10
|
+
- feat(observability): #155 资源监控扩展——新增 $DSH_HOME 目录总大小监控 (#176)
|
|
11
|
+
- feat(observability): #155 插件状态查询聚合——统一 status-query 事件 + /plugin-status API (#170)
|
|
12
|
+
- chore(plugins): #165 清理失效的 dsh.client.inject 声明(13 插件) (#167)
|
|
13
|
+
|
|
14
|
+
## [0.2.0] - 2026-09-07
|
|
15
|
+
|
|
16
|
+
### 变更
|
|
17
|
+
|
|
18
|
+
- feat(observability): #155 错误上报统一——plugin_error 事件类型 + /errors API (#177)
|
|
19
|
+
- feat(observability): #155 资源监控扩展——新增 $DSH_HOME 目录总大小监控 (#176)
|
|
20
|
+
- feat(observability): #155 插件状态查询聚合——统一 status-query 事件 + /plugin-status API (#170)
|
|
21
|
+
- chore(plugins): #165 清理失效的 dsh.client.inject 声明(13 插件) (#167)
|
|
22
|
+
|
|
23
|
+
## [0.1.7] - 2026-09-06
|
|
24
|
+
|
|
25
|
+
### 变更
|
|
26
|
+
|
|
27
|
+
- fix(task-reliability,observability): #165 Session.events 迁移——snapshotEvents 优先 + 旧 API 兜底 (#166)
|
|
28
|
+
- feat(observability,task-reliability): #154 插件关键行为纳入事件审计(干预/ask 决策/救场/verify/恢复)+ 文件体积控制 (#158)
|
|
29
|
+
- fix(observability): #142 dispose 落盘链可等待,修复 release 门禁 flaky 测试 (#148)
|
|
30
|
+
|
|
5
31
|
## [0.1.6] - 2026-09-04
|
|
6
32
|
|
|
7
33
|
### 变更
|
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,57 @@ 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
|
+
const { type, data } = buildPluginEventData(name, payload)
|
|
75
|
+
record({ type, sessionId, data })
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function buildPluginEventData(name, payload) {
|
|
79
|
+
const data = {
|
|
80
|
+
plugin: pluginNameOf(name),
|
|
81
|
+
event: eventNameOf(name),
|
|
82
|
+
action: truncate(String(payload?.action ?? '')),
|
|
83
|
+
reason: truncate(String(payload?.reason ?? '')),
|
|
84
|
+
params: summarizeParams(payload),
|
|
85
|
+
}
|
|
86
|
+
const error = typeof payload?.error === 'string' && payload.error !== '' ? truncate(payload.error) : null
|
|
87
|
+
if (error !== null) data.error = error
|
|
88
|
+
return { type: error !== null ? 'plugin_error' : 'plugin_event', data }
|
|
89
|
+
}
|
|
90
|
+
|
|
28
91
|
/**
|
|
29
92
|
* session/event → user_message 事件(会话标题来源)。
|
|
30
93
|
* 轨迹回放面板需要"对话可读标题"而非 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/resource-monitor.js
CHANGED
|
@@ -14,7 +14,9 @@
|
|
|
14
14
|
* 采样自身开销:15s 一次 process.cpuUsage/memoryUsage + fs.stat(<0.01% CPU、
|
|
15
15
|
* 零分配大对象),远低于「监控不能放大被监控对象」的护栏(resource-budget-review)。
|
|
16
16
|
*/
|
|
17
|
-
import { statSync } from 'node:fs'
|
|
17
|
+
import { statSync, readdirSync } from 'node:fs'
|
|
18
|
+
import { join } from 'node:path'
|
|
19
|
+
import { homedir } from 'node:os'
|
|
18
20
|
import { evaluateResourceAlerts, shouldEnterDegrade, shouldExitDegrade, DEFAULT_LIMITS } from './resource-rules.js'
|
|
19
21
|
import { jsonlFile } from './store-persist.js'
|
|
20
22
|
|
|
@@ -31,6 +33,7 @@ export function createResourceMonitor(ctx, options = {}) {
|
|
|
31
33
|
const state = {
|
|
32
34
|
timer: null,
|
|
33
35
|
file: jsonlFile(),
|
|
36
|
+
dshHome: process.env.DSH_HOME || join(homedir(), '.dsh'),
|
|
34
37
|
lastSample: null,
|
|
35
38
|
lastCpu: process.cpuUsage(),
|
|
36
39
|
history: [],
|
|
@@ -59,6 +62,12 @@ function sample(state, limits, onDegrade, onRecover) {
|
|
|
59
62
|
} catch {
|
|
60
63
|
// 审计文件尚未创建:字节为 0
|
|
61
64
|
}
|
|
65
|
+
let homeBytes = 0
|
|
66
|
+
try {
|
|
67
|
+
homeBytes = dshHomeSize(state.dshHome)
|
|
68
|
+
} catch {
|
|
69
|
+
// $DSH_HOME 不可达
|
|
70
|
+
}
|
|
62
71
|
const prev = state.lastSample
|
|
63
72
|
if (prev !== null) {
|
|
64
73
|
const deltaMs = Math.max(now - prev.time, 1)
|
|
@@ -66,7 +75,7 @@ function sample(state, limits, onDegrade, onRecover) {
|
|
|
66
75
|
const cpuPercent = (cpuDelta / 1000 / deltaMs) * 100
|
|
67
76
|
const byteDelta = fileBytes - prev.fileBytes
|
|
68
77
|
const writeRateBytesPerHour = byteDelta > 0 ? (byteDelta / deltaMs) * 3600 * 1000 : 0
|
|
69
|
-
const sample = { time: now, cpuPercent, memoryBytes, fileBytes, writeRateBytesPerHour }
|
|
78
|
+
const sample = { time: now, cpuPercent, memoryBytes, fileBytes, writeRateBytesPerHour, homeBytes }
|
|
70
79
|
state.history.push(sample)
|
|
71
80
|
if (state.history.length > MAX_HISTORY) state.history.splice(0, state.history.length - MAX_HISTORY)
|
|
72
81
|
state.lastSample = sample
|
|
@@ -78,7 +87,7 @@ function sample(state, limits, onDegrade, onRecover) {
|
|
|
78
87
|
degraded: state.degraded,
|
|
79
88
|
}
|
|
80
89
|
}
|
|
81
|
-
state.lastSample = { time: now, fileBytes, memoryBytes, cpuPercent: 0, writeRateBytesPerHour: 0 }
|
|
90
|
+
state.lastSample = { time: now, fileBytes, memoryBytes, cpuPercent: 0, writeRateBytesPerHour: 0, homeBytes }
|
|
82
91
|
return { ...state.lastSample, history: [...state.history], alerts: [], degraded: state.degraded }
|
|
83
92
|
}
|
|
84
93
|
|
|
@@ -116,3 +125,25 @@ function stopMonitor(state) {
|
|
|
116
125
|
state.timer = null
|
|
117
126
|
}
|
|
118
127
|
}
|
|
128
|
+
|
|
129
|
+
/** 递归计算目录总字节(best-effort,跳过不可达文件)。 */
|
|
130
|
+
function dshHomeSize(dir) {
|
|
131
|
+
let total = 0
|
|
132
|
+
try {
|
|
133
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
134
|
+
const full = join(dir, entry.name)
|
|
135
|
+
if (entry.isDirectory()) {
|
|
136
|
+
total += dshHomeSize(full)
|
|
137
|
+
} else if (entry.isFile()) {
|
|
138
|
+
try {
|
|
139
|
+
total += statSync(full).size
|
|
140
|
+
} catch {
|
|
141
|
+
// 文件不可达
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
} catch {
|
|
146
|
+
// 目录不可达
|
|
147
|
+
}
|
|
148
|
+
return total
|
|
149
|
+
}
|
package/lib/routes.js
CHANGED
|
@@ -67,6 +67,13 @@ function isMethod(method, request, name, verb) {
|
|
|
67
67
|
|
|
68
68
|
/** 按 method 分派到具体 handler;未识别返回 false(调用方回 404)。 */
|
|
69
69
|
async function dispatchMethod(method, request, response, url, ctx, store, monitor, options) {
|
|
70
|
+
const handled = await dispatchCore(method, request, response, url, ctx, store, monitor, options)
|
|
71
|
+
if (handled) return true
|
|
72
|
+
return dispatchExtended(method, request, response, url, ctx, store)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** 核心路由(复杂度 ≤10)。 */
|
|
76
|
+
async function dispatchCore(method, request, response, url, ctx, store, monitor, options) {
|
|
70
77
|
if (isMethod(method, request, 'sessions', 'GET')) {
|
|
71
78
|
writeJson(response, 200, { ok: true, value: store.sessions() })
|
|
72
79
|
return true
|
|
@@ -105,6 +112,19 @@ async function dispatchMethod(method, request, response, url, ctx, store, monito
|
|
|
105
112
|
return false
|
|
106
113
|
}
|
|
107
114
|
|
|
115
|
+
/** 扩展路由(#154/#155 新增)。 */
|
|
116
|
+
function dispatchExtended(method, request, response, url, ctx, store) {
|
|
117
|
+
if (isMethod(method, request, 'plugin-status', 'GET')) {
|
|
118
|
+
void handlePluginStatus(ctx, response)
|
|
119
|
+
return true
|
|
120
|
+
}
|
|
121
|
+
if (isMethod(method, request, 'errors', 'GET')) {
|
|
122
|
+
handleErrors(store, url, response)
|
|
123
|
+
return true
|
|
124
|
+
}
|
|
125
|
+
return false
|
|
126
|
+
}
|
|
127
|
+
|
|
108
128
|
// ── handlers ───────────────────────────────────────────────────────────────
|
|
109
129
|
|
|
110
130
|
/** 状态:审计统计 + 功能开关(aiReview 只暴露开关)。 */
|
|
@@ -117,6 +137,44 @@ function statusValue(store, options) {
|
|
|
117
137
|
}
|
|
118
138
|
}
|
|
119
139
|
|
|
140
|
+
/**
|
|
141
|
+
* 插件状态聚合:广播 plugin:status-query 事件,收集所有插件状态。
|
|
142
|
+
* 每个插件返回 { plugin, config, running, lastActions }(config 脱敏,lastActions ≤5)。
|
|
143
|
+
* 超时 3s 未响应的插件标记为 { plugin, running: false, error: 'timeout' }。
|
|
144
|
+
*/
|
|
145
|
+
async function handlePluginStatus(ctx, response) {
|
|
146
|
+
const TIMEOUT_MS = 3000
|
|
147
|
+
const results = []
|
|
148
|
+
try {
|
|
149
|
+
// 广播查询事件,带超时
|
|
150
|
+
const statuses = await Promise.allSettled(
|
|
151
|
+
(ctx.bundler?.plugins ?? []).map(async (p) => {
|
|
152
|
+
const name = p.name ?? p.constructor?.name ?? 'unknown'
|
|
153
|
+
try {
|
|
154
|
+
const result = await Promise.race([
|
|
155
|
+
ctx.emit('plugin:status-query', { plugin: name }),
|
|
156
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), TIMEOUT_MS)),
|
|
157
|
+
])
|
|
158
|
+
return result ?? { plugin: name, running: true, config: {}, lastActions: [] }
|
|
159
|
+
} catch {
|
|
160
|
+
return { plugin: name, running: false, error: 'timeout' }
|
|
161
|
+
}
|
|
162
|
+
}),
|
|
163
|
+
)
|
|
164
|
+
for (const s of statuses) {
|
|
165
|
+
results.push(s.status === 'fulfilled' ? s.value : { plugin: 'unknown', running: false, error: 'rejected' })
|
|
166
|
+
}
|
|
167
|
+
} catch {
|
|
168
|
+
// bundler 不可用时返回空列表
|
|
169
|
+
}
|
|
170
|
+
writeJson(response, 200, { ok: true, value: results })
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** 错误事件查询(#155 错误上报统一):返回 plugin_error 类型事件。 */
|
|
174
|
+
function handleErrors(store, url, response) {
|
|
175
|
+
writeJson(response, 200, { ok: true, value: store.events(null, 'plugin_error', limitOf(url)) })
|
|
176
|
+
}
|
|
177
|
+
|
|
120
178
|
/** git status:非仓库路径 400。 */
|
|
121
179
|
async function handleGitStatus(response, repoPath) {
|
|
122
180
|
if (repoPath === '') {
|
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.
|
|
3
|
+
"version": "0.3.0",
|
|
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",
|
|
@@ -37,10 +37,7 @@
|
|
|
37
37
|
"patch": "./cordis.patch.yml"
|
|
38
38
|
},
|
|
39
39
|
"client": {
|
|
40
|
-
"platform": "web"
|
|
41
|
-
"inject": [
|
|
42
|
-
"@deepseek-ai/dsh-client-runtime"
|
|
43
|
-
]
|
|
40
|
+
"platform": "web"
|
|
44
41
|
}
|
|
45
42
|
},
|
|
46
43
|
"peerDependencies": {
|