pmx-canvas 0.1.36 → 0.2.1

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 (91) hide show
  1. package/CHANGELOG.md +447 -0
  2. package/Readme.md +2 -2
  3. package/dist/json-render/index.js +89 -334
  4. package/dist/types/mcp/canvas-access.d.ts +5 -171
  5. package/dist/types/server/ax-state-manager.d.ts +267 -0
  6. package/dist/types/server/ax-state.d.ts +3 -1
  7. package/dist/types/server/canvas-db.d.ts +13 -0
  8. package/dist/types/server/canvas-operations.d.ts +1 -12
  9. package/dist/types/server/canvas-state.d.ts +8 -23
  10. package/dist/types/server/index.d.ts +6 -24
  11. package/dist/types/server/operations/composites.d.ts +121 -0
  12. package/dist/types/server/operations/http.d.ts +7 -0
  13. package/dist/types/server/operations/index.d.ts +8 -0
  14. package/dist/types/server/operations/invoker.d.ts +13 -0
  15. package/dist/types/server/operations/mcp.d.ts +15 -0
  16. package/dist/types/server/operations/ops/annotation.d.ts +2 -0
  17. package/dist/types/server/operations/ops/app.d.ts +33 -0
  18. package/dist/types/server/operations/ops/ax-await.d.ts +2 -0
  19. package/dist/types/server/operations/ops/ax-shared.d.ts +31 -0
  20. package/dist/types/server/operations/ops/ax-state.d.ts +2 -0
  21. package/dist/types/server/operations/ops/ax-timeline.d.ts +2 -0
  22. package/dist/types/server/operations/ops/ax-work.d.ts +2 -0
  23. package/dist/types/server/operations/ops/batch.d.ts +19 -0
  24. package/dist/types/server/operations/ops/edges.d.ts +2 -0
  25. package/dist/types/server/operations/ops/groups.d.ts +2 -0
  26. package/dist/types/server/operations/ops/json-render.d.ts +31 -0
  27. package/dist/types/server/operations/ops/nodes.d.ts +62 -0
  28. package/dist/types/server/operations/ops/query.d.ts +2 -0
  29. package/dist/types/server/operations/ops/snapshots.d.ts +2 -0
  30. package/dist/types/server/operations/ops/validate.d.ts +2 -0
  31. package/dist/types/server/operations/ops/viewport.d.ts +2 -0
  32. package/dist/types/server/operations/ops/webview.d.ts +2 -0
  33. package/dist/types/server/operations/registry.d.ts +15 -0
  34. package/dist/types/server/operations/types.d.ts +116 -0
  35. package/dist/types/server/operations/webview-runner.d.ts +69 -0
  36. package/docs/RELEASE.md +5 -0
  37. package/docs/adr-001-bun-only-runtime.md +46 -0
  38. package/docs/api-stability.md +57 -0
  39. package/docs/ax-host-adapter-contract.md +19 -1
  40. package/docs/ax-state-contract.md +72 -0
  41. package/docs/http-api.md +4 -0
  42. package/docs/mcp.md +61 -12
  43. package/docs/plans/plan-005-operation-registry.md +84 -0
  44. package/docs/plans/plan-006-mcp-tool-consolidation.md +109 -0
  45. package/docs/plans/plan-007-ax-domain.md +99 -0
  46. package/docs/plans/plan-008-registry-finish.md +91 -0
  47. package/docs/tech-debt-assessment-2026-06.md +90 -0
  48. package/package.json +3 -3
  49. package/skills/pmx-canvas/SKILL.md +221 -193
  50. package/skills/pmx-canvas/evals/evals.json +3 -3
  51. package/skills/pmx-canvas/references/ax-html-control-surface.md +93 -0
  52. package/skills/pmx-canvas/references/codex-app-adapter.md +13 -14
  53. package/skills/pmx-canvas/references/github-copilot-app-adapter.md +26 -11
  54. package/src/cli/agent.ts +52 -31
  55. package/src/mcp/canvas-access.ts +30 -830
  56. package/src/mcp/server.ts +162 -2014
  57. package/src/server/ax-context.ts +8 -1
  58. package/src/server/ax-state-manager.ts +826 -0
  59. package/src/server/ax-state.ts +10 -2
  60. package/src/server/canvas-db.ts +35 -0
  61. package/src/server/canvas-operations.ts +2 -328
  62. package/src/server/canvas-schema.ts +2 -2
  63. package/src/server/canvas-state.ts +103 -465
  64. package/src/server/index.ts +54 -190
  65. package/src/server/operations/composites.ts +355 -0
  66. package/src/server/operations/http.ts +103 -0
  67. package/src/server/operations/index.ts +65 -0
  68. package/src/server/operations/invoker.ts +87 -0
  69. package/src/server/operations/mcp.ts +221 -0
  70. package/src/server/operations/ops/annotation.ts +60 -0
  71. package/src/server/operations/ops/app.ts +447 -0
  72. package/src/server/operations/ops/ax-await.ts +216 -0
  73. package/src/server/operations/ops/ax-shared.ts +38 -0
  74. package/src/server/operations/ops/ax-state.ts +249 -0
  75. package/src/server/operations/ops/ax-timeline.ts +381 -0
  76. package/src/server/operations/ops/ax-work.ts +635 -0
  77. package/src/server/operations/ops/batch.ts +365 -0
  78. package/src/server/operations/ops/edges.ts +166 -0
  79. package/src/server/operations/ops/groups.ts +176 -0
  80. package/src/server/operations/ops/json-render.ts +691 -0
  81. package/src/server/operations/ops/nodes.ts +1047 -0
  82. package/src/server/operations/ops/query.ts +281 -0
  83. package/src/server/operations/ops/snapshots.ts +366 -0
  84. package/src/server/operations/ops/validate.ts +37 -0
  85. package/src/server/operations/ops/viewport.ts +219 -0
  86. package/src/server/operations/ops/webview.ts +339 -0
  87. package/src/server/operations/registry.ts +79 -0
  88. package/src/server/operations/types.ts +150 -0
  89. package/src/server/operations/webview-runner.ts +77 -0
  90. package/src/server/server.ts +158 -2255
  91. package/src/server/web-artifacts.ts +6 -2
