pmx-canvas 0.1.0

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 (226) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/LICENSE +21 -0
  3. package/Readme.md +865 -0
  4. package/dist/canvas/global.css +3173 -0
  5. package/dist/canvas/index.js +183 -0
  6. package/dist/json-render/index.css +2 -0
  7. package/dist/json-render/index.js +389 -0
  8. package/dist/types/cli/agent.d.ts +13 -0
  9. package/dist/types/cli/index.d.ts +2 -0
  10. package/dist/types/cli/watch.d.ts +5 -0
  11. package/dist/types/client/App.d.ts +1 -0
  12. package/dist/types/client/canvas/AttentionHistory.d.ts +1 -0
  13. package/dist/types/client/canvas/AttentionToast.d.ts +1 -0
  14. package/dist/types/client/canvas/CanvasNode.d.ts +8 -0
  15. package/dist/types/client/canvas/CanvasViewport.d.ts +8 -0
  16. package/dist/types/client/canvas/CommandPalette.d.ts +4 -0
  17. package/dist/types/client/canvas/ContextMenu.d.ts +24 -0
  18. package/dist/types/client/canvas/ContextPinBar.d.ts +1 -0
  19. package/dist/types/client/canvas/ContextPinHud.d.ts +1 -0
  20. package/dist/types/client/canvas/DockedNode.d.ts +4 -0
  21. package/dist/types/client/canvas/EdgeLayer.d.ts +8 -0
  22. package/dist/types/client/canvas/ExpandedNodeOverlay.d.ts +1 -0
  23. package/dist/types/client/canvas/FocusFieldLayer.d.ts +1 -0
  24. package/dist/types/client/canvas/Minimap.d.ts +23 -0
  25. package/dist/types/client/canvas/SelectionBar.d.ts +1 -0
  26. package/dist/types/client/canvas/ShortcutOverlay.d.ts +3 -0
  27. package/dist/types/client/canvas/SnapshotPanel.d.ts +7 -0
  28. package/dist/types/client/canvas/snap-guides.d.ts +23 -0
  29. package/dist/types/client/canvas/use-node-drag.d.ts +15 -0
  30. package/dist/types/client/canvas/use-node-resize.d.ts +15 -0
  31. package/dist/types/client/canvas/use-pan-zoom.d.ts +16 -0
  32. package/dist/types/client/ext-app/bridge.d.ts +161 -0
  33. package/dist/types/client/icons.d.ts +70 -0
  34. package/dist/types/client/index.d.ts +1 -0
  35. package/dist/types/client/nodes/ContextNode.d.ts +34 -0
  36. package/dist/types/client/nodes/ExtAppFrame.d.ts +18 -0
  37. package/dist/types/client/nodes/FileNode.d.ts +5 -0
  38. package/dist/types/client/nodes/GroupNode.d.ts +6 -0
  39. package/dist/types/client/nodes/ImageNode.d.ts +10 -0
  40. package/dist/types/client/nodes/InlineFormatBar.d.ts +7 -0
  41. package/dist/types/client/nodes/InlineMarkdownEditor.d.ts +14 -0
  42. package/dist/types/client/nodes/LedgerNode.d.ts +4 -0
  43. package/dist/types/client/nodes/MarkdownNode.d.ts +6 -0
  44. package/dist/types/client/nodes/McpAppNode.d.ts +4 -0
  45. package/dist/types/client/nodes/MdFormatBar.d.ts +8 -0
  46. package/dist/types/client/nodes/PromptNode.d.ts +5 -0
  47. package/dist/types/client/nodes/ResponseNode.d.ts +5 -0
  48. package/dist/types/client/nodes/StatusNode.d.ts +4 -0
  49. package/dist/types/client/nodes/StatusSummary.d.ts +4 -0
  50. package/dist/types/client/nodes/TraceNode.d.ts +4 -0
  51. package/dist/types/client/nodes/WebpageNode.d.ts +5 -0
  52. package/dist/types/client/nodes/image-warnings.d.ts +6 -0
  53. package/dist/types/client/nodes/inline-editor-commands.d.ts +11 -0
  54. package/dist/types/client/nodes/md-format.d.ts +25 -0
  55. package/dist/types/client/state/attention-bridge.d.ts +3 -0
  56. package/dist/types/client/state/attention-store.d.ts +25 -0
  57. package/dist/types/client/state/canvas-store.d.ts +74 -0
  58. package/dist/types/client/state/intent-bridge.d.ts +158 -0
  59. package/dist/types/client/state/sse-bridge.d.ts +5 -0
  60. package/dist/types/client/theme/tokens.d.ts +27 -0
  61. package/dist/types/client/types.d.ts +40 -0
  62. package/dist/types/client/utils/ext-app-tool-result.d.ts +1 -0
  63. package/dist/types/client/utils/placement.d.ts +1 -0
  64. package/dist/types/client/utils/platform.d.ts +2 -0
  65. package/dist/types/json-render/catalog.d.ts +815 -0
  66. package/dist/types/json-render/charts/components.d.ts +54 -0
  67. package/dist/types/json-render/charts/definitions.d.ts +103 -0
  68. package/dist/types/json-render/charts/extra-components.d.ts +58 -0
  69. package/dist/types/json-render/charts/extra-definitions.d.ts +181 -0
  70. package/dist/types/json-render/renderer/index.d.ts +16 -0
  71. package/dist/types/json-render/schema.d.ts +46 -0
  72. package/dist/types/json-render/server.d.ts +55 -0
  73. package/dist/types/mcp/server.d.ts +22 -0
  74. package/dist/types/server/agent-context.d.ts +21 -0
  75. package/dist/types/server/artifact-paths.d.ts +3 -0
  76. package/dist/types/server/canvas-operations.d.ts +154 -0
  77. package/dist/types/server/canvas-provenance.d.ts +13 -0
  78. package/dist/types/server/canvas-schema.d.ts +49 -0
  79. package/dist/types/server/canvas-serialization.d.ts +25 -0
  80. package/dist/types/server/canvas-state.d.ts +174 -0
  81. package/dist/types/server/canvas-validation.d.ts +33 -0
  82. package/dist/types/server/chart-template.d.ts +29 -0
  83. package/dist/types/server/code-graph.d.ts +67 -0
  84. package/dist/types/server/context-cards.d.ts +24 -0
  85. package/dist/types/server/diagram-presets.d.ts +28 -0
  86. package/dist/types/server/ext-app-call-registry.d.ts +16 -0
  87. package/dist/types/server/ext-app-tool-result.d.ts +1 -0
  88. package/dist/types/server/file-watcher.d.ts +16 -0
  89. package/dist/types/server/index.d.ts +243 -0
  90. package/dist/types/server/mcp-app-candidate.d.ts +25 -0
  91. package/dist/types/server/mcp-app-host.d.ts +65 -0
  92. package/dist/types/server/mcp-app-runtime.d.ts +47 -0
  93. package/dist/types/server/mutation-history.d.ts +105 -0
  94. package/dist/types/server/placement.d.ts +37 -0
  95. package/dist/types/server/server.d.ts +103 -0
  96. package/dist/types/server/spatial-analysis.d.ts +87 -0
  97. package/dist/types/server/trace-manager.d.ts +48 -0
  98. package/dist/types/server/web-artifacts.d.ts +50 -0
  99. package/dist/types/server/webpage-node.d.ts +25 -0
  100. package/dist/types/shared/auto-arrange.d.ts +29 -0
  101. package/dist/types/shared/ext-app-tool-result.d.ts +9 -0
  102. package/dist/types/shared/placement.d.ts +26 -0
  103. package/dist/types/shared/semantic-attention.d.ts +97 -0
  104. package/package.json +109 -0
  105. package/skills/data-analysis/SKILL.md +324 -0
  106. package/skills/doc-coauthoring/SKILL.md +375 -0
  107. package/skills/frontend-design/SKILL.md +45 -0
  108. package/skills/json-render-codegen/SKILL.md +112 -0
  109. package/skills/json-render-core/SKILL.md +265 -0
  110. package/skills/json-render-ink/SKILL.md +273 -0
  111. package/skills/json-render-mcp/SKILL.md +132 -0
  112. package/skills/json-render-react/SKILL.md +264 -0
  113. package/skills/json-render-shadcn/SKILL.md +159 -0
  114. package/skills/playwright-cli/SKILL.md +67 -0
  115. package/skills/pmx-canvas/SKILL.md +668 -0
  116. package/skills/pmx-canvas/evals/evals.json +186 -0
  117. package/skills/pmx-canvas-testing/SKILL.md +78 -0
  118. package/skills/published-consumer-e2e/SKILL.md +43 -0
  119. package/skills/published-consumer-e2e/scripts/run-published-consumer-e2e.sh +241 -0
  120. package/skills/web-artifacts-builder/SKILL.md +80 -0
  121. package/skills/web-artifacts-builder/scripts/bundle-artifact.sh +167 -0
  122. package/skills/web-artifacts-builder/scripts/init-artifact.sh +425 -0
  123. package/skills/web-artifacts-builder/scripts/shadcn-components.tar.gz +0 -0
  124. package/skills/web-design-guidelines/SKILL.md +39 -0
  125. package/src/cli/agent.ts +2144 -0
  126. package/src/cli/index.ts +622 -0
  127. package/src/cli/watch.ts +88 -0
  128. package/src/client/App.tsx +507 -0
  129. package/src/client/canvas/AttentionHistory.tsx +81 -0
  130. package/src/client/canvas/AttentionToast.tsx +19 -0
  131. package/src/client/canvas/CanvasNode.tsx +363 -0
  132. package/src/client/canvas/CanvasViewport.tsx +590 -0
  133. package/src/client/canvas/CommandPalette.tsx +302 -0
  134. package/src/client/canvas/ContextMenu.tsx +601 -0
  135. package/src/client/canvas/ContextPinBar.tsx +25 -0
  136. package/src/client/canvas/ContextPinHud.tsx +22 -0
  137. package/src/client/canvas/DockedNode.tsx +66 -0
  138. package/src/client/canvas/EdgeLayer.tsx +280 -0
  139. package/src/client/canvas/ExpandedNodeOverlay.tsx +260 -0
  140. package/src/client/canvas/FocusFieldLayer.tsx +107 -0
  141. package/src/client/canvas/Minimap.tsx +301 -0
  142. package/src/client/canvas/SelectionBar.tsx +69 -0
  143. package/src/client/canvas/ShortcutOverlay.tsx +69 -0
  144. package/src/client/canvas/SnapshotPanel.tsx +236 -0
  145. package/src/client/canvas/snap-guides.ts +170 -0
  146. package/src/client/canvas/use-node-drag.ts +51 -0
  147. package/src/client/canvas/use-node-resize.ts +59 -0
  148. package/src/client/canvas/use-pan-zoom.ts +191 -0
  149. package/src/client/ext-app/bridge.ts +542 -0
  150. package/src/client/icons.tsx +424 -0
  151. package/src/client/index.tsx +7 -0
  152. package/src/client/nodes/ContextNode.tsx +412 -0
  153. package/src/client/nodes/ExtAppFrame.tsx +509 -0
  154. package/src/client/nodes/FileNode.tsx +256 -0
  155. package/src/client/nodes/GroupNode.tsx +39 -0
  156. package/src/client/nodes/ImageNode.tsx +160 -0
  157. package/src/client/nodes/InlineFormatBar.tsx +169 -0
  158. package/src/client/nodes/InlineMarkdownEditor.tsx +123 -0
  159. package/src/client/nodes/LedgerNode.tsx +37 -0
  160. package/src/client/nodes/MarkdownNode.tsx +359 -0
  161. package/src/client/nodes/McpAppNode.tsx +85 -0
  162. package/src/client/nodes/MdFormatBar.tsx +109 -0
  163. package/src/client/nodes/PromptNode.tsx +597 -0
  164. package/src/client/nodes/ResponseNode.tsx +153 -0
  165. package/src/client/nodes/StatusNode.tsx +84 -0
  166. package/src/client/nodes/StatusSummary.tsx +38 -0
  167. package/src/client/nodes/TraceNode.tsx +120 -0
  168. package/src/client/nodes/WebpageNode.tsx +288 -0
  169. package/src/client/nodes/image-warnings.ts +95 -0
  170. package/src/client/nodes/inline-editor-commands.ts +37 -0
  171. package/src/client/nodes/md-format.ts +206 -0
  172. package/src/client/state/attention-bridge.ts +328 -0
  173. package/src/client/state/attention-store.ts +73 -0
  174. package/src/client/state/canvas-store.ts +631 -0
  175. package/src/client/state/intent-bridge.ts +315 -0
  176. package/src/client/state/sse-bridge.ts +965 -0
  177. package/src/client/theme/global.css +3173 -0
  178. package/src/client/theme/tokens.ts +72 -0
  179. package/src/client/types-shims.d.ts +5 -0
  180. package/src/client/types.ts +81 -0
  181. package/src/client/utils/ext-app-tool-result.ts +4 -0
  182. package/src/client/utils/placement.ts +4 -0
  183. package/src/client/utils/platform.ts +2 -0
  184. package/src/json-render/catalog.ts +256 -0
  185. package/src/json-render/charts/components.tsx +198 -0
  186. package/src/json-render/charts/definitions.ts +81 -0
  187. package/src/json-render/charts/extra-components.tsx +267 -0
  188. package/src/json-render/charts/extra-definitions.ts +145 -0
  189. package/src/json-render/renderer/index.css +174 -0
  190. package/src/json-render/renderer/index.tsx +86 -0
  191. package/src/json-render/schema.ts +62 -0
  192. package/src/json-render/server.ts +597 -0
  193. package/src/mcp/server.ts +1377 -0
  194. package/src/server/agent-context.ts +242 -0
  195. package/src/server/artifact-paths.ts +17 -0
  196. package/src/server/canvas-operations.ts +1279 -0
  197. package/src/server/canvas-provenance.ts +243 -0
  198. package/src/server/canvas-schema.ts +432 -0
  199. package/src/server/canvas-serialization.ts +95 -0
  200. package/src/server/canvas-state.ts +1134 -0
  201. package/src/server/canvas-validation.ts +114 -0
  202. package/src/server/chart-template.ts +449 -0
  203. package/src/server/code-graph.ts +370 -0
  204. package/src/server/context-cards.ts +31 -0
  205. package/src/server/diagram-presets.ts +71 -0
  206. package/src/server/ext-app-call-registry.ts +77 -0
  207. package/src/server/ext-app-tool-result.ts +4 -0
  208. package/src/server/file-watcher.ts +121 -0
  209. package/src/server/index.ts +647 -0
  210. package/src/server/mcp-app-candidate.ts +174 -0
  211. package/src/server/mcp-app-host.ts +814 -0
  212. package/src/server/mcp-app-runtime.ts +459 -0
  213. package/src/server/mutation-history.ts +350 -0
  214. package/src/server/placement.ts +125 -0
  215. package/src/server/server.ts +3846 -0
  216. package/src/server/spatial-analysis.ts +356 -0
  217. package/src/server/trace-manager.ts +333 -0
  218. package/src/server/web-artifacts/scripts/bundle-artifact.sh +167 -0
  219. package/src/server/web-artifacts/scripts/init-artifact.sh +426 -0
  220. package/src/server/web-artifacts/scripts/shadcn-components.tar.gz +0 -0
  221. package/src/server/web-artifacts.ts +442 -0
  222. package/src/server/webpage-node.ts +328 -0
  223. package/src/shared/auto-arrange.ts +439 -0
  224. package/src/shared/ext-app-tool-result.ts +76 -0
  225. package/src/shared/placement.ts +81 -0
  226. package/src/shared/semantic-attention.ts +598 -0
