pmx-canvas 0.1.25 → 0.1.27

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 (63) hide show
  1. package/.github/extensions/pmx-canvas/extension.mjs +191 -0
  2. package/CHANGELOG.md +116 -0
  3. package/Readme.md +74 -27
  4. package/dist/canvas/index.js +82 -82
  5. package/dist/json-render/index.css +1 -1
  6. package/dist/json-render/index.js +944 -164
  7. package/dist/types/json-render/catalog.d.ts +195 -20
  8. package/dist/types/json-render/charts/components.d.ts +7 -0
  9. package/dist/types/json-render/charts/definitions.d.ts +13 -1
  10. package/dist/types/json-render/charts/tufte-components.d.ts +65 -0
  11. package/dist/types/json-render/charts/tufte-definitions.d.ts +164 -0
  12. package/dist/types/json-render/directives.d.ts +23 -0
  13. package/dist/types/json-render/renderer/index.d.ts +1 -0
  14. package/dist/types/json-render/server.d.ts +32 -1
  15. package/dist/types/mcp/canvas-access.d.ts +62 -0
  16. package/dist/types/server/ax-state.d.ts +170 -0
  17. package/dist/types/server/canvas-db.d.ts +17 -1
  18. package/dist/types/server/canvas-operations.d.ts +45 -0
  19. package/dist/types/server/canvas-schema.d.ts +5 -1
  20. package/dist/types/server/canvas-state.d.ts +95 -4
  21. package/dist/types/server/index.d.ts +118 -2
  22. package/dist/types/server/mutation-history.d.ts +1 -1
  23. package/docs/cli.md +42 -0
  24. package/docs/http-api.md +64 -0
  25. package/docs/mcp.md +23 -5
  26. package/docs/node-types.md +1 -1
  27. package/docs/screenshots/codex-app.png +0 -0
  28. package/docs/screenshots/github-copilot-app.png +0 -0
  29. package/docs/sdk.md +19 -1
  30. package/package.json +10 -7
  31. package/skills/control-session-orchestrator/SKILL.md +359 -0
  32. package/skills/control-session-orchestrator/evals/evals.json +75 -0
  33. package/skills/data-analysis/SKILL.md +6 -0
  34. package/skills/pmx-canvas/SKILL.md +63 -4
  35. package/skills/pmx-canvas/references/github-copilot-app-adapter.md +6 -0
  36. package/skills/tufte-viz/SKILL.md +157 -0
  37. package/skills/tufte-viz/references/analytical-design.md +217 -0
  38. package/skills/tufte-viz/references/tufte-principles.md +147 -0
  39. package/src/cli/agent.ts +280 -2
  40. package/src/cli/index.ts +2 -1
  41. package/src/client/nodes/ExtAppFrame.tsx +23 -1
  42. package/src/client/nodes/McpAppNode.tsx +6 -2
  43. package/src/json-render/catalog.ts +22 -1
  44. package/src/json-render/charts/components.tsx +97 -10
  45. package/src/json-render/charts/definitions.ts +19 -2
  46. package/src/json-render/charts/extra-components.tsx +5 -4
  47. package/src/json-render/charts/tufte-components.tsx +383 -0
  48. package/src/json-render/charts/tufte-definitions.ts +128 -0
  49. package/src/json-render/directives.ts +29 -0
  50. package/src/json-render/renderer/index.css +101 -0
  51. package/src/json-render/renderer/index.tsx +33 -0
  52. package/src/json-render/server.ts +257 -5
  53. package/src/mcp/canvas-access.ts +261 -0
  54. package/src/mcp/server.ts +500 -7
  55. package/src/server/ax-context.ts +8 -3
  56. package/src/server/ax-state.ts +447 -0
  57. package/src/server/canvas-db.ts +184 -1
  58. package/src/server/canvas-operations.ts +107 -0
  59. package/src/server/canvas-schema.ts +26 -3
  60. package/src/server/canvas-state.ts +349 -2
  61. package/src/server/index.ts +250 -2
  62. package/src/server/mutation-history.ts +6 -0
  63. package/src/server/server.ts +428 -2
