pmx-canvas 0.3.0 → 0.3.2

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 (161) hide show
  1. package/CHANGELOG.md +73 -0
  2. package/dist/canvas/index.js +65 -65
  3. package/dist/types/cli/agent.d.ts +9 -1
  4. package/dist/types/cli/daemon.d.ts +74 -0
  5. package/dist/types/cli/watch.d.ts +2 -2
  6. package/dist/types/client/canvas/CanvasViewport.d.ts +1 -1
  7. package/dist/types/client/canvas/CommandPalette.d.ts +1 -1
  8. package/dist/types/client/canvas/Minimap.d.ts +1 -1
  9. package/dist/types/client/nodes/ExtAppFrame.d.ts +9 -1
  10. package/dist/types/client/nodes/FileNode.d.ts +1 -1
  11. package/dist/types/client/nodes/ImageNode.d.ts +1 -1
  12. package/dist/types/client/nodes/InlineFormatBar.d.ts +1 -1
  13. package/dist/types/client/nodes/MarkdownNode.d.ts +1 -1
  14. package/dist/types/client/nodes/PromptNode.d.ts +1 -1
  15. package/dist/types/client/nodes/ResponseNode.d.ts +1 -1
  16. package/dist/types/server/canvas-schema.d.ts +1 -1
  17. package/dist/types/server/html-primitives.d.ts +1 -1
  18. package/dist/types/server/index.d.ts +4 -4
  19. package/dist/types/server/operations/index.d.ts +1 -1
  20. package/dist/types/server/operations/ops/ax-read.d.ts +2 -0
  21. package/dist/types/server/operations/ops/canvas-wire.d.ts +2 -0
  22. package/dist/types/server/operations/ops/ext-app.d.ts +2 -0
  23. package/dist/types/server/server.d.ts +8 -0
  24. package/docs/cli.md +10 -1
  25. package/docs/http-api.md +28 -0
  26. package/docs/plans/plan-009-tech-debt-backlog.md +5 -5
  27. package/docs/screenshot.png +0 -0
  28. package/docs/tech-debt-assessment-2026-07.md +2 -2
  29. package/package.json +5 -2
  30. package/skills/pmx-canvas/SKILL.md +15 -10
  31. package/skills/pmx-canvas/references/full-reference.md +17 -3
  32. package/src/cli/agent.ts +1951 -1571
  33. package/src/cli/daemon.ts +460 -0
  34. package/src/cli/index.ts +80 -323
  35. package/src/cli/watch.ts +2 -10
  36. package/src/client/App.tsx +48 -46
  37. package/src/client/canvas/AttentionHistory.tsx +11 -1
  38. package/src/client/canvas/CanvasNode.tsx +41 -29
  39. package/src/client/canvas/CanvasViewport.tsx +101 -66
  40. package/src/client/canvas/CommandPalette.tsx +61 -27
  41. package/src/client/canvas/ContextMenu.tsx +13 -20
  42. package/src/client/canvas/ContextPinBar.tsx +1 -5
  43. package/src/client/canvas/ContextPinHud.tsx +1 -6
  44. package/src/client/canvas/DockedNode.tsx +4 -4
  45. package/src/client/canvas/EdgeLayer.tsx +37 -36
  46. package/src/client/canvas/ExpandedNodeOverlay.tsx +69 -45
  47. package/src/client/canvas/FocusFieldLayer.tsx +20 -22
  48. package/src/client/canvas/IntentLayer.tsx +31 -14
  49. package/src/client/canvas/Minimap.tsx +11 -16
  50. package/src/client/canvas/SelectionBar.tsx +4 -11
  51. package/src/client/canvas/ShortcutOverlay.tsx +3 -1
  52. package/src/client/canvas/SnapshotPanel.tsx +77 -95
  53. package/src/client/canvas/auto-fit.ts +15 -14
  54. package/src/client/canvas/snap-guides.ts +12 -12
  55. package/src/client/canvas/use-node-resize.ts +1 -5
  56. package/src/client/canvas/use-pan-zoom.ts +25 -26
  57. package/src/client/ext-app/bridge.ts +3 -12
  58. package/src/client/icons.tsx +63 -20
  59. package/src/client/nodes/ContextNode.tsx +14 -25
  60. package/src/client/nodes/ExtAppFrame.tsx +194 -80
  61. package/src/client/nodes/FileNode.tsx +74 -62
  62. package/src/client/nodes/GroupNode.tsx +4 -6
  63. package/src/client/nodes/HtmlNode.tsx +76 -46
  64. package/src/client/nodes/ImageNode.tsx +18 -27
  65. package/src/client/nodes/InlineFormatBar.tsx +4 -21
  66. package/src/client/nodes/InlineMarkdownEditor.tsx +0 -1
  67. package/src/client/nodes/LedgerNode.tsx +10 -4
  68. package/src/client/nodes/MarkdownNode.tsx +3 -10
  69. package/src/client/nodes/McpAppNode.tsx +26 -22
  70. package/src/client/nodes/MdFormatBar.tsx +10 -7
  71. package/src/client/nodes/PromptNode.tsx +23 -51
  72. package/src/client/nodes/ResponseNode.tsx +3 -13
  73. package/src/client/nodes/StatusNode.tsx +5 -9
  74. package/src/client/nodes/StatusSummary.tsx +2 -8
  75. package/src/client/nodes/WebpageNode.tsx +20 -14
  76. package/src/client/nodes/iframe-document-url.ts +25 -16
  77. package/src/client/nodes/image-warnings.ts +1 -7
  78. package/src/client/nodes/md-format.ts +20 -5
  79. package/src/client/state/attention-bridge.ts +4 -9
  80. package/src/client/state/attention-store.ts +1 -7
  81. package/src/client/state/canvas-store.ts +52 -36
  82. package/src/client/state/intent-bridge.ts +176 -112
  83. package/src/client/state/intent-store.ts +4 -1
  84. package/src/client/state/sse-bridge.ts +53 -70
  85. package/src/json-render/catalog.ts +12 -16
  86. package/src/json-render/charts/components.tsx +16 -20
  87. package/src/json-render/charts/extra-components.tsx +8 -16
  88. package/src/json-render/charts/extra-definitions.ts +1 -2
  89. package/src/json-render/charts/tufte-components.tsx +37 -20
  90. package/src/json-render/charts/tufte-definitions.ts +8 -2
  91. package/src/json-render/renderer/index.tsx +42 -22
  92. package/src/json-render/schema.ts +6 -3
  93. package/src/json-render/server.ts +33 -39
  94. package/src/mcp/canvas-access.ts +35 -21
  95. package/src/mcp/server.ts +132 -70
  96. package/src/server/agent-context.ts +63 -36
  97. package/src/server/ax-context.ts +7 -5
  98. package/src/server/ax-interaction.ts +176 -43
  99. package/src/server/ax-state-manager.ts +182 -39
  100. package/src/server/ax-state.ts +142 -47
  101. package/src/server/canvas-db.ts +213 -95
  102. package/src/server/canvas-operations.ts +180 -123
  103. package/src/server/canvas-provenance.ts +1 -4
  104. package/src/server/canvas-schema.ts +454 -73
  105. package/src/server/canvas-serialization.ts +27 -35
  106. package/src/server/canvas-state.ts +150 -58
  107. package/src/server/chart-template.ts +4 -5
  108. package/src/server/code-graph.ts +19 -6
  109. package/src/server/diagram-presets.ts +28 -29
  110. package/src/server/ext-app-lookup.ts +3 -12
  111. package/src/server/html-node-summary.ts +19 -10
  112. package/src/server/html-primitives.ts +326 -97
  113. package/src/server/html-surface.ts +6 -9
  114. package/src/server/image-source.ts +6 -4
  115. package/src/server/index.ts +320 -217
  116. package/src/server/intent-registry.ts +2 -5
  117. package/src/server/mcp-app-candidate.ts +5 -10
  118. package/src/server/mcp-app-host.ts +14 -38
  119. package/src/server/mcp-app-runtime.ts +12 -20
  120. package/src/server/mutation-history.ts +15 -5
  121. package/src/server/operations/composites.ts +1 -3
  122. package/src/server/operations/http.ts +2 -3
  123. package/src/server/operations/index.ts +7 -1
  124. package/src/server/operations/invoker.ts +4 -3
  125. package/src/server/operations/mcp.ts +22 -30
  126. package/src/server/operations/ops/annotation.ts +122 -10
  127. package/src/server/operations/ops/app.ts +98 -73
  128. package/src/server/operations/ops/ax-await.ts +17 -10
  129. package/src/server/operations/ops/ax-read.ts +347 -0
  130. package/src/server/operations/ops/ax-shared.ts +2 -7
  131. package/src/server/operations/ops/ax-state.ts +32 -14
  132. package/src/server/operations/ops/ax-timeline.ts +32 -19
  133. package/src/server/operations/ops/ax-work.ts +54 -37
  134. package/src/server/operations/ops/batch.ts +41 -15
  135. package/src/server/operations/ops/canvas-wire.ts +91 -0
  136. package/src/server/operations/ops/edges.ts +37 -25
  137. package/src/server/operations/ops/ext-app.ts +346 -0
  138. package/src/server/operations/ops/groups.ts +49 -20
  139. package/src/server/operations/ops/intent.ts +18 -12
  140. package/src/server/operations/ops/json-render.ts +239 -98
  141. package/src/server/operations/ops/nodes.ts +298 -109
  142. package/src/server/operations/ops/query.ts +46 -28
  143. package/src/server/operations/ops/snapshots.ts +35 -26
  144. package/src/server/operations/ops/validate.ts +2 -1
  145. package/src/server/operations/ops/viewport.ts +60 -16
  146. package/src/server/operations/ops/webview.ts +44 -18
  147. package/src/server/operations/registry.ts +2 -3
  148. package/src/server/operations/types.ts +7 -5
  149. package/src/server/operations/webview-runner.ts +1 -3
  150. package/src/server/placement.ts +8 -18
  151. package/src/server/server.ts +122 -1028
  152. package/src/server/spatial-analysis.ts +39 -25
  153. package/src/server/trace-manager.ts +3 -8
  154. package/src/server/web-artifacts.ts +23 -27
  155. package/src/server/webpage-node.ts +5 -13
  156. package/src/shared/auto-arrange.ts +12 -5
  157. package/src/shared/content-height-reporter.ts +8 -6
  158. package/src/shared/ext-app-tool-result.ts +2 -6
  159. package/src/shared/placement.ts +1 -4
  160. package/src/shared/semantic-attention.ts +39 -37
  161. package/src/shared/surface.ts +8 -4
