@principal-ai/principal-view-core 0.26.24 → 0.26.26

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.
Files changed (51) hide show
  1. package/dist/codegen/type-generator.d.ts.map +1 -1
  2. package/dist/codegen/type-generator.js +45 -17
  3. package/dist/codegen/type-generator.js.map +1 -1
  4. package/dist/index.d.ts +2 -2
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +3 -2
  7. package/dist/index.js.map +1 -1
  8. package/dist/matchers/SpanMatcher.d.ts.map +1 -1
  9. package/dist/matchers/SpanMatcher.js +10 -2
  10. package/dist/matchers/SpanMatcher.js.map +1 -1
  11. package/dist/registry/EventRegistry.d.ts.map +1 -1
  12. package/dist/registry/EventRegistry.js +39 -16
  13. package/dist/registry/EventRegistry.js.map +1 -1
  14. package/dist/spans/utils.d.ts.map +1 -1
  15. package/dist/spans/utils.js +21 -0
  16. package/dist/spans/utils.js.map +1 -1
  17. package/dist/storyboard/builder.d.ts.map +1 -1
  18. package/dist/storyboard/builder.js +10 -5
  19. package/dist/storyboard/builder.js.map +1 -1
  20. package/dist/telemetry/coverage.d.ts.map +1 -1
  21. package/dist/telemetry/coverage.js +29 -15
  22. package/dist/telemetry/coverage.js.map +1 -1
  23. package/dist/telemetry/event-validator.d.ts.map +1 -1
  24. package/dist/telemetry/event-validator.js +24 -9
  25. package/dist/telemetry/event-validator.js.map +1 -1
  26. package/dist/types/canvas.d.ts +4 -6
  27. package/dist/types/canvas.d.ts.map +1 -1
  28. package/dist/types/canvas.js +8 -1
  29. package/dist/types/canvas.js.map +1 -1
  30. package/dist/types/library.d.ts +21 -0
  31. package/dist/types/library.d.ts.map +1 -1
  32. package/dist/utils/CanvasConverter.d.ts +20 -0
  33. package/dist/utils/CanvasConverter.d.ts.map +1 -1
  34. package/dist/utils/CanvasConverter.js +209 -187
  35. package/dist/utils/CanvasConverter.js.map +1 -1
  36. package/dist/workflow/edge-validation.d.ts.map +1 -1
  37. package/dist/workflow/edge-validation.js +13 -4
  38. package/dist/workflow/edge-validation.js.map +1 -1
  39. package/package.json +1 -1
  40. package/src/codegen/type-generator.ts +46 -20
  41. package/src/index.ts +2 -0
  42. package/src/matchers/SpanMatcher.ts +11 -2
  43. package/src/registry/EventRegistry.ts +41 -17
  44. package/src/spans/utils.ts +22 -0
  45. package/src/storyboard/builder.ts +11 -4
  46. package/src/telemetry/coverage.ts +28 -15
  47. package/src/telemetry/event-validator.ts +26 -11
  48. package/src/types/canvas.ts +9 -6
  49. package/src/types/library.ts +22 -0
  50. package/src/utils/CanvasConverter.ts +218 -181
  51. package/src/workflow/edge-validation.ts +13 -4
