pmx-canvas 0.1.26 → 0.1.28

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 (64) hide show
  1. package/.github/extensions/pmx-canvas/extension.mjs +191 -0
  2. package/CHANGELOG.md +110 -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 +17 -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 +33 -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 +53 -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 +120 -3
  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 +23 -5
  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 +50 -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 +302 -3
  40. package/src/cli/index.ts +2 -1
  41. package/src/client/nodes/ExtAppFrame.tsx +48 -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 +127 -15
  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 +395 -0
  48. package/src/json-render/charts/tufte-definitions.ts +128 -0
  49. package/src/json-render/directives.ts +64 -0
  50. package/src/json-render/renderer/index.css +107 -1
  51. package/src/json-render/renderer/index.tsx +33 -0
  52. package/src/json-render/server.ts +275 -5
  53. package/src/mcp/canvas-access.ts +264 -1
  54. package/src/mcp/server.ts +498 -9
  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 +123 -2
  59. package/src/server/canvas-schema.ts +27 -3
  60. package/src/server/canvas-state.ts +349 -2
  61. package/src/server/index.ts +259 -7
  62. package/src/server/mutation-history.ts +6 -0
  63. package/src/server/server.ts +442 -5
  64. package/src/server/web-artifacts.ts +31 -5
@@ -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,8 +32,12 @@ 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,
39
+ IMAGE_NODE_DEFAULT_SIZE,
40
+ LEDGER_NODE_DEFAULT_SIZE,
17
41
  applyCanvasNodeUpdates,
18
42
  arrangeCanvasNodes,
19
43
  clearCanvas,
