@principal-ai/principal-view-react 0.16.21 → 0.16.23
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/dist/components/GraphRenderer.js.map +1 -1
- package/dist/components/session-events/SessionEventFeed.d.ts.map +1 -1
- package/dist/components/session-events/SessionEventFeed.js +10 -3
- package/dist/components/session-events/SessionEventFeed.js.map +1 -1
- package/dist/graphify/consolidated.d.ts +170 -0
- package/dist/graphify/consolidated.d.ts.map +1 -0
- package/dist/graphify/consolidated.js +11 -0
- package/dist/graphify/consolidated.js.map +1 -0
- package/dist/graphify/index.d.ts +9 -0
- package/dist/graphify/index.d.ts.map +1 -0
- package/dist/graphify/index.js +8 -0
- package/dist/graphify/index.js.map +1 -0
- package/dist/graphify/types.d.ts +141 -0
- package/dist/graphify/types.d.ts.map +1 -0
- package/dist/graphify/types.js +8 -0
- package/dist/graphify/types.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/subsystem/SubsystemComponentGraph.d.ts +28 -0
- package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -0
- package/dist/subsystem/SubsystemComponentGraph.js +365 -0
- package/dist/subsystem/SubsystemComponentGraph.js.map +1 -0
- package/dist/subsystem/model.d.ts +131 -0
- package/dist/subsystem/model.d.ts.map +1 -0
- package/dist/subsystem/model.js +296 -0
- package/dist/subsystem/model.js.map +1 -0
- package/dist/subsystem/nodes.d.ts +28 -0
- package/dist/subsystem/nodes.d.ts.map +1 -0
- package/dist/subsystem/nodes.js +230 -0
- package/dist/subsystem/nodes.js.map +1 -0
- package/dist/utils/elkLayout.d.ts +19 -0
- package/dist/utils/elkLayout.d.ts.map +1 -1
- package/dist/utils/elkLayout.js +72 -9
- package/dist/utils/elkLayout.js.map +1 -1
- package/package.json +3 -3
- package/src/components/GraphRenderer.tsx +2 -2
- package/src/components/session-events/SessionEventFeed.tsx +20 -3
- package/src/graphify/consolidated.ts +202 -0
- package/src/graphify/index.ts +36 -0
- package/src/graphify/types.ts +164 -0
- package/src/index.ts +31 -0
- package/src/stories/SubsystemComponentGraph.stories.tsx +761 -0
- package/src/subsystem/SubsystemComponentGraph.tsx +585 -0
- package/src/subsystem/model.test.ts +81 -0
- package/src/subsystem/model.ts +434 -0
- package/src/subsystem/nodes.tsx +353 -0
- package/src/utils/elkLayout.ts +98 -9
|
@@ -0,0 +1,761 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import '@xyflow/react/dist/style.css';
|
|
3
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
4
|
+
import { ThemeProvider, defaultTheme, defaultEditorTheme } from '@principal-ade/industry-theme';
|
|
5
|
+
import { SubsystemComponentGraph } from '../subsystem/SubsystemComponentGraph';
|
|
6
|
+
import type { SubsystemComponent, SubsystemComponentEdge } from '../subsystem/model';
|
|
7
|
+
import type { GraphifyComponentDetail } from '../graphify';
|
|
8
|
+
const meta = {
|
|
9
|
+
title: 'Subsystem/Component Graph',
|
|
10
|
+
component: SubsystemComponentGraph,
|
|
11
|
+
parameters: {
|
|
12
|
+
layout: 'padded',
|
|
13
|
+
},
|
|
14
|
+
tags: ['autodocs'],
|
|
15
|
+
decorators: [
|
|
16
|
+
(Story) => (
|
|
17
|
+
<ThemeProvider theme={defaultEditorTheme}>
|
|
18
|
+
<Story />
|
|
19
|
+
</ThemeProvider>
|
|
20
|
+
),
|
|
21
|
+
],
|
|
22
|
+
} satisfies Meta<typeof SubsystemComponentGraph>;
|
|
23
|
+
|
|
24
|
+
export default meta;
|
|
25
|
+
type Story = StoryObj<typeof meta>;
|
|
26
|
+
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// Build a subsystem graph from a compact spec - helpers
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
function components(
|
|
31
|
+
spec: Array<[id: string, name: string, kind: SubsystemComponent['kind'], file: string, purl: string, purpose?: string, symbol?: string, detail?: GraphifyComponentDetail]>,
|
|
32
|
+
): SubsystemComponent[] {
|
|
33
|
+
return spec.map(([id, name, kind, file, purl, purpose, symbol, detail]) => ({
|
|
34
|
+
id,
|
|
35
|
+
name,
|
|
36
|
+
kind,
|
|
37
|
+
file,
|
|
38
|
+
purl,
|
|
39
|
+
purpose,
|
|
40
|
+
symbol,
|
|
41
|
+
detail,
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function edges(
|
|
46
|
+
spec: Array<[from: string, to: string, mechanism: SubsystemComponentEdge['mechanism'], refs?: string[]]>,
|
|
47
|
+
): SubsystemComponentEdge[] {
|
|
48
|
+
return spec.map(([from, to, mechanism, refs], i) => ({
|
|
49
|
+
id: `e${i}`,
|
|
50
|
+
from,
|
|
51
|
+
to,
|
|
52
|
+
mechanism,
|
|
53
|
+
refs,
|
|
54
|
+
}));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
// Minimal: two nodes, one edge — the simplest possible label layout test.
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
const twoNodeComponents = components([
|
|
61
|
+
['src', 'TranscriptParser', 'class', 'transcript.ts', 'pkg:github/principal-ai/agent-monitoring', 'parses session records'],
|
|
62
|
+
['dst', 'SessionReader', 'class', 'SessionReader.ts', 'pkg:github/principal-ai/agent-monitoring', 'normalizes sessions into events'],
|
|
63
|
+
]);
|
|
64
|
+
|
|
65
|
+
const twoNodeEdges = edges([
|
|
66
|
+
['src', 'dst', 'imports'],
|
|
67
|
+
]);
|
|
68
|
+
|
|
69
|
+
function TwoNodeDemo({ showEdgeLabels = true }: { showEdgeLabels?: boolean }) {
|
|
70
|
+
const [selected, setSelected] = useState<string | null>(null);
|
|
71
|
+
const [selectedEdge, setSelectedEdge] = useState<SubsystemComponentEdge | null>(null);
|
|
72
|
+
return (
|
|
73
|
+
<div style={{ width: '100%', maxWidth: 640, height: 360, display: 'flex', flexDirection: 'column' }}>
|
|
74
|
+
<SubsystemComponentGraph
|
|
75
|
+
components={twoNodeComponents}
|
|
76
|
+
edges={twoNodeEdges}
|
|
77
|
+
onSelect={(id) => setSelected(id)}
|
|
78
|
+
onEdgeSelect={(e) => setSelectedEdge(e)}
|
|
79
|
+
showEdgeLabels={showEdgeLabels}
|
|
80
|
+
/>
|
|
81
|
+
<div style={{ marginTop: 8, fontFamily: 'monospace', fontSize: 12, color: '#aaa' }}>
|
|
82
|
+
{selectedEdge
|
|
83
|
+
? `edge: ${selectedEdge.from} --${selectedEdge.mechanism}--> ${selectedEdge.to}`
|
|
84
|
+
: selected
|
|
85
|
+
? `selected: ${selected}`
|
|
86
|
+
: 'click a component or edge to select it'}
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export const TwoNodeSingleEdge: Story = {
|
|
93
|
+
render: () => <TwoNodeDemo />,
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export const TwoNodeNoLabels: Story = {
|
|
97
|
+
render: () => <TwoNodeDemo showEdgeLabels={false} />,
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
// Playground: control the number of nodes in left and right layers.
|
|
102
|
+
// All left nodes connect to all right nodes (fan-in / fan-out).
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
function PlaygroundDemo({ leftCount, rightCount, showEdgeLabels }: { leftCount: number; rightCount: number; showEdgeLabels: boolean }) {
|
|
105
|
+
const comps: SubsystemComponent[] = [];
|
|
106
|
+
const edgeList: SubsystemComponentEdge[] = [];
|
|
107
|
+
|
|
108
|
+
for (let i = 0; i < leftCount; i++) {
|
|
109
|
+
comps.push({
|
|
110
|
+
id: `l${i}`,
|
|
111
|
+
name: `Left${i}`,
|
|
112
|
+
kind: 'class',
|
|
113
|
+
file: `left${i}.ts`,
|
|
114
|
+
purl: 'pkg:github/principal-ai/playground',
|
|
115
|
+
purpose: `left layer node ${i}`,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
for (let i = 0; i < rightCount; i++) {
|
|
119
|
+
comps.push({
|
|
120
|
+
id: `r${i}`,
|
|
121
|
+
name: `Right${i}`,
|
|
122
|
+
kind: 'class',
|
|
123
|
+
file: `right${i}.ts`,
|
|
124
|
+
purl: 'pkg:github/principal-ai/playground',
|
|
125
|
+
purpose: `right layer node ${i}`,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
let ei = 0;
|
|
130
|
+
for (let i = 0; i < leftCount; i++) {
|
|
131
|
+
for (let j = 0; j < rightCount; j++) {
|
|
132
|
+
edgeList.push({ id: `e${ei++}`, from: `l${i}`, to: `r${j}`, mechanism: 'imports' });
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return (
|
|
137
|
+
<div style={{ width: '100%', height: 400 }}>
|
|
138
|
+
<SubsystemComponentGraph components={comps} edges={edgeList} showEdgeLabels={showEdgeLabels} />
|
|
139
|
+
</div>
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export const Playground: Story = {
|
|
144
|
+
render: (args) => <PlaygroundDemo {...args} />,
|
|
145
|
+
args: {
|
|
146
|
+
leftCount: 1,
|
|
147
|
+
rightCount: 1,
|
|
148
|
+
showEdgeLabels: true,
|
|
149
|
+
},
|
|
150
|
+
argTypes: {
|
|
151
|
+
leftCount: { control: { type: 'number', min: 0, max: 6, step: 1 } },
|
|
152
|
+
rightCount: { control: { type: 'number', min: 0, max: 6, step: 1 } },
|
|
153
|
+
showEdgeLabels: { control: 'boolean' },
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
// ---------------------------------------------------------------------------
|
|
158
|
+
// Primary: opencode V2 event reader subsystem (agent-monitoring) + consumers
|
|
159
|
+
// ---------------------------------------------------------------------------
|
|
160
|
+
const readerDetail: GraphifyComponentDetail = {
|
|
161
|
+
kind: 'class',
|
|
162
|
+
methods: [
|
|
163
|
+
{ nodeId: 'm1', name: 'normalize', returnType: 'SessionEvent[]' },
|
|
164
|
+
{ nodeId: 'm2', name: 'readSession', parameters: ['string'], returnType: 'SessionRecord' },
|
|
165
|
+
{ nodeId: 'm3', name: 'toUniversalEvents', returnType: 'UniversalEvent[]' },
|
|
166
|
+
],
|
|
167
|
+
properties: [{ name: 'sessionId', type: 'string' }],
|
|
168
|
+
extends: [],
|
|
169
|
+
implements: ['SessionReaderLike'],
|
|
170
|
+
instantiations: [{ nodeId: 'caller1', name: 'capture-session' }],
|
|
171
|
+
references: [{ nodeId: 'ref1', name: 'supported-agents', context: 'type' }],
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const v2ReaderComponents = components([
|
|
175
|
+
['capture', 'capture script', 'script', 'scripts/capture-session.ts', 'pkg:github/principal-ai/agent-monitoring', 'captures a real session for fixtures'],
|
|
176
|
+
['transcript', 'transcript', 'module', 'transcript.ts', 'pkg:github/principal-ai/agent-monitoring', 'parses session records + type guards'],
|
|
177
|
+
['paths', 'paths', 'module', 'paths.ts', 'pkg:github/principal-ai/agent-monitoring', 'extracts tool names + file paths'],
|
|
178
|
+
['reader', 'SessionReader', 'class', 'SessionReader.ts', 'pkg:github/principal-ai/agent-monitoring', 'normalizes a session into universal events', 'SessionReader.normalize', readerDetail],
|
|
179
|
+
['registry', 'supported-agents', 'registry', 'supported-agents.ts', 'pkg:github/principal-ai/agent-monitoring', 'registry of supported agents (the shared seam)', 'registerAgent'],
|
|
180
|
+
]);
|
|
181
|
+
|
|
182
|
+
const v2ReaderEdges = edges([
|
|
183
|
+
['transcript', 'reader', 'imports'],
|
|
184
|
+
['paths', 'reader', 'imports'],
|
|
185
|
+
['capture', 'reader', 'calls'],
|
|
186
|
+
['reader', 'registry', 'registers-into', ['supported-agents.ts']],
|
|
187
|
+
// Consumer packages - cross-package edges leave the subgraph
|
|
188
|
+
['reader', 'trail-viewer-host', 'imports'],
|
|
189
|
+
['reader', 'core-sessions', 'imports'],
|
|
190
|
+
['reader', 'cli-session', 'imports'],
|
|
191
|
+
]);
|
|
192
|
+
|
|
193
|
+
function V2ReaderDemo() {
|
|
194
|
+
const [selected, setSelected] = useState<string | null>(null);
|
|
195
|
+
const [selectedEdge, setSelectedEdge] = useState<SubsystemComponentEdge | null>(null);
|
|
196
|
+
return (
|
|
197
|
+
<div style={{ width: '100%', maxWidth: 1280, height: 560, display: 'flex', flexDirection: 'column' }}>
|
|
198
|
+
<SubsystemComponentGraph
|
|
199
|
+
components={v2ReaderComponents}
|
|
200
|
+
edges={v2ReaderEdges}
|
|
201
|
+
onSelect={(id) => setSelected(id)}
|
|
202
|
+
onEdgeSelect={(e) => setSelectedEdge(e)}
|
|
203
|
+
/>
|
|
204
|
+
<div style={{ marginTop: 8, fontFamily: 'monospace', fontSize: 12, color: '#aaa' }}>
|
|
205
|
+
{selectedEdge
|
|
206
|
+
? `edge: ${selectedEdge.from} --${selectedEdge.mechanism}--> ${selectedEdge.to}${selectedEdge.refs?.length ? ` [refs: ${selectedEdge.refs.join(', ')}]` : ''}`
|
|
207
|
+
: selected
|
|
208
|
+
? `selected: ${selected}`
|
|
209
|
+
: 'click a component or edge to select it'}
|
|
210
|
+
</div>
|
|
211
|
+
</div>
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export const V2ReaderSubsystem: Story = {
|
|
216
|
+
render: () => <V2ReaderDemo />,
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
// ---------------------------------------------------------------------------
|
|
220
|
+
// Empty state
|
|
221
|
+
// ---------------------------------------------------------------------------
|
|
222
|
+
export const Empty: Story = {
|
|
223
|
+
render: () => (
|
|
224
|
+
<div style={{ width: '100%' }}>
|
|
225
|
+
<SubsystemComponentGraph components={[]} edges={[]} />
|
|
226
|
+
</div>
|
|
227
|
+
),
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
// ---------------------------------------------------------------------------
|
|
231
|
+
// Single package (no consumers) - the investigate-and-pin pattern.
|
|
232
|
+
// Conveys the idea via LAYERS: readers (input) → accumulator (processing).
|
|
233
|
+
// ---------------------------------------------------------------------------
|
|
234
|
+
const investigateOnlyComponents: SubsystemComponent[] = [
|
|
235
|
+
{
|
|
236
|
+
id: 'v1',
|
|
237
|
+
name: 'V1EventBridgeProcessor',
|
|
238
|
+
kind: 'class',
|
|
239
|
+
file: 'src/event-processing/V1EventBridge.ts',
|
|
240
|
+
purl: 'pkg:github/principal-ai/agent-monitoring',
|
|
241
|
+
purpose: 'normalizes V1 DB rows into universal events',
|
|
242
|
+
symbol: 'V1EventBridgeProcessor',
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
id: 'v2',
|
|
246
|
+
name: 'V2EventBridgeProcessor',
|
|
247
|
+
kind: 'class',
|
|
248
|
+
file: 'src/event-processing/V2EventBridge.ts',
|
|
249
|
+
purl: 'pkg:github/principal-ai/agent-monitoring',
|
|
250
|
+
purpose: 'normalizes V2 durable events into universal events',
|
|
251
|
+
symbol: 'V2EventBridgeProcessor',
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
id: 'input',
|
|
255
|
+
name: 'RepoNormalizedUniversalAgentSessionEvent',
|
|
256
|
+
kind: 'type',
|
|
257
|
+
file: 'types/RepoNormalizedUniversalAgentSessionEvent.ts',
|
|
258
|
+
purl: 'pkg:github/principal-ai/agent-monitoring',
|
|
259
|
+
purpose: 'the subsystem\u2019s input type — a repo-normalized universal event the readers produce and the accumulator consumes',
|
|
260
|
+
symbol: 'RepoNormalizedUniversalAgentSessionEvent',
|
|
261
|
+
detail: {
|
|
262
|
+
kind: 'type',
|
|
263
|
+
properties: [
|
|
264
|
+
{ name: 'eventType', type: 'NormalizedEventType' },
|
|
265
|
+
{ name: 'sessionId', type: 'string' },
|
|
266
|
+
{ name: 'timestamp', type: 'number' },
|
|
267
|
+
],
|
|
268
|
+
usedBy: [{ nodeId: 'acc', name: 'accumulateToAgentSessionEvents', context: 'parameter_type' }],
|
|
269
|
+
implementors: [],
|
|
270
|
+
} satisfies GraphifyComponentDetail,
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
id: 'acc',
|
|
274
|
+
name: 'accumulateToAgentSessionEvents',
|
|
275
|
+
kind: 'function',
|
|
276
|
+
file: 'src/event-processing/accumulator.ts',
|
|
277
|
+
purl: 'pkg:github/principal-ai/agent-monitoring',
|
|
278
|
+
purpose: 'accumulates normalized events into agent session events',
|
|
279
|
+
symbol: 'accumulateToAgentSessionEvents',
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
id: 'out',
|
|
283
|
+
name: 'AgentSessionEvent',
|
|
284
|
+
kind: 'type',
|
|
285
|
+
file: 'src/event-processing/accumulator.ts',
|
|
286
|
+
purl: 'pkg:github/principal-ai/agent-monitoring',
|
|
287
|
+
purpose: 'the subsystem\u2019s output type — an accumulated agent-session event',
|
|
288
|
+
symbol: 'AgentSessionEvent',
|
|
289
|
+
detail: {
|
|
290
|
+
kind: 'type',
|
|
291
|
+
properties: [
|
|
292
|
+
{ name: 'sessionName', type: 'string' },
|
|
293
|
+
{ name: 'operation', type: 'AgentSessionEventOperation' },
|
|
294
|
+
{ name: 'description', type: 'string' },
|
|
295
|
+
],
|
|
296
|
+
usedBy: [{ nodeId: 'acc', name: 'accumulateToAgentSessionEvents', context: 'return_type' }],
|
|
297
|
+
implementors: [],
|
|
298
|
+
} satisfies GraphifyComponentDetail,
|
|
299
|
+
},
|
|
300
|
+
];
|
|
301
|
+
|
|
302
|
+
const investigateOnlyEdges = edges([
|
|
303
|
+
// Concept-level data flow — the LLM's semantic intent.
|
|
304
|
+
['v1', 'input', 'produces'],
|
|
305
|
+
['v2', 'input', 'produces'],
|
|
306
|
+
['input', 'acc', 'feeds'],
|
|
307
|
+
['acc', 'out', 'produces'],
|
|
308
|
+
]);
|
|
309
|
+
|
|
310
|
+
export const InvestigateOnly: Story = {
|
|
311
|
+
render: () => (
|
|
312
|
+
<div style={{ width: '100%', height: 400 }}>
|
|
313
|
+
<SubsystemComponentGraph components={investigateOnlyComponents} edges={investigateOnlyEdges} />
|
|
314
|
+
</div>
|
|
315
|
+
),
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
/** Same pipeline with a narrower `maxNodeWidth` (140) — long names wrap. */
|
|
319
|
+
export const NarrowMaxWidth: Story = {
|
|
320
|
+
render: () => (
|
|
321
|
+
<div style={{ width: '100%', height: 400 }}>
|
|
322
|
+
<SubsystemComponentGraph
|
|
323
|
+
components={investigateOnlyComponents}
|
|
324
|
+
edges={investigateOnlyEdges}
|
|
325
|
+
maxNodeWidth={140}
|
|
326
|
+
/>
|
|
327
|
+
</div>
|
|
328
|
+
),
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
// ---------------------------------------------------------------------------
|
|
332
|
+
// Naming-convention wrap check — one node per common symbol convention.
|
|
333
|
+
// ---------------------------------------------------------------------------
|
|
334
|
+
const namingConventionComponents: SubsystemComponent[] = [
|
|
335
|
+
{
|
|
336
|
+
id: 'camel',
|
|
337
|
+
name: 'accumulateToAgentSessionEvents',
|
|
338
|
+
kind: 'function',
|
|
339
|
+
file: 'src/event-processing/accumulator.ts',
|
|
340
|
+
purl: 'pkg:github/principal-ai/agent-monitoring',
|
|
341
|
+
symbol: 'accumulateToAgentSessionEvents',
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
id: 'snake',
|
|
345
|
+
name: 'repo_normalized_universal_event',
|
|
346
|
+
kind: 'type',
|
|
347
|
+
file: 'src/event-processing/repo_normalized.ts',
|
|
348
|
+
purl: 'pkg:github/principal-ai/agent-monitoring',
|
|
349
|
+
symbol: 'repo_normalized_universal_event',
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
id: 'pascal',
|
|
353
|
+
name: 'RepoNormalizedUniversalAgentSessionEvent',
|
|
354
|
+
kind: 'class',
|
|
355
|
+
file: 'src/event-processing/RepoNormalized.ts',
|
|
356
|
+
purl: 'pkg:github/principal-ai/agent-monitoring',
|
|
357
|
+
symbol: 'RepoNormalizedUniversalAgentSessionEvent',
|
|
358
|
+
},
|
|
359
|
+
{
|
|
360
|
+
id: 'acronym',
|
|
361
|
+
name: 'ProcessSSEStreamForEventToken',
|
|
362
|
+
kind: 'function',
|
|
363
|
+
file: 'src/event-processing/sse.ts',
|
|
364
|
+
purl: 'pkg:github/principal-ai/agent-monitoring',
|
|
365
|
+
symbol: 'ProcessSSEStreamForEventToken',
|
|
366
|
+
},
|
|
367
|
+
{
|
|
368
|
+
id: 'method',
|
|
369
|
+
name: 'normalize',
|
|
370
|
+
kind: 'method',
|
|
371
|
+
file: 'src/session/SessionReader.ts',
|
|
372
|
+
purl: 'pkg:github/principal-ai/agent-monitoring',
|
|
373
|
+
symbol: 'SessionReader.normalize',
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
id: 'pkg',
|
|
377
|
+
name: 'trail-viewer',
|
|
378
|
+
kind: 'package',
|
|
379
|
+
file: '',
|
|
380
|
+
purl: 'pkg:npm/@principal-ai/trail-viewer',
|
|
381
|
+
symbol: '',
|
|
382
|
+
},
|
|
383
|
+
];
|
|
384
|
+
|
|
385
|
+
/** Nodes with compact `maxNodeWidth` so the different conventions visibly wrap
|
|
386
|
+
* at their word boundaries (camelCase, snake_case, PascalCase, acronyms). */
|
|
387
|
+
export const NamingConventions: Story = {
|
|
388
|
+
render: () => (
|
|
389
|
+
<div style={{ width: '100%', height: 460 }}>
|
|
390
|
+
<SubsystemComponentGraph
|
|
391
|
+
components={namingConventionComponents}
|
|
392
|
+
edges={[]}
|
|
393
|
+
maxNodeWidth={180}
|
|
394
|
+
/>
|
|
395
|
+
</div>
|
|
396
|
+
),
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
// ---------------------------------------------------------------------------
|
|
400
|
+
// One node per GraphifyComponentDetail kind — what each looks like + drills down
|
|
401
|
+
// ---------------------------------------------------------------------------
|
|
402
|
+
const detailKindComponents: SubsystemComponent[] = [
|
|
403
|
+
{
|
|
404
|
+
id: 'detail-class',
|
|
405
|
+
name: 'SessionReader',
|
|
406
|
+
kind: 'class',
|
|
407
|
+
file: 'src/session/SessionReader.ts',
|
|
408
|
+
purl: 'pkg:github/principal-ai/agent-monitoring',
|
|
409
|
+
purpose: 'class-like: owns outgoing method edges',
|
|
410
|
+
symbol: '',
|
|
411
|
+
detail: readerDetail,
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
id: 'detail-method',
|
|
415
|
+
name: 'normalize',
|
|
416
|
+
kind: 'method',
|
|
417
|
+
file: 'src/session/SessionReader.ts',
|
|
418
|
+
purl: 'pkg:github/principal-ai/agent-monitoring',
|
|
419
|
+
purpose: 'a class method — the specific thing this session focused on',
|
|
420
|
+
symbol: 'SessionReader.normalize',
|
|
421
|
+
detail: {
|
|
422
|
+
kind: 'function',
|
|
423
|
+
parameters: [{ name: 'session', type: 'SessionRecord' }],
|
|
424
|
+
returnType: 'SessionEvent[]',
|
|
425
|
+
callers: [{ nodeId: 'c1', name: 'capture-session', source_location: 'L120' }],
|
|
426
|
+
callees: [{ nodeId: 'c2', name: 'toUniversalEvents', source_location: 'L64' }],
|
|
427
|
+
} satisfies GraphifyComponentDetail,
|
|
428
|
+
},
|
|
429
|
+
{
|
|
430
|
+
id: 'detail-fn',
|
|
431
|
+
name: 'normalizeSession',
|
|
432
|
+
kind: 'function',
|
|
433
|
+
file: 'src/event-processing/normalize.ts',
|
|
434
|
+
purl: 'pkg:github/principal-ai/agent-monitoring',
|
|
435
|
+
purpose: 'function-like: label ends () with no method edges',
|
|
436
|
+
symbol: 'normalizeSession',
|
|
437
|
+
detail: {
|
|
438
|
+
kind: 'function',
|
|
439
|
+
parameters: [
|
|
440
|
+
{ name: 'session', type: 'SessionRecord' },
|
|
441
|
+
{ name: 'limit', type: 'number' },
|
|
442
|
+
],
|
|
443
|
+
returnType: 'SessionEvent[]',
|
|
444
|
+
callers: [{ nodeId: 'c1', name: 'SessionReader.normalize', source_location: 'L120' }],
|
|
445
|
+
callees: [{ nodeId: 'c2', name: 'toUniversalEvents', source_location: 'L64' }],
|
|
446
|
+
} satisfies GraphifyComponentDetail,
|
|
447
|
+
},
|
|
448
|
+
{
|
|
449
|
+
id: 'detail-type',
|
|
450
|
+
name: 'SessionRecord',
|
|
451
|
+
kind: 'type',
|
|
452
|
+
file: 'src/session/transcript.ts',
|
|
453
|
+
purl: 'pkg:github/principal-ai/agent-monitoring',
|
|
454
|
+
purpose: 'type-like: revealed by incoming implements edges',
|
|
455
|
+
symbol: 'SessionRecord',
|
|
456
|
+
detail: {
|
|
457
|
+
kind: 'type',
|
|
458
|
+
properties: [
|
|
459
|
+
{ name: 'id', type: 'string' },
|
|
460
|
+
{ name: 'admittedSeq', type: 'number' },
|
|
461
|
+
],
|
|
462
|
+
usedBy: [{ nodeId: 'u1', name: 'normalizeSession', context: 'type' }],
|
|
463
|
+
implementors: ['SessionReaderLike'],
|
|
464
|
+
} satisfies GraphifyComponentDetail,
|
|
465
|
+
},
|
|
466
|
+
{
|
|
467
|
+
id: 'detail-module',
|
|
468
|
+
name: 'CodexRolloutRecord',
|
|
469
|
+
kind: 'type',
|
|
470
|
+
file: 'src/session/transcript.ts',
|
|
471
|
+
purl: 'pkg:github/principal-ai/agent-monitoring',
|
|
472
|
+
purpose: 'a type symbol that lives in the transcript module; the node is the symbol, the file is its location',
|
|
473
|
+
symbol: 'CodexRolloutRecord',
|
|
474
|
+
detail: {
|
|
475
|
+
kind: 'type',
|
|
476
|
+
properties: [
|
|
477
|
+
{ name: 'type', type: 'string' },
|
|
478
|
+
{ name: 'id', type: 'string' },
|
|
479
|
+
],
|
|
480
|
+
usedBy: [{ nodeId: 'u1', name: 'isCodexRolloutRecord', context: 'type' }],
|
|
481
|
+
implementors: [],
|
|
482
|
+
} satisfies GraphifyComponentDetail,
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
id: 'detail-external',
|
|
486
|
+
// name = the package's name after the namespace; namespace shown above.
|
|
487
|
+
name: 'trail-viewer',
|
|
488
|
+
kind: 'package',
|
|
489
|
+
file: '',
|
|
490
|
+
purl: 'pkg:npm/@principal-ai/trail-viewer',
|
|
491
|
+
purpose: 'an npm package consumer — the whole package as a node',
|
|
492
|
+
symbol: '',
|
|
493
|
+
detail: {
|
|
494
|
+
kind: 'external',
|
|
495
|
+
label: 'pkg:npm/@principal-ai/trail-viewer',
|
|
496
|
+
} satisfies GraphifyComponentDetail,
|
|
497
|
+
},
|
|
498
|
+
];
|
|
499
|
+
|
|
500
|
+
function DetailKindsDemo() {
|
|
501
|
+
const [selected, setSelected] = useState<string | null>(null);
|
|
502
|
+
return (
|
|
503
|
+
<div style={{ width: '100%', maxWidth: 1280, height: 420, display: 'flex', flexDirection: 'column' }}>
|
|
504
|
+
<SubsystemComponentGraph
|
|
505
|
+
components={detailKindComponents}
|
|
506
|
+
edges={[]}
|
|
507
|
+
onSelect={(id) => setSelected(id)}
|
|
508
|
+
/>
|
|
509
|
+
<div style={{ marginTop: 8, fontFamily: 'monospace', fontSize: 12, color: '#aaa' }}>
|
|
510
|
+
{selected ? `selected: ${selected}` : 'click a component to see its GraphifyComponentDetail'}
|
|
511
|
+
</div>
|
|
512
|
+
</div>
|
|
513
|
+
);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
export const DetailKinds: Story = {
|
|
517
|
+
render: () => <DetailKindsDemo />,
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
// ---------------------------------------------------------------------------
|
|
521
|
+
// Minimal (pre-graphify) vs Resolved (post-graphify) — the SAME subsystem,
|
|
522
|
+
// showing what the LLM emits (no detail) and what graphify enrichment adds.
|
|
523
|
+
// ---------------------------------------------------------------------------
|
|
524
|
+
|
|
525
|
+
/** The minimal LLM-authored substrate: symbols only, no `detail`. */
|
|
526
|
+
const minimalComponents = components([
|
|
527
|
+
['reader', 'SessionReader', 'class', 'SessionReader.ts', 'pkg:github/principal-ai/agent-monitoring', 'normalizes a session into universal events', 'SessionReader'],
|
|
528
|
+
['normalize', 'normalize', 'method', 'SessionReader.ts', 'pkg:github/principal-ai/agent-monitoring', 'maps a session into universal events', 'SessionReader.normalize'],
|
|
529
|
+
['record', 'SessionRecord', 'type', 'transcript.ts', 'pkg:github/principal-ai/agent-monitoring', 'the parsed codex session record', 'SessionRecord'],
|
|
530
|
+
['transcript', 'transcript', 'module', 'transcript.ts', 'pkg:github/principal-ai/agent-monitoring', 'parses session records + type guards', 'transcript'],
|
|
531
|
+
]);
|
|
532
|
+
|
|
533
|
+
const sharedEdges = edges([
|
|
534
|
+
['transcript', 'record', 'defines'],
|
|
535
|
+
['reader', 'normalize', 'method'],
|
|
536
|
+
['normalize', 'record', 'references'],
|
|
537
|
+
]);
|
|
538
|
+
|
|
539
|
+
/** The same subsystem after `resolveSubsystemToGraphify` populates `detail`. */
|
|
540
|
+
const resolvedComponents: SubsystemComponent[] = [
|
|
541
|
+
{ ...minimalComponents[0], detail: readerDetail },
|
|
542
|
+
{
|
|
543
|
+
...minimalComponents[1],
|
|
544
|
+
detail: {
|
|
545
|
+
kind: 'function',
|
|
546
|
+
parameters: [{ name: 'session', type: 'SessionRecord' }],
|
|
547
|
+
returnType: 'SessionEvent[]',
|
|
548
|
+
callers: [{ nodeId: 'c1', name: 'capture-session', source_location: 'L120' }],
|
|
549
|
+
callees: [{ nodeId: 'c2', name: 'toUniversalEvents', source_location: 'L64' }],
|
|
550
|
+
} satisfies GraphifyComponentDetail,
|
|
551
|
+
},
|
|
552
|
+
{
|
|
553
|
+
...minimalComponents[2],
|
|
554
|
+
detail: {
|
|
555
|
+
kind: 'type',
|
|
556
|
+
properties: [
|
|
557
|
+
{ name: 'id', type: 'string' },
|
|
558
|
+
{ name: 'type', type: 'string' },
|
|
559
|
+
],
|
|
560
|
+
usedBy: [{ nodeId: 'u1', name: 'normalize', context: 'type' }],
|
|
561
|
+
implementors: ['SessionReaderLike'],
|
|
562
|
+
} satisfies GraphifyComponentDetail,
|
|
563
|
+
},
|
|
564
|
+
{
|
|
565
|
+
...minimalComponents[3],
|
|
566
|
+
detail: {
|
|
567
|
+
kind: 'module',
|
|
568
|
+
exports: ['CodexRolloutRecord', 'CodexSessionMeta'],
|
|
569
|
+
imports: [{ nodeId: 'i1', name: 'paths', relation: 'imports_from' }],
|
|
570
|
+
symbols: ['CodexRolloutRecord', 'CodexSessionMeta'],
|
|
571
|
+
} satisfies GraphifyComponentDetail,
|
|
572
|
+
},
|
|
573
|
+
];
|
|
574
|
+
|
|
575
|
+
function MinimalVsResolvedDemo({ resolved }: { resolved: boolean }) {
|
|
576
|
+
const [selected, setSelected] = useState<string | null>(null);
|
|
577
|
+
return (
|
|
578
|
+
<div style={{ width: '100%', maxWidth: 1280, height: 380, display: 'flex', flexDirection: 'column' }}>
|
|
579
|
+
<SubsystemComponentGraph
|
|
580
|
+
components={resolved ? resolvedComponents : minimalComponents}
|
|
581
|
+
edges={sharedEdges}
|
|
582
|
+
onSelect={(id) => setSelected(id)}
|
|
583
|
+
/>
|
|
584
|
+
<div style={{ marginTop: 8, fontFamily: 'monospace', fontSize: 12, color: '#aaa' }}>
|
|
585
|
+
{resolved
|
|
586
|
+
? 'post-graphify: click a component to see the enriched GraphifyComponentDetail'
|
|
587
|
+
: 'pre-graphify: LLM-authored symbols only — no detail yet'}
|
|
588
|
+
</div>
|
|
589
|
+
</div>
|
|
590
|
+
);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
export const MinimalPreGraphify: Story = {
|
|
594
|
+
render: () => <MinimalVsResolvedDemo resolved={false} />,
|
|
595
|
+
};
|
|
596
|
+
|
|
597
|
+
export const ResolvedPostGraphify: Story = {
|
|
598
|
+
render: () => <MinimalVsResolvedDemo resolved />,
|
|
599
|
+
};
|
|
600
|
+
|
|
601
|
+
// ---------------------------------------------------------------------------
|
|
602
|
+
// Investigation-derived subsystem — grok session 019fd2a9 (t3code)
|
|
603
|
+
// Read-only analysis of "provider / event threading". Components are occupied
|
|
604
|
+
// (analyzed, not edited) — no code touched, but a coherent concept was worked.
|
|
605
|
+
// ---------------------------------------------------------------------------
|
|
606
|
+
const investigationComponents: SubsystemComponent[] = [
|
|
607
|
+
{
|
|
608
|
+
id: 'adapter',
|
|
609
|
+
name: 'OpenCodeAdapter',
|
|
610
|
+
kind: 'module',
|
|
611
|
+
file: 'apps/server/src/provider/Layers/OpenCodeAdapter.ts',
|
|
612
|
+
purl: 'pkg:github/t3code/t3code',
|
|
613
|
+
purpose: 'adapts opencode session/threads + events to the t3 runtime',
|
|
614
|
+
symbol: 'makeOpenCodeAdapter',
|
|
615
|
+
capture: 'analyzed',
|
|
616
|
+
detail: {
|
|
617
|
+
kind: 'module',
|
|
618
|
+
exports: ['makeOpenCodeAdapter', 'OpenCodeAdapterLiveOptions'],
|
|
619
|
+
imports: [{ nodeId: 'i1', name: 'orchestration', relation: 'imports_from' }],
|
|
620
|
+
symbols: ['makeOpenCodeAdapter', 'isOpenCodeNotFound', 'OpenCodeSessionContext'],
|
|
621
|
+
} satisfies GraphifyComponentDetail,
|
|
622
|
+
},
|
|
623
|
+
{
|
|
624
|
+
id: 'ingestion',
|
|
625
|
+
name: 'ProviderRuntimeIngestion',
|
|
626
|
+
kind: 'module',
|
|
627
|
+
file: 'apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts',
|
|
628
|
+
purl: 'pkg:github/t3code/t3code',
|
|
629
|
+
purpose: 'ingests provider events into the runtime',
|
|
630
|
+
symbol: 'ProviderRuntimeIngestion',
|
|
631
|
+
capture: 'analyzed',
|
|
632
|
+
},
|
|
633
|
+
{
|
|
634
|
+
id: 'contracts',
|
|
635
|
+
name: 'orchestration',
|
|
636
|
+
kind: 'module',
|
|
637
|
+
file: 'packages/contracts/src/orchestration.ts',
|
|
638
|
+
purl: 'pkg:github/t3code/t3code',
|
|
639
|
+
purpose: 'contracts for orchestration/providers',
|
|
640
|
+
symbol: 'ORCHESTRATION_WS_METHODS',
|
|
641
|
+
capture: 'analyzed',
|
|
642
|
+
},
|
|
643
|
+
];
|
|
644
|
+
|
|
645
|
+
const investigationEdges = edges([
|
|
646
|
+
['adapter', 'contracts', 'imports_from'],
|
|
647
|
+
['ingestion', 'adapter', 'uses'],
|
|
648
|
+
['ingestion', 'contracts', 'references'],
|
|
649
|
+
]);
|
|
650
|
+
|
|
651
|
+
function InvestigationDemo() {
|
|
652
|
+
const [selected, setSelected] = useState<string | null>(null);
|
|
653
|
+
return (
|
|
654
|
+
<div style={{ width: '100%', maxWidth: 1280, height: 360, display: 'flex', flexDirection: 'column' }}>
|
|
655
|
+
<SubsystemComponentGraph
|
|
656
|
+
components={investigationComponents}
|
|
657
|
+
edges={investigationEdges}
|
|
658
|
+
onSelect={(id) => setSelected(id)}
|
|
659
|
+
/>
|
|
660
|
+
<div style={{ marginTop: 8, fontFamily: 'monospace', fontSize: 12, color: '#aaa' }}>
|
|
661
|
+
read-only investigation snapshot (grok 019fd2a9) — components analyzed, not edited
|
|
662
|
+
{selected ? ` · selected: ${selected}` : ''}
|
|
663
|
+
</div>
|
|
664
|
+
</div>
|
|
665
|
+
);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
export const InvestigationDerived: Story = {
|
|
669
|
+
render: () => <InvestigationDemo />,
|
|
670
|
+
};
|
|
671
|
+
|
|
672
|
+
// ---------------------------------------------------------------------------
|
|
673
|
+
// Mermaid diagram rendering pipeline (industry-themed-markdown)
|
|
674
|
+
// ---------------------------------------------------------------------------
|
|
675
|
+
const mermaidPurl = 'pkg:github/principal-ade/industry-themed-markdown';
|
|
676
|
+
|
|
677
|
+
const mermaidComponents: SubsystemComponent[] = [
|
|
678
|
+
{
|
|
679
|
+
id: 'input',
|
|
680
|
+
name: 'MarkdownContent',
|
|
681
|
+
kind: 'type',
|
|
682
|
+
file: 'industryMarkdown/components/IndustryMarkdownSlide.tsx',
|
|
683
|
+
purl: mermaidPurl,
|
|
684
|
+
purpose: 'raw markdown string — the slide\u2019s input',
|
|
685
|
+
symbol: 'MarkdownContent',
|
|
686
|
+
},
|
|
687
|
+
{
|
|
688
|
+
id: 'slide',
|
|
689
|
+
name: 'IndustryMarkdownSlide',
|
|
690
|
+
kind: 'class',
|
|
691
|
+
file: 'industryMarkdown/components/IndustryMarkdownSlide.tsx',
|
|
692
|
+
purl: mermaidPurl,
|
|
693
|
+
purpose: 'orchestrator — parses markdown into chunks, maps mermaid chunks to diagrams',
|
|
694
|
+
symbol: 'IndustryMarkdownSlide',
|
|
695
|
+
},
|
|
696
|
+
{
|
|
697
|
+
id: 'chunk',
|
|
698
|
+
name: 'MermaidChunk',
|
|
699
|
+
kind: 'type',
|
|
700
|
+
file: 'industryMarkdown/types/customMarkdownChunks.ts',
|
|
701
|
+
purl: mermaidPurl,
|
|
702
|
+
purpose: 'parsed chunk — code string + id, the slide\u2019s output per mermaid block',
|
|
703
|
+
symbol: 'MermaidChunk',
|
|
704
|
+
},
|
|
705
|
+
{
|
|
706
|
+
id: 'lazy',
|
|
707
|
+
name: 'IndustryLazyMermaidDiagram',
|
|
708
|
+
kind: 'class',
|
|
709
|
+
file: 'industryMarkdown/components/IndustryLazyMermaidDiagram.tsx',
|
|
710
|
+
purl: mermaidPurl,
|
|
711
|
+
purpose: 'IntersectionObserver lazy-loading wrapper \u2014 defers render until scrolled into view',
|
|
712
|
+
symbol: 'IndustryLazyMermaidDiagram',
|
|
713
|
+
},
|
|
714
|
+
{
|
|
715
|
+
id: 'diagram',
|
|
716
|
+
name: 'IndustryMermaidDiagram',
|
|
717
|
+
kind: 'class',
|
|
718
|
+
file: 'industryMarkdown/components/IndustryMermaidDiagram.tsx',
|
|
719
|
+
purl: mermaidPurl,
|
|
720
|
+
purpose: 'core renderer \u2014 picks engine (beautiful-mermaid vs mermaid.js), renders themed SVG',
|
|
721
|
+
symbol: 'IndustryMermaidDiagram',
|
|
722
|
+
},
|
|
723
|
+
{
|
|
724
|
+
id: 'helpers',
|
|
725
|
+
name: 'beautifulMermaid',
|
|
726
|
+
kind: 'module',
|
|
727
|
+
file: 'industryMarkdown/utils/beautifulMermaid.ts',
|
|
728
|
+
purl: mermaidPurl,
|
|
729
|
+
purpose: 'engine detection, theme\u2192options mapping, SVG post-processing',
|
|
730
|
+
symbol: 'beautifulMermaid',
|
|
731
|
+
},
|
|
732
|
+
];
|
|
733
|
+
|
|
734
|
+
const mermaidEdges = edges([
|
|
735
|
+
['input', 'slide', 'feeds'],
|
|
736
|
+
['slide', 'chunk', 'produces'],
|
|
737
|
+
['chunk', 'lazy', 'feeds'],
|
|
738
|
+
['lazy', 'diagram', 'wraps'],
|
|
739
|
+
['diagram', 'helpers', 'uses'],
|
|
740
|
+
]);
|
|
741
|
+
|
|
742
|
+
function MermaidDemo() {
|
|
743
|
+
const [selected, setSelected] = useState<string | null>(null);
|
|
744
|
+
return (
|
|
745
|
+
<div style={{ width: '100%', maxWidth: 1280, height: 500, display: 'flex', flexDirection: 'column' }}>
|
|
746
|
+
<SubsystemComponentGraph
|
|
747
|
+
components={mermaidComponents}
|
|
748
|
+
edges={mermaidEdges}
|
|
749
|
+
onSelect={(id) => setSelected(id)}
|
|
750
|
+
/>
|
|
751
|
+
<div style={{ marginTop: 8, fontFamily: 'monospace', fontSize: 12, color: '#aaa' }}>
|
|
752
|
+
mermaid diagram rendering pipeline — lazy → render → zoom → modal
|
|
753
|
+
{selected ? ` · selected: ${selected}` : ''}
|
|
754
|
+
</div>
|
|
755
|
+
</div>
|
|
756
|
+
);
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
export const MermaidPipeline: Story = {
|
|
760
|
+
render: () => <MermaidDemo />,
|
|
761
|
+
};
|