@@ -2,7 +2,27 @@ import { EventEmitter } from 'node:events';
2
2
  import { canvasState, IMAGE_MIME_MAP } from './canvas-state.js';
3
3
  import type { CanvasAnnotation, CanvasNodeState, CanvasEdge, CanvasLayout, ViewportState } from './canvas-state.js';
4
4
  import { buildCanvasAxContext } from './ax-context.js';
5
- import type { PmxAxContext, PmxAxFocusState, PmxAxSource, PmxAxState } from './ax-state.js';
5
+ import type {
6
+ PmxAxApprovalGate,
7
+ PmxAxContext,
8
+ PmxAxEvent,
9
+ PmxAxEvidence,
10
+ PmxAxEvidenceKind,
11
+ PmxAxFocusState,
12
+ PmxAxHostCapability,
13
+ PmxAxReviewAnchorType,
14
+ PmxAxReviewAnnotation,
15
+ PmxAxReviewKind,
16
+ PmxAxReviewRegion,
17
+ PmxAxReviewSeverity,
18
+ PmxAxReviewStatus,
19
+ PmxAxSource,
20
+ PmxAxState,
21
+ PmxAxSteeringMessage,
22
+ PmxAxWorkItem,
23
+ PmxAxWorkItemStatus,
24
+ } from './ax-state.js';
25
+ import type { AxTimelineQuery } from './canvas-db.js';
6
26
  import { findCanvasExtAppNodeId } from './ext-app-lookup.js';
7
27
  import { onFileNodeChanged } from './file-watcher.js';
8
28
  import { findOpenCanvasPosition, computeGroupBounds } from './placement.js';