@@ -8,6 +8,9 @@ import type {
8
8
  ExtendedCanvas,
9
9
  ExtendedCanvasNode,
10
10
  ExtendedCanvasTextNode,
11
+ ExtendedCanvasFileNode,
12
+ ExtendedCanvasLinkNode,
13
+ ExtendedCanvasGroupNode,
11
14
  ExtendedCanvasEdge,
12
15
  PVEdgeTypeDefinition,
13
16
  PVNodeShape,
@@ -18,6 +21,7 @@ import type {
18
21
  PVOtelExtension,
19
22
  PVEventSchema,
20
23
  PVBoundaryExtension,
24
+ OtelNode,
21
25
  } from '../types/canvas';
22
26
  import type { JsonValue } from '../types';
23
27
  import { resolveCanvasColor, isOtelNode } from '../types/canvas';
@@ -160,6 +164,74 @@ export class CanvasConverter {
160
164
  * Convert a single canvas node to React Flow node
161
165
  */
162
166
  private static convertNode(node: ExtendedCanvasNode, _canvas: ExtendedCanvas): ReactFlowNode {
167
+ // Handle OTEL nodes separately (they never use pv field)
168
+ if (isOtelNode(node)) {
169
+ return this.convertOtelNode(node);
170
+ }
171
+
172
+ // Handle standard JSON Canvas nodes (may use pv extensions)
173
+ return this.convertStandardNode(node);
174
+ }
175
+
176
+ /**
177
+ * Convert an OTEL node to React Flow node
178
+ * OTEL nodes use top-level fields (label, otel, event, etc.) and never use pv
179
+ */
180
+ private static convertOtelNode(node: OtelNode): ReactFlowNode {
181
+ const color = resolveCanvasColor(node.color);
182
+
183
+ const data: ReactFlowNode['data'] = {
184
+ name: node.label,
185
+ nodeType: node.type,
186
+ canvasType: node.type,
187
+ shape: node.shape || 'rectangle',
188
+ icon: node.icon,
189
+ color: node.fill || color,
190
+ width: node.width,
191
+ height: node.height,
192
+ };
193
+
194
+ if ('description' in node && node.description) data.description = node.description;
195
+
196
+ // Add OTEL-specific data from top-level fields
197
+ if ('otel' in node && node.otel) {
198
+ (data as Record<string, unknown>).otel = node.otel;
199
+ if (node.otel.status) data.status = node.otel.status;
200
+ if (node.otel.references) data.references = node.otel.references;
201
+ }
202
+ if ('event' in node && node.event) {
203
+ (data as Record<string, unknown>).event = node.event;
204
+ }
205
+ if ('eventRef' in node && node.eventRef) {
206
+ (data as Record<string, unknown>).eventRef = node.eventRef;
207
+ }
208
+ if ('dataSchema' in node && node.dataSchema) {
209
+ data.dataSchema = node.dataSchema;
210
+ }
211
+ if ('boundary' in node && node.boundary) {
212
+ (data as Record<string, unknown>).boundary = node.boundary;
213
+ }
214
+ if ('workflowChips' in node && node.workflowChips) {
215
+ (data as Record<string, unknown>).workflowChips = node.workflowChips;
216
+ }
217
+
218
+ return {
219
+ id: node.id,
220
+ type: node.shape || 'default',
221
+ position: { x: node.x, y: node.y },
222
+ data,
223
+ style: {
224
+ width: node.width,
225
+ height: node.height,
226
+ },
227
+ };
228
+ }
229
+
230
+ /**
231
+ * Convert a standard JSON Canvas node to React Flow node
232
+ * Standard nodes (text/file/link/group) may use pv extensions
233
+ */
234
+ private static convertStandardNode(node: ExtendedCanvasTextNode | ExtendedCanvasFileNode | ExtendedCanvasLinkNode | ExtendedCanvasGroupNode): ReactFlowNode {
163
235
  const pv = node.pv;
164
236
  const color = resolveCanvasColor(node.color);
165
237
 
@@ -178,25 +250,17 @@ export class CanvasConverter {
178
250
  case 'group':
179
251
  nodeName = node.label || 'Group';
180
252
  break;
181
- // OTEL node types use the label field directly
182
- case 'otel-event':
183
- case 'otel-span-convention':
184
- case 'otel-scope':
185
- case 'otel-resource':
186
- case 'otel-boundary':
187
- nodeName = node.label;
253
+ default:
254
+ nodeName = (node as { id: string }).id;
188
255
  break;
189
256
  }
190
257
 
191
- // Build the data object based on canvas node type
192
- // For OTEL nodes, use the node type directly; for standard canvas types, use pv or default
193
- const isOtelType = isOtelNode(node);
194
258
  const data: ReactFlowNode['data'] = {
195
259
  name: nodeName,
196
- nodeType: isOtelType ? node.type : (pv?.nodeType || node.type),
260
+ nodeType: pv?.nodeType || node.type,
197
261
  canvasType: node.type,
198
- shape: isOtelType ? (node.shape || 'rectangle') : (pv?.shape || 'rectangle'),
199
- icon: isOtelType ? node.icon : pv?.icon,
262
+ shape: pv?.shape || 'rectangle',
263
+ icon: pv?.icon,
200
264
  color: pv?.states?.idle?.color || color,
201
265
  width: node.width,
202
266
  height: node.height,
@@ -211,48 +275,8 @@ export class CanvasConverter {
211
275
  data.url = node.url;
212
276
  }
213
277
 
214
- // Handle OTEL node types (new format with top-level fields)
215
- if (
216
- node.type === 'otel-event' ||
217
- node.type === 'otel-span-convention' ||
218
- node.type === 'otel-scope' ||
219
- node.type === 'otel-resource' ||
220
- node.type === 'otel-boundary'
221
- ) {
222
- // OTEL nodes use top-level fields, not pv wrapper
223
- data.nodeType = node.type;
224
- if (node.icon) data.icon = node.icon;
225
- if (node.fill) data.color = node.fill;
226
- if (node.shape) data.shape = node.shape;
227
- if ('description' in node && node.description) data.description = node.description;
228
-
229
- // Add OTEL-specific data
230
- if ('otel' in node && node.otel) {
231
- (data as Record<string, unknown>).otel = node.otel;
232
- if (node.otel.status) data.status = node.otel.status;
233
- if (node.otel.references) data.references = node.otel.references;
234
- }
235
- if ('event' in node && node.event) {
236
- (data as Record<string, unknown>).event = node.event;
237
- }
238
- if ('eventRef' in node && node.eventRef) {
239
- (data as Record<string, unknown>).eventRef = node.eventRef;
240
- }
241
- if ('dataSchema' in node && node.dataSchema) {
242
- data.dataSchema = node.dataSchema;
243
- }
244
- if ('boundary' in node && node.boundary) {
245
- (data as Record<string, unknown>).boundary = node.boundary;
246
- }
247
- // Pass through workflow chips if present (for span convention nodes)
248
- if ('workflowChips' in node && node.workflowChips) {
249
- (data as Record<string, unknown>).workflowChips = node.workflowChips;
250
- }
251
- }
252
-
253
- // Add PV extensions if present (for standard canvas nodes only)
254
- // OTEL nodes use top-level fields, not pv wrapper
255
- if (pv && !isOtelType) {
278
+ // Add PV extensions if present
279
+ if (pv) {
256
280
  if (pv.states) data.states = pv.states;
257
281
  if (pv.sources) data.sources = pv.sources; // deprecated, use references
258
282
  if (pv.references) data.references = pv.references;
@@ -260,17 +284,9 @@ export class CanvasConverter {
260
284
  if (pv.dataSchema) data.dataSchema = pv.dataSchema;
261
285
  }
262
286
 
263
- // Determine React Flow node type from shape
264
- let reactFlowType = 'default';
265
- if (isOtelType && node.shape) {
266
- reactFlowType = node.shape;
267
- } else if (pv?.shape) {
268
- reactFlowType = pv.shape;
269
- }
270
-
271
287
  return {
272
288
  id: node.id,
273
- type: reactFlowType,
289
+ type: pv?.shape || 'default',
274
290
  position: { x: node.x, y: node.y },
275
291
  data,
276
292
  style: {
@@ -340,122 +356,12 @@ export class CanvasConverter {
340
356
  // Convert nodes
341
357
  if (canvas.nodes) {
342
358
  for (const node of canvas.nodes) {
343
- const pv = node.pv;
344
- // Get name and description based on node type
345
- // For text nodes: line 1 = name, rest = description
346
- let nodeName: string;
347
- let nodeDescription: string | undefined;
348
- switch (node.type) {
349
- case 'text': {
350
- const lines = node.text?.split('\n') || [];
351
- nodeName = lines[0]?.replace(/^#+ /, '').substring(0, 50) || 'Text';
352
- // Description is everything after the first line, trimmed
353
- nodeDescription = lines.slice(1).join('\n').trim() || undefined;
354
- break;
355
- }
356
- case 'file':
357
- nodeName = node.file?.split('/').pop() || node.file || 'File';
358
- break;
359
- case 'link':
360
- nodeName = node.url || 'Link';
361
- break;
362
- case 'group':
363
- nodeName = node.label || 'Group';
364
- break;
365
- // OTEL node types use the label field directly
366
- case 'otel-event':
367
- case 'otel-span-convention':
368
- case 'otel-scope':
369
- case 'otel-resource':
370
- case 'otel-boundary':
371
- nodeName = node.label;
372
- break;
373
- default:
374
- // For unknown custom types, use node id
375
- nodeName = (node as { id: string }).id;
376
- break;
377
- }
378
-
379
- // pv.name takes priority over parsed name from text
380
- const finalName = pv?.name || nodeName;
381
- // pv.description takes priority over parsed description from text
382
- const finalDescription = pv?.description || nodeDescription;
383
-
384
- // Check if this is an OTEL node type
385
- const isOtelType = isOtelNode(node);
386
-
387
- // Build data object, filtering out undefined values
388
- const nodeData: Record<string, JsonValue> = {
389
- description: finalDescription || '',
390
- shape: isOtelType ? (node.shape || 'rectangle') : (pv?.shape || 'rectangle'),
391
- color: isOtelType ? (node.fill || resolveCanvasColor(node.color) || '') : (pv?.fill || resolveCanvasColor(node.color) || ''),
392
- width: node.width,
393
- height: node.height,
394
- sources: pv?.sources || [], // deprecated, use references
395
- references: pv?.references || [],
396
- canvasType: node.type,
397
- };
398
-
399
- // Add optional pv properties for standard canvas nodes only
400
- // Note: pv.otel and pv.resourceMatch are removed - use otel-* node types instead
401
- if (!isOtelType && pv) {
402
- if (pv.icon) nodeData.icon = pv.icon;
403
- if (pv.stroke) nodeData.stroke = pv.stroke;
404
- if (pv.states) nodeData.states = pv.states as JsonValue;
405
- if (pv.status) nodeData.status = pv.status;
406
- }
407
-
408
- // Add type-specific data
409
- if (node.type === 'text' && node.text) nodeData.text = node.text;
410
- if (node.type === 'file' && node.file) nodeData.file = node.file;
411
- if (node.type === 'link' && node.url) nodeData.url = node.url;
412
-
413
- // Handle OTEL node types (new format with top-level fields)
359
+ // Handle OTEL nodes separately (they never use pv field)
414
360
  if (isOtelNode(node)) {
415
- nodeData.nodeType = node.type;
416
- if (node.icon) nodeData.icon = node.icon;
417
- if (node.fill) nodeData.color = node.fill;
418
- if (node.shape) nodeData.shape = node.shape;
419
- if ('description' in node && node.description) nodeData.description = node.description;
420
-
421
- // Add OTEL-specific data from top-level fields
422
- if ('otel' in node && node.otel) {
423
- nodeData.otel = node.otel as JsonValue;
424
- if (node.otel.status) nodeData.status = node.otel.status;
425
- if (node.otel.references) nodeData.references = node.otel.references;
426
- }
427
- if ('event' in node && node.event) {
428
- nodeData.event = node.event as unknown as JsonValue;
429
- }
430
- if ('eventRef' in node && node.eventRef) {
431
- nodeData.eventRef = node.eventRef;
432
- }
433
- if ('dataSchema' in node && node.dataSchema) {
434
- nodeData.dataSchema = node.dataSchema as JsonValue;
435
- }
436
- if ('boundary' in node && node.boundary) {
437
- nodeData.boundary = node.boundary as unknown as JsonValue;
438
- }
439
- // Pass through workflow chips if present (for span convention nodes)
440
- if ('workflowChips' in node && node.workflowChips) {
441
- nodeData.workflowChips = node.workflowChips as unknown as JsonValue;
442
- }
361
+ nodes.push(this.convertOtelNodeToGraph(node, now));
362
+ } else {
363
+ nodes.push(this.convertStandardNodeToGraph(node, now));
443
364
  }
444
-
445
- nodes.push({
446
- id: node.id,
447
- type: pv?.nodeType || node.type,
448
- name: finalName,
449
- data: nodeData,
450
- position: { x: node.x, y: node.y },
451
- // Persist node dimensions at top level for xyflow
452
- width: node.width,
453
- height: node.height,
454
- // Don't set a default state - only show state labels when explicitly set via events
455
- state: undefined,
456
- createdAt: now,
457
- updatedAt: now,
458
- });
459
365
  }
460
366
  }
461
367
 
@@ -502,6 +408,136 @@ export class CanvasConverter {
502
408
  return { nodes, edges };
503
409
  }
504
410
 
411
+ /**
412
+ * Convert an OTEL node to NodeState format
413
+ * OTEL nodes use top-level fields and never use pv
414
+ */
415
+ private static convertOtelNodeToGraph(node: OtelNode, now: number): NodeState {
416
+ const nodeData: Record<string, JsonValue> = {
417
+ description: ('description' in node && node.description) || '',
418
+ shape: node.shape || 'rectangle',
419
+ color: node.fill || resolveCanvasColor(node.color) || '',
420
+ width: node.width,
421
+ height: node.height,
422
+ sources: [], // not used for OTEL nodes
423
+ references: [],
424
+ canvasType: node.type,
425
+ nodeType: node.type,
426
+ };
427
+
428
+ if (node.icon) nodeData.icon = node.icon;
429
+ if (node.stroke) nodeData.stroke = node.stroke;
430
+
431
+ // Add OTEL-specific data from top-level fields
432
+ if ('otel' in node && node.otel) {
433
+ nodeData.otel = node.otel as JsonValue;
434
+ if (node.otel.status) nodeData.status = node.otel.status;
435
+ if (node.otel.references) nodeData.references = node.otel.references;
436
+ }
437
+ if ('event' in node && node.event) {
438
+ nodeData.event = node.event as unknown as JsonValue;
439
+ }
440
+ if ('eventRef' in node && node.eventRef) {
441
+ nodeData.eventRef = node.eventRef;
442
+ }
443
+ if ('dataSchema' in node && node.dataSchema) {
444
+ nodeData.dataSchema = node.dataSchema as JsonValue;
445
+ }
446
+ if ('boundary' in node && node.boundary) {
447
+ nodeData.boundary = node.boundary as unknown as JsonValue;
448
+ }
449
+ if ('workflowChips' in node && node.workflowChips) {
450
+ nodeData.workflowChips = node.workflowChips as unknown as JsonValue;
451
+ }
452
+
453
+ return {
454
+ id: node.id,
455
+ type: node.type,
456
+ name: node.label,
457
+ data: nodeData,
458
+ position: { x: node.x, y: node.y },
459
+ width: node.width,
460
+ height: node.height,
461
+ state: undefined,
462
+ createdAt: now,
463
+ updatedAt: now,
464
+ };
465
+ }
466
+
467
+ /**
468
+ * Convert a standard JSON Canvas node to NodeState format
469
+ * Standard nodes may use pv extensions
470
+ */
471
+ private static convertStandardNodeToGraph(node: ExtendedCanvasTextNode | ExtendedCanvasFileNode | ExtendedCanvasLinkNode | ExtendedCanvasGroupNode, now: number): NodeState {
472
+ const pv = node.pv;
473
+
474
+ // Get name and description based on node type
475
+ let nodeName: string;
476
+ let nodeDescription: string | undefined;
477
+
478
+ switch (node.type) {
479
+ case 'text': {
480
+ const lines = node.text?.split('\n') || [];
481
+ nodeName = lines[0]?.replace(/^#+ /, '').substring(0, 50) || 'Text';
482
+ nodeDescription = lines.slice(1).join('\n').trim() || undefined;
483
+ break;
484
+ }
485
+ case 'file':
486
+ nodeName = node.file?.split('/').pop() || node.file || 'File';
487
+ break;
488
+ case 'link':
489
+ nodeName = node.url || 'Link';
490
+ break;
491
+ case 'group':
492
+ nodeName = node.label || 'Group';
493
+ break;
494
+ default:
495
+ nodeName = (node as { id: string }).id;
496
+ break;
497
+ }
498
+
499
+ // pv.name/description take priority over parsed values
500
+ const finalName = pv?.name || nodeName;
501
+ const finalDescription = pv?.description || nodeDescription;
502
+
503
+ const nodeData: Record<string, JsonValue> = {
504
+ description: finalDescription || '',
505
+ shape: pv?.shape || 'rectangle',
506
+ color: pv?.fill || resolveCanvasColor(node.color) || '',
507
+ width: node.width,
508
+ height: node.height,
509
+ sources: pv?.sources || [],
510
+ references: pv?.references || [],
511
+ canvasType: node.type,
512
+ };
513
+
514
+ // Add optional pv properties
515
+ if (pv) {
516
+ if (pv.icon) nodeData.icon = pv.icon;
517
+ if (pv.stroke) nodeData.stroke = pv.stroke;
518
+ if (pv.states) nodeData.states = pv.states as JsonValue;
519
+ if (pv.status) nodeData.status = pv.status;
520
+ }
521
+
522
+ // Add type-specific data
523
+ if (node.type === 'text' && node.text) nodeData.text = node.text;
524
+ if (node.type === 'file' && node.file) nodeData.file = node.file;
525
+ if (node.type === 'link' && node.url) nodeData.url = node.url;
526
+
527
+ return {
528
+ id: node.id,
529
+ type: pv?.nodeType || node.type,
530
+ name: finalName,
531
+ data: nodeData,
532
+ position: { x: node.x, y: node.y },
533
+ width: node.width,
534
+ height: node.height,
535
+ state: undefined,
536
+ createdAt: now,
537
+ updatedAt: now,
538
+ };
539
+ }
540
+
505
541
  /**
506
542
  * Convert React Flow nodes/edges back to Extended Canvas format
507
543
  */
@@ -540,9 +576,10 @@ export class CanvasConverter {
540
576
  canvasNode.color = node.data.color;
541
577
  }
542
578
 
543
- // Add PV extension if there's custom data
544
- if (node.data.nodeType || node.data.shape || node.data.sources?.length || node.data.references?.length) {
545
- canvasNode.pv = {
579
+ // Add PV extension if there's custom data (only for standard canvas nodes)
580
+ if (!isOtelNode(canvasNode as ExtendedCanvasNode) &&
581
+ (node.data.nodeType || node.data.shape || node.data.sources?.length || node.data.references?.length)) {
582
+ (canvasNode as ExtendedCanvasTextNode).pv = {
546
583
  nodeType: (node.data.nodeType as string) || node.id,
547
584
  shape: node.data.shape as PVNodeShape | undefined,
548
585
  icon: node.data.icon as string | undefined,
@@ -8,6 +8,7 @@
8
8
  */
9
9
 
10
10
  import type { ExtendedCanvas, ExtendedCanvasNode } from '../types/canvas';
11
+ import { isOtelSpanConventionNode, isStandardCanvasNode } from '../types/canvas';
11
12
  import type { DerivedEdge } from './edge-derivation';
12
13
  import { minimatch } from 'minimatch';
13
14
 
@@ -145,12 +146,20 @@ export function extractSpanNodes(canvas: ExtendedCanvas): SpanNodeInfo[] {
145
146
  const spanNodes: SpanNodeInfo[] = [];
146
147
 
147
148
  for (const node of canvas.nodes) {
148
- const spanPattern = (node as ExtendedCanvasNode).pv?.otel?.spanPattern;
149
- if (spanPattern) {
149
+ // Check OTEL span convention nodes (top-level otel.spanPattern)
150
+ if (isOtelSpanConventionNode(node) && node.otel?.spanPattern) {
150
151
  spanNodes.push({
151
152
  nodeId: node.id,
152
- spanPattern,
153
- name: (node as ExtendedCanvasNode).pv?.name,
153
+ spanPattern: node.otel.spanPattern,
154
+ name: node.label,
155
+ });
156
+ }
157
+ // Check standard nodes (pv.otel.spanPattern)
158
+ else if (isStandardCanvasNode(node) && node.pv?.otel?.spanPattern) {
159
+ spanNodes.push({
160
+ nodeId: node.id,
161
+ spanPattern: node.pv.otel.spanPattern,
162
+ name: node.pv.name,
154
163
  });
155
164
  }
156
165
  }