@dxos/react-ui-trace 0.11.1 → 0.13.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/README.md +1 -1
- package/dist/lib/index.mjs +1722 -561
- package/dist/lib/index.mjs.map +1 -1
- package/dist/lib/testing.mjs +3 -3
- package/dist/lib/testing.mjs.map +1 -1
- package/dist/lib/translations.mjs +5 -0
- package/dist/lib/translations.mjs.map +1 -1
- package/dist/types/src/components/Gantt/Gantt.d.ts +109 -24
- package/dist/types/src/components/Gantt/Gantt.d.ts.map +1 -1
- package/dist/types/src/components/Gantt/Gantt.stories.d.ts +73 -3
- package/dist/types/src/components/Gantt/Gantt.stories.d.ts.map +1 -1
- package/dist/types/src/components/Gantt/gantt-rows.d.ts +26 -0
- package/dist/types/src/components/Gantt/gantt-rows.d.ts.map +1 -0
- package/dist/types/src/components/Gantt/gantt-rows.test.d.ts +2 -0
- package/dist/types/src/components/Gantt/gantt-rows.test.d.ts.map +1 -0
- package/dist/types/src/components/Gantt/gantt-scale.d.ts +57 -0
- package/dist/types/src/components/Gantt/gantt-scale.d.ts.map +1 -0
- package/dist/types/src/components/Gantt/gantt-scale.test.d.ts +2 -0
- package/dist/types/src/components/Gantt/gantt-scale.test.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 -1
- package/dist/types/src/components/Gantt/useEnter.d.ts +13 -0
- package/dist/types/src/components/Gantt/useEnter.d.ts.map +1 -0
- package/dist/types/src/components/Gantt/useRangeTween.d.ts +12 -0
- package/dist/types/src/components/Gantt/useRangeTween.d.ts.map +1 -0
- package/dist/types/src/components/ProcessTree/ProcessTree.d.ts +3 -3
- package/dist/types/src/components/ProcessTree/ProcessTree.d.ts.map +1 -1
- package/dist/types/src/components/ProcessTree/ProcessTree.stories.d.ts.map +1 -1
- package/dist/types/src/components/Timeline/Timeline.d.ts +10 -2
- package/dist/types/src/components/Timeline/Timeline.d.ts.map +1 -1
- package/dist/types/src/components/Timeline/Timeline.stories.d.ts +8 -4
- package/dist/types/src/components/Timeline/Timeline.stories.d.ts.map +1 -1
- package/dist/types/src/components/Timeline/timeline-layout.d.ts +35 -0
- package/dist/types/src/components/Timeline/timeline-layout.d.ts.map +1 -0
- package/dist/types/src/components/Timeline/timeline-layout.test.d.ts +2 -0
- package/dist/types/src/components/Timeline/timeline-layout.test.d.ts.map +1 -0
- package/dist/types/src/components/TracePanel/SpanTreeView.d.ts +15 -0
- package/dist/types/src/components/TracePanel/SpanTreeView.d.ts.map +1 -0
- package/dist/types/src/components/TracePanel/TracePanel.d.ts +3 -3
- package/dist/types/src/components/TracePanel/TracePanel.d.ts.map +1 -1
- package/dist/types/src/components/TracePanel/TracePanel.stories.d.ts +46 -0
- package/dist/types/src/components/TracePanel/TracePanel.stories.d.ts.map +1 -0
- package/dist/types/src/components/TracePanel/trace-filter.d.ts +7 -2
- package/dist/types/src/components/TracePanel/trace-filter.d.ts.map +1 -1
- package/dist/types/src/components/TracePanel/useTraceMenu.d.ts.map +1 -1
- package/dist/types/src/execution-graph/execution-graph.d.ts +1 -1
- package/dist/types/src/execution-graph/execution-graph.d.ts.map +1 -1
- package/dist/types/src/hooks/useExecutionGraph.d.ts +2 -2
- package/dist/types/src/hooks/useExecutionGraph.d.ts.map +1 -1
- package/dist/types/src/hooks/useSessionTimeline.d.ts +1 -1
- package/dist/types/src/hooks/useSessionTimeline.d.ts.map +1 -1
- package/dist/types/src/hooks/useTraceMessages.d.ts +1 -1
- package/dist/types/src/hooks/useTraceMessages.d.ts.map +1 -1
- package/dist/types/src/session-timeline/TaskHistory.stories.d.ts +12 -0
- package/dist/types/src/session-timeline/TaskHistory.stories.d.ts.map +1 -0
- package/dist/types/src/session-timeline/gantt-mapping.d.ts +14 -0
- package/dist/types/src/session-timeline/gantt-mapping.d.ts.map +1 -0
- package/dist/types/src/session-timeline/gantt-mapping.test.d.ts +2 -0
- package/dist/types/src/session-timeline/gantt-mapping.test.d.ts.map +1 -0
- package/dist/types/src/session-timeline/index.d.ts +1 -0
- package/dist/types/src/session-timeline/index.d.ts.map +1 -1
- package/dist/types/src/session-timeline/session-timeline.d.ts +17 -2
- package/dist/types/src/session-timeline/session-timeline.d.ts.map +1 -1
- package/dist/types/src/session-timeline/types.d.ts +32 -0
- package/dist/types/src/session-timeline/types.d.ts.map +1 -1
- package/dist/types/src/testing/simulated-agent.d.ts +3 -2
- package/dist/types/src/testing/simulated-agent.d.ts.map +1 -1
- package/dist/types/src/testing/test-trace.d.ts +2 -2
- package/dist/types/src/testing/test-trace.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +5 -0
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +30 -26
- package/src/components/Gantt/Gantt.stories.tsx +914 -96
- package/src/components/Gantt/Gantt.tsx +964 -261
- package/src/components/Gantt/gantt-rows.test.ts +123 -0
- package/src/components/Gantt/gantt-rows.ts +71 -0
- package/src/components/Gantt/gantt-scale.test.ts +105 -0
- package/src/components/Gantt/gantt-scale.ts +143 -0
- package/src/components/Gantt/index.ts +2 -0
- package/src/components/Gantt/useEnter.ts +33 -0
- package/src/components/Gantt/useRangeTween.ts +61 -0
- package/src/components/ProcessTree/ProcessTree.stories.tsx +2 -3
- package/src/components/ProcessTree/ProcessTree.tsx +99 -90
- package/src/components/Timeline/Timeline.stories.tsx +110 -22
- package/src/components/Timeline/Timeline.tsx +319 -271
- package/src/components/Timeline/timeline-layout.test.ts +177 -0
- package/src/components/Timeline/timeline-layout.ts +153 -0
- package/src/components/TracePanel/SpanTreeView.tsx +39 -0
- package/src/components/TracePanel/TracePanel.stories.tsx +106 -0
- package/src/components/TracePanel/TracePanel.tsx +113 -106
- package/src/components/TracePanel/trace-filter.test.ts +31 -2
- package/src/components/TracePanel/trace-filter.ts +28 -3
- package/src/components/TracePanel/useTraceMenu.ts +0 -1
- package/src/execution-graph/SPEC.md +10 -10
- package/src/execution-graph/execution-graph.test.ts +5 -6
- package/src/execution-graph/execution-graph.ts +108 -60
- package/src/execution-graph/pending-block-status.ts +1 -1
- package/src/execution-graph/sub-agent-delegation.test.ts +1 -1
- package/src/hooks/useExecutionGraph.ts +14 -11
- package/src/hooks/useSessionTimeline.ts +35 -9
- package/src/hooks/useTraceMessages.ts +1 -1
- package/src/session-timeline/TaskHistory.stories.tsx +142 -0
- package/src/session-timeline/gantt-mapping.test.ts +118 -0
- package/src/session-timeline/gantt-mapping.ts +158 -0
- package/src/session-timeline/index.ts +1 -0
- package/src/session-timeline/session-timeline.test.ts +567 -19
- package/src/session-timeline/session-timeline.ts +433 -62
- package/src/session-timeline/types.ts +14 -0
- package/src/testing/simulated-agent.ts +3 -2
- package/src/testing/test-trace.ts +2 -2
- package/src/translations.ts +8 -2
- package/dist/types/src/execution-graph/remote-snapshot.test.d.ts +0 -2
- package/dist/types/src/execution-graph/remote-snapshot.test.d.ts.map +0 -1
- package/src/execution-graph/remote-snapshot.test.ts +0 -116
|
@@ -5,31 +5,27 @@
|
|
|
5
5
|
import React, { useCallback, useDeferredValue, useMemo, useState } from 'react';
|
|
6
6
|
|
|
7
7
|
import type * as Process from '@dxos/compute/Process';
|
|
8
|
-
import {
|
|
9
|
-
Accordion,
|
|
10
|
-
Panel,
|
|
11
|
-
ScrollContainer,
|
|
12
|
-
type ThemedClassName,
|
|
13
|
-
composable,
|
|
14
|
-
composableProps,
|
|
15
|
-
useTranslation,
|
|
16
|
-
} from '@dxos/react-ui';
|
|
17
8
|
import { ActionToolbar } from '@dxos/react-ui-menu';
|
|
18
9
|
import { JsonHighlighter } from '@dxos/react-ui-syntax-highlighter';
|
|
10
|
+
import * as Accordion from '@dxos/react-ui/Accordion';
|
|
11
|
+
import * as Hooks from '@dxos/react-ui/Hooks';
|
|
12
|
+
import * as Panel from '@dxos/react-ui/Panel';
|
|
13
|
+
import * as ScrollContainer from '@dxos/react-ui/ScrollContainer';
|
|
14
|
+
import * as Util from '@dxos/react-ui/Util';
|
|
19
15
|
import { mx } from '@dxos/ui-theme';
|
|
20
16
|
|
|
21
17
|
import { type ExecutionGraph } from '../../execution-graph/index.ts';
|
|
22
18
|
import { translationKey } from '../../translations.ts';
|
|
23
19
|
import { ProcessTree, type ProcessTreeProps } from '../ProcessTree/index.ts';
|
|
24
20
|
import { type Commit, Timeline } from '../Timeline/index.ts';
|
|
25
|
-
|
|
21
|
+
import { SpanTreeView } from './SpanTreeView.tsx';
|
|
26
22
|
import { type ProcessEnvironment, filterProcesses } from './trace-filter.ts';
|
|
27
23
|
import { useTraceMenu } from './useTraceMenu.ts';
|
|
28
24
|
|
|
29
|
-
export type TracePanelProps = ThemedClassName<
|
|
25
|
+
export type TracePanelProps = Util.ThemedClassName<
|
|
30
26
|
Pick<ProcessTreeProps, 'resolveLabel' | 'selected' | 'onSelectedChange' | 'onProcessTerminate'> & {
|
|
31
27
|
/** The live process tree; narrowed here by `environments`. */
|
|
32
|
-
processes: readonly Process.
|
|
28
|
+
processes: readonly Process.Process[];
|
|
33
29
|
/** The trace as a commit graph, built by `useExecutionGraph` (already narrowed to `selected`). */
|
|
34
30
|
graph: ExecutionGraph;
|
|
35
31
|
/** Process environments shown; the toolbar's funnel edits it. */
|
|
@@ -47,7 +43,7 @@ export type TracePanelProps = ThemedClassName<
|
|
|
47
43
|
* event. Presentation only — the host resolves the process monitor, the trace feed, settings and
|
|
48
44
|
* navigation and hands them in as props.
|
|
49
45
|
*/
|
|
50
|
-
export const TracePanel = composable<HTMLDivElement, TracePanelProps>(
|
|
46
|
+
export const TracePanel = Util.composable<HTMLDivElement, TracePanelProps>(
|
|
51
47
|
(
|
|
52
48
|
{
|
|
53
49
|
classNames,
|
|
@@ -65,7 +61,7 @@ export const TracePanel = composable<HTMLDivElement, TracePanelProps>(
|
|
|
65
61
|
},
|
|
66
62
|
forwardedRef,
|
|
67
63
|
) => {
|
|
68
|
-
const { t } = useTranslation(translationKey);
|
|
64
|
+
const { t } = Hooks.useTranslation(translationKey);
|
|
69
65
|
const handleClearSelection = useCallback(() => onSelectedChange?.([]), [onSelectedChange]);
|
|
70
66
|
const menu = useTraceMenu({
|
|
71
67
|
selected: environments,
|
|
@@ -78,9 +74,16 @@ export const TracePanel = composable<HTMLDivElement, TracePanelProps>(
|
|
|
78
74
|
// See the comment in `ProcessTreeContainer` for more details.
|
|
79
75
|
const { branches, commits, spanTree, details } = useDeferredValue(graph);
|
|
80
76
|
|
|
81
|
-
|
|
77
|
+
// A commit the graph no longer holds (the process selection moved) is no longer picked.
|
|
78
|
+
const [pickedCommit, setSelectedCommit] = useState<Commit | undefined>();
|
|
79
|
+
const selectedCommit = useMemo(
|
|
80
|
+
() => (pickedCommit && commits.some((commit) => commit.id === pickedCommit.id) ? pickedCommit : undefined),
|
|
81
|
+
[pickedCommit, commits],
|
|
82
|
+
);
|
|
82
83
|
// Remembered across selections, so collapsing a section once keeps it collapsed.
|
|
83
84
|
const [openSections, setOpenSections] = useState<string[]>(SECTIONS.map((section) => section.id));
|
|
85
|
+
// The timeline windows its rows against this scroller.
|
|
86
|
+
const [traceViewport, setTraceViewport] = useState<HTMLDivElement | null>(null);
|
|
84
87
|
const handleCommitSelect = useCallback(
|
|
85
88
|
(commit: Commit | undefined) => {
|
|
86
89
|
setSelectedCommit(commit);
|
|
@@ -98,103 +101,107 @@ export const TracePanel = composable<HTMLDivElement, TracePanelProps>(
|
|
|
98
101
|
}, [selected, branches]);
|
|
99
102
|
|
|
100
103
|
return (
|
|
101
|
-
<Panel.Root {...composableProps(props, { classNames: ['h-full', classNames] })} ref={forwardedRef}>
|
|
102
|
-
<Panel.
|
|
104
|
+
<Panel.Root {...Util.composableProps(props, { classNames: ['h-full', classNames] })} ref={forwardedRef}>
|
|
105
|
+
<Panel.Header>
|
|
103
106
|
<ActionToolbar {...menu} alwaysActive classNames='justify-end' />
|
|
104
|
-
</Panel.
|
|
107
|
+
</Panel.Header>
|
|
105
108
|
|
|
106
|
-
<Panel.
|
|
107
|
-
<Accordion.Root
|
|
108
|
-
items={SECTIONS}
|
|
109
|
+
<Panel.Body>
|
|
110
|
+
<Accordion.Root
|
|
109
111
|
value={openSections}
|
|
110
112
|
onValueChange={setOpenSections}
|
|
111
|
-
classNames='h-full min-h-0
|
|
113
|
+
classNames='h-full min-h-0'
|
|
114
|
+
border={false}
|
|
112
115
|
>
|
|
113
|
-
{(
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
<Accordion.
|
|
119
|
-
<
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
<
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
116
|
+
{SECTIONS.map((section) => {
|
|
117
|
+
switch (section.id) {
|
|
118
|
+
case 'processes':
|
|
119
|
+
return (
|
|
120
|
+
<Accordion.Item key={section.id} value={section.id}>
|
|
121
|
+
<Accordion.ItemTrigger>
|
|
122
|
+
<span className='text-sm text-fg-muted'>{t('trace-processes.label')}</span>
|
|
123
|
+
</Accordion.ItemTrigger>
|
|
124
|
+
<Accordion.ItemContent classNames='p-0'>
|
|
125
|
+
<ProcessTreeContainer
|
|
126
|
+
classNames='max-h-[8lh]'
|
|
127
|
+
processes={processes}
|
|
128
|
+
environments={environments}
|
|
129
|
+
resolveLabel={resolveLabel}
|
|
130
|
+
selected={selected}
|
|
131
|
+
onSelectedChange={onSelectedChange}
|
|
132
|
+
onProcessTerminate={onProcessTerminate}
|
|
133
|
+
/>
|
|
134
|
+
</Accordion.ItemContent>
|
|
135
|
+
</Accordion.Item>
|
|
136
|
+
);
|
|
137
|
+
case 'trace':
|
|
138
|
+
return (
|
|
139
|
+
// The trace takes the slack: item and body are flex columns so the scroll
|
|
140
|
+
// container inside gets a definite height to scroll within. The body's slide
|
|
141
|
+
// animation is off here — it ramps to a measured height, and this body's height
|
|
142
|
+
// comes from the flex slack, not its content.
|
|
143
|
+
<Accordion.Item
|
|
144
|
+
key={section.id}
|
|
145
|
+
value={section.id}
|
|
146
|
+
disabled
|
|
147
|
+
classNames={mx(
|
|
148
|
+
'dx-grow flex flex-col',
|
|
149
|
+
'[&>[data-part=item-content]]:dx-grow [&>[data-part=item-content]]:flex [&>[data-part=item-content]]:flex-col [&>[data-part=item-content]]:animate-none',
|
|
150
|
+
)}
|
|
151
|
+
>
|
|
152
|
+
<Accordion.ItemTrigger>
|
|
153
|
+
<span className='text-sm text-fg-muted'>{t('trace.label')}</span>
|
|
154
|
+
</Accordion.ItemTrigger>
|
|
155
|
+
<Accordion.ItemContent classNames='dx-grow grid grid-rows-[minmax(0,1fr)]'>
|
|
156
|
+
{/* Opens at the tail and follows new entries while pinned; scrolling up unpins. */}
|
|
157
|
+
<ScrollContainer.Root pin>
|
|
158
|
+
<ScrollContainer.Content width='thin'>
|
|
159
|
+
<ScrollContainer.Fade classNames='h-8' />
|
|
160
|
+
<ScrollContainer.Viewport ref={setTraceViewport}>
|
|
161
|
+
{debug ? (
|
|
162
|
+
<SpanTreeView spanTree={spanTree} />
|
|
163
|
+
) : (
|
|
164
|
+
<Timeline
|
|
165
|
+
branches={branches}
|
|
166
|
+
branch={currentBranch}
|
|
167
|
+
commits={commits}
|
|
168
|
+
showTimestamp
|
|
169
|
+
scroller={traceViewport}
|
|
170
|
+
onSelect={handleCommitSelect}
|
|
171
|
+
/>
|
|
172
|
+
)}
|
|
173
|
+
</ScrollContainer.Viewport>
|
|
174
|
+
<ScrollContainer.ScrollDownButton />
|
|
175
|
+
</ScrollContainer.Content>
|
|
176
|
+
</ScrollContainer.Root>
|
|
177
|
+
</Accordion.ItemContent>
|
|
178
|
+
</Accordion.Item>
|
|
179
|
+
);
|
|
180
|
+
case 'details': {
|
|
181
|
+
const commit = debug ? undefined : selectedCommit;
|
|
182
|
+
return (
|
|
183
|
+
// With nothing selected the section stays as a plain, closed row.
|
|
184
|
+
<Accordion.Item key={section.id} value={section.id} disabled={!commit}>
|
|
185
|
+
<Accordion.ItemTrigger>
|
|
186
|
+
<span className='flex items-center truncate text-sm text-fg-muted'>
|
|
187
|
+
{t('trace-details.label')}
|
|
188
|
+
</span>
|
|
189
|
+
</Accordion.ItemTrigger>
|
|
190
|
+
{commit && (
|
|
191
|
+
<Accordion.ItemContent classNames='p-0'>
|
|
192
|
+
<JsonHighlighter
|
|
193
|
+
data={details[commit.id] ?? commit}
|
|
194
|
+
classNames='max-h-[20lh] text-xs p-1.5'
|
|
131
195
|
/>
|
|
132
|
-
</Accordion.
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
return (
|
|
137
|
-
// The trace takes the slack: item and body are flex columns so the scroll
|
|
138
|
-
// container inside gets a definite height to scroll within. The body's slide
|
|
139
|
-
// animation is off here — it ramps to a measured height, and this body's height
|
|
140
|
-
// comes from the flex slack, not its content.
|
|
141
|
-
<Accordion.Item
|
|
142
|
-
key={section.id}
|
|
143
|
-
item={section}
|
|
144
|
-
classNames={mx(
|
|
145
|
-
'border-x-0 dx-grow flex flex-col',
|
|
146
|
-
'[&>[data-part=item-content]]:dx-grow [&>[data-part=item-content]]:flex [&>[data-part=item-content]]:flex-col [&>[data-part=item-content]]:animate-none',
|
|
147
|
-
)}
|
|
148
|
-
>
|
|
149
|
-
<Accordion.ItemHeader hover>
|
|
150
|
-
<span className='text-sm text-description'>{t('trace.label')}</span>
|
|
151
|
-
</Accordion.ItemHeader>
|
|
152
|
-
<Accordion.ItemBody classNames='dx-grow grid grid-rows-[minmax(0,1fr)]'>
|
|
153
|
-
<ScrollContainer.Root pin>
|
|
154
|
-
<ScrollContainer.Content thin>
|
|
155
|
-
<ScrollContainer.Fade />
|
|
156
|
-
<ScrollContainer.Viewport>
|
|
157
|
-
{debug ? (
|
|
158
|
-
<JsonHighlighter data={spanTree} classNames='text-xs' />
|
|
159
|
-
) : (
|
|
160
|
-
<Timeline
|
|
161
|
-
branches={branches}
|
|
162
|
-
branch={currentBranch}
|
|
163
|
-
commits={commits}
|
|
164
|
-
showTimestamp
|
|
165
|
-
onSelect={handleCommitSelect}
|
|
166
|
-
/>
|
|
167
|
-
)}
|
|
168
|
-
</ScrollContainer.Viewport>
|
|
169
|
-
<ScrollContainer.ScrollDownButton />
|
|
170
|
-
</ScrollContainer.Content>
|
|
171
|
-
</ScrollContainer.Root>
|
|
172
|
-
</Accordion.ItemBody>
|
|
173
|
-
</Accordion.Item>
|
|
174
|
-
);
|
|
175
|
-
case 'details': {
|
|
176
|
-
const commit = debug ? undefined : selectedCommit;
|
|
177
|
-
return (
|
|
178
|
-
// With nothing selected the section stays as a plain, closed row.
|
|
179
|
-
<Accordion.Item key={section.id} item={section} disabled={!commit} classNames='border-x-0'>
|
|
180
|
-
<Accordion.ItemHeader hover>
|
|
181
|
-
<span className='block truncate text-sm text-description'>
|
|
182
|
-
{commit?.message ?? t('trace-details.label')}
|
|
183
|
-
</span>
|
|
184
|
-
</Accordion.ItemHeader>
|
|
185
|
-
{commit && (
|
|
186
|
-
<Accordion.ItemBody classNames='p-0'>
|
|
187
|
-
<JsonHighlighter data={details[commit.id] ?? commit} classNames='max-h-[20lh] text-xs' />
|
|
188
|
-
</Accordion.ItemBody>
|
|
189
|
-
)}
|
|
190
|
-
</Accordion.Item>
|
|
191
|
-
);
|
|
192
|
-
}
|
|
196
|
+
</Accordion.ItemContent>
|
|
197
|
+
)}
|
|
198
|
+
</Accordion.Item>
|
|
199
|
+
);
|
|
193
200
|
}
|
|
194
|
-
}
|
|
195
|
-
}
|
|
201
|
+
}
|
|
202
|
+
})}
|
|
196
203
|
</Accordion.Root>
|
|
197
|
-
</Panel.
|
|
204
|
+
</Panel.Body>
|
|
198
205
|
</Panel.Root>
|
|
199
206
|
);
|
|
200
207
|
},
|
|
@@ -208,9 +215,9 @@ const SECTIONS: TraceSection[] = [{ id: 'processes' }, { id: 'trace' }, { id: 'd
|
|
|
208
215
|
|
|
209
216
|
const NO_SELECTION: readonly string[] = [];
|
|
210
217
|
|
|
211
|
-
type ProcessTreeContainerProps = ThemedClassName<
|
|
218
|
+
type ProcessTreeContainerProps = Util.ThemedClassName<
|
|
212
219
|
Pick<ProcessTreeProps, 'resolveLabel' | 'selected' | 'onSelectedChange' | 'onProcessTerminate'> & {
|
|
213
|
-
processes: readonly Process.
|
|
220
|
+
processes: readonly Process.Process[];
|
|
214
221
|
environments: readonly ProcessEnvironment[];
|
|
215
222
|
}
|
|
216
223
|
>;
|
|
@@ -10,12 +10,12 @@ import { Obj } from '@dxos/echo';
|
|
|
10
10
|
import { SpaceId, URI } from '@dxos/keys';
|
|
11
11
|
|
|
12
12
|
import { makeProcess } from '../../testing/index.ts';
|
|
13
|
-
|
|
14
13
|
import {
|
|
15
14
|
ALL_PROCESS_ENVIRONMENTS,
|
|
16
15
|
DEFAULT_PROCESS_ENVIRONMENTS,
|
|
17
16
|
ProcessEnvironment,
|
|
18
17
|
filterProcesses,
|
|
18
|
+
filterProcessesBySelection,
|
|
19
19
|
filterTraceMessages,
|
|
20
20
|
parseProcessEnvironments,
|
|
21
21
|
processEnvironment,
|
|
@@ -99,7 +99,7 @@ describe('parseProcessEnvironments', () => {
|
|
|
99
99
|
});
|
|
100
100
|
});
|
|
101
101
|
|
|
102
|
-
const makeInfo = (name: string, environment: Process.Environment): Process.
|
|
102
|
+
const makeInfo = (name: string, environment: Process.Environment): Process.Process =>
|
|
103
103
|
makeProcess({ pid: Process.ID.make(name), name, state: Process.State.RUNNING, environment });
|
|
104
104
|
|
|
105
105
|
describe('filterTraceMessages', () => {
|
|
@@ -135,3 +135,32 @@ describe('filterTraceMessages', () => {
|
|
|
135
135
|
expect(filterTraceMessages(messages, ['z'])).toEqual([]);
|
|
136
136
|
});
|
|
137
137
|
});
|
|
138
|
+
|
|
139
|
+
describe('filterProcessesBySelection', () => {
|
|
140
|
+
const process = (pid: string, parentPid?: string): Process.Process =>
|
|
141
|
+
makeProcess({
|
|
142
|
+
pid: Process.ID.make(pid),
|
|
143
|
+
parentPid: parentPid === undefined ? null : Process.ID.make(parentPid),
|
|
144
|
+
name: pid,
|
|
145
|
+
state: Process.State.RUNNING,
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test('an empty selection is no filter', ({ expect }) => {
|
|
149
|
+
const processes = [process('a'), process('b')];
|
|
150
|
+
expect(filterProcessesBySelection(processes, [])).toBe(processes);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test('a selected process keeps its descendants and drops the rest', ({ expect }) => {
|
|
154
|
+
const processes = [process('agent'), process('tool', 'agent'), process('nested', 'tool'), process('other')];
|
|
155
|
+
expect(filterProcessesBySelection(processes, ['agent']).map((info) => info.pid)).toEqual([
|
|
156
|
+
'agent',
|
|
157
|
+
'tool',
|
|
158
|
+
'nested',
|
|
159
|
+
]);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test('a cyclic parent chain terminates', ({ expect }) => {
|
|
163
|
+
const processes = [process('a', 'b'), process('b', 'a')];
|
|
164
|
+
expect(filterProcessesBySelection(processes, ['z'])).toEqual([]);
|
|
165
|
+
});
|
|
166
|
+
});
|
|
@@ -10,6 +10,7 @@ import type * as Trace from '@dxos/compute/Trace';
|
|
|
10
10
|
*
|
|
11
11
|
* Bucketed from the inherited {@link Process.Environment}, so filtering keeps whole subtrees together.
|
|
12
12
|
*/
|
|
13
|
+
// TODO(burdon): Factor out.
|
|
13
14
|
export const ProcessEnvironment = {
|
|
14
15
|
/** Not scoped to a space: layout, navigation, identity, settings, space lifecycle. */
|
|
15
16
|
App: 'app',
|
|
@@ -43,7 +44,7 @@ const ENVIRONMENT_ICONS: Record<ProcessEnvironment, string> = {
|
|
|
43
44
|
export const environmentIcon = (environment: ProcessEnvironment): string => ENVIRONMENT_ICONS[environment];
|
|
44
45
|
|
|
45
46
|
/** `conversation` outranks `space`, since a conversation always runs inside one. */
|
|
46
|
-
export const processEnvironment = (process: Process.
|
|
47
|
+
export const processEnvironment = (process: Process.Process): ProcessEnvironment => {
|
|
47
48
|
if (process.environment.conversation !== undefined) {
|
|
48
49
|
return ProcessEnvironment.Conversation;
|
|
49
50
|
}
|
|
@@ -54,9 +55,9 @@ export const processEnvironment = (process: Process.Info): ProcessEnvironment =>
|
|
|
54
55
|
};
|
|
55
56
|
|
|
56
57
|
export const filterProcesses = (
|
|
57
|
-
processes: readonly Process.
|
|
58
|
+
processes: readonly Process.Process[],
|
|
58
59
|
selected: readonly ProcessEnvironment[],
|
|
59
|
-
): readonly Process.
|
|
60
|
+
): readonly Process.Process[] => {
|
|
60
61
|
// Identity when nothing is excluded, so `ProcessTree`'s `React.memo` still holds.
|
|
61
62
|
if (selected.length === ALL_PROCESS_ENVIRONMENTS.length) {
|
|
62
63
|
return processes;
|
|
@@ -89,6 +90,30 @@ export const parseProcessEnvironments = (selected: readonly string[] | undefined
|
|
|
89
90
|
* runs as a child of the process it serves, so its events are that process's work. An empty
|
|
90
91
|
* selection is no filter.
|
|
91
92
|
*/
|
|
93
|
+
/**
|
|
94
|
+
* Narrows the process tree to the selected processes and their descendants, the process-side twin of
|
|
95
|
+
* {@link filterTraceMessages}: a running child of a picked process is that process's live work.
|
|
96
|
+
*/
|
|
97
|
+
export const filterProcessesBySelection = (
|
|
98
|
+
processes: readonly Process.Process[],
|
|
99
|
+
selected: readonly string[],
|
|
100
|
+
): readonly Process.Process[] => {
|
|
101
|
+
if (selected.length === 0) {
|
|
102
|
+
return processes;
|
|
103
|
+
}
|
|
104
|
+
const parentByPid = new Map<string, string | null>(processes.map((process) => [process.pid, process.parentPid]));
|
|
105
|
+
const selection = new Set(selected);
|
|
106
|
+
const isSelected = (pid: string, depth = 0): boolean => {
|
|
107
|
+
if (selection.has(pid)) {
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
const parent = parentByPid.get(pid);
|
|
111
|
+
// Bounded so a cyclic parent chain (a corrupt tree) terminates.
|
|
112
|
+
return parent !== null && parent !== undefined && depth < processes.length && isSelected(parent, depth + 1);
|
|
113
|
+
};
|
|
114
|
+
return processes.filter((process) => isSelected(process.pid));
|
|
115
|
+
};
|
|
116
|
+
|
|
92
117
|
export const filterTraceMessages = (
|
|
93
118
|
messages: readonly Trace.Message[],
|
|
94
119
|
selected: readonly string[],
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# Trace Execution Graph — Specification
|
|
2
2
|
|
|
3
3
|
This document specifies the pure-data layer that powers the `TracePanel` Timeline view.
|
|
4
|
-
Source: `packages/
|
|
4
|
+
Source: `packages/ui/react-ui-trace/src/execution-graph/`.
|
|
5
5
|
|
|
6
6
|
## 1. Purpose & scope
|
|
7
7
|
|
|
8
8
|
The module converts a flat list of `Trace.Message` records (and a snapshot of active
|
|
9
9
|
processes) into a Mercurial/Git-style commit graph consumable by the
|
|
10
|
-
|
|
10
|
+
`Timeline` component (`../components/Timeline`).
|
|
11
11
|
|
|
12
12
|
In scope:
|
|
13
13
|
|
|
@@ -25,7 +25,7 @@ Out of scope:
|
|
|
25
25
|
- Filtering / search of commits.
|
|
26
26
|
- Mutating, ack-ing, or replaying trace events.
|
|
27
27
|
|
|
28
|
-
Inputs: `traceMessages: Trace.Message[]`, `activeProcesses?: Process.
|
|
28
|
+
Inputs: `traceMessages: Trace.Message[]`, `activeProcesses?: Process.Process[]`, `eventLimit?: number`,
|
|
29
29
|
`spanTimeoutMs?: number`, `now?: number`.
|
|
30
30
|
Output: `ExecutionGraph = { branches, commits, spanTree, details }`.
|
|
31
31
|
|
|
@@ -57,7 +57,7 @@ forwarded as `SpanMeta` for downstream display but never branch or anchor logic.
|
|
|
57
57
|
`isEphemeral` is preserved on `FlatEvent` but is not consulted; ephemeral events
|
|
58
58
|
participate exactly like persistent events.
|
|
59
59
|
|
|
60
|
-
### 2.2 `Process.
|
|
60
|
+
### 2.2 `Process.Process` (active processes)
|
|
61
61
|
|
|
62
62
|
Defined in `packages/core/compute/compute/src/Process.ts#L365`.
|
|
63
63
|
Optional input via `activeProcesses` (defaults to `[]`). Fields the builder reads:
|
|
@@ -106,7 +106,7 @@ export type ExecutionGraphDetailsMap = Record<string, Trace.FlatEvent | undefine
|
|
|
106
106
|
|
|
107
107
|
### 3.2 `Commit` (set fields only)
|
|
108
108
|
|
|
109
|
-
From `packages/ui/react-ui-
|
|
109
|
+
From `packages/ui/react-ui-trace/src/components/Timeline/Timeline.tsx#L40`. The
|
|
110
110
|
builder sets:
|
|
111
111
|
|
|
112
112
|
- `id` — see §9.
|
|
@@ -681,13 +681,13 @@ Each entry: **scenario → expected output → rationale**.
|
|
|
681
681
|
|
|
682
682
|
| Symbol | File | Line |
|
|
683
683
|
| ------------------------------------------------------- | ---------------------------------------------------------------------------- | ----------- |
|
|
684
|
-
| `buildSpanTree` | `packages/
|
|
684
|
+
| `buildSpanTree` | `packages/ui/react-ui-trace/src/execution-graph/span-tree.ts` | L112 |
|
|
685
685
|
| `applyEventLimit` | same | L211 |
|
|
686
686
|
| `makeTimeoutEndEvent` / `DEFAULT_SPAN_TIMEOUT_MS` | same | L252 / L13 |
|
|
687
687
|
| `ROOT_SPAN_ID` | same | L12 |
|
|
688
688
|
| `BEGIN_EVENT_TYPES`/`END_EVENT_TYPES` | same | L47 |
|
|
689
689
|
| `walkSpanTree`/`flattenSpanTree` | same | L245 |
|
|
690
|
-
| `buildExecutionGraph` | `packages/
|
|
690
|
+
| `buildExecutionGraph` | `packages/ui/react-ui-trace/src/execution-graph/execution-graph.ts` | L91 |
|
|
691
691
|
| `presentEvent` | same | L118 |
|
|
692
692
|
| `isCollapsibleSpan` | same | L206 |
|
|
693
693
|
| `spanTreeToCommits` | same | L239 |
|
|
@@ -696,10 +696,10 @@ Each entry: **scenario → expected output → rationale**.
|
|
|
696
696
|
| `CommitSelector` | same | L446 |
|
|
697
697
|
| `GraphBuilder` / `doctor` | same | L579 / L610 |
|
|
698
698
|
| `Trace.Meta` / `Trace.flatten` | `packages/core/compute/compute/src/Trace.ts` | L102 / L178 |
|
|
699
|
-
| `Process.
|
|
699
|
+
| `Process.Process` / `Process.State` | `packages/core/compute/compute/src/Process.ts` | L365 / L321 |
|
|
700
700
|
| `AGENT_PROCESS_KEY` | `packages/core/compute/functions-runtime/src/agent-service/agent-process.ts` | L54 |
|
|
701
|
-
| `Commit` type | `packages/ui/react-ui-
|
|
702
|
-
| `TracePanel` `useExecutionGraph` / default `eventLimit` | `packages/
|
|
701
|
+
| `Commit` type | `packages/ui/react-ui-trace/src/components/Timeline/Timeline.tsx` | L40 |
|
|
702
|
+
| `TracePanel` `useExecutionGraph` / default `eventLimit` | `packages/ui/react-ui-trace/src/hooks/useExecutionGraph.ts` | L138 / L153 |
|
|
703
703
|
|
|
704
704
|
## Appendix B: Visual contract examples
|
|
705
705
|
|
|
@@ -6,14 +6,13 @@ import * as Effect from 'effect/Effect';
|
|
|
6
6
|
import * as Option from 'effect/Option';
|
|
7
7
|
import { describe, test } from 'vitest';
|
|
8
8
|
|
|
9
|
-
import { RUN_AGAIN_ERROR_CODE, RUN_AGAIN_MESSAGE } from '@dxos/compute';
|
|
10
9
|
import * as Process from '@dxos/compute/Process';
|
|
11
10
|
import * as Trace from '@dxos/compute/Trace';
|
|
12
11
|
import { Annotation } from '@dxos/echo';
|
|
13
12
|
import { EntityId, URI } from '@dxos/keys';
|
|
14
13
|
import { LogLevel } from '@dxos/log';
|
|
15
|
-
import { type Commit, renderTimelineAscii } from '../components/Timeline/index.ts';
|
|
16
14
|
|
|
15
|
+
import { type Commit, renderTimelineAscii } from '../components/Timeline/index.ts';
|
|
17
16
|
import { CommitSelector, buildExecutionGraph } from './execution-graph.ts';
|
|
18
17
|
import { collectTraceEvents, withMeta } from './testing/index.ts';
|
|
19
18
|
|
|
@@ -189,8 +188,8 @@ const MESSAGE_ID = '01HQ0000000000000000000000';
|
|
|
189
188
|
const SHIMMER_EFFECT_TAG = 'effect:shimmer';
|
|
190
189
|
|
|
191
190
|
const makeActiveProcess = (
|
|
192
|
-
overrides: Partial<Process.
|
|
193
|
-
): Process.
|
|
191
|
+
overrides: Partial<Process.Process> & Pick<Process.Process, 'pid' | 'key' | 'state'>,
|
|
192
|
+
): Process.Process => ({
|
|
194
193
|
parentPid: null,
|
|
195
194
|
params: { name: null, annotations: {} },
|
|
196
195
|
environment: {},
|
|
@@ -930,7 +929,7 @@ describe('buildExecutionGraph scenarios', () => {
|
|
|
930
929
|
name: 'Sync Google Mail',
|
|
931
930
|
outcome: 'failure',
|
|
932
931
|
error: 'Run again',
|
|
933
|
-
errorCode: RUN_AGAIN_ERROR_CODE,
|
|
932
|
+
errorCode: Process.RUN_AGAIN_ERROR_CODE,
|
|
934
933
|
});
|
|
935
934
|
}),
|
|
936
935
|
),
|
|
@@ -956,7 +955,7 @@ describe('buildExecutionGraph scenarios', () => {
|
|
|
956
955
|
key: 'sync',
|
|
957
956
|
name: 'Sync Google Mail',
|
|
958
957
|
outcome: 'failure',
|
|
959
|
-
error: RUN_AGAIN_MESSAGE,
|
|
958
|
+
error: Process.RUN_AGAIN_MESSAGE,
|
|
960
959
|
});
|
|
961
960
|
}),
|
|
962
961
|
),
|