@@ -69,28 +69,24 @@ import {
69
69
  setGroupChildrenFromApi,
70
70
  } from './operations/ops/nodes.js';
71
71
  import { streamJsonRenderCore } from './operations/ops/json-render.js';
72
- import {
73
- executeOperation,
74
- runCanvasBatchOperation,
75
- type OpenMcpAppCoreResult,
76
- } from './operations/index.js';
72
+ import { executeOperation, runCanvasBatchOperation, type OpenMcpAppCoreResult } from './operations/index.js';
77
73
  import { validateCanvasLayout } from './canvas-validation.js';
78
74
  import { describeCanvasSchema, validateStructuredCanvasPayload } from './canvas-schema.js';
79
75
  import { serializeCanvasNode, type SerializedCanvasNode } from './canvas-serialization.js';
80
- import { buildHtmlPrimitive, getHtmlPrimitiveSemanticMetadata, isHtmlPrimitiveKind, listHtmlPrimitiveDescriptors } from './html-primitives.js';
76
+ import {
77
+ buildHtmlPrimitive,
78
+ getHtmlPrimitiveSemanticMetadata,
79
+ isHtmlPrimitiveKind,
80
+ listHtmlPrimitiveDescriptors,
81
+ } from './html-primitives.js';
81
82
  import type { HtmlPrimitiveKind } from './html-primitives.js';