@@ -12,6 +32,8 @@ import { recomputeCodeGraph, buildCodeGraphSummary, formatCodeGraph } from './co
12
32
  import {
13
33
  addCanvasNode,
14
34
  addCanvasEdge,
35
+ appendCanvasJsonRenderStream,
36
+ createCanvasStreamingJsonRenderNode,
15
37
  MARKDOWN_NODE_DEFAULT_SIZE,
16
38
  MCP_APP_NODE_DEFAULT_SIZE,
17
39
  applyCanvasNodeUpdates,
@@ -30,6 +52,7 @@ import {
30
52
  refreshCanvasWebpageNode,
31
53
  removeCanvasNode,
32
54
  removeCanvasEdge,
55
+ resolveHtmlContent,
33
56
  restoreCanvasSnapshot,
34
57
  saveCanvasSnapshot,
35
58
  scheduleCodeGraphRecompute,
@@ -167,6 +190,10 @@ export class PmxCanvas extends EventEmitter {
167
190
  type: CanvasNodeState['type'];
168
191
  title?: string;
169
192
  content?: string;
193
+ children?: string[];
194
+ childIds?: string[];
195
+ childLayout?: 'grid' | 'column' | 'flow';
196
+ color?: string;
170
197
  toolName?: string;
171
198
  category?: string;
172
199
  status?: string;
@@ -182,6 +209,18 @@ export class PmxCanvas extends EventEmitter {
182
209
  if (input.type === 'webpage') {
183
210
  throw new Error('Use addWebpageNode for webpage nodes so page content is fetched and cached on the server.');
184
211
  }
212
+ if (input.type === 'group') {
213
+ return this.createGroup({
214
+ ...(typeof input.title === 'string' ? { title: input.title } : {}),
215
+ childIds: input.childIds ?? input.children ?? [],
216
+ ...(typeof input.x === 'number' ? { x: input.x } : {}),
217
+ ...(typeof input.y === 'number' ? { y: input.y } : {}),
218
+ ...(typeof input.width === 'number' ? { width: input.width } : {}),
219
+ ...(typeof input.height === 'number' ? { height: input.height } : {}),
220
+ ...(typeof input.color === 'string' ? { color: input.color } : {}),
221
+ ...(input.childLayout ? { childLayout: input.childLayout } : {}),
222
+ });
223
+ }
185
224
  const { id, needsCodeGraphRecompute } = addCanvasNode({
186
225
  ...input,
187
226
  defaultWidth: input.type === 'markdown'
@@ -437,6 +476,132 @@ export class PmxCanvas extends EventEmitter {
437
476
  return focus;
438
477
  }
439
478
 
479
+ recordAxEvent(
480
+ input: { kind: PmxAxEvent['kind']; summary: string; detail?: string | null; nodeIds?: string[]; data?: Record<string, unknown> | null },
481
+ options?: { source?: PmxAxSource },
482
+ ): PmxAxEvent {
483
+ const event = canvasState.recordAxEvent(input, { source: options?.source ?? 'sdk' });
484
+ emitPrimaryWorkbenchEvent('ax-event-created', { event });
485
+ return event;
486
+ }
487
+
488
+ sendSteering(message: string, options?: { source?: PmxAxSource }): PmxAxSteeringMessage {
489
+ const steering = canvasState.recordSteeringMessage(message, { source: options?.source ?? 'sdk' });
490
+ emitPrimaryWorkbenchEvent('ax-event-created', { steering });
491
+ return steering;
492
+ }
493
+
494
+ markSteeringDelivered(id: string): boolean {
495
+ const ok = canvasState.markSteeringDelivered(id);
496
+ if (ok) emitPrimaryWorkbenchEvent('ax-event-created', { steeringDelivered: id });
497
+ return ok;
498
+ }
499
+
500
+ getAxTimeline(query?: AxTimelineQuery): ReturnType<typeof canvasState.getAxTimeline> {
501
+ return canvasState.getAxTimeline(query);
502
+ }
503
+
504
+ listWorkItems(): PmxAxWorkItem[] {
505
+ return canvasState.getWorkItems();
506
+ }
507
+
508
+ addWorkItem(
509
+ input: { title: string; status?: PmxAxWorkItemStatus; detail?: string | null; nodeIds?: string[] },
510
+ options?: { source?: PmxAxSource },
511
+ ): PmxAxWorkItem {
512
+ const workItem = canvasState.addWorkItem(input, { source: options?.source ?? 'sdk' });
513
+ emitPrimaryWorkbenchEvent('ax-state-changed', { workItem });
514
+ return workItem;
515
+ }
516
+
517
+ updateWorkItem(
518
+ id: string,
519
+ patch: { title?: string; status?: PmxAxWorkItemStatus; detail?: string | null; nodeIds?: string[] },
520
+ options?: { source?: PmxAxSource },
521
+ ): PmxAxWorkItem | null {
522
+ const workItem = canvasState.updateWorkItem(id, patch, { source: options?.source ?? 'sdk' });
523
+ if (workItem) emitPrimaryWorkbenchEvent('ax-state-changed', { workItem });
524
+ return workItem;
525
+ }
526
+
527
+ listApprovalGates(): PmxAxApprovalGate[] {
528
+ return canvasState.getApprovalGates();
529
+ }
530
+
531
+ requestApproval(
532
+ input: { title: string; detail?: string | null; action?: string | null; nodeIds?: string[] },
533
+ options?: { source?: PmxAxSource },
534
+ ): PmxAxApprovalGate {
535
+ const approvalGate = canvasState.requestApproval(input, { source: options?.source ?? 'sdk' });
536
+ emitPrimaryWorkbenchEvent('ax-state-changed', { approvalGate });
537
+ return approvalGate;
538
+ }
539
+
540
+ resolveApproval(
541
+ id: string,
542
+ decision: 'approved' | 'rejected',
543
+ options?: { resolution?: string; source?: PmxAxSource },
544
+ ): PmxAxApprovalGate | null {
545
+ const approvalGate = canvasState.resolveApproval(id, decision, {
546
+ ...(options?.resolution !== undefined ? { resolution: options.resolution } : {}),
547
+ source: options?.source ?? 'sdk',
548
+ });
549
+ if (approvalGate) emitPrimaryWorkbenchEvent('ax-state-changed', { approvalGate });
550
+ return approvalGate;
551
+ }
552
+
553
+ addEvidence(
554
+ input: { kind: PmxAxEvidenceKind; title: string; body?: string | null; ref?: string | null; nodeIds?: string[]; data?: Record<string, unknown> | null },
555
+ options?: { source?: PmxAxSource },
556
+ ): PmxAxEvidence {
557
+ const evidence = canvasState.addEvidence(input, { source: options?.source ?? 'sdk' });
558
+ emitPrimaryWorkbenchEvent('ax-event-created', { evidence });
559
+ return evidence;
560
+ }
561
+
562
+ listReviewAnnotations(): PmxAxReviewAnnotation[] {
563
+ return canvasState.getReviewAnnotations();
564
+ }
565
+
566
+ addReviewAnnotation(
567
+ input: {
568
+ body: string;
569
+ kind?: PmxAxReviewKind;
570
+ severity?: PmxAxReviewSeverity;
571
+ anchorType?: PmxAxReviewAnchorType;
572
+ nodeId?: string | null;
573
+ file?: string | null;
574
+ region?: PmxAxReviewRegion | null;
575
+ author?: string | null;
576
+ },
577
+ options?: { source?: PmxAxSource },
578
+ ): PmxAxReviewAnnotation | null {
579
+ const reviewAnnotation = canvasState.addReviewAnnotation(input, { source: options?.source ?? 'sdk' });
580
+ if (!reviewAnnotation) return null;
581
+ emitPrimaryWorkbenchEvent('ax-state-changed', { reviewAnnotation });
582
+ return reviewAnnotation;
583
+ }
584
+
585
+ updateReviewAnnotation(
586
+ id: string,
587
+ patch: { body?: string; status?: PmxAxReviewStatus; severity?: PmxAxReviewSeverity; kind?: PmxAxReviewKind },
588
+ options?: { source?: PmxAxSource },
589
+ ): PmxAxReviewAnnotation | null {
590
+ const reviewAnnotation = canvasState.updateReviewAnnotation(id, patch, { source: options?.source ?? 'sdk' });
591
+ if (reviewAnnotation) emitPrimaryWorkbenchEvent('ax-state-changed', { reviewAnnotation });
592
+ return reviewAnnotation;
593
+ }
594
+
595
+ getHostCapability(): PmxAxHostCapability | null {
596
+ return canvasState.getHostCapability();
597
+ }
598
+
599
+ reportHostCapability(input: unknown, options?: { source?: PmxAxSource }): PmxAxHostCapability {
600
+ const host = canvasState.setHostCapability(input, { source: options?.source ?? 'sdk' });
601
+ emitPrimaryWorkbenchEvent('ax-state-changed', { host });
602
+ return host;
603
+ }
604
+
440
605
  fitView(options?: {
441
606
  width?: number;
442
607
  height?: number;
@@ -671,6 +836,65 @@ export class PmxCanvas extends EventEmitter {
671
836
  return result;
672
837
  }
673
838
 
839
+ /**
840
+ * Progressively build a json-render node from SpecStream patches. Omit nodeId
841
+ * to create a new streaming node; pass the same nodeId on later calls to
842
+ * append more patches. The server accumulates the spec and the browser
843
+ * reloads the viewer as the specVersion bumps.
844
+ */
845
+ streamJsonRenderNode(input: {
846
+ nodeId?: string;
847
+ title?: string;
848
+ patches?: unknown[];
849
+ done?: boolean;
850
+ x?: number;
851
+ y?: number;
852
+ width?: number;
853
+ height?: number;
854
+ strictSize?: boolean;
855
+ }): {
856
+ id: string;
857
+ url: string;
858
+ applied: number;
859
+ skipped: number;
860
+ specVersion: number;
861
+ elementCount: number;
862
+ streamStatus: 'open' | 'closed';
863
+ } {
864
+ let nodeId = input.nodeId;
865
+ let url = '';
866
+ if (!nodeId) {
867
+ const created = createCanvasStreamingJsonRenderNode({
868
+ ...(input.title !== undefined ? { title: input.title } : {}),
869
+ ...(input.x !== undefined ? { x: input.x } : {}),
870
+ ...(input.y !== undefined ? { y: input.y } : {}),
871
+ ...(input.width !== undefined ? { width: input.width } : {}),
872
+ ...(input.height !== undefined ? { height: input.height } : {}),
873
+ ...(input.strictSize ? { strictSize: true } : {}),
874
+ });
875
+ nodeId = created.id;
876
+ url = created.url;
877
+ } else {
878
+ url = String(canvasState.getNode(nodeId)?.data.url ?? '');
879
+ }
880
+ const result = appendCanvasJsonRenderStream(
881
+ nodeId,
882
+ Array.isArray(input.patches) ? input.patches : [],
883
+ input.done === true,
884
+ );
885
+ if (!result.ok) throw new Error(result.error);
886
+ emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
887
+ return {
888
+ id: nodeId,
889
+ url,
890
+ applied: result.applied,
891
+ skipped: result.skipped,
892
+ specVersion: result.specVersion,
893
+ elementCount: result.elementCount,
894
+ streamStatus: result.streamStatus,
895
+ };
896
+ }
897
+
674
898
  addHtmlNode(input: {
675
899
  html: string;
676
900
  title?: string;
@@ -691,7 +915,7 @@ export class PmxCanvas extends EventEmitter {
691
915
  type: 'html',
692
916
  ...(typeof input.title === 'string' ? { title: input.title } : {}),
693
917
  data: {
694
- html: input.html,
918
+ html: resolveHtmlContent(input.html),
695
919
  ...(typeof input.summary === 'string' ? { summary: input.summary } : {}),
696
920
  ...(typeof input.agentSummary === 'string' ? { agentSummary: input.agentSummary } : {}),
697
921
  ...(typeof input.description === 'string' ? { description: input.description } : {}),
@@ -859,3 +1083,27 @@ export type {
859
1083
  export type { GraphNodeInput, JsonRenderNodeInput, JsonRenderSpec } from '../json-render/server.js';
860
1084
  export type { HtmlPrimitiveKind, HtmlPrimitiveDescriptor, HtmlPrimitiveInput, HtmlPrimitiveBuildResult } from './html-primitives.js';
861
1085
  export { traceManager } from './trace-manager.js';
1086
+ export type {
1087
+ PmxAxApprovalGate,
1088
+ PmxAxApprovalStatus,
1089
+ PmxAxContext,
1090
+ PmxAxEvent,
1091
+ PmxAxEventKind,
1092
+ PmxAxEvidence,
1093
+ PmxAxEvidenceKind,
1094
+ PmxAxFocusState,
1095
+ PmxAxHostCapability,
1096
+ PmxAxReviewAnchorType,
1097
+ PmxAxReviewAnnotation,
1098
+ PmxAxReviewKind,
1099
+ PmxAxReviewRegion,
1100
+ PmxAxReviewSeverity,
1101
+ PmxAxReviewStatus,
1102
+ PmxAxSource,
1103
+ PmxAxState,
1104
+ PmxAxSteeringMessage,
1105
+ PmxAxTimelineSummary,
1106
+ PmxAxWorkItem,
1107
+ PmxAxWorkItemStatus,
1108
+ } from './ax-state.js';
1109
+ export type { AxTimelineQuery } from './canvas-db.js';
@@ -29,6 +29,12 @@ export type MutationOp =
29
29
  | 'restoreSnapshot'
30
30
  | 'setPins'
31
31
  | 'setAxFocus'
32
+ | 'addWorkItem'
33
+ | 'updateWorkItem'
34
+ | 'requestApproval'
35
+ | 'resolveApproval'
36
+ | 'addReviewAnnotation'
37
+ | 'updateReviewAnnotation'
32
38
  | 'batch'
33
39
  | 'viewport'
34
40
  | 'groupNodes'