@@ -31,7 +31,6 @@ import type {
31
31
  PmxAxWorkItemStatus,
32
32
  } from './ax-state.js';
33
33
  import type { AxTimelineQuery } from './canvas-db.js';
34
- import { findCanvasExtAppNodeId } from './ext-app-lookup.js';
35
34
  import { onFileNodeChanged } from './file-watcher.js';
36
35
  import { findOpenCanvasPosition, computeGroupBounds } from './placement.js';
37
36
  import { searchNodes, buildSpatialContext } from './spatial-analysis.js';
@@ -40,27 +39,18 @@ import { recomputeCodeGraph, buildCodeGraphSummary, formatCodeGraph } from './co
40
39
  import {
41
40
  addCanvasNode,
42
41
  addCanvasEdge,
43
- appendCanvasJsonRenderStream,
44
- createCanvasStreamingJsonRenderNode,
45
- MARKDOWN_NODE_DEFAULT_SIZE,
46
- MCP_APP_NODE_DEFAULT_SIZE,
47
- IMAGE_NODE_DEFAULT_SIZE,
48
- LEDGER_NODE_DEFAULT_SIZE,
49
42
  applyCanvasNodeUpdates,
50
43
  arrangeCanvasNodes,
51
44
  clearCanvas,
52
45
  createCanvasGraphNode,
53
46
  createCanvasGroup,
54
47
  createCanvasJsonRenderNode,
55
- buildStructuredNodeUpdate,
56
48
  fitCanvasView,
57
49
  deleteCanvasSnapshot,
58
- executeCanvasBatch,
59
50
  gcCanvasSnapshots,
60
51
  groupCanvasNodes,
61
52
  listCanvasSnapshots,
62
53
  refreshCanvasWebpageNode,
63
- removeCanvasNode,
64
54
  removeCanvasEdge,
65
55
  resolveHtmlContent,
66
56
  restoreCanvasSnapshot,
@@ -69,11 +59,19 @@ import {
69
59
  syncCanvasRuntimeBackends,
70
60
  setCanvasContextPins,
71
61
  ungroupCanvasNodes,
72
- validateCanvasNodePatch,
73
- hasStructuredNodeUpdateFields,
74
- hasTraceNodeDataFields,
75
- mergeTraceNodeDataFields,
76
62
  } from './canvas-operations.js';
63
+ import {
64
+ buildNodePatch,
65
+ createBasicCanvasNode,
66
+ removeNodeCore,
67
+ setGroupChildrenFromApi,
68
+ } from './operations/ops/nodes.js';
69
+ import { streamJsonRenderCore } from './operations/ops/json-render.js';
70
+ import {
71
+ executeOperation,
72
+ runCanvasBatchOperation,
73
+ type OpenMcpAppCoreResult,
74
+ } from './operations/index.js';
77
75
  import { validateCanvasLayout } from './canvas-validation.js';
78
76
  import { describeCanvasSchema, validateStructuredCanvasPayload } from './canvas-schema.js';
79
77
  import { serializeCanvasNode, type SerializedCanvasNode } from './canvas-serialization.js';
@@ -86,13 +84,9 @@ import {
86
84
  } from './web-artifacts.js';
87
85
  import {
88
86
  closeMcpAppSession,
89
- openMcpApp as openExternalMcpApp,
90
87
  type ExternalMcpTransportConfig,
91
88
  } from './mcp-app-runtime.js';
92
89
  import {
93
- buildExcalidrawOpenMcpAppInput,
94
- ensureExcalidrawCheckpointId,
95
- isExcalidrawCreateView,
96
90
  type DiagramPresetOpenInput,
97
91
  } from './diagram-presets.js';
98
92
  import {
@@ -259,29 +253,9 @@ export class PmxCanvas extends EventEmitter {
259
253
  if (!groupNode) throw new Error(`Group node "${groupId}" was not created.`);
260
254
  return toSdkNode(groupNode);
261
255
  }
262
- const { id, needsCodeGraphRecompute } = addCanvasNode({
263
- ...input,
264
- defaultWidth: input.type === 'markdown'
265
- ? MARKDOWN_NODE_DEFAULT_SIZE.width
266
- : input.type === 'mcp-app'
267
- ? MCP_APP_NODE_DEFAULT_SIZE.width
268
- : input.type === 'image'
269
- ? IMAGE_NODE_DEFAULT_SIZE.width
270
- : input.type === 'ledger'
271
- ? LEDGER_NODE_DEFAULT_SIZE.width
272
- : 360,
273
- defaultHeight: input.type === 'markdown'
274
- ? MARKDOWN_NODE_DEFAULT_SIZE.height
275
- : input.type === 'mcp-app'
276
- ? MCP_APP_NODE_DEFAULT_SIZE.height
277
- : input.type === 'image'
278
- ? IMAGE_NODE_DEFAULT_SIZE.height
279
- : input.type === 'ledger'
280
- ? LEDGER_NODE_DEFAULT_SIZE.height
281
- : 200,
282
- fileMode: 'path',
283
- ...(input.strictSize ? { strictSize: true } : {}),
284
- });
256
+ // Thin wrapper over the shared operation core (plan-005); the SDK keeps
257
+ // fileMode 'path' as an explicit visible parameter instead of forked code.
258
+ const { node, needsCodeGraphRecompute } = createBasicCanvasNode(input, { fileMode: 'path' });
285
259
 
286
260
  emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
287
261
 
@@ -291,8 +265,6 @@ export class PmxCanvas extends EventEmitter {
291
265
  });
292
266
  }