@@ -0,0 +1,647 @@
1
+ import { EventEmitter } from 'node:events';
2
+ import { canvasState, IMAGE_MIME_MAP } from './canvas-state.js';
3
+ import type { CanvasNodeState, CanvasEdge, CanvasLayout, ViewportState } from './canvas-state.js';
4
+ import { onFileNodeChanged } from './file-watcher.js';
5
+ import { findOpenCanvasPosition, computeGroupBounds } from './placement.js';
6
+ import { searchNodes, buildSpatialContext } from './spatial-analysis.js';
7
+ import { mutationHistory, diffLayouts, formatDiff } from './mutation-history.js';
8
+ import { recomputeCodeGraph, buildCodeGraphSummary, formatCodeGraph } from './code-graph.js';
9
+ import {
10
+ addCanvasNode,
11
+ addCanvasEdge,
12
+ applyCanvasNodeUpdates,
13
+ arrangeCanvasNodes,
14
+ clearCanvas,
15
+ createCanvasGraphNode,
16
+ createCanvasGroup,
17
+ createCanvasJsonRenderNode,
18
+ deleteCanvasSnapshot,
19
+ executeCanvasBatch,
20
+ groupCanvasNodes,
21
+ listCanvasSnapshots,
22
+ refreshCanvasWebpageNode,
23
+ removeCanvasNode,
24
+ removeCanvasEdge,
25
+ restoreCanvasSnapshot,
26
+ saveCanvasSnapshot,
27
+ scheduleCodeGraphRecompute,
28
+ syncCanvasRuntimeBackends,
29
+ setCanvasContextPins,
30
+ ungroupCanvasNodes,
31
+ validateCanvasNodePatch,
32
+ } from './canvas-operations.js';
33
+ import { validateCanvasLayout } from './canvas-validation.js';
34
+ import { describeCanvasSchema, validateStructuredCanvasPayload } from './canvas-schema.js';
35
+ import {
36
+ buildWebArtifactOnCanvas,
37
+ type WebArtifactBuildInput,
38
+ type WebArtifactCanvasBuildResult,
39
+ } from './web-artifacts.js';
40
+ import {
41
+ closeMcpAppSession,
42
+ openMcpApp as openExternalMcpApp,
43
+ type ExternalMcpTransportConfig,
44
+ } from './mcp-app-runtime.js';
45
+ import {
46
+ buildExcalidrawOpenMcpAppInput,
47
+ type DiagramPresetOpenInput,
48
+ } from './diagram-presets.js';
49
+ import {
50
+ buildGraphSpec,
51
+ buildJsonRenderViewerHtml,
52
+ createJsonRenderNodeData,
53
+ GRAPH_NODE_SIZE,
54
+ JSON_RENDER_NODE_SIZE,
55
+ normalizeAndValidateJsonRenderSpec,
56
+ type GraphNodeInput,
57
+ type JsonRenderNodeInput,
58
+ type JsonRenderSpec,
59
+ } from '../json-render/server.js';
60
+ import {
61
+ startCanvasServer,
62
+ stopCanvasServer,
63
+ getCanvasServerPort,
64
+ openUrlInExternalBrowser,
65
+ getCanvasAutomationWebViewStatus,
66
+ startCanvasAutomationWebView,
67
+ stopCanvasAutomationWebView,
68
+ evaluateCanvasAutomationWebView,
69
+ resizeCanvasAutomationWebView,
70
+ screenshotCanvasAutomationWebView,
71
+ emitPrimaryWorkbenchEvent,
72
+ setPrimaryWorkbenchCanvasPromptHandler,
73
+ setPrimaryWorkbenchAutoOpenEnabled,
74
+ consumePrimaryWorkbenchIntents,
75
+ } from './server.js';
76
+ import type {
77
+ CanvasAutomationWebViewOptions,
78
+ CanvasAutomationWebViewStatus,
79
+ PrimaryWorkbenchCanvasPromptRequest,
80
+ PrimaryWorkbenchIntent,
81
+ } from './server.js';
82
+
83
+ export class PmxCanvas extends EventEmitter {
84
+ private _port: number;
85
+ private _server: string | null = null;
86
+
87
+ constructor(options?: { port?: number }) {
88
+ super();
89
+ this._port = options?.port ?? 4313;
90
+ }
91
+
92
+ async start(options?: {
93
+ open?: boolean;
94
+ automationWebView?: boolean | CanvasAutomationWebViewOptions;
95
+ }): Promise<void> {
96
+ const base = startCanvasServer({ port: this._port });
97
+ if (!base) {
98
+ throw new Error(`Failed to start canvas server on port ${this._port}`);
99
+ }
100
+ this._server = base;
101
+ this._port = getCanvasServerPort() ?? this._port;
102
+
103
+ // Wire up mutation history recorder
104
+ canvasState.onMutation((info) => {
105
+ mutationHistory.record({
106
+ description: info.description,
107
+ operationType: info.operationType,
108
+ forward: info.forward,
109
+ inverse: info.inverse,
110
+ });
111
+ });
112
+
113
+ // Wire up prompt handler to emit events
114
+ setPrimaryWorkbenchCanvasPromptHandler(async (request) => {
115
+ this.emit('prompt', request);
116
+ });
117
+
118
+ // Wire up file watcher: push SSE updates when watched files change
119
+ onFileNodeChanged(() => {
120
+ emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
121
+ scheduleCodeGraphRecompute(() => {
122
+ emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
123
+ });
124
+ });
125
+
126
+ // Initial code graph computation for restored file nodes
127
+ scheduleCodeGraphRecompute(() => {
128
+ emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
129
+ });
130
+
131
+ if (options?.automationWebView) {
132
+ try {
133
+ await startCanvasAutomationWebView(
134
+ `${base}/workbench`,
135
+ options.automationWebView === true ? {} : options.automationWebView,
136
+ );
137
+ } catch (error) {
138
+ stopCanvasServer();
139
+ throw error;
140
+ }
141
+ }
142
+
143
+ if (options?.open !== false) {
144
+ openUrlInExternalBrowser(`${base}/workbench`);
145
+ }
146
+ }
147
+
148
+ stop(): void {
149
+ stopCanvasServer();
150
+ this._server = null;
151
+ }
152
+
153
+ addNode(input: {
154
+ type: CanvasNodeState['type'];
155
+ title?: string;
156
+ content?: string;
157
+ x?: number;
158
+ y?: number;
159
+ width?: number;
160
+ height?: number;
161
+ }): string {
162
+ if (input.type === 'webpage') {
163
+ throw new Error('Use addWebpageNode for webpage nodes so page content is fetched and cached on the server.');
164
+ }
165
+ const { id, needsCodeGraphRecompute } = addCanvasNode({
166
+ ...input,
167
+ defaultWidth: 360,
168
+ defaultHeight: 200,
169
+ fileMode: 'path',
170
+ });
171
+
172
+ emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
173
+
174
+ if (needsCodeGraphRecompute) {
175
+ scheduleCodeGraphRecompute(() => {
176
+ emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
177
+ });
178
+ }
179
+
180
+ return id;
181
+ }
182
+
183
+ async addWebpageNode(input: {
184
+ title?: string;
185
+ url: string;
186
+ x?: number;
187
+ y?: number;
188
+ width?: number;
189
+ height?: number;
190
+ }): Promise<{ ok: boolean; id: string; error?: string; fetch: { ok: boolean; error?: string } }> {
191
+ const { id } = addCanvasNode({
192
+ type: 'webpage',
193
+ ...(typeof input.title === 'string' ? { title: input.title } : {}),
194
+ content: input.url,
195
+ ...(typeof input.x === 'number' ? { x: input.x } : {}),
196
+ ...(typeof input.y === 'number' ? { y: input.y } : {}),
197
+ ...(typeof input.width === 'number' ? { width: input.width } : {}),
198
+ ...(typeof input.height === 'number' ? { height: input.height } : {}),
199
+ defaultWidth: 520,
200
+ defaultHeight: 420,
201
+ });
202
+ emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
203
+ const result = await refreshCanvasWebpageNode(id);
204
+ emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
205
+ return {
206
+ ok: true,
207
+ id,
208
+ fetch: result.ok
209
+ ? { ok: true }
210
+ : { ok: false, error: result.error ?? 'Failed to fetch webpage content.' },
211
+ ...(result.ok ? {} : { error: result.error }),
212
+ };
213
+ }
214
+
215
+ async refreshWebpageNode(id: string, url?: string): Promise<{ ok: boolean; id: string; error?: string }> {
216
+ const result = await refreshCanvasWebpageNode(id, { ...(url ? { url } : {}) });
217
+ emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
218
+ return result;
219
+ }
220
+
221
+ updateNode(id: string, patch: Partial<CanvasNodeState>): void {
222
+ const error = validateCanvasNodePatch({
223
+ ...(patch.position ? { position: patch.position } : {}),
224
+ ...(patch.size ? { size: patch.size } : {}),
225
+ });
226
+ if (error) {
227
+ throw new Error(error);
228
+ }
229
+ canvasState.updateNode(id, patch);
230
+ emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
231
+ }
232
+
233
+ removeNode(id: string): void {
234
+ const existing = canvasState.getNode(id);
235
+ const appSessionId =
236
+ existing?.type === 'mcp-app' && typeof existing.data.appSessionId === 'string'
237
+ ? existing.data.appSessionId
238
+ : null;
239
+ if (appSessionId) {
240
+ closeMcpAppSession(appSessionId);
241
+ }
242
+ const { removed, needsCodeGraphRecompute } = removeCanvasNode(id);
243
+ if (!removed) return;
244
+ emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
245
+
246
+ if (needsCodeGraphRecompute) {
247
+ scheduleCodeGraphRecompute(() => {
248
+ emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
249
+ });
250
+ }
251
+ }
252
+
253
+ addEdge(input: {
254
+ from?: string;
255
+ to?: string;
256
+ fromSearch?: string;
257
+ toSearch?: string;
258
+ type: CanvasEdge['type'];
259
+ label?: string;
260
+ style?: CanvasEdge['style'];
261
+ animated?: boolean;
262
+ }): string {
263
+ const { id } = addCanvasEdge(input);
264
+ emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
265
+ return id;
266
+ }
267
+
268
+ removeEdge(id: string): void {
269
+ removeCanvasEdge(id);
270
+ emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
271
+ }
272
+
273
+ /**
274
+ * Create a group node and optionally add child nodes to it.
275
+ * If childIds are provided, the group auto-sizes to contain them with padding.
276
+ */
277
+ createGroup(input: {
278
+ title?: string;
279
+ childIds?: string[];
280
+ x?: number;
281
+ y?: number;
282
+ width?: number;
283
+ height?: number;
284
+ color?: string;
285
+ childLayout?: 'grid' | 'column' | 'flow';
286
+ }): string {
287
+ const { id } = createCanvasGroup(input);
288
+
289
+ emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
290
+ return id;
291
+ }
292
+
293
+ /** Add nodes to an existing group. */
294
+ groupNodes(groupId: string, childIds: string[], options?: { childLayout?: 'grid' | 'column' | 'flow' }): boolean {
295
+ const { ok } = groupCanvasNodes(groupId, childIds, options);
296
+ if (ok) {
297
+ emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
298
+ }
299
+ return ok;
300
+ }
301
+
302
+ /** Remove all children from a group (the group node remains). */
303
+ ungroupNodes(groupId: string): boolean {
304
+ const { ok } = ungroupCanvasNodes(groupId);
305
+ if (ok) {
306
+ emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
307
+ }
308
+ return ok;
309
+ }
310
+
311
+ clear(): void {
312
+ for (const node of canvasState.getLayout().nodes) {
313
+ if (node.type !== 'mcp-app') continue;
314
+ const sessionId = typeof node.data.appSessionId === 'string' ? node.data.appSessionId : '';
315
+ if (sessionId) closeMcpAppSession(sessionId);
316
+ }
317
+ clearCanvas();
318
+ emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
319
+ }
320
+
321
+ arrange(layout?: 'grid' | 'column' | 'flow'): void {
322
+ arrangeCanvasNodes(layout ?? 'grid');
323
+ emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
324
+ }
325
+
326
+ focusNode(id: string): void {
327
+ const node = canvasState.getNode(id);
328
+ if (!node) return;
329
+ canvasState.setViewport({
330
+ x: node.position.x - 100,
331
+ y: node.position.y - 100,
332
+ });
333
+ emitPrimaryWorkbenchEvent('canvas-focus-node', { nodeId: id });
334
+ emitPrimaryWorkbenchEvent('canvas-viewport-update', { viewport: canvasState.viewport });
335
+ emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
336
+ }
337
+
338
+ getLayout(): CanvasLayout {
339
+ return canvasState.getLayout();
340
+ }
341
+
342
+ getNode(id: string): CanvasNodeState | undefined {
343
+ return canvasState.getNode(id);
344
+ }
345
+
346
+ search(query: string): ReturnType<typeof searchNodes> {
347
+ return searchNodes(canvasState.getLayout().nodes, query);
348
+ }
349
+
350
+ getSpatialContext() {
351
+ const layout = canvasState.getLayout();
352
+ return buildSpatialContext(layout.nodes, layout.edges, canvasState.contextPinnedNodeIds);
353
+ }
354
+
355
+ async undo(): Promise<{ ok: boolean; description?: string }> {
356
+ const entry = mutationHistory.undo();
357
+ if (!entry) return { ok: false, description: 'Nothing to undo' };
358
+ await syncCanvasRuntimeBackends();
359
+ emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
360
+ return { ok: true, description: `Undid: ${entry.description}` };
361
+ }
362
+
363
+ async redo(): Promise<{ ok: boolean; description?: string }> {
364
+ const entry = mutationHistory.redo();
365
+ if (!entry) return { ok: false, description: 'Nothing to redo' };
366
+ await syncCanvasRuntimeBackends();
367
+ emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
368
+ return { ok: true, description: `Redid: ${entry.description}` };
369
+ }
370
+
371
+ getHistory() {
372
+ return {
373
+ text: mutationHistory.toHumanReadable(),
374
+ entries: mutationHistory.getSummaries(),
375
+ canUndo: mutationHistory.canUndo(),
376
+ canRedo: mutationHistory.canRedo(),
377
+ };
378
+ }
379
+
380
+ applyUpdates(updates: Array<{
381
+ id: string;
382
+ position?: { x: number; y: number };
383
+ size?: { width: number; height: number };
384
+ collapsed?: boolean;
385
+ dockPosition?: 'left' | 'right' | null;
386
+ }>): { applied: number; skipped: number } {
387
+ return applyCanvasNodeUpdates(updates);
388
+ }
389
+
390
+ setContextPins(nodeIds: string[], mode: 'set' | 'add' | 'remove' = 'set'): { count: number; nodeIds: string[] } {
391
+ const result = setCanvasContextPins(nodeIds, mode);
392
+ emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
393
+ return result;
394
+ }
395
+
396
+ listSnapshots() {
397
+ return listCanvasSnapshots();
398
+ }
399
+
400
+ saveSnapshot(name: string) {
401
+ return saveCanvasSnapshot(name);
402
+ }
403
+
404
+ async restoreSnapshot(id: string): Promise<{ ok: boolean }> {
405
+ const result = await restoreCanvasSnapshot(id);
406
+ if (result.ok) {
407
+ emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
408
+ }
409
+ return result;
410
+ }
411
+
412
+ deleteSnapshot(id: string): { ok: boolean } {
413
+ return deleteCanvasSnapshot(id);
414
+ }
415
+
416
+ diffSnapshot(idOrName: string): { ok: boolean; text?: string; diff?: ReturnType<typeof diffLayouts>; error?: string } {
417
+ const snapData = canvasState.getSnapshotData(idOrName);
418
+ if (!snapData) return { ok: false, error: `Snapshot "${idOrName}" not found` };
419
+
420
+ const current = canvasState.getLayout();
421
+ const diff = diffLayouts(snapData.name, snapData, current);
422
+ return { ok: true, text: formatDiff(diff), diff };
423
+ }
424
+
425
+ getCodeGraph() {
426
+ const summary = buildCodeGraphSummary();
427
+ return { text: formatCodeGraph(summary), summary };
428
+ }
429
+
430
+ validate() {
431
+ return validateCanvasLayout(canvasState.getLayout());
432
+ }
433
+
434
+ private findCanvasExtAppNodeId(toolCallId: string): string | null {
435
+ const directId = `ext-app-${toolCallId}`;
436
+ if (canvasState.getNode(directId)) return directId;
437
+ for (const node of canvasState.getLayout().nodes) {
438
+ if (
439
+ node.type === 'mcp-app' &&
440
+ node.data.mode === 'ext-app' &&
441
+ node.data.toolCallId === toolCallId
442
+ ) {
443
+ return node.id;
444
+ }
445
+ }
446
+ return null;
447
+ }
448
+
449
+ describeSchema() {
450
+ return describeCanvasSchema();
451
+ }
452
+
453
+ validateSpec(input: {
454
+ type: 'json-render' | 'graph';
455
+ spec?: unknown;
456
+ graph?: GraphNodeInput;
457
+ }) {
458
+ return validateStructuredCanvasPayload(input);
459
+ }
460
+
461
+ async runBatch(operations: Array<{ op: string; assign?: string; args?: Record<string, unknown> }>) {
462
+ const result = await executeCanvasBatch(operations);
463
+ emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
464
+ return result;
465
+ }
466
+
467
+ async buildWebArtifact(
468
+ input: WebArtifactBuildInput & { openInCanvas?: boolean },
469
+ ): Promise<WebArtifactCanvasBuildResult> {
470
+ return buildWebArtifactOnCanvas(input);
471
+ }
472
+
473
+ async openMcpApp(input: {
474
+ transport: ExternalMcpTransportConfig;
475
+ toolName: string;
476
+ toolArguments?: Record<string, unknown>;
477
+ serverName?: string;
478
+ title?: string;
479
+ x?: number;
480
+ y?: number;
481
+ width?: number;
482
+ height?: number;
483
+ }): Promise<{ ok: true; nodeId: string | null; toolCallId: string; sessionId: string; resourceUri: string }> {
484
+ const opened = await openExternalMcpApp({
485
+ transport: input.transport,
486
+ toolName: input.toolName,
487
+ ...(input.toolArguments ? { toolArguments: input.toolArguments } : {}),
488
+ ...(input.serverName ? { serverName: input.serverName } : {}),
489
+ });
490
+ const toolCallId = `ext-app-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
491
+ emitPrimaryWorkbenchEvent('ext-app-open', {
492
+ toolCallId,
493
+ title: input.title ?? opened.tool.title ?? opened.tool.name,
494
+ html: opened.html,
495
+ toolInput: opened.toolInput,
496
+ serverName: opened.serverName,
497
+ toolName: opened.toolName,
498
+ appSessionId: opened.sessionId,
499
+ transportConfig: input.transport,
500
+ resourceUri: opened.resourceUri,
501
+ toolDefinition: opened.tool,
502
+ sessionStatus: 'ready',
503
+ sessionError: null,
504
+ ...(opened.resourceMeta ? { resourceMeta: opened.resourceMeta } : {}),
505
+ ...(typeof input.x === 'number' ? { x: input.x } : {}),
506
+ ...(typeof input.y === 'number' ? { y: input.y } : {}),
507
+ ...(typeof input.width === 'number' ? { width: input.width } : {}),
508
+ ...(typeof input.height === 'number' ? { height: input.height } : {}),
509
+ });
510
+ emitPrimaryWorkbenchEvent('ext-app-result', {
511
+ toolCallId,
512
+ serverName: opened.serverName,
513
+ toolName: opened.toolName,
514
+ success: opened.toolResult.isError !== true,
515
+ result: opened.toolResult,
516
+ });
517
+ const nodeId = this.findCanvasExtAppNodeId(toolCallId);
518
+ return {
519
+ ok: true,
520
+ nodeId,
521
+ toolCallId,
522
+ sessionId: opened.sessionId,
523
+ resourceUri: opened.resourceUri,
524
+ };
525
+ }
526
+
527
+ async addDiagram(
528
+ input: DiagramPresetOpenInput,
529
+ ): Promise<{ ok: true; nodeId: string | null; toolCallId: string; sessionId: string; resourceUri: string }> {
530
+ const built = buildExcalidrawOpenMcpAppInput(input);
531
+ return this.openMcpApp(built);
532
+ }
533
+
534
+ addJsonRenderNode(
535
+ input: JsonRenderNodeInput,
536
+ ): { id: string; url: string; spec: JsonRenderSpec } {
537
+ const result = createCanvasJsonRenderNode(input);
538
+ emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
539
+ return result;
540
+ }
541
+
542
+ addGraphNode(input: GraphNodeInput): { id: string; url: string; spec: JsonRenderSpec } {
543
+ const result = createCanvasGraphNode(input);
544
+ emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
545
+ return result;
546
+ }
547
+
548
+ get port(): number {
549
+ return this._port;
550
+ }
551
+
552
+ async startAutomationWebView(
553
+ options: CanvasAutomationWebViewOptions = {},
554
+ ): Promise<CanvasAutomationWebViewStatus> {
555
+ const base = this._server ?? startCanvasServer({ port: this._port });
556
+ if (!base) {
557
+ throw new Error(`Failed to start canvas server on port ${this._port}`);
558
+ }
559
+ this._server = base;
560
+ this._port = getCanvasServerPort() ?? this._port;
561
+ return startCanvasAutomationWebView(`${base}/workbench`, options);
562
+ }
563
+
564
+ async stopAutomationWebView(): Promise<boolean> {
565
+ return stopCanvasAutomationWebView();
566
+ }
567
+
568
+ getAutomationWebViewStatus(): CanvasAutomationWebViewStatus {
569
+ return getCanvasAutomationWebViewStatus();
570
+ }
571
+
572
+ async evaluateAutomationWebView(expression: string): Promise<unknown> {
573
+ return evaluateCanvasAutomationWebView(expression);
574
+ }
575
+
576
+ async resizeAutomationWebView(
577
+ width: number,
578
+ height: number,
579
+ ): Promise<CanvasAutomationWebViewStatus> {
580
+ return resizeCanvasAutomationWebView(width, height);
581
+ }
582
+
583
+ async screenshotAutomationWebView(options: Record<string, unknown> = {}): Promise<Uint8Array> {
584
+ return screenshotCanvasAutomationWebView(options);
585
+ }
586
+ }
587
+
588
+ export function createCanvas(options?: { port?: number }): PmxCanvas {
589
+ return new PmxCanvas(options);
590
+ }
591
+
592
+ export type { CanvasNodeState, CanvasEdge, CanvasLayout, ViewportState } from './canvas-state.js';
593
+ export type {
594
+ CanvasAutomationWebViewOptions,
595
+ CanvasAutomationWebViewStatus,
596
+ PrimaryWorkbenchCanvasPromptRequest,
597
+ PrimaryWorkbenchIntent,
598
+ } from './server.js';
599
+ export {
600
+ emitPrimaryWorkbenchEvent,
601
+ consumePrimaryWorkbenchIntents,
602
+ setPrimaryWorkbenchAutoOpenEnabled,
603
+ setPrimaryWorkbenchCanvasPromptHandler,
604
+ startCanvasServer,
605
+ stopCanvasServer,
606
+ getCanvasServerPort,
607
+ openUrlInExternalBrowser,
608
+ getCanvasAutomationWebViewStatus,
609
+ startCanvasAutomationWebView,
610
+ stopCanvasAutomationWebView,
611
+ evaluateCanvasAutomationWebView,
612
+ resizeCanvasAutomationWebView,
613
+ screenshotCanvasAutomationWebView,
614
+ } from './server.js';
615
+ export { canvasState } from './canvas-state.js';
616
+ export type { CanvasSnapshot } from './canvas-state.js';
617
+ export { findOpenCanvasPosition } from './placement.js';
618
+ export { searchNodes, buildSpatialContext, detectClusters, findNeighborhoods } from './spatial-analysis.js';
619
+ export type { SpatialCluster, SpatialContext, SpatialNeighbor, NodeSpatialInfo } from './spatial-analysis.js';
620
+ export { mutationHistory, diffLayouts, formatDiff } from './mutation-history.js';
621
+ export { recomputeCodeGraph, buildCodeGraphSummary, formatCodeGraph } from './code-graph.js';
622
+ export { describeCanvasSchema, validateStructuredCanvasPayload } from './canvas-schema.js';
623
+ export {
624
+ buildWebArtifactOnCanvas,
625
+ executeWebArtifactBuild,
626
+ openWebArtifactInCanvas,
627
+ resolveWebArtifactScriptPath,
628
+ resolveWorkspacePath,
629
+ } from './web-artifacts.js';
630
+ export {
631
+ buildGraphSpec,
632
+ buildJsonRenderViewerHtml,
633
+ createJsonRenderNodeData,
634
+ GRAPH_NODE_SIZE,
635
+ JSON_RENDER_NODE_SIZE,
636
+ normalizeAndValidateJsonRenderSpec,
637
+ } from '../json-render/server.js';
638
+ export type { CodeGraphSummary, CodeGraphEdge } from './code-graph.js';
639
+ export type { MutationEntry, MutationSummary, SnapshotDiffResult } from './mutation-history.js';
640
+ export type {
641
+ WebArtifactBuildInput,
642
+ WebArtifactBuildOutput,
643
+ WebArtifactCanvasBuildResult,
644
+ WebArtifactCanvasOpenResult,
645
+ } from './web-artifacts.js';
646
+ export type { GraphNodeInput, JsonRenderNodeInput, JsonRenderSpec } from '../json-render/server.js';
647
+ export { traceManager } from './trace-manager.js';