@dxos/react-ui-trace 0.11.1
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/LICENSE +105 -0
- package/README.md +3 -0
- package/dist/lib/index.mjs +2868 -0
- package/dist/lib/index.mjs.map +1 -0
- package/dist/lib/testing.mjs +338 -0
- package/dist/lib/testing.mjs.map +1 -0
- package/dist/lib/translations.mjs +19 -0
- package/dist/lib/translations.mjs.map +1 -0
- package/dist/types/src/components/Gantt/Gantt.d.ts +68 -0
- package/dist/types/src/components/Gantt/Gantt.d.ts.map +1 -0
- package/dist/types/src/components/Gantt/Gantt.stories.d.ts +30 -0
- package/dist/types/src/components/Gantt/Gantt.stories.d.ts.map +1 -0
- package/dist/types/src/components/Gantt/index.d.ts +2 -0
- package/dist/types/src/components/Gantt/index.d.ts.map +1 -0
- package/dist/types/src/components/ProcessTree/ProcessTree.d.ts +32 -0
- package/dist/types/src/components/ProcessTree/ProcessTree.d.ts.map +1 -0
- package/dist/types/src/components/ProcessTree/ProcessTree.stories.d.ts +8 -0
- package/dist/types/src/components/ProcessTree/ProcessTree.stories.d.ts.map +1 -0
- package/dist/types/src/components/ProcessTree/index.d.ts +2 -0
- package/dist/types/src/components/ProcessTree/index.d.ts.map +1 -0
- package/dist/types/src/components/Timeline/Timeline.d.ts +50 -0
- package/dist/types/src/components/Timeline/Timeline.d.ts.map +1 -0
- package/dist/types/src/components/Timeline/Timeline.stories.d.ts +24 -0
- package/dist/types/src/components/Timeline/Timeline.stories.d.ts.map +1 -0
- package/dist/types/src/components/Timeline/index.d.ts +4 -0
- package/dist/types/src/components/Timeline/index.d.ts.map +1 -0
- package/dist/types/src/components/Timeline/timeline-options.d.ts +9 -0
- package/dist/types/src/components/Timeline/timeline-options.d.ts.map +1 -0
- package/dist/types/src/components/Timeline/timeline-printer.d.ts +7 -0
- package/dist/types/src/components/Timeline/timeline-printer.d.ts.map +1 -0
- package/dist/types/src/components/Timeline/timeline-printer.test.d.ts +2 -0
- package/dist/types/src/components/Timeline/timeline-printer.test.d.ts.map +1 -0
- package/dist/types/src/components/TracePanel/TracePanel.d.ts +28 -0
- package/dist/types/src/components/TracePanel/TracePanel.d.ts.map +1 -0
- package/dist/types/src/components/TracePanel/dxn-extractor.d.ts +19 -0
- package/dist/types/src/components/TracePanel/dxn-extractor.d.ts.map +1 -0
- package/dist/types/src/components/TracePanel/dxn-extractor.test.d.ts +2 -0
- package/dist/types/src/components/TracePanel/dxn-extractor.test.d.ts.map +1 -0
- package/dist/types/src/components/TracePanel/index.d.ts +4 -0
- package/dist/types/src/components/TracePanel/index.d.ts.map +1 -0
- package/dist/types/src/components/TracePanel/trace-filter.d.ts +39 -0
- package/dist/types/src/components/TracePanel/trace-filter.d.ts.map +1 -0
- package/dist/types/src/components/TracePanel/trace-filter.test.d.ts +2 -0
- package/dist/types/src/components/TracePanel/trace-filter.test.d.ts.map +1 -0
- package/dist/types/src/components/TracePanel/useTraceMenu.d.ts +15 -0
- package/dist/types/src/components/TracePanel/useTraceMenu.d.ts.map +1 -0
- package/dist/types/src/components/index.d.ts +5 -0
- package/dist/types/src/components/index.d.ts.map +1 -0
- package/dist/types/src/execution-graph/execution-graph.d.ts +139 -0
- package/dist/types/src/execution-graph/execution-graph.d.ts.map +1 -0
- package/dist/types/src/execution-graph/execution-graph.test.d.ts +2 -0
- package/dist/types/src/execution-graph/execution-graph.test.d.ts.map +1 -0
- package/dist/types/src/execution-graph/index.d.ts +4 -0
- package/dist/types/src/execution-graph/index.d.ts.map +1 -0
- package/dist/types/src/execution-graph/pending-block-status.d.ts +26 -0
- package/dist/types/src/execution-graph/pending-block-status.d.ts.map +1 -0
- package/dist/types/src/execution-graph/pending-block-status.test.d.ts +2 -0
- package/dist/types/src/execution-graph/pending-block-status.test.d.ts.map +1 -0
- package/dist/types/src/execution-graph/remote-snapshot.test.d.ts +2 -0
- package/dist/types/src/execution-graph/remote-snapshot.test.d.ts.map +1 -0
- package/dist/types/src/execution-graph/span-tree.d.ts +106 -0
- package/dist/types/src/execution-graph/span-tree.d.ts.map +1 -0
- package/dist/types/src/execution-graph/span-tree.test.d.ts +2 -0
- package/dist/types/src/execution-graph/span-tree.test.d.ts.map +1 -0
- package/dist/types/src/execution-graph/sub-agent-delegation.test.d.ts +2 -0
- package/dist/types/src/execution-graph/sub-agent-delegation.test.d.ts.map +1 -0
- package/dist/types/src/execution-graph/testing/collect-trace-events.d.ts +40 -0
- package/dist/types/src/execution-graph/testing/collect-trace-events.d.ts.map +1 -0
- package/dist/types/src/execution-graph/testing/index.d.ts +2 -0
- package/dist/types/src/execution-graph/testing/index.d.ts.map +1 -0
- package/dist/types/src/hooks/index.d.ts +4 -0
- package/dist/types/src/hooks/index.d.ts.map +1 -0
- package/dist/types/src/hooks/useExecutionGraph.d.ts +17 -0
- package/dist/types/src/hooks/useExecutionGraph.d.ts.map +1 -0
- package/dist/types/src/hooks/useSessionTimeline.d.ts +17 -0
- package/dist/types/src/hooks/useSessionTimeline.d.ts.map +1 -0
- package/dist/types/src/hooks/useTraceMessages.d.ts +17 -0
- package/dist/types/src/hooks/useTraceMessages.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +5 -0
- package/dist/types/src/index.d.ts.map +1 -0
- package/dist/types/src/session-timeline/index.d.ts +3 -0
- package/dist/types/src/session-timeline/index.d.ts.map +1 -0
- package/dist/types/src/session-timeline/session-timeline.d.ts +19 -0
- package/dist/types/src/session-timeline/session-timeline.d.ts.map +1 -0
- package/dist/types/src/session-timeline/session-timeline.test.d.ts +2 -0
- package/dist/types/src/session-timeline/session-timeline.test.d.ts.map +1 -0
- package/dist/types/src/session-timeline/types.d.ts +129 -0
- package/dist/types/src/session-timeline/types.d.ts.map +1 -0
- package/dist/types/src/testing/index.d.ts +5 -0
- package/dist/types/src/testing/index.d.ts.map +1 -0
- package/dist/types/src/testing/simulated-agent.d.ts +63 -0
- package/dist/types/src/testing/simulated-agent.d.ts.map +1 -0
- package/dist/types/src/testing/snapshot-playback.d.ts +12 -0
- package/dist/types/src/testing/snapshot-playback.d.ts.map +1 -0
- package/dist/types/src/testing/test-trace.d.ts +5 -0
- package/dist/types/src/testing/test-trace.d.ts.map +1 -0
- package/dist/types/src/translations.d.ts +19 -0
- package/dist/types/src/translations.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -0
- package/package.json +77 -0
- package/src/components/Gantt/Gantt.stories.tsx +241 -0
- package/src/components/Gantt/Gantt.tsx +589 -0
- package/src/components/Gantt/index.ts +5 -0
- package/src/components/ProcessTree/ProcessTree.stories.tsx +95 -0
- package/src/components/ProcessTree/ProcessTree.tsx +265 -0
- package/src/components/ProcessTree/index.ts +5 -0
- package/src/components/Timeline/Timeline.stories.tsx +326 -0
- package/src/components/Timeline/Timeline.tsx +534 -0
- package/src/components/Timeline/git.drawio.svg +821 -0
- package/src/components/Timeline/index.ts +7 -0
- package/src/components/Timeline/timeline-options.ts +27 -0
- package/src/components/Timeline/timeline-printer.test.ts +76 -0
- package/src/components/Timeline/timeline-printer.ts +223 -0
- package/src/components/TracePanel/TracePanel.tsx +248 -0
- package/src/components/TracePanel/dxn-extractor.test.ts +153 -0
- package/src/components/TracePanel/dxn-extractor.ts +166 -0
- package/src/components/TracePanel/index.ts +7 -0
- package/src/components/TracePanel/trace-filter.test.ts +137 -0
- package/src/components/TracePanel/trace-filter.ts +120 -0
- package/src/components/TracePanel/useTraceMenu.ts +95 -0
- package/src/components/index.ts +8 -0
- package/src/execution-graph/SPEC.md +749 -0
- package/src/execution-graph/execution-graph.test.ts +1205 -0
- package/src/execution-graph/execution-graph.ts +1076 -0
- package/src/execution-graph/index.ts +35 -0
- package/src/execution-graph/pending-block-status.test.ts +118 -0
- package/src/execution-graph/pending-block-status.ts +88 -0
- package/src/execution-graph/remote-snapshot.test.ts +116 -0
- package/src/execution-graph/span-tree.test.ts +370 -0
- package/src/execution-graph/span-tree.ts +404 -0
- package/src/execution-graph/sub-agent-delegation.test.ts +65 -0
- package/src/execution-graph/testing/collect-trace-events.ts +76 -0
- package/src/execution-graph/testing/index.ts +5 -0
- package/src/execution-graph/testing/sub-agent-delegation.json +1285 -0
- package/src/hooks/index.ts +7 -0
- package/src/hooks/useExecutionGraph.ts +107 -0
- package/src/hooks/useSessionTimeline.ts +53 -0
- package/src/hooks/useTraceMessages.ts +58 -0
- package/src/index.ts +8 -0
- package/src/session-timeline/index.ts +6 -0
- package/src/session-timeline/session-timeline.test.ts +662 -0
- package/src/session-timeline/session-timeline.ts +605 -0
- package/src/session-timeline/types.ts +100 -0
- package/src/testing/index.ts +8 -0
- package/src/testing/simulated-agent.ts +293 -0
- package/src/testing/snapshot-playback.ts +45 -0
- package/src/testing/test-trace.ts +22 -0
- package/src/translations.ts +29 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { describe, test } from 'vitest';
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
extractDxnFromString,
|
|
9
|
+
extractDxnsFromObject,
|
|
10
|
+
extractFirstDxnFromToolInput,
|
|
11
|
+
extractFirstDxnFromToolResult,
|
|
12
|
+
} from './dxn-extractor.ts';
|
|
13
|
+
|
|
14
|
+
describe('dxn-extractor', () => {
|
|
15
|
+
describe('extractDxnFromString', () => {
|
|
16
|
+
test('extracts plain DXN from string', ({ expect }) => {
|
|
17
|
+
const result = extractDxnFromString('Found object echo:/01KG7R1ZXWFMWQ4DA1Q6TN1DG4');
|
|
18
|
+
expect(result).toHaveLength(1);
|
|
19
|
+
expect(result[0].toString()).toBe('echo:/01KG7R1ZXWFMWQ4DA1Q6TN1DG4');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test('extracts @-prefixed reference from string', ({ expect }) => {
|
|
23
|
+
const result = extractDxnFromString('Reference to @echo:/01KG7R1ZXWFMWQ4DA1Q6TN1DG4 in text');
|
|
24
|
+
expect(result).toHaveLength(1);
|
|
25
|
+
expect(result[0].toString()).toBe('echo:/01KG7R1ZXWFMWQ4DA1Q6TN1DG4');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('extracts multiple references from string', ({ expect }) => {
|
|
29
|
+
const result = extractDxnFromString(
|
|
30
|
+
'Objects: echo:/01KG7R1ZXWFMWQ4DA1Q6TN1DG4 and @echo://BBBBBBBBBBBBBBBBBBBBBBBBBB/01KG7R1ZXWFMWQ4DA1Q6TN1DG5',
|
|
31
|
+
);
|
|
32
|
+
expect(result).toHaveLength(2);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('deduplicates DXNs', ({ expect }) => {
|
|
36
|
+
const result = extractDxnFromString(
|
|
37
|
+
'Same object echo:/01KG7R1ZXWFMWQ4DA1Q6TN1DG4 and echo:/01KG7R1ZXWFMWQ4DA1Q6TN1DG4',
|
|
38
|
+
);
|
|
39
|
+
expect(result).toHaveLength(1);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('returns empty array for string without DXNs', ({ expect }) => {
|
|
43
|
+
const result = extractDxnFromString('No DXNs here');
|
|
44
|
+
expect(result).toHaveLength(0);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe('extractDxnsFromObject', () => {
|
|
49
|
+
test('extracts IPLD-style reference { "/": "echo:..." }', ({ expect }) => {
|
|
50
|
+
const result = extractDxnsFromObject({
|
|
51
|
+
'/': 'echo:/01KG7R1ZXWFMWQ4DA1Q6TN1DG4',
|
|
52
|
+
});
|
|
53
|
+
expect(result).toHaveLength(1);
|
|
54
|
+
expect(result[0].toString()).toBe('echo:/01KG7R1ZXWFMWQ4DA1Q6TN1DG4');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('extracts @uri key reference { "@uri": "echo:..." }', ({ expect }) => {
|
|
58
|
+
const result = extractDxnsFromObject({
|
|
59
|
+
'@uri': 'echo:/01KG7R1ZXWFMWQ4DA1Q6TN1DG4',
|
|
60
|
+
'name': 'Test',
|
|
61
|
+
});
|
|
62
|
+
expect(result).toHaveLength(1);
|
|
63
|
+
expect(result[0].toString()).toBe('echo:/01KG7R1ZXWFMWQ4DA1Q6TN1DG4');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test('extracts reference block { _tag: "reference", reference: { uri: ... } }', ({ expect }) => {
|
|
67
|
+
const result = extractDxnsFromObject({
|
|
68
|
+
_tag: 'reference',
|
|
69
|
+
reference: {
|
|
70
|
+
uri: 'echo:/01KG7R1ZXWFMWQ4DA1Q6TN1DG4',
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
expect(result).toHaveLength(1);
|
|
74
|
+
expect(result[0].toString()).toBe('echo:/01KG7R1ZXWFMWQ4DA1Q6TN1DG4');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('extracts DXN from nested object', ({ expect }) => {
|
|
78
|
+
const result = extractDxnsFromObject({
|
|
79
|
+
data: {
|
|
80
|
+
items: [
|
|
81
|
+
{
|
|
82
|
+
ref: { '/': 'echo:/01KG7R1ZXWFMWQ4DA1Q6TN1DG4' },
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
expect(result).toHaveLength(1);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test('extracts DXN from string property', ({ expect }) => {
|
|
91
|
+
const result = extractDxnsFromObject({
|
|
92
|
+
message: 'Created object echo:/01KG7R1ZXWFMWQ4DA1Q6TN1DG4',
|
|
93
|
+
});
|
|
94
|
+
expect(result).toHaveLength(1);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('extracts DXN from array', ({ expect }) => {
|
|
98
|
+
const result = extractDxnsFromObject([
|
|
99
|
+
{ '/': 'echo:/01KG7R1ZXWFMWQ4DA1Q6TN1DG4' },
|
|
100
|
+
{ '/': 'echo:/01KG7R1ZXWFMWQ4DA1Q6TN1DG5' },
|
|
101
|
+
]);
|
|
102
|
+
expect(result).toHaveLength(2);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test('handles null and undefined', ({ expect }) => {
|
|
106
|
+
expect(extractDxnsFromObject(null)).toHaveLength(0);
|
|
107
|
+
expect(extractDxnsFromObject(undefined)).toHaveLength(0);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe('extractFirstDxnFromToolInput', () => {
|
|
112
|
+
test('extracts DXN from JSON tool input', ({ expect }) => {
|
|
113
|
+
const input = JSON.stringify({
|
|
114
|
+
objectRef: { '/': 'echo:/01KG7R1ZXWFMWQ4DA1Q6TN1DG4' },
|
|
115
|
+
});
|
|
116
|
+
const result = extractFirstDxnFromToolInput(input);
|
|
117
|
+
expect(result?.toString()).toBe('echo:/01KG7R1ZXWFMWQ4DA1Q6TN1DG4');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test('extracts DXN from invalid JSON string', ({ expect }) => {
|
|
121
|
+
const input = 'Invalid JSON but contains echo:/01KG7R1ZXWFMWQ4DA1Q6TN1DG4';
|
|
122
|
+
const result = extractFirstDxnFromToolInput(input);
|
|
123
|
+
expect(result?.toString()).toBe('echo:/01KG7R1ZXWFMWQ4DA1Q6TN1DG4');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test('returns undefined for input without DXNs', ({ expect }) => {
|
|
127
|
+
const input = JSON.stringify({ name: 'Test' });
|
|
128
|
+
const result = extractFirstDxnFromToolInput(input);
|
|
129
|
+
expect(result).toBeUndefined();
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
describe('extractFirstDxnFromToolResult', () => {
|
|
134
|
+
test('extracts DXN from JSON tool result', ({ expect }) => {
|
|
135
|
+
const result = JSON.stringify({
|
|
136
|
+
created: { '/': 'echo:/01KG7R1ZXWFMWQ4DA1Q6TN1DG4' },
|
|
137
|
+
});
|
|
138
|
+
const dxn = extractFirstDxnFromToolResult(result);
|
|
139
|
+
expect(dxn?.toString()).toBe('echo:/01KG7R1ZXWFMWQ4DA1Q6TN1DG4');
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test('returns undefined for undefined result', ({ expect }) => {
|
|
143
|
+
const dxn = extractFirstDxnFromToolResult(undefined);
|
|
144
|
+
expect(dxn).toBeUndefined();
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test('returns undefined for result without DXNs', ({ expect }) => {
|
|
148
|
+
const result = JSON.stringify({ success: true });
|
|
149
|
+
const dxn = extractFirstDxnFromToolResult(result);
|
|
150
|
+
expect(dxn).toBeUndefined();
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
});
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { URI } from '@dxos/keys';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Patterns for extracting references from tool call params/results.
|
|
9
|
+
*
|
|
10
|
+
* Supported formats:
|
|
11
|
+
* - `{ "/": "echo:…" | "dxn:…" }` - IPLD-style encoded references
|
|
12
|
+
* - `{ "@uri": "echo:…" }` - Self URI key
|
|
13
|
+
* - `@echo:…` / `@dxn:…` - In-text references (with @ prefix)
|
|
14
|
+
* - `echo:…` / `dxn:…` - Plain URI strings
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Regex matching a canonical reference URI: an `echo:` EID or a `dxn:<nsid>[:<version>]` type DXN.
|
|
19
|
+
*/
|
|
20
|
+
const URI_PATTERN = /(?:echo:\/{1,3}[A-Za-z0-9/]+|dxn:[a-zA-Z][a-zA-Z0-9-]*(?:\.[a-zA-Z0-9-]+)+(?::\d+\.\d+\.\d+)?)/g;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Regex matching an `@`-prefixed reference URI in text.
|
|
24
|
+
*/
|
|
25
|
+
const AT_URI_PATTERN = new RegExp(`@(${URI_PATTERN.source})`, 'g');
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Extracts all reference URIs from a string.
|
|
29
|
+
*/
|
|
30
|
+
export const extractDxnFromString = (text: string): URI.URI[] => {
|
|
31
|
+
const dxns: URI.URI[] = [];
|
|
32
|
+
const seen = new Set<string>();
|
|
33
|
+
|
|
34
|
+
const add = (uri: string) => {
|
|
35
|
+
if (!seen.has(uri)) {
|
|
36
|
+
dxns.push(URI.make(uri));
|
|
37
|
+
seen.add(uri);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// Match @-prefixed references.
|
|
42
|
+
let match;
|
|
43
|
+
while ((match = AT_URI_PATTERN.exec(text)) !== null) {
|
|
44
|
+
add(match[1]);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Match plain URI strings.
|
|
48
|
+
while ((match = URI_PATTERN.exec(text)) !== null) {
|
|
49
|
+
add(match[0]);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return dxns;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Extracts DXN references from a JSON object.
|
|
57
|
+
* Handles IPLD-style references `{ "/": "dxn:..." }` and `{ "@dxn": "dxn:..." }`.
|
|
58
|
+
*/
|
|
59
|
+
export const extractDxnsFromObject = (obj: unknown): URI.URI[] => {
|
|
60
|
+
const dxns: URI.URI[] = [];
|
|
61
|
+
const seen = new Set<string>();
|
|
62
|
+
|
|
63
|
+
const addDxn = (dxnStr: string) => {
|
|
64
|
+
if (!seen.has(dxnStr)) {
|
|
65
|
+
dxns.push(URI.make(dxnStr));
|
|
66
|
+
seen.add(dxnStr);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const traverse = (value: unknown): void => {
|
|
71
|
+
if (value === null || value === undefined) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (typeof value === 'string') {
|
|
76
|
+
// Check if the string itself is a reference URI.
|
|
77
|
+
if (value.startsWith('echo:') || value.startsWith('dxn:')) {
|
|
78
|
+
addDxn(value);
|
|
79
|
+
} else if (value.startsWith('@echo:') || value.startsWith('@dxn:')) {
|
|
80
|
+
addDxn(value.slice(1));
|
|
81
|
+
}
|
|
82
|
+
// Also extract references from within the string.
|
|
83
|
+
const extracted = extractDxnFromString(value);
|
|
84
|
+
for (const dxn of extracted) {
|
|
85
|
+
addDxn(dxn);
|
|
86
|
+
}
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (Array.isArray(value)) {
|
|
91
|
+
for (const item of value) {
|
|
92
|
+
traverse(item);
|
|
93
|
+
}
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (typeof value === 'object') {
|
|
98
|
+
const record = value as Record<string, unknown>;
|
|
99
|
+
|
|
100
|
+
// Check for IPLD-style reference: { "/": "echo:…" | "dxn:…" }
|
|
101
|
+
if ('/' in record && typeof record['/'] === 'string') {
|
|
102
|
+
const refStr = record['/'];
|
|
103
|
+
if (typeof refStr === 'string' && (refStr.startsWith('echo:') || refStr.startsWith('dxn:'))) {
|
|
104
|
+
addDxn(refStr);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Check for self URI key: { "@uri": "echo:…" }
|
|
109
|
+
const selfUri = record['@uri'];
|
|
110
|
+
if (typeof selfUri === 'string' && (selfUri.startsWith('dxn:') || selfUri.startsWith('echo:'))) {
|
|
111
|
+
addDxn(selfUri);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Check for reference blocks: { _tag: 'reference', reference: { uri: ... } }
|
|
115
|
+
if (record._tag === 'reference' && record.reference && typeof record.reference === 'object') {
|
|
116
|
+
const ref = record.reference as Record<string, unknown>;
|
|
117
|
+
const refUri = ref.uri;
|
|
118
|
+
if (typeof refUri === 'string' && (refUri.startsWith('echo:') || refUri.startsWith('dxn:'))) {
|
|
119
|
+
addDxn(refUri);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Recursively traverse all properties.
|
|
124
|
+
for (const key of Object.keys(record)) {
|
|
125
|
+
traverse(record[key]);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
traverse(obj);
|
|
131
|
+
return dxns;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Extracts the first DXN from a tool call input string (JSON).
|
|
136
|
+
*/
|
|
137
|
+
export const extractFirstDxnFromToolInput = (input: string): URI.URI | undefined => {
|
|
138
|
+
try {
|
|
139
|
+
const parsed = JSON.parse(input);
|
|
140
|
+
const dxns = extractDxnsFromObject(parsed);
|
|
141
|
+
return dxns[0];
|
|
142
|
+
} catch {
|
|
143
|
+
// If JSON parsing fails, try to extract from the raw string.
|
|
144
|
+
const dxns = extractDxnFromString(input);
|
|
145
|
+
return dxns[0];
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Extracts the first DXN from a tool result string (JSON).
|
|
151
|
+
*/
|
|
152
|
+
export const extractFirstDxnFromToolResult = (result: string | undefined): URI.URI | undefined => {
|
|
153
|
+
if (!result) {
|
|
154
|
+
return undefined;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
try {
|
|
158
|
+
const parsed = JSON.parse(result);
|
|
159
|
+
const dxns = extractDxnsFromObject(parsed);
|
|
160
|
+
return dxns[0];
|
|
161
|
+
} catch {
|
|
162
|
+
// If JSON parsing fails, try to extract from the raw string.
|
|
163
|
+
const dxns = extractDxnFromString(result);
|
|
164
|
+
return dxns[0];
|
|
165
|
+
}
|
|
166
|
+
};
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { describe, test } from 'vitest';
|
|
6
|
+
|
|
7
|
+
import * as Process from '@dxos/compute/Process';
|
|
8
|
+
import * as Trace from '@dxos/compute/Trace';
|
|
9
|
+
import { Obj } from '@dxos/echo';
|
|
10
|
+
import { SpaceId, URI } from '@dxos/keys';
|
|
11
|
+
|
|
12
|
+
import { makeProcess } from '../../testing/index.ts';
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
ALL_PROCESS_ENVIRONMENTS,
|
|
16
|
+
DEFAULT_PROCESS_ENVIRONMENTS,
|
|
17
|
+
ProcessEnvironment,
|
|
18
|
+
filterProcesses,
|
|
19
|
+
filterTraceMessages,
|
|
20
|
+
parseProcessEnvironments,
|
|
21
|
+
processEnvironment,
|
|
22
|
+
toggleProcessEnvironment,
|
|
23
|
+
} from './trace-filter.ts';
|
|
24
|
+
|
|
25
|
+
const SPACE_ID = SpaceId.random();
|
|
26
|
+
const CONVERSATION = URI.make('eid:BA25QRC2FWNUGWENQZ26MK5W6C64ZQPTC:01J00000000000000000000000');
|
|
27
|
+
|
|
28
|
+
describe('processEnvironment', () => {
|
|
29
|
+
test('an unscoped process is app-level', ({ expect }) => {
|
|
30
|
+
expect(processEnvironment(makeInfo('layout', {}))).toBe(ProcessEnvironment.App);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('a space-scoped process is space-level', ({ expect }) => {
|
|
34
|
+
expect(processEnvironment(makeInfo('sync', { space: SPACE_ID }))).toBe(ProcessEnvironment.Space);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('a conversation outranks its space', ({ expect }) => {
|
|
38
|
+
expect(processEnvironment(makeInfo('agent', { space: SPACE_ID, conversation: CONVERSATION }))).toBe(
|
|
39
|
+
ProcessEnvironment.Conversation,
|
|
40
|
+
);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe('filterProcesses', () => {
|
|
45
|
+
const processes = [
|
|
46
|
+
makeInfo('layout', {}),
|
|
47
|
+
makeInfo('sync', { space: SPACE_ID }),
|
|
48
|
+
makeInfo('agent', { space: SPACE_ID, conversation: CONVERSATION }),
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
test('keeps only the selected environments', ({ expect }) => {
|
|
52
|
+
const visible = filterProcesses(processes, [ProcessEnvironment.Conversation]);
|
|
53
|
+
expect(visible.map((process) => process.params.name)).toEqual(['agent']);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('an empty selection hides everything', ({ expect }) => {
|
|
57
|
+
expect(filterProcesses(processes, [])).toEqual([]);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('selecting every environment passes the list through unchanged', ({ expect }) => {
|
|
61
|
+
expect(filterProcesses(processes, ALL_PROCESS_ENVIRONMENTS)).toBe(processes);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('the default selection hides app-level processes', ({ expect }) => {
|
|
65
|
+
const visible = filterProcesses(processes, DEFAULT_PROCESS_ENVIRONMENTS);
|
|
66
|
+
expect(visible.map((process) => process.params.name)).toEqual(['sync', 'agent']);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe('toggleProcessEnvironment', () => {
|
|
71
|
+
test('adds in canonical order rather than at the end', ({ expect }) => {
|
|
72
|
+
expect(toggleProcessEnvironment([ProcessEnvironment.Conversation], ProcessEnvironment.App)).toEqual([
|
|
73
|
+
ProcessEnvironment.App,
|
|
74
|
+
ProcessEnvironment.Conversation,
|
|
75
|
+
]);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('removes a selected environment', ({ expect }) => {
|
|
79
|
+
expect(
|
|
80
|
+
toggleProcessEnvironment([ProcessEnvironment.App, ProcessEnvironment.Conversation], ProcessEnvironment.App),
|
|
81
|
+
).toEqual([ProcessEnvironment.Conversation]);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe('parseProcessEnvironments', () => {
|
|
86
|
+
test('unset settings fall back to the default selection', ({ expect }) => {
|
|
87
|
+
expect(parseProcessEnvironments(undefined)).toEqual(DEFAULT_PROCESS_ENVIRONMENTS);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test('an empty selection is preserved, not treated as unset', ({ expect }) => {
|
|
91
|
+
expect(parseProcessEnvironments([])).toEqual([]);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test('drops values outside the vocabulary and restores canonical order', ({ expect }) => {
|
|
95
|
+
expect(parseProcessEnvironments([ProcessEnvironment.Conversation, 'retired', ProcessEnvironment.App])).toEqual([
|
|
96
|
+
ProcessEnvironment.App,
|
|
97
|
+
ProcessEnvironment.Conversation,
|
|
98
|
+
]);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const makeInfo = (name: string, environment: Process.Environment): Process.Info =>
|
|
103
|
+
makeProcess({ pid: Process.ID.make(name), name, state: Process.State.RUNNING, environment });
|
|
104
|
+
|
|
105
|
+
describe('filterTraceMessages', () => {
|
|
106
|
+
const message = (pid: string, parentPid?: string): Trace.Message =>
|
|
107
|
+
Obj.make(Trace.Message, {
|
|
108
|
+
meta: { pid, ...(parentPid ? { parentPid } : {}) },
|
|
109
|
+
isEphemeral: false,
|
|
110
|
+
events: [{ type: 'test', timestamp: 0, data: {} }],
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('an empty selection is no filter', ({ expect }) => {
|
|
114
|
+
const messages = [message('a'), message('b')];
|
|
115
|
+
expect(filterTraceMessages(messages, [])).toBe(messages);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("a selected process keeps its own messages and its descendants'", ({ expect }) => {
|
|
119
|
+
const messages = [message('agent'), message('tool', 'agent'), message('nested', 'tool'), message('other')];
|
|
120
|
+
expect(filterTraceMessages(messages, ['agent']).map((m) => m.meta.pid)).toEqual(['agent', 'tool', 'nested']);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test('several selected processes are unioned', ({ expect }) => {
|
|
124
|
+
const messages = [message('a'), message('b'), message('c')];
|
|
125
|
+
expect(filterTraceMessages(messages, ['a', 'c']).map((m) => m.meta.pid)).toEqual(['a', 'c']);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test('a message with no pid is dropped once anything is selected', ({ expect }) => {
|
|
129
|
+
const orphan = Obj.make(Trace.Message, { meta: {}, isEphemeral: false, events: [] });
|
|
130
|
+
expect(filterTraceMessages([orphan, message('a')], ['a']).map((m) => m.meta.pid)).toEqual(['a']);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test('a cyclic parent chain terminates', ({ expect }) => {
|
|
134
|
+
const messages = [message('a', 'b'), message('b', 'a')];
|
|
135
|
+
expect(filterTraceMessages(messages, ['z'])).toEqual([]);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import type * as Process from '@dxos/compute/Process';
|
|
6
|
+
import type * as Trace from '@dxos/compute/Trace';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* What a running process is working on behalf of, as a single bucket.
|
|
10
|
+
*
|
|
11
|
+
* Bucketed from the inherited {@link Process.Environment}, so filtering keeps whole subtrees together.
|
|
12
|
+
*/
|
|
13
|
+
export const ProcessEnvironment = {
|
|
14
|
+
/** Not scoped to a space: layout, navigation, identity, settings, space lifecycle. */
|
|
15
|
+
App: 'app',
|
|
16
|
+
/** Scoped to a space, but not to a conversation within it. */
|
|
17
|
+
Space: 'space',
|
|
18
|
+
/** Serving a conversation: an agent's turn and every tool call it spawns. */
|
|
19
|
+
Conversation: 'conversation',
|
|
20
|
+
} as const;
|
|
21
|
+
|
|
22
|
+
export type ProcessEnvironment = (typeof ProcessEnvironment)[keyof typeof ProcessEnvironment];
|
|
23
|
+
|
|
24
|
+
/** Every environment, in menu order — widening scope, so the filter reads the same on every trace. */
|
|
25
|
+
export const ALL_PROCESS_ENVIRONMENTS: readonly ProcessEnvironment[] = [
|
|
26
|
+
ProcessEnvironment.App,
|
|
27
|
+
ProcessEnvironment.Space,
|
|
28
|
+
ProcessEnvironment.Conversation,
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
/** App-level processes start hidden: that bucket fires on every click, whatever the user is watching. */
|
|
32
|
+
export const DEFAULT_PROCESS_ENVIRONMENTS: readonly ProcessEnvironment[] = [
|
|
33
|
+
ProcessEnvironment.Space,
|
|
34
|
+
ProcessEnvironment.Conversation,
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
const ENVIRONMENT_ICONS: Record<ProcessEnvironment, string> = {
|
|
38
|
+
[ProcessEnvironment.App]: 'ph--app-window--regular',
|
|
39
|
+
[ProcessEnvironment.Space]: 'ph--planet--regular',
|
|
40
|
+
[ProcessEnvironment.Conversation]: 'ph--chat-teardrop-text--regular',
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const environmentIcon = (environment: ProcessEnvironment): string => ENVIRONMENT_ICONS[environment];
|
|
44
|
+
|
|
45
|
+
/** `conversation` outranks `space`, since a conversation always runs inside one. */
|
|
46
|
+
export const processEnvironment = (process: Process.Info): ProcessEnvironment => {
|
|
47
|
+
if (process.environment.conversation !== undefined) {
|
|
48
|
+
return ProcessEnvironment.Conversation;
|
|
49
|
+
}
|
|
50
|
+
if (process.environment.space !== undefined) {
|
|
51
|
+
return ProcessEnvironment.Space;
|
|
52
|
+
}
|
|
53
|
+
return ProcessEnvironment.App;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export const filterProcesses = (
|
|
57
|
+
processes: readonly Process.Info[],
|
|
58
|
+
selected: readonly ProcessEnvironment[],
|
|
59
|
+
): readonly Process.Info[] => {
|
|
60
|
+
// Identity when nothing is excluded, so `ProcessTree`'s `React.memo` still holds.
|
|
61
|
+
if (selected.length === ALL_PROCESS_ENVIRONMENTS.length) {
|
|
62
|
+
return processes;
|
|
63
|
+
}
|
|
64
|
+
const selection = new Set(selected);
|
|
65
|
+
return processes.filter((process) => selection.has(processEnvironment(process)));
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/** Toggles an environment, preserving the canonical order. */
|
|
69
|
+
export const toggleProcessEnvironment = (
|
|
70
|
+
selected: readonly ProcessEnvironment[],
|
|
71
|
+
environment: ProcessEnvironment,
|
|
72
|
+
): ProcessEnvironment[] =>
|
|
73
|
+
ALL_PROCESS_ENVIRONMENTS.filter((candidate) =>
|
|
74
|
+
candidate === environment ? !selected.includes(environment) : selected.includes(candidate),
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Narrows a persisted selection to the known environments, in canonical order.
|
|
79
|
+
*
|
|
80
|
+
* Settings outlive the vocabulary that wrote them; an unrecognized value would filter the panel to nothing.
|
|
81
|
+
*/
|
|
82
|
+
export const parseProcessEnvironments = (selected: readonly string[] | undefined): readonly ProcessEnvironment[] =>
|
|
83
|
+
selected === undefined
|
|
84
|
+
? DEFAULT_PROCESS_ENVIRONMENTS
|
|
85
|
+
: ALL_PROCESS_ENVIRONMENTS.filter((environment) => selected.includes(environment));
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Narrows trace messages to the selected processes and their descendants: a tool call or sub-agent
|
|
89
|
+
* runs as a child of the process it serves, so its events are that process's work. An empty
|
|
90
|
+
* selection is no filter.
|
|
91
|
+
*/
|
|
92
|
+
export const filterTraceMessages = (
|
|
93
|
+
messages: readonly Trace.Message[],
|
|
94
|
+
selected: readonly string[],
|
|
95
|
+
): readonly Trace.Message[] => {
|
|
96
|
+
if (selected.length === 0) {
|
|
97
|
+
return messages;
|
|
98
|
+
}
|
|
99
|
+
const parentByPid = new Map<string, string | undefined>();
|
|
100
|
+
for (const message of messages) {
|
|
101
|
+
if (message.meta.pid !== undefined) {
|
|
102
|
+
parentByPid.set(message.meta.pid, message.meta.parentPid);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const selection = new Set(selected);
|
|
106
|
+
const matches = new Map<string, boolean>();
|
|
107
|
+
const isSelected = (pid: string): boolean => {
|
|
108
|
+
const known = matches.get(pid);
|
|
109
|
+
if (known !== undefined) {
|
|
110
|
+
return known;
|
|
111
|
+
}
|
|
112
|
+
// Pinned before the walk so a cyclic parent chain (a corrupt trace) terminates.
|
|
113
|
+
matches.set(pid, false);
|
|
114
|
+
const parent = parentByPid.get(pid);
|
|
115
|
+
const result = selection.has(pid) || (parent !== undefined && isSelected(parent));
|
|
116
|
+
matches.set(pid, result);
|
|
117
|
+
return result;
|
|
118
|
+
};
|
|
119
|
+
return messages.filter((message) => message.meta.pid !== undefined && isSelected(message.meta.pid));
|
|
120
|
+
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { useCallback } from 'react';
|
|
6
|
+
|
|
7
|
+
import { MenuBuilder, useMenuBuilder } from '@dxos/react-ui-menu';
|
|
8
|
+
|
|
9
|
+
import { translationKey } from '../../translations.ts';
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
ALL_PROCESS_ENVIRONMENTS,
|
|
13
|
+
type ProcessEnvironment,
|
|
14
|
+
environmentIcon,
|
|
15
|
+
toggleProcessEnvironment,
|
|
16
|
+
} from './trace-filter.ts';
|
|
17
|
+
|
|
18
|
+
export type UseTraceMenuOptions = {
|
|
19
|
+
/** Process environments currently shown. */
|
|
20
|
+
selected: readonly ProcessEnvironment[];
|
|
21
|
+
onSelectedChange: (environments: ProcessEnvironment[]) => void;
|
|
22
|
+
/** How many processes are picked; the clear action shows only while there are some. */
|
|
23
|
+
selectionCount?: number;
|
|
24
|
+
onClearSelection?: () => void;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Environment filter menu for the trace panel's toolbar.
|
|
29
|
+
* The group collapses to a funnel trigger, so the panel's own rows keep the width.
|
|
30
|
+
*/
|
|
31
|
+
export const useTraceMenu = ({
|
|
32
|
+
selected,
|
|
33
|
+
onSelectedChange,
|
|
34
|
+
selectionCount = 0,
|
|
35
|
+
onClearSelection,
|
|
36
|
+
}: UseTraceMenuOptions) => {
|
|
37
|
+
const handleToggle = useCallback(
|
|
38
|
+
(environment: ProcessEnvironment) => onSelectedChange(toggleProcessEnvironment(selected, environment)),
|
|
39
|
+
[selected, onSelectedChange],
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
return useMenuBuilder(
|
|
43
|
+
() =>
|
|
44
|
+
MenuBuilder.make()
|
|
45
|
+
.action(
|
|
46
|
+
'clearSelection',
|
|
47
|
+
{
|
|
48
|
+
label: ['trace-clear-selection.label', { ns: translationKey }],
|
|
49
|
+
icon: 'ph--selection-slash--regular',
|
|
50
|
+
iconOnly: true,
|
|
51
|
+
hidden: selectionCount === 0,
|
|
52
|
+
testId: 'tracePanel.clearSelection',
|
|
53
|
+
},
|
|
54
|
+
() => onClearSelection?.(),
|
|
55
|
+
)
|
|
56
|
+
.group(
|
|
57
|
+
'processEnvironments',
|
|
58
|
+
{
|
|
59
|
+
label: ['trace-filter.menu', { ns: translationKey }],
|
|
60
|
+
icon: 'ph--funnel--regular',
|
|
61
|
+
iconOnly: true,
|
|
62
|
+
variant: 'dropdownMenu',
|
|
63
|
+
selectCardinality: 'multiple',
|
|
64
|
+
value: [...selected],
|
|
65
|
+
testId: 'tracePanel.filter',
|
|
66
|
+
},
|
|
67
|
+
(group) => {
|
|
68
|
+
for (const environment of ALL_PROCESS_ENVIRONMENTS) {
|
|
69
|
+
group.action(
|
|
70
|
+
environment,
|
|
71
|
+
{
|
|
72
|
+
label: [`trace-environment-${environment}.label`, { ns: translationKey }],
|
|
73
|
+
icon: environmentIcon(environment),
|
|
74
|
+
checked: selected.includes(environment),
|
|
75
|
+
},
|
|
76
|
+
() => handleToggle(environment),
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
group.separator('line');
|
|
80
|
+
group.action(
|
|
81
|
+
'all',
|
|
82
|
+
{ label: ['trace-filter-all.label', { ns: translationKey }], icon: 'ph--list-checks--regular' },
|
|
83
|
+
() => onSelectedChange([...ALL_PROCESS_ENVIRONMENTS]),
|
|
84
|
+
);
|
|
85
|
+
group.action(
|
|
86
|
+
'none',
|
|
87
|
+
{ label: ['trace-filter-none.label', { ns: translationKey }], icon: 'ph--prohibit--regular' },
|
|
88
|
+
() => onSelectedChange([]),
|
|
89
|
+
);
|
|
90
|
+
},
|
|
91
|
+
)
|
|
92
|
+
.build(),
|
|
93
|
+
[handleToggle, selected, onSelectedChange, selectionCount, onClearSelection],
|
|
94
|
+
);
|
|
95
|
+
};
|