@@ -30,6 +54,7 @@ import {
30
54
  refreshCanvasWebpageNode,
31
55
  removeCanvasNode,
32
56
  removeCanvasEdge,
57
+ resolveHtmlContent,
33
58
  restoreCanvasSnapshot,
34
59
  saveCanvasSnapshot,
35
60
  scheduleCodeGraphRecompute,
@@ -163,6 +188,11 @@ export class PmxCanvas extends EventEmitter {
163
188
  this._server = null;
164
189
  }
165
190
 
191
+ /**
192
+ * Add a node to the canvas and return the created node (including its `id`,
193
+ * resolved geometry, and data). Destructure `const { id } = canvas.addNode(...)`
194
+ * or keep the whole node — both work. (Previously returned a bare id string.)
195
+ */
166
196
  addNode(input: {
167
197
  type: CanvasNodeState['type'];
168
198
  title?: string;
@@ -182,12 +212,12 @@ export class PmxCanvas extends EventEmitter {
182
212
  width?: number;
183
213
  height?: number;
184
214
  strictSize?: boolean;
185
- }): string {
215
+ }): CanvasNodeState {
186
216
  if (input.type === 'webpage') {
187
217
  throw new Error('Use addWebpageNode for webpage nodes so page content is fetched and cached on the server.');
188
218
  }
189
219
  if (input.type === 'group') {
190
- return this.createGroup({
220
+ const groupId = this.createGroup({
191
221
  ...(typeof input.title === 'string' ? { title: input.title } : {}),
192
222
  childIds: input.childIds ?? input.children ?? [],
193
223
  ...(typeof input.x === 'number' ? { x: input.x } : {}),
@@ -197,6 +227,9 @@ export class PmxCanvas extends EventEmitter {
197
227
  ...(typeof input.color === 'string' ? { color: input.color } : {}),
198
228
  ...(input.childLayout ? { childLayout: input.childLayout } : {}),
199
229
  });
230
+ const groupNode = canvasState.getNode(groupId);
231
+ if (!groupNode) throw new Error(`Group node "${groupId}" was not created.`);
232
+ return groupNode;
200
233
  }
201
234
  const { id, needsCodeGraphRecompute } = addCanvasNode({
202
235
  ...input,
@@ -204,12 +237,20 @@ export class PmxCanvas extends EventEmitter {
204
237
  ? MARKDOWN_NODE_DEFAULT_SIZE.width
205
238
  : input.type === 'mcp-app'
206
239
  ? MCP_APP_NODE_DEFAULT_SIZE.width
207
- : 360,
240
+ : input.type === 'image'
241
+ ? IMAGE_NODE_DEFAULT_SIZE.width
242
+ : input.type === 'ledger'
243
+ ? LEDGER_NODE_DEFAULT_SIZE.width
244
+ : 360,
208
245
  defaultHeight: input.type === 'markdown'
209
246
  ? MARKDOWN_NODE_DEFAULT_SIZE.height
210
247
  : input.type === 'mcp-app'
211
248
  ? MCP_APP_NODE_DEFAULT_SIZE.height
212
- : 200,
249
+ : input.type === 'image'
250
+ ? IMAGE_NODE_DEFAULT_SIZE.height
251
+ : input.type === 'ledger'
252
+ ? LEDGER_NODE_DEFAULT_SIZE.height
253
+ : 200,
213
254
  fileMode: 'path',
214
255
  ...(input.strictSize ? { strictSize: true } : {}),
215
256
  });
@@ -222,7 +263,9 @@ export class PmxCanvas extends EventEmitter {
222
263
  });
223
264
  }
224
265
 
225
- return id;
266
+ const node = canvasState.getNode(id);
267
+ if (!node) throw new Error(`Node "${id}" was not created.`);
268
+ return node;
226
269
  }
227
270
 
228
271
  async addWebpageNode(input: {
@@ -453,6 +496,132 @@ export class PmxCanvas extends EventEmitter {
453
496
  return focus;
454
497
  }
455
498
 
499
+ recordAxEvent(
500
+ input: { kind: PmxAxEvent['kind']; summary: string; detail?: string | null; nodeIds?: string[]; data?: Record<string, unknown> | null },
501
+ options?: { source?: PmxAxSource },
502
+ ): PmxAxEvent {
503
+ const event = canvasState.recordAxEvent(input, { source: options?.source ?? 'sdk' });
504
+ emitPrimaryWorkbenchEvent('ax-event-created', { event });
505
+ return event;
506
+ }
507
+
508
+ sendSteering(message: string, options?: { source?: PmxAxSource }): PmxAxSteeringMessage {
509
+ const steering = canvasState.recordSteeringMessage(message, { source: options?.source ?? 'sdk' });
510
+ emitPrimaryWorkbenchEvent('ax-event-created', { steering });
511
+ return steering;
512
+ }
513
+
514
+ markSteeringDelivered(id: string): boolean {
515
+ const ok = canvasState.markSteeringDelivered(id);
516
+ if (ok) emitPrimaryWorkbenchEvent('ax-event-created', { steeringDelivered: id });
517
+ return ok;
518
+ }
519
+
520
+ getAxTimeline(query?: AxTimelineQuery): ReturnType<typeof canvasState.getAxTimeline> {
521
+ return canvasState.getAxTimeline(query);
522
+ }
523
+
524
+ listWorkItems(): PmxAxWorkItem[] {
525
+ return canvasState.getWorkItems();
526
+ }
527
+
528
+ addWorkItem(
529
+ input: { title: string; status?: PmxAxWorkItemStatus; detail?: string | null; nodeIds?: string[] },
530
+ options?: { source?: PmxAxSource },
531
+ ): PmxAxWorkItem {
532
+ const workItem = canvasState.addWorkItem(input, { source: options?.source ?? 'sdk' });
533
+ emitPrimaryWorkbenchEvent('ax-state-changed', { workItem });
534
+ return workItem;
535
+ }
536
+
537
+ updateWorkItem(
538
+ id: string,
539
+ patch: { title?: string; status?: PmxAxWorkItemStatus; detail?: string | null; nodeIds?: string[] },
540
+ options?: { source?: PmxAxSource },
541
+ ): PmxAxWorkItem | null {
542
+ const workItem = canvasState.updateWorkItem(id, patch, { source: options?.source ?? 'sdk' });
543
+ if (workItem) emitPrimaryWorkbenchEvent('ax-state-changed', { workItem });
544
+ return workItem;
545
+ }
546
+
547
+ listApprovalGates(): PmxAxApprovalGate[] {
548
+ return canvasState.getApprovalGates();
549
+ }
550
+
551
+ requestApproval(
552
+ input: { title: string; detail?: string | null; action?: string | null; nodeIds?: string[] },
553
+ options?: { source?: PmxAxSource },
554
+ ): PmxAxApprovalGate {
555
+ const approvalGate = canvasState.requestApproval(input, { source: options?.source ?? 'sdk' });
556
+ emitPrimaryWorkbenchEvent('ax-state-changed', { approvalGate });
557
+ return approvalGate;
558
+ }
559
+
560
+ resolveApproval(
561
+ id: string,
562
+ decision: 'approved' | 'rejected',
563
+ options?: { resolution?: string; source?: PmxAxSource },
564
+ ): PmxAxApprovalGate | null {
565
+ const approvalGate = canvasState.resolveApproval(id, decision, {
566
+ ...(options?.resolution !== undefined ? { resolution: options.resolution } : {}),
567
+ source: options?.source ?? 'sdk',
568
+ });
569
+ if (approvalGate) emitPrimaryWorkbenchEvent('ax-state-changed', { approvalGate });
570
+ return approvalGate;
571
+ }
572
+
573
+ addEvidence(
574
+ input: { kind: PmxAxEvidenceKind; title: string; body?: string | null; ref?: string | null; nodeIds?: string[]; data?: Record<string, unknown> | null },
575
+ options?: { source?: PmxAxSource },
576
+ ): PmxAxEvidence {
577
+ const evidence = canvasState.addEvidence(input, { source: options?.source ?? 'sdk' });
578
+ emitPrimaryWorkbenchEvent('ax-event-created', { evidence });
579
+ return evidence;
580
+ }
581
+
582
+ listReviewAnnotations(): PmxAxReviewAnnotation[] {
583
+ return canvasState.getReviewAnnotations();
584
+ }
585
+
586
+ addReviewAnnotation(
587
+ input: {
588
+ body: string;
589
+ kind?: PmxAxReviewKind;
590
+ severity?: PmxAxReviewSeverity;
591
+ anchorType?: PmxAxReviewAnchorType;
592
+ nodeId?: string | null;
593
+ file?: string | null;
594
+ region?: PmxAxReviewRegion | null;
595
+ author?: string | null;
596
+ },
597
+ options?: { source?: PmxAxSource },
598
+ ): PmxAxReviewAnnotation | null {
599
+ const reviewAnnotation = canvasState.addReviewAnnotation(input, { source: options?.source ?? 'sdk' });
600
+ if (!reviewAnnotation) return null;
601
+ emitPrimaryWorkbenchEvent('ax-state-changed', { reviewAnnotation });
602
+ return reviewAnnotation;
603
+ }
604
+
605
+ updateReviewAnnotation(
606
+ id: string,
607
+ patch: { body?: string; status?: PmxAxReviewStatus; severity?: PmxAxReviewSeverity; kind?: PmxAxReviewKind },
608
+ options?: { source?: PmxAxSource },
609
+ ): PmxAxReviewAnnotation | null {
610
+ const reviewAnnotation = canvasState.updateReviewAnnotation(id, patch, { source: options?.source ?? 'sdk' });
611
+ if (reviewAnnotation) emitPrimaryWorkbenchEvent('ax-state-changed', { reviewAnnotation });
612
+ return reviewAnnotation;
613
+ }
614
+
615
+ getHostCapability(): PmxAxHostCapability | null {
616
+ return canvasState.getHostCapability();
617
+ }
618
+
619
+ reportHostCapability(input: unknown, options?: { source?: PmxAxSource }): PmxAxHostCapability {
620
+ const host = canvasState.setHostCapability(input, { source: options?.source ?? 'sdk' });
621
+ emitPrimaryWorkbenchEvent('ax-state-changed', { host });
622
+ return host;
623
+ }
624
+
456
625
  fitView(options?: {
457
626
  width?: number;
458
627
  height?: number;
@@ -687,6 +856,65 @@ export class PmxCanvas extends EventEmitter {
687
856
  return result;
688
857
  }
689
858
 
859
+ /**
860
+ * Progressively build a json-render node from SpecStream patches. Omit nodeId
861
+ * to create a new streaming node; pass the same nodeId on later calls to
862
+ * append more patches. The server accumulates the spec and the browser
863
+ * reloads the viewer as the specVersion bumps.
864
+ */
865
+ streamJsonRenderNode(input: {
866
+ nodeId?: string;
867
+ title?: string;
868
+ patches?: unknown[];
869
+ done?: boolean;
870
+ x?: number;
871
+ y?: number;
872
+ width?: number;
873
+ height?: number;
874
+ strictSize?: boolean;
875
+ }): {
876
+ id: string;
877
+ url: string;
878
+ applied: number;
879
+ skipped: number;
880
+ specVersion: number;
881
+ elementCount: number;
882
+ streamStatus: 'open' | 'closed';
883
+ } {
884
+ let nodeId = input.nodeId;
885
+ let url = '';
886
+ if (!nodeId) {
887
+ const created = createCanvasStreamingJsonRenderNode({
888
+ ...(input.title !== undefined ? { title: input.title } : {}),
889
+ ...(input.x !== undefined ? { x: input.x } : {}),
890
+ ...(input.y !== undefined ? { y: input.y } : {}),
891
+ ...(input.width !== undefined ? { width: input.width } : {}),
892
+ ...(input.height !== undefined ? { height: input.height } : {}),
893
+ ...(input.strictSize ? { strictSize: true } : {}),
894
+ });
895
+ nodeId = created.id;
896
+ url = created.url;
897
+ } else {
898
+ url = String(canvasState.getNode(nodeId)?.data.url ?? '');
899
+ }
900
+ const result = appendCanvasJsonRenderStream(
901
+ nodeId,
902
+ Array.isArray(input.patches) ? input.patches : [],
903
+ input.done === true,
904
+ );
905
+ if (!result.ok) throw new Error(result.error);
906
+ emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
907
+ return {
908
+ id: nodeId,
909
+ url,
910
+ applied: result.applied,
911
+ skipped: result.skipped,
912
+ specVersion: result.specVersion,
913
+ elementCount: result.elementCount,
914
+ streamStatus: result.streamStatus,
915
+ };
916
+ }
917
+
690
918
  addHtmlNode(input: {
691
919
  html: string;
692
920
  title?: string;
@@ -707,7 +935,7 @@ export class PmxCanvas extends EventEmitter {
707
935
  type: 'html',
708
936
  ...(typeof input.title === 'string' ? { title: input.title } : {}),
709
937
  data: {
710
- html: input.html,
938
+ html: resolveHtmlContent(input.html),
711
939
  ...(typeof input.summary === 'string' ? { summary: input.summary } : {}),
712
940
  ...(typeof input.agentSummary === 'string' ? { agentSummary: input.agentSummary } : {}),
713
941
  ...(typeof input.description === 'string' ? { description: input.description } : {}),
@@ -875,3 +1103,27 @@ export type {
875
1103
  export type { GraphNodeInput, JsonRenderNodeInput, JsonRenderSpec } from '../json-render/server.js';
876
1104
  export type { HtmlPrimitiveKind, HtmlPrimitiveDescriptor, HtmlPrimitiveInput, HtmlPrimitiveBuildResult } from './html-primitives.js';
877
1105
  export { traceManager } from './trace-manager.js';
1106
+ export type {
1107
+ PmxAxApprovalGate,
1108
+ PmxAxApprovalStatus,
1109
+ PmxAxContext,
1110
+ PmxAxEvent,
1111
+ PmxAxEventKind,
1112
+ PmxAxEvidence,
1113
+ PmxAxEvidenceKind,
1114
+ PmxAxFocusState,
1115
+ PmxAxHostCapability,
1116
+ PmxAxReviewAnchorType,
1117
+ PmxAxReviewAnnotation,
1118
+ PmxAxReviewKind,
1119
+ PmxAxReviewRegion,
1120
+ PmxAxReviewSeverity,
1121
+ PmxAxReviewStatus,
1122
+ PmxAxSource,
1123
+ PmxAxState,
1124
+ PmxAxSteeringMessage,
1125
+ PmxAxTimelineSummary,
1126
+ PmxAxWorkItem,
1127
+ PmxAxWorkItemStatus,
1128
+ } from './ax-state.js';
1129
+ 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'