82
83
  import {
83
84
  buildWebArtifactOnCanvas,
84
85
  type WebArtifactBuildInput,
85
86
  type WebArtifactCanvasBuildResult,
86
87
  } from './web-artifacts.js';
87
- import {
88
- closeMcpAppSession,
89
- type ExternalMcpTransportConfig,
90
- } from './mcp-app-runtime.js';
91
- import {
92
- type DiagramPresetOpenInput,
93
- } from './diagram-presets.js';
88
+ import { closeMcpAppSession, type ExternalMcpTransportConfig } from './mcp-app-runtime.js';
89
+ import type { DiagramPresetOpenInput } from './diagram-presets.js';
94
90
  import {
95
91
  buildGraphSpec,
96
92
  GRAPH_NODE_SIZE,
@@ -256,39 +252,44 @@ export class PmxCanvas extends EventEmitter {
256
252
  height?: number;
257
253
  strictSize?: boolean;
258
254
  }): SdkCanvasNode {
259
- return this.runIntentCommit(input.intentId, ['create'], () => {
260
- if (input.type === 'webpage') {
261
- throw new Error('Use addWebpageNode for webpage nodes so page content is fetched and cached on the server.');
262
- }
263
- if (input.type === 'group') {
264
- const groupId = this.createGroup({
265
- ...(typeof input.title === 'string' ? { title: input.title } : {}),
266
- childIds: input.childIds ?? input.children ?? [],
267
- ...(typeof input.x === 'number' ? { x: input.x } : {}),
268
- ...(typeof input.y === 'number' ? { y: input.y } : {}),
269
- ...(typeof input.width === 'number' ? { width: input.width } : {}),
270
- ...(typeof input.height === 'number' ? { height: input.height } : {}),
271
- ...(typeof input.color === 'string' ? { color: input.color } : {}),
272
- ...(input.childLayout ? { childLayout: input.childLayout } : {}),
273
- });
274
- const groupNode = canvasState.getNode(groupId);
275
- if (!groupNode) throw new Error(`Group node "${groupId}" was not created.`);
276
- return toSdkNode(groupNode);
277
- }
278
- // Thin wrapper over the shared operation core (plan-005); the SDK keeps
279
- // fileMode 'path' as an explicit visible parameter instead of forked code.
280
- const { node, needsCodeGraphRecompute } = createBasicCanvasNode(input, { fileMode: 'path' });
255
+ return this.runIntentCommit(
256
+ input.intentId,
257
+ ['create'],
258
+ () => {
259
+ if (input.type === 'webpage') {
260
+ throw new Error('Use addWebpageNode for webpage nodes so page content is fetched and cached on the server.');
261
+ }
262
+ if (input.type === 'group') {
263
+ const groupId = this.createGroup({
264
+ ...(typeof input.title === 'string' ? { title: input.title } : {}),
265
+ childIds: input.childIds ?? input.children ?? [],
266
+ ...(typeof input.x === 'number' ? { x: input.x } : {}),
267
+ ...(typeof input.y === 'number' ? { y: input.y } : {}),
268
+ ...(typeof input.width === 'number' ? { width: input.width } : {}),
269
+ ...(typeof input.height === 'number' ? { height: input.height } : {}),
270
+ ...(typeof input.color === 'string' ? { color: input.color } : {}),
271
+ ...(input.childLayout ? { childLayout: input.childLayout } : {}),
272
+ });
273
+ const groupNode = canvasState.getNode(groupId);
274
+ if (!groupNode) throw new Error(`Group node "${groupId}" was not created.`);
275
+ return toSdkNode(groupNode);
276
+ }
277
+ // Thin wrapper over the shared operation core (plan-005); the SDK keeps
278
+ // fileMode 'path' as an explicit visible parameter instead of forked code.
279
+ const { node, needsCodeGraphRecompute } = createBasicCanvasNode(input, { fileMode: 'path' });
281
280
 
282
- emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
281
+ emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
283
282
 
284
- if (needsCodeGraphRecompute) {
285
- scheduleCodeGraphRecompute(() => {
286
- emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
287
- });
288
- }
283
+ if (needsCodeGraphRecompute) {
284
+ scheduleCodeGraphRecompute(() => {
285
+ emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
286
+ });
287
+ }
289
288
 
290
- return toSdkNode(node);
291
- }, (node) => node.id);
289
+ return toSdkNode(node);
290
+ },
291
+ (node) => node.id,
292
+ );
292
293
  }
293
294
 