293
267
 
294
- const node = canvasState.getNode(id);
295
- if (!node) throw new Error(`Node "${id}" was not created.`);
296
268
  return toSdkNode(node);
297
269
  }
298
270
 
@@ -339,58 +311,18 @@ export class PmxCanvas extends EventEmitter {
339
311
  updateNode(id: string, patch: Partial<CanvasNodeState> & Record<string, unknown>): void {
340
312
  const existing = canvasState.getNode(id);
341
313
  if (!existing) return;
342
- const resolvedPatch: Partial<CanvasNodeState> = {};
343
- if (patch.position) resolvedPatch.position = patch.position;
344
- if (patch.size) resolvedPatch.size = patch.size;
345
- if (patch.collapsed !== undefined) resolvedPatch.collapsed = patch.collapsed;
346
- if (patch.pinned !== undefined) resolvedPatch.pinned = patch.pinned;
347
- if (patch.dockPosition !== undefined) resolvedPatch.dockPosition = patch.dockPosition;
348
-
349
- if (hasStructuredNodeUpdateFields(patch)) {
350
- resolvedPatch.data = buildStructuredNodeUpdate(existing, patch).data;
351
- } else if (
352
- patch.data !== undefined ||
353
- patch.title !== undefined ||
354
- patch.content !== undefined ||
355
- typeof patch.arrangeLocked === 'boolean' ||
356
- typeof patch.strictSize === 'boolean' ||
357
- (existing.type === 'trace' && hasTraceNodeDataFields(patch))
358
- ) {
359
- const nextData = {
360
- ...existing.data,
361
- ...(patch.data && typeof patch.data === 'object' && !Array.isArray(patch.data) ? patch.data : {}),
362
- ...(typeof patch.title === 'string' ? { title: patch.title } : {}),
363
- ...(typeof patch.content === 'string' ? { content: patch.content } : {}),
364
- ...(typeof patch.arrangeLocked === 'boolean' ? { arrangeLocked: patch.arrangeLocked } : {}),
365
- ...(typeof patch.strictSize === 'boolean' ? { strictSize: patch.strictSize } : {}),
366
- };
367
- resolvedPatch.data = existing.type === 'trace'
368
- ? mergeTraceNodeDataFields(nextData, patch)
369
- : nextData;
370
- }
371
-
372
- const error = validateCanvasNodePatch({
373
- ...(resolvedPatch.position ? { position: resolvedPatch.position } : {}),
374
- ...(resolvedPatch.size ? { size: resolvedPatch.size } : {}),
375
- });
376
- if (error) {
377
- throw new Error(error);
378
- }
314
+ // Thin wrapper over the shared patch core (plan-005): the SDK now carries
315
+ // the same superset semantics as HTTP/MCP (webpage titleSource/url, html
316
+ // top-level fields, axCapabilities merge, group children).
317
+ const { patch: resolvedPatch, groupChildIds } = buildNodePatch(existing, patch);
379
318
  canvasState.updateNode(id, resolvedPatch);
319
+ if (groupChildIds !== undefined) setGroupChildrenFromApi(id, groupChildIds);
380
320
  emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
381
321
  }
382
322
 
323
+ /** Remove a node. Missing id throws (plan-005 unifies this across surfaces). */
383
324
  removeNode(id: string): void {
384
- const existing = canvasState.getNode(id);
385
- const appSessionId =
386
- existing?.type === 'mcp-app' && typeof existing.data.appSessionId === 'string'
387
- ? existing.data.appSessionId
388
- : null;
389
- if (appSessionId) {
390
- closeMcpAppSession(appSessionId);
391
- }
392
- const { removed, needsCodeGraphRecompute } = removeCanvasNode(id);
393
- if (!removed) return;
325
+ const { needsCodeGraphRecompute } = removeNodeCore(id);
394
326
  emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
395
327
 
396
328
  if (needsCodeGraphRecompute) {
@@ -930,13 +862,6 @@ export class PmxCanvas extends EventEmitter {
930
862
  return validateCanvasLayout(canvasState.getLayout());
931
863
  }
932
864
 
933
- private findCanvasExtAppNodeId(toolCallId: string): string | null {
934
- return findCanvasExtAppNodeId(toolCallId, {
935
- getNode: (id) => canvasState.getNode(id),
936
- listNodes: () => canvasState.getLayout().nodes,
937
- });
938
- }
939
-
940
865
  describeSchema() {
941
866
  return describeCanvasSchema();
942
867
  }
@@ -950,14 +875,22 @@ export class PmxCanvas extends EventEmitter {
950
875
  }
951
876
 
952
877
  async runBatch(operations: Array<{ op: string; assign?: string; args?: Record<string, unknown> }>) {
953
- const result = await executeCanvasBatch(operations);
954
- emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
955
- return result;
878
+ // Delegates to the canvas.batch registry meta-op (plan-008 Wave 2). The op
879
+ // emits the single final canvas-layout-update itself (via the registry
880
+ // emitter, which server.ts wires to emitPrimaryWorkbenchEvent) — do NOT
881
+ // emit again here or the frame would fire twice.
882
+ return await runCanvasBatchOperation(operations);
956
883
  }
957
884
 
958
885
  async buildWebArtifact(
959
- input: WebArtifactBuildInput & { openInCanvas?: boolean },
886
+ input: WebArtifactBuildInput & { openInCanvas?: boolean; includeLogs?: boolean },
960
887
  ): Promise<WebArtifactCanvasBuildResult> {
888
+ // The registry's webartifact.build op wraps buildWebArtifactOnCanvas and
889
+ // returns a wire ENVELOPE (path/bytes/…); the SDK's documented return is the
890
+ // full WebArtifactCanvasBuildResult, so the SDK calls the build runtime
891
+ // directly here (the op core is the same buildWebArtifactOnCanvas; the node
892
+ // creation emits its own canvas-layout-update). The op and the SDK share the
893
+ // single build runtime — no behavior divergence.
961
894
  return buildWebArtifactOnCanvas(input);
962
895
  }
963
896
 
@@ -973,75 +906,21 @@ export class PmxCanvas extends EventEmitter {
973
906
  width?: number;
974
907
  height?: number;
975
908
  timeoutMs?: number;
976
- }): Promise<{ ok: true; id?: string; nodeId: string | null; toolCallId: string; sessionId: string; resourceUri: string }> {
977
- const targetNode = input.nodeId ? canvasState.getNode(input.nodeId) : undefined;
978
- if (input.nodeId && !targetNode) {
979
- throw new Error(`Node "${input.nodeId}" not found.`);
980
- }
981
- if (targetNode && (targetNode.type !== 'mcp-app' || targetNode.data.mode !== 'ext-app')) {
982
- throw new Error(`Node "${input.nodeId}" is not an external app node.`);
983
- }
984
-
985
- const opened = await openExternalMcpApp({
986
- transport: input.transport,
987
- toolName: input.toolName,
988
- ...(input.toolArguments ? { toolArguments: input.toolArguments } : {}),
989
- ...(input.serverName ? { serverName: input.serverName } : {}),
990
- ...(typeof input.timeoutMs === 'number' ? { timeoutMs: input.timeoutMs } : {}),
991
- });
992
- const toolCallId = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
993
- const previousSessionId = targetNode?.data.appSessionId;
994
- if (typeof previousSessionId === 'string' && previousSessionId.trim().length > 0) {
995
- closeMcpAppSession(previousSessionId);
996
- }
997
- const nodeIdSeed = input.nodeId ?? `ext-app-${toolCallId}`;
998
- const toolResult = isExcalidrawCreateView(opened.serverName, opened.toolName)
999
- ? ensureExcalidrawCheckpointId(opened.toolResult, nodeIdSeed)
1000
- : opened.toolResult;
1001
- emitPrimaryWorkbenchEvent('ext-app-open', {
1002
- toolCallId,
1003
- nodeId: nodeIdSeed,
1004
- title: input.title ?? opened.tool.title ?? opened.tool.name,
1005
- html: opened.html,
1006
- toolInput: opened.toolInput,
1007
- serverName: opened.serverName,
1008
- toolName: opened.toolName,
1009
- appSessionId: opened.sessionId,
1010
- transportConfig: input.transport,
1011
- resourceUri: opened.resourceUri,
1012
- toolDefinition: opened.tool,
1013
- sessionStatus: 'ready',
1014
- sessionError: null,
1015
- ...(opened.resourceMeta ? { resourceMeta: opened.resourceMeta } : {}),
1016
- ...(typeof input.x === 'number' ? { x: input.x } : {}),
1017
- ...(typeof input.y === 'number' ? { y: input.y } : {}),
1018
- ...(typeof input.width === 'number' ? { width: input.width } : {}),
1019
- ...(typeof input.height === 'number' ? { height: input.height } : {}),
1020
- });
1021
- emitPrimaryWorkbenchEvent('ext-app-result', {
1022
- toolCallId,
1023
- nodeId: nodeIdSeed,
1024
- serverName: opened.serverName,
1025
- toolName: opened.toolName,
1026
- success: toolResult.isError !== true,
1027
- result: toolResult,
1028
- });
1029
- const nodeId = input.nodeId ?? this.findCanvasExtAppNodeId(toolCallId);
1030
- return {
1031
- ok: true,
1032
- ...(nodeId ? { id: nodeId } : {}),
1033
- nodeId,
1034
- toolCallId,
1035
- sessionId: opened.sessionId,
1036
- resourceUri: opened.resourceUri,
1037
- };
909
+ }): Promise<OpenMcpAppCoreResult> {
910
+ // Delegate to the mcpapp.open registry op (plan-008 Wave 4). The op handler
911
+ // is the relocated legacy body (toolCallId, openExternalMcpApp, prior-session
912
+ // close, ext-app-open + ext-app-result via ctx.emit the registry emitter
913
+ // wired to emitPrimaryWorkbenchEvent). mutates:false, so the registry adds no
914
+ // canvas-layout-update; the two ext-app-* frames fire exactly once.
915
+ return await executeOperation('mcpapp.open', input) as OpenMcpAppCoreResult;
1038
916
  }
1039
917
 
1040
918
  async addDiagram(
1041
919
  input: DiagramPresetOpenInput,
1042
- ): Promise<{ ok: true; id?: string; nodeId: string | null; toolCallId: string; sessionId: string; resourceUri: string }> {
1043
- const built = buildExcalidrawOpenMcpAppInput(input);
1044
- return this.openMcpApp(built);
920
+ ): Promise<OpenMcpAppCoreResult> {
921
+ // Delegate to the diagram.open registry op, which builds the Excalidraw
922
+ // OpenMcpApp input and dispatches to the shared open core (one ext-app-* pair).
923
+ return await executeOperation('diagram.open', input) as OpenMcpAppCoreResult;
1045
924
  }
1046
925
 
1047
926
  addJsonRenderNode(
@@ -1077,32 +956,17 @@ export class PmxCanvas extends EventEmitter {
1077
956
  elementCount: number;
1078
957
  streamStatus: 'open' | 'closed';
1079
958
  } {
1080
- let nodeId = input.nodeId;
1081
- let url = '';
1082
- if (!nodeId) {
1083
- const created = createCanvasStreamingJsonRenderNode({
1084
- ...(input.title !== undefined ? { title: input.title } : {}),
1085
- ...(input.x !== undefined ? { x: input.x } : {}),
1086
- ...(input.y !== undefined ? { y: input.y } : {}),
1087
- ...(input.width !== undefined ? { width: input.width } : {}),
1088
- ...(input.height !== undefined ? { height: input.height } : {}),
1089
- ...(input.strictSize ? { strictSize: true } : {}),
1090
- });
1091
- nodeId = created.id;
1092
- url = created.url;
1093
- } else {
1094
- url = String(canvasState.getNode(nodeId)?.data.url ?? '');
1095
- }
1096
- const result = appendCanvasJsonRenderStream(
1097
- nodeId,
1098
- Array.isArray(input.patches) ? input.patches : [],
1099
- input.done === true,
1100
- );
1101
- if (!result.ok) throw new Error(result.error);
959
+ // Thin wrapper over the shared create-or-append core (plan-005). The op
960
+ // handler and this SDK method now share one implementation; the SDK emits
961
+ // the layout update itself (it does not flow through the registry's
962
+ // `mutates` path). `streamJsonRenderCore` throws OperationError (an Error
963
+ // subclass with the same message) on a bad append target. The core's
964
+ // result carries an extra `ok: true`; the SDK's wire shape omits it.
965
+ const result = streamJsonRenderCore(input);
1102
966
  emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
1103
967
  return {
1104
- id: nodeId,
1105
- url,
968
+ id: result.id,
969
+ url: result.url,
1106
970
  applied: result.applied,
1107
971
  skipped: result.skipped,
1108
972
  specVersion: result.specVersion,