294
295
  async addWebpageNode(input: {
@@ -320,9 +321,7 @@ export class PmxCanvas extends EventEmitter {
320
321
  return {
321
322
  ok: true,
322
323
  id,
323
- fetch: result.ok
324
- ? { ok: true }
325
- : { ok: false, error: result.error ?? 'Failed to fetch webpage content.' },
324
+ fetch: result.ok ? { ok: true } : { ok: false, error: result.error ?? 'Failed to fetch webpage content.' },
326
325
  ...(result.ok ? {} : { error: result.error }),
327
326
  };
328
327
  };
@@ -338,34 +337,44 @@ export class PmxCanvas extends EventEmitter {
338
337
 
339
338
  updateNode(id: string, patch: Partial<CanvasNodeState> & Record<string, unknown>): void {
340
339
  const intentId = typeof patch.intentId === 'string' ? patch.intentId : undefined;
341
- this.runIntentCommit(intentId, ['move', 'edit'], () => {
342
- const existing = canvasState.getNode(id);
343
- if (!existing) {
344
- if (intentId !== undefined) throw new Error(`Node "${id}" not found.`);
345
- return;
346
- }
347
- // Thin wrapper over the shared patch core (plan-005): the SDK now carries
348
- // the same superset semantics as HTTP/MCP (webpage titleSource/url, html
349
- // top-level fields, axCapabilities merge, group children).
350
- const { patch: resolvedPatch, groupChildIds } = buildNodePatch(existing, patch);
351
- canvasState.updateNode(id, resolvedPatch);
352
- if (groupChildIds !== undefined) setGroupChildrenFromApi(id, groupChildIds);
353
- emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
354
- }, () => id);
340
+ this.runIntentCommit(
341
+ intentId,
342
+ ['move', 'edit'],
343
+ () => {
344
+ const existing = canvasState.getNode(id);
345
+ if (!existing) {
346
+ if (intentId !== undefined) throw new Error(`Node "${id}" not found.`);
347
+ return;
348
+ }
349
+ // Thin wrapper over the shared patch core (plan-005): the SDK now carries
350
+ // the same superset semantics as HTTP/MCP (webpage titleSource/url, html
351
+ // top-level fields, axCapabilities merge, group children).
352
+ const { patch: resolvedPatch, groupChildIds } = buildNodePatch(existing, patch);
353
+ canvasState.updateNode(id, resolvedPatch);
354
+ if (groupChildIds !== undefined) setGroupChildrenFromApi(id, groupChildIds);
355
+ emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
356
+ },
357
+ () => id,
358
+ );
355
359
  }
356
360
 
357
361
  /** Remove a node. Missing id throws (plan-005 unifies this across surfaces). */
358
362
  removeNode(id: string, options?: { intentId?: string }): void {
359
- this.runIntentCommit(options?.intentId, ['remove'], () => {
360
- const { needsCodeGraphRecompute } = removeNodeCore(id);
361
- emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
363
+ this.runIntentCommit(
364
+ options?.intentId,
365
+ ['remove'],
366
+ () => {
367
+ const { needsCodeGraphRecompute } = removeNodeCore(id);
368
+ emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
362
369
 
363
- if (needsCodeGraphRecompute) {
364
- scheduleCodeGraphRecompute(() => {
365
- emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
366
- });
367
- }
368
- }, () => undefined);
370
+ if (needsCodeGraphRecompute) {
371
+ scheduleCodeGraphRecompute(() => {
372
+ emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
373
+ });
374
+ }
375
+ },
376
+ () => undefined,
377
+ );
369
378
  }
370
379
 
371
380
  addEdge(input: {
@@ -379,11 +388,16 @@ export class PmxCanvas extends EventEmitter {
379
388
  style?: CanvasEdge['style'];
380
389
  animated?: boolean;
381
390
  }): string {
382
- return this.runIntentCommit(input.intentId, ['connect'], () => {
383
- const { id } = addCanvasEdge(input);
384
- emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
385
- return id;
386
- }, () => undefined);
391
+ return this.runIntentCommit(
392
+ input.intentId,
393
+ ['connect'],
394
+ () => {
395
+ const { id } = addCanvasEdge(input);
396
+ emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
397
+ return id;
398
+ },
399
+ () => undefined,
400
+ );
387
401
  }
388
402
 
389
403
  addAnnotation(input: Omit<CanvasAnnotation, 'id' | 'createdAt'> & { id?: string; createdAt?: string }): string {
@@ -423,39 +437,58 @@ export class PmxCanvas extends EventEmitter {
423
437
  color?: string;
424
438
  childLayout?: 'grid' | 'column' | 'flow';
425
439
  }): string {
426
- return this.runIntentCommit(input.intentId, ['create'], () => {
427
- const { id } = createCanvasGroup(input);
428
- emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
429
- return id;
430
- }, (id) => id);
440
+ return this.runIntentCommit(
441
+ input.intentId,
442
+ ['create'],
443
+ () => {
444
+ const { id } = createCanvasGroup(input);
445
+ emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
446
+ return id;
447
+ },
448
+ (id) => id,
449
+ );
431
450
  }
432
451
 
433
452
  /** Add nodes to an existing group. */
434
- groupNodes(groupId: string, childIds: string[], options?: { childLayout?: 'grid' | 'column' | 'flow'; intentId?: string }): boolean {
435
- return this.runIntentCommit(options?.intentId, ['edit'], () => {
436
- const { ok } = groupCanvasNodes(groupId, childIds, options);
437
- if (!ok && options?.intentId !== undefined) {
438
- throw new Error(`Group "${groupId}" could not be updated.`);
439
- }
440
- if (ok) {
441
- emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
442
- }
443
- return ok;
444
- }, () => groupId);
453
+ groupNodes(
454
+ groupId: string,
455
+ childIds: string[],
456
+ options?: { childLayout?: 'grid' | 'column' | 'flow'; intentId?: string },
457
+ ): boolean {
458
+ return this.runIntentCommit(
459
+ options?.intentId,
460
+ ['edit'],
461
+ () => {
462
+ const { ok } = groupCanvasNodes(groupId, childIds, options);
463
+ if (!ok && options?.intentId !== undefined) {
464
+ throw new Error(`Group "${groupId}" could not be updated.`);
465
+ }
466
+ if (ok) {
467
+ emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
468
+ }
469
+ return ok;
470
+ },
471
+ () => groupId,
472
+ );
445
473
  }
446
474
 
447
475
  /** Remove all children from a group (the group node remains). */
448
476
  ungroupNodes(groupId: string, options?: { intentId?: string }): boolean {
449
- return this.runIntentCommit(options?.intentId, ['edit'], () => {
450
- const { ok } = ungroupCanvasNodes(groupId);
451
- if (!ok && options?.intentId !== undefined) {
452
- throw new Error(`Group "${groupId}" could not be updated.`);
453
- }
454
- if (ok) {
455
- emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
456
- }
457
- return ok;
458
- }, () => groupId);
477
+ return this.runIntentCommit(
478
+ options?.intentId,
479
+ ['edit'],
480
+ () => {
481
+ const { ok } = ungroupCanvasNodes(groupId);
482
+ if (!ok && options?.intentId !== undefined) {
483
+ throw new Error(`Group "${groupId}" could not be updated.`);
484
+ }
485
+ if (ok) {
486
+ emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
487
+ }
488
+ return ok;
489
+ },
490
+ () => groupId,
491
+ );
459
492
  }
460
493
 
461
494
  clear(): void {
@@ -508,7 +541,13 @@ export class PmxCanvas extends EventEmitter {
508
541
  }
509
542
 
510
543
  recordAxEvent(
511
- input: { kind: PmxAxEvent['kind']; summary: string; detail?: string | null; nodeIds?: string[]; data?: Record<string, unknown> | null },
544
+ input: {
545
+ kind: PmxAxEvent['kind'];
546
+ summary: string;
547
+ detail?: string | null;
548
+ nodeIds?: string[];
549
+ data?: Record<string, unknown> | null;
550
+ },
512
551
  options?: { source?: PmxAxSource },
513
552
  ): PmxAxEvent {
514
553
  const event = canvasState.recordAxEvent(input, { source: options?.source ?? 'sdk' });
@@ -616,7 +655,14 @@ export class PmxCanvas extends EventEmitter {
616
655
  }
617
656
 
618
657
  addEvidence(
619
- input: { kind: PmxAxEvidenceKind; title: string; body?: string | null; ref?: string | null; nodeIds?: string[]; data?: Record<string, unknown> | null },
658
+ input: {
659
+ kind: PmxAxEvidenceKind;
660
+ title: string;
661
+ body?: string | null;
662
+ ref?: string | null;
663
+ nodeIds?: string[];
664
+ data?: Record<string, unknown> | null;
665
+ },
620
666
  options?: { source?: PmxAxSource },
621
667
  ): PmxAxEvidence {
622
668
  const evidence = canvasState.addEvidence(input, { source: options?.source ?? 'sdk' });
@@ -708,7 +754,10 @@ export class PmxCanvas extends EventEmitter {
708
754
  decision: 'approved' | 'rejected',
709
755
  options?: { resolution?: string; source?: PmxAxSource },
710
756
  ): PmxAxModeRequest | null {
711
- const modeRequest = canvasState.resolveModeRequest(id, decision, { ...(options ?? {}), source: options?.source ?? 'sdk' });
757
+ const modeRequest = canvasState.resolveModeRequest(id, decision, {
758
+ ...(options ?? {}),
759
+ source: options?.source ?? 'sdk',
760
+ });
712
761
  if (modeRequest) emitPrimaryWorkbenchEvent('ax-state-changed', { modeRequest });
713
762
  return modeRequest;
714
763
  }
@@ -726,11 +775,23 @@ export class PmxCanvas extends EventEmitter {
726
775
  reactions?: {
727
776
  workItem?: false | { status?: PmxAxWorkItemStatus; detail?: string | null };
728
777
  evidence?: false | { kind?: PmxAxEvidenceKind; body?: string | null };
729
- review?: false | { severity?: PmxAxReviewSeverity; kind?: PmxAxReviewKind; anchorType?: PmxAxReviewAnchorType; nodeId?: string | null };
778
+ review?:
779
+ | false
780
+ | {
781
+ severity?: PmxAxReviewSeverity;
782
+ kind?: PmxAxReviewKind;
783
+ anchorType?: PmxAxReviewAnchorType;
784
+ nodeId?: string | null;
785
+ };
730
786
  };
731
787
  },
732
788
  options?: { source?: PmxAxSource },
733
- ): { event: PmxAxEvent; workItem: PmxAxWorkItem | null; evidence: PmxAxEvidence | null; review: PmxAxReviewAnnotation | null } {
789
+ ): {
790
+ event: PmxAxEvent;
791
+ workItem: PmxAxWorkItem | null;
792
+ evidence: PmxAxEvidence | null;
793
+ review: PmxAxReviewAnnotation | null;
794
+ } {
734
795
  const result = canvasState.ingestActivity(input, { source: options?.source ?? 'sdk' });
735
796
  emitPrimaryWorkbenchEvent('ax-event-created', { event: result.event });
736
797
  if (result.workItem) emitPrimaryWorkbenchEvent('ax-state-changed', { workItem: result.workItem });
@@ -795,7 +856,11 @@ export class PmxCanvas extends EventEmitter {
795
856
  return canvasState.getCommandRegistry();
796
857
  }
797
858
 
798
- invokeCommand(name: string, args?: Record<string, unknown> | null, options?: { source?: PmxAxSource }): PmxAxEvent | null {
859
+ invokeCommand(
860
+ name: string,
861
+ args?: Record<string, unknown> | null,
862
+ options?: { source?: PmxAxSource },
863
+ ): PmxAxEvent | null {
799
864
  const event = canvasState.invokeCommand(name, args ?? null, { source: options?.source ?? 'sdk' });
800
865
  if (event) emitPrimaryWorkbenchEvent('ax-event-created', { event });
801
866
  return event;
@@ -873,13 +938,15 @@ export class PmxCanvas extends EventEmitter {
873
938
  };
874
939
  }
875
940
 
876
- applyUpdates(updates: Array<{
877
- id: string;
878
- position?: { x: number; y: number };
879
- size?: { width: number; height: number };
880
- collapsed?: boolean;
881
- dockPosition?: 'left' | 'right' | null;
882
- }>): { applied: number; skipped: number } {
941
+ applyUpdates(
942
+ updates: Array<{
943
+ id: string;
944
+ position?: { x: number; y: number };
945
+ size?: { width: number; height: number };
946
+ collapsed?: boolean;
947
+ dockPosition?: 'left' | 'right' | null;
948
+ }>,
949
+ ): { applied: number; skipped: number } {
883
950
  return applyCanvasNodeUpdates(updates);
884
951
  }
885
952
 
@@ -913,7 +980,12 @@ export class PmxCanvas extends EventEmitter {
913
980
  return gcCanvasSnapshots(options);
914
981
  }
915
982
 
916
- diffSnapshot(idOrName: string): { ok: boolean; text?: string; diff?: ReturnType<typeof diffLayouts>; error?: string } {
983
+ diffSnapshot(idOrName: string): {
984
+ ok: boolean;
985
+ text?: string;
986
+ diff?: ReturnType<typeof diffLayouts>;
987
+ error?: string;
988
+ } {
917
989
  const snapData = canvasState.getSnapshotData(idOrName);
918
990
  if (!snapData) return { ok: false, error: `Snapshot "${idOrName}" not found` };
919
991
 
@@ -935,11 +1007,7 @@ export class PmxCanvas extends EventEmitter {
935
1007
  return describeCanvasSchema();
936
1008
  }
937
1009
 
938
- validateSpec(input: {
939
- type: 'json-render' | 'graph';
940
- spec?: unknown;
941
- graph?: GraphNodeInput;
942
- }) {
1010
+ validateSpec(input: { type: 'json-render' | 'graph'; spec?: unknown; graph?: GraphNodeInput }) {
943
1011
  return validateStructuredCanvasPayload(input);
944
1012
  }
945
1013
 
@@ -981,25 +1049,30 @@ export class PmxCanvas extends EventEmitter {
981
1049
  // close, ext-app-open + ext-app-result via ctx.emit → the registry emitter
982
1050
  // wired to emitPrimaryWorkbenchEvent). mutates:false, so the registry adds no
983
1051
  // canvas-layout-update; the two ext-app-* frames fire exactly once.
984
- return await executeOperation('mcpapp.open', input) as OpenMcpAppCoreResult;
1052
+ return (await executeOperation('mcpapp.open', input)) as OpenMcpAppCoreResult;
985
1053
  }
986
1054
 
987
- async addDiagram(
988
- input: DiagramPresetOpenInput,
989
- ): Promise<OpenMcpAppCoreResult> {
1055
+ async addDiagram(input: DiagramPresetOpenInput): Promise<OpenMcpAppCoreResult> {
990
1056
  // Delegate to the diagram.open registry op, which builds the Excalidraw
991
1057
  // OpenMcpApp input and dispatches to the shared open core (one ext-app-* pair).
992
- return await executeOperation('diagram.open', input) as OpenMcpAppCoreResult;
1058
+ return (await executeOperation('diagram.open', input)) as OpenMcpAppCoreResult;
993
1059
  }
994
1060
 
995
- addJsonRenderNode(
996
- input: JsonRenderNodeInput & { intentId?: string },
997
- ): { id: string; url: string; spec: JsonRenderSpec } {
998
- return this.runIntentCommit(input.intentId, ['create'], () => {
999
- const result = createCanvasJsonRenderNode(input);
1000
- emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
1001
- return result;
1002
- }, (result) => result.id);
1061
+ addJsonRenderNode(input: JsonRenderNodeInput & { intentId?: string }): {
1062
+ id: string;
1063
+ url: string;
1064
+ spec: JsonRenderSpec;
1065
+ } {
1066
+ return this.runIntentCommit(
1067
+ input.intentId,
1068
+ ['create'],
1069
+ () => {
1070
+ const result = createCanvasJsonRenderNode(input);
1071
+ emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
1072
+ return result;
1073
+ },
1074
+ (result) => result.id,
1075
+ );
1003
1076
  }
1004
1077
 
1005
1078
  /**
@@ -1034,19 +1107,24 @@ export class PmxCanvas extends EventEmitter {
1034
1107
  // `mutates` path). `streamJsonRenderCore` throws OperationError (an Error
1035
1108
  // subclass with the same message) on a bad append target. The core's
1036
1109
  // result carries an extra `ok: true`; the SDK's wire shape omits it.
1037
- return this.runIntentCommit(input.intentId, input.nodeId ? ['edit'] : ['create'], () => {
1038
- const result = streamJsonRenderCore(input);
1039
- emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
1040
- return {
1041
- id: result.id,
1042
- url: result.url,
1043
- applied: result.applied,
1044
- skipped: result.skipped,
1045
- specVersion: result.specVersion,
1046
- elementCount: result.elementCount,
1047
- streamStatus: result.streamStatus,
1048
- };
1049
- }, (result) => result.id);
1110
+ return this.runIntentCommit(
1111
+ input.intentId,
1112
+ input.nodeId ? ['edit'] : ['create'],
1113
+ () => {
1114
+ const result = streamJsonRenderCore(input);
1115
+ emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
1116
+ return {
1117
+ id: result.id,
1118
+ url: result.url,
1119
+ applied: result.applied,
1120
+ skipped: result.skipped,
1121
+ specVersion: result.specVersion,
1122
+ elementCount: result.elementCount,
1123
+ streamStatus: result.streamStatus,
1124
+ };
1125
+ },
1126
+ (result) => result.id,
1127
+ );
1050
1128
  }
1051
1129
 
1052
1130
  addHtmlNode(input: {
@@ -1069,34 +1147,39 @@ export class PmxCanvas extends EventEmitter {
1069
1147
  * the html capability ceiling server-side; cannot escalate. */
1070
1148
  axCapabilities?: { enabled?: boolean; allowed?: string[] };
1071
1149
  }): SdkCanvasNode {
1072
- return this.runIntentCommit(input.intentId, ['create'], () => {
1073
- const { id } = addCanvasNode({
1074
- type: 'html',
1075
- ...(typeof input.title === 'string' ? { title: input.title } : {}),
1076
- data: {
1077
- html: resolveHtmlContent(input.html),
1078
- ...(typeof input.summary === 'string' ? { summary: input.summary } : {}),
1079
- ...(typeof input.agentSummary === 'string' ? { agentSummary: input.agentSummary } : {}),
1080
- ...(typeof input.description === 'string' ? { description: input.description } : {}),
1081
- ...(input.presentation === true ? { presentation: true } : {}),
1082
- ...(Array.isArray(input.slideTitles) ? { slideTitles: input.slideTitles } : {}),
1083
- ...(Array.isArray(input.embeddedNodeIds) ? { embeddedNodeIds: input.embeddedNodeIds } : {}),
1084
- ...(Array.isArray(input.embeddedUrls) ? { embeddedUrls: input.embeddedUrls } : {}),
1085
- ...(input.axCapabilities ? { axCapabilities: input.axCapabilities } : {}),
1086
- },
1087
- ...(typeof input.x === 'number' ? { x: input.x } : {}),
1088
- ...(typeof input.y === 'number' ? { y: input.y } : {}),
1089
- ...(typeof input.width === 'number' ? { width: input.width } : {}),
1090
- ...(typeof input.height === 'number' ? { height: input.height } : {}),
1091
- ...(input.strictSize ? { strictSize: true } : {}),
1092
- defaultWidth: 720,
1093
- defaultHeight: 640,
1094
- });
1095
- emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
1096
- const node = canvasState.getNode(id);
1097
- if (!node) throw new Error(`HTML node "${id}" was not created.`);
1098
- return toSdkNode(node);
1099
- }, (node) => node.id);
1150
+ return this.runIntentCommit(
1151
+ input.intentId,
1152
+ ['create'],
1153
+ () => {
1154
+ const { id } = addCanvasNode({
1155
+ type: 'html',
1156
+ ...(typeof input.title === 'string' ? { title: input.title } : {}),
1157
+ data: {
1158
+ html: resolveHtmlContent(input.html),
1159
+ ...(typeof input.summary === 'string' ? { summary: input.summary } : {}),
1160
+ ...(typeof input.agentSummary === 'string' ? { agentSummary: input.agentSummary } : {}),
1161
+ ...(typeof input.description === 'string' ? { description: input.description } : {}),
1162
+ ...(input.presentation === true ? { presentation: true } : {}),
1163
+ ...(Array.isArray(input.slideTitles) ? { slideTitles: input.slideTitles } : {}),
1164
+ ...(Array.isArray(input.embeddedNodeIds) ? { embeddedNodeIds: input.embeddedNodeIds } : {}),
1165
+ ...(Array.isArray(input.embeddedUrls) ? { embeddedUrls: input.embeddedUrls } : {}),
1166
+ ...(input.axCapabilities ? { axCapabilities: input.axCapabilities } : {}),
1167
+ },
1168
+ ...(typeof input.x === 'number' ? { x: input.x } : {}),
1169
+ ...(typeof input.y === 'number' ? { y: input.y } : {}),
1170
+ ...(typeof input.width === 'number' ? { width: input.width } : {}),
1171
+ ...(typeof input.height === 'number' ? { height: input.height } : {}),
1172
+ ...(input.strictSize ? { strictSize: true } : {}),
1173
+ defaultWidth: 720,
1174
+ defaultHeight: 640,
1175
+ });
1176
+ emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
1177
+ const node = canvasState.getNode(id);
1178
+ if (!node) throw new Error(`HTML node "${id}" was not created.`);
1179
+ return toSdkNode(node);
1180
+ },
1181
+ (node) => node.id,
1182
+ );
1100
1183
  }
1101
1184
 
1102
1185
  addHtmlPrimitive(input: {
@@ -1110,52 +1193,60 @@ export class PmxCanvas extends EventEmitter {
1110
1193
  height?: number;
1111
1194
  strictSize?: boolean;
1112
1195
  }): { id: string; kind: HtmlPrimitiveKind; title: string; htmlBytes: number } {
1113
- return this.runIntentCommit(input.intentId, ['create'], () => {
1114
- const built = buildHtmlPrimitive({
1115
- kind: input.kind,
1116
- ...(typeof input.title === 'string' ? { title: input.title } : {}),
1117
- ...(input.data ? { data: input.data } : {}),
1118
- });
1119
- const { id } = addCanvasNode({
1120
- type: 'html',
1121
- title: built.title,
1122
- data: {
1123
- html: built.html,
1124
- htmlPrimitive: built.kind,
1125
- primitiveData: built.data,
1126
- description: built.summary,
1127
- agentSummary: typeof input.data?.agentSummary === 'string' ? input.data.agentSummary : built.summary,
1128
- ...(typeof input.data?.summary === 'string' ? { summary: input.data.summary } : {}),
1129
- ...getHtmlPrimitiveSemanticMetadata(built.data),
1130
- },
1131
- ...(typeof input.x === 'number' ? { x: input.x } : {}),
1132
- ...(typeof input.y === 'number' ? { y: input.y } : {}),
1133
- ...(typeof input.width === 'number' ? { width: input.width } : {}),
1134
- ...(typeof input.height === 'number' ? { height: input.height } : {}),
1135
- ...(input.strictSize ? { strictSize: true } : {}),
1136
- defaultWidth: built.defaultSize.width,
1137
- defaultHeight: built.defaultSize.height,
1138
- });
1139
- emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
1140
- return { id, kind: built.kind, title: built.title, htmlBytes: Buffer.byteLength(built.html, 'utf-8') };
1141
- }, (result) => result.id);
1196
+ return this.runIntentCommit(
1197
+ input.intentId,
1198
+ ['create'],
1199
+ () => {
1200
+ const built = buildHtmlPrimitive({
1201
+ kind: input.kind,
1202
+ ...(typeof input.title === 'string' ? { title: input.title } : {}),
1203
+ ...(input.data ? { data: input.data } : {}),
1204
+ });
1205
+ const { id } = addCanvasNode({
1206
+ type: 'html',
1207
+ title: built.title,
1208
+ data: {
1209
+ html: built.html,
1210
+ htmlPrimitive: built.kind,
1211
+ primitiveData: built.data,
1212
+ description: built.summary,
1213
+ agentSummary: typeof input.data?.agentSummary === 'string' ? input.data.agentSummary : built.summary,
1214
+ ...(typeof input.data?.summary === 'string' ? { summary: input.data.summary } : {}),
1215
+ ...getHtmlPrimitiveSemanticMetadata(built.data),
1216
+ },
1217
+ ...(typeof input.x === 'number' ? { x: input.x } : {}),
1218
+ ...(typeof input.y === 'number' ? { y: input.y } : {}),
1219
+ ...(typeof input.width === 'number' ? { width: input.width } : {}),
1220
+ ...(typeof input.height === 'number' ? { height: input.height } : {}),
1221
+ ...(input.strictSize ? { strictSize: true } : {}),
1222
+ defaultWidth: built.defaultSize.width,
1223
+ defaultHeight: built.defaultSize.height,
1224
+ });
1225
+ emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
1226
+ return { id, kind: built.kind, title: built.title, htmlBytes: Buffer.byteLength(built.html, 'utf-8') };
1227
+ },
1228
+ (result) => result.id,
1229
+ );
1142
1230
  }
1143
1231
 
1144
1232
  addGraphNode(input: GraphNodeInput & { intentId?: string }): { id: string; url: string; spec: JsonRenderSpec } {
1145
- return this.runIntentCommit(input.intentId, ['create'], () => {
1146
- const result = createCanvasGraphNode(input);
1147
- emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
1148
- return result;
1149
- }, (result) => result.id);
1233
+ return this.runIntentCommit(
1234
+ input.intentId,
1235
+ ['create'],
1236
+ () => {
1237
+ const result = createCanvasGraphNode(input);
1238
+ emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
1239
+ return result;
1240
+ },
1241
+ (result) => result.id,
1242
+ );
1150
1243
  }
1151
1244
 
1152
1245
  get port(): number {
1153
1246
  return this._port;
1154
1247
  }
1155
1248
 
1156
- async startAutomationWebView(
1157
- options: CanvasAutomationWebViewOptions = {},
1158
- ): Promise<CanvasAutomationWebViewStatus> {
1249
+ async startAutomationWebView(options: CanvasAutomationWebViewOptions = {}): Promise<CanvasAutomationWebViewStatus> {
1159
1250
  const base = this._server ?? startCanvasServer({ port: this._port, allowPortFallback: false });
1160
1251
  if (!base) {
1161
1252
  throw new Error(`Failed to start canvas server on port ${this._port}`);
@@ -1177,10 +1268,7 @@ export class PmxCanvas extends EventEmitter {
1177
1268
  return evaluateCanvasAutomationWebView(expression);
1178
1269
  }
1179
1270
 
1180
- async resizeAutomationWebView(
1181
- width: number,
1182
- height: number,
1183
- ): Promise<CanvasAutomationWebViewStatus> {
1271
+ async resizeAutomationWebView(width: number, height: number): Promise<CanvasAutomationWebViewStatus> {
1184
1272
  return resizeCanvasAutomationWebView(width, height);
1185
1273
  }
1186
1274
 
@@ -1217,14 +1305,24 @@ export {
1217
1305
  screenshotCanvasAutomationWebView,
1218
1306
  } from './server.js';
1219
1307
  export { canvasState } from './canvas-state.js';
1220
- export type { CanvasAnnotation, CanvasSnapshot, CanvasSnapshotGcResult, CanvasSnapshotListOptions } from './canvas-state.js';
1308
+ export type {
1309
+ CanvasAnnotation,
1310
+ CanvasSnapshot,
1311
+ CanvasSnapshotGcResult,
1312
+ CanvasSnapshotListOptions,
1313
+ } from './canvas-state.js';
1221
1314
  export { findOpenCanvasPosition } from './placement.js';
1222
1315
  export { searchNodes, buildSpatialContext, detectClusters, findNeighborhoods } from './spatial-analysis.js';
1223
1316
  export type { SpatialCluster, SpatialContext, SpatialNeighbor, NodeSpatialInfo } from './spatial-analysis.js';
1224
1317
  export { mutationHistory, diffLayouts, formatDiff } from './mutation-history.js';
1225
1318
  export { recomputeCodeGraph, buildCodeGraphSummary, formatCodeGraph } from './code-graph.js';
1226
1319
  export { describeCanvasSchema, validateStructuredCanvasPayload } from './canvas-schema.js';
1227
- export { buildHtmlPrimitive, getHtmlPrimitiveSemanticMetadata, isHtmlPrimitiveKind, listHtmlPrimitiveDescriptors } from './html-primitives.js';
1320
+ export {
1321
+ buildHtmlPrimitive,
1322
+ getHtmlPrimitiveSemanticMetadata,
1323
+ isHtmlPrimitiveKind,
1324
+ listHtmlPrimitiveDescriptors,
1325
+ } from './html-primitives.js';
1228
1326
  export {
1229
1327
  buildWebArtifactOnCanvas,
1230
1328
  executeWebArtifactBuild,
@@ -1249,7 +1347,12 @@ export type {
1249
1347
  WebArtifactCanvasOpenResult,
1250
1348
  } from './web-artifacts.js';
1251
1349
  export type { GraphNodeInput, JsonRenderNodeInput, JsonRenderSpec } from '../json-render/server.js';
1252
- export type { HtmlPrimitiveKind, HtmlPrimitiveDescriptor, HtmlPrimitiveInput, HtmlPrimitiveBuildResult } from './html-primitives.js';
1350
+ export type {
1351
+ HtmlPrimitiveKind,
1352
+ HtmlPrimitiveDescriptor,
1353
+ HtmlPrimitiveInput,
1354
+ HtmlPrimitiveBuildResult,
1355
+ } from './html-primitives.js';
1253
1356
  export { traceManager } from './trace-manager.js';
1254
1357
  export type {
1255
1358
  PmxAxApprovalGate,