pmx-canvas 0.3.0 → 0.3.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 (156) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/dist/canvas/index.js +2 -2
  3. package/dist/types/cli/daemon.d.ts +74 -0
  4. package/dist/types/cli/watch.d.ts +2 -2
  5. package/dist/types/client/canvas/CanvasViewport.d.ts +1 -1
  6. package/dist/types/client/canvas/CommandPalette.d.ts +1 -1
  7. package/dist/types/client/canvas/Minimap.d.ts +1 -1
  8. package/dist/types/client/nodes/FileNode.d.ts +1 -1
  9. package/dist/types/client/nodes/ImageNode.d.ts +1 -1
  10. package/dist/types/client/nodes/InlineFormatBar.d.ts +1 -1
  11. package/dist/types/client/nodes/MarkdownNode.d.ts +1 -1
  12. package/dist/types/client/nodes/PromptNode.d.ts +1 -1
  13. package/dist/types/client/nodes/ResponseNode.d.ts +1 -1
  14. package/dist/types/server/canvas-schema.d.ts +1 -1
  15. package/dist/types/server/html-primitives.d.ts +1 -1
  16. package/dist/types/server/index.d.ts +4 -4
  17. package/dist/types/server/operations/index.d.ts +1 -1
  18. package/dist/types/server/operations/ops/ax-read.d.ts +2 -0
  19. package/dist/types/server/operations/ops/canvas-wire.d.ts +2 -0
  20. package/dist/types/server/operations/ops/ext-app.d.ts +2 -0
  21. package/docs/http-api.md +28 -0
  22. package/docs/plans/plan-009-tech-debt-backlog.md +5 -5
  23. package/docs/tech-debt-assessment-2026-07.md +2 -2
  24. package/package.json +5 -2
  25. package/skills/pmx-canvas/SKILL.md +3 -1
  26. package/skills/pmx-canvas/references/full-reference.md +10 -3
  27. package/src/cli/agent.ts +1861 -1548
  28. package/src/cli/daemon.ts +460 -0
  29. package/src/cli/index.ts +63 -326
  30. package/src/cli/watch.ts +2 -10
  31. package/src/client/App.tsx +48 -46
  32. package/src/client/canvas/AttentionHistory.tsx +11 -1
  33. package/src/client/canvas/CanvasNode.tsx +41 -29
  34. package/src/client/canvas/CanvasViewport.tsx +101 -66
  35. package/src/client/canvas/CommandPalette.tsx +61 -27
  36. package/src/client/canvas/ContextMenu.tsx +13 -20
  37. package/src/client/canvas/ContextPinBar.tsx +1 -5
  38. package/src/client/canvas/ContextPinHud.tsx +1 -6
  39. package/src/client/canvas/DockedNode.tsx +4 -4
  40. package/src/client/canvas/EdgeLayer.tsx +37 -36
  41. package/src/client/canvas/ExpandedNodeOverlay.tsx +69 -45
  42. package/src/client/canvas/FocusFieldLayer.tsx +20 -22
  43. package/src/client/canvas/IntentLayer.tsx +31 -14
  44. package/src/client/canvas/Minimap.tsx +11 -16
  45. package/src/client/canvas/SelectionBar.tsx +4 -11
  46. package/src/client/canvas/ShortcutOverlay.tsx +3 -1
  47. package/src/client/canvas/SnapshotPanel.tsx +77 -95
  48. package/src/client/canvas/auto-fit.ts +15 -14
  49. package/src/client/canvas/snap-guides.ts +12 -12
  50. package/src/client/canvas/use-node-resize.ts +1 -5
  51. package/src/client/canvas/use-pan-zoom.ts +25 -26
  52. package/src/client/ext-app/bridge.ts +3 -12
  53. package/src/client/icons.tsx +63 -20
  54. package/src/client/nodes/ContextNode.tsx +14 -25
  55. package/src/client/nodes/ExtAppFrame.tsx +60 -39
  56. package/src/client/nodes/FileNode.tsx +74 -62
  57. package/src/client/nodes/GroupNode.tsx +4 -6
  58. package/src/client/nodes/HtmlNode.tsx +76 -46
  59. package/src/client/nodes/ImageNode.tsx +18 -27
  60. package/src/client/nodes/InlineFormatBar.tsx +4 -21
  61. package/src/client/nodes/InlineMarkdownEditor.tsx +0 -1
  62. package/src/client/nodes/LedgerNode.tsx +10 -4
  63. package/src/client/nodes/MarkdownNode.tsx +3 -10
  64. package/src/client/nodes/McpAppNode.tsx +26 -22
  65. package/src/client/nodes/MdFormatBar.tsx +10 -7
  66. package/src/client/nodes/PromptNode.tsx +23 -51
  67. package/src/client/nodes/ResponseNode.tsx +3 -13
  68. package/src/client/nodes/StatusNode.tsx +5 -9
  69. package/src/client/nodes/StatusSummary.tsx +2 -8
  70. package/src/client/nodes/WebpageNode.tsx +20 -14
  71. package/src/client/nodes/iframe-document-url.ts +25 -16
  72. package/src/client/nodes/image-warnings.ts +1 -7
  73. package/src/client/nodes/md-format.ts +20 -5
  74. package/src/client/state/attention-bridge.ts +4 -9
  75. package/src/client/state/attention-store.ts +1 -7
  76. package/src/client/state/canvas-store.ts +52 -36
  77. package/src/client/state/intent-bridge.ts +176 -112
  78. package/src/client/state/intent-store.ts +4 -1
  79. package/src/client/state/sse-bridge.ts +53 -70
  80. package/src/json-render/catalog.ts +12 -16
  81. package/src/json-render/charts/components.tsx +16 -20
  82. package/src/json-render/charts/extra-components.tsx +8 -16
  83. package/src/json-render/charts/extra-definitions.ts +1 -2
  84. package/src/json-render/charts/tufte-components.tsx +37 -20
  85. package/src/json-render/charts/tufte-definitions.ts +8 -2
  86. package/src/json-render/renderer/index.tsx +42 -22
  87. package/src/json-render/schema.ts +6 -3
  88. package/src/json-render/server.ts +33 -39
  89. package/src/mcp/canvas-access.ts +35 -21
  90. package/src/mcp/server.ts +132 -70
  91. package/src/server/agent-context.ts +63 -36
  92. package/src/server/ax-context.ts +7 -5
  93. package/src/server/ax-interaction.ts +176 -43
  94. package/src/server/ax-state-manager.ts +182 -39
  95. package/src/server/ax-state.ts +142 -47
  96. package/src/server/canvas-db.ts +213 -95
  97. package/src/server/canvas-operations.ts +177 -120
  98. package/src/server/canvas-provenance.ts +1 -4
  99. package/src/server/canvas-schema.ts +454 -73
  100. package/src/server/canvas-serialization.ts +27 -35
  101. package/src/server/canvas-state.ts +150 -58
  102. package/src/server/chart-template.ts +4 -5
  103. package/src/server/code-graph.ts +19 -6
  104. package/src/server/diagram-presets.ts +28 -29
  105. package/src/server/ext-app-lookup.ts +3 -12
  106. package/src/server/html-node-summary.ts +19 -10
  107. package/src/server/html-primitives.ts +326 -97
  108. package/src/server/html-surface.ts +6 -9
  109. package/src/server/image-source.ts +6 -4
  110. package/src/server/index.ts +320 -217
  111. package/src/server/intent-registry.ts +2 -5
  112. package/src/server/mcp-app-candidate.ts +5 -10
  113. package/src/server/mcp-app-host.ts +14 -38
  114. package/src/server/mcp-app-runtime.ts +12 -20
  115. package/src/server/mutation-history.ts +15 -5
  116. package/src/server/operations/composites.ts +1 -3
  117. package/src/server/operations/http.ts +2 -3
  118. package/src/server/operations/index.ts +7 -1
  119. package/src/server/operations/invoker.ts +4 -3
  120. package/src/server/operations/mcp.ts +22 -30
  121. package/src/server/operations/ops/annotation.ts +122 -10
  122. package/src/server/operations/ops/app.ts +98 -73
  123. package/src/server/operations/ops/ax-await.ts +17 -10
  124. package/src/server/operations/ops/ax-read.ts +347 -0
  125. package/src/server/operations/ops/ax-shared.ts +2 -7
  126. package/src/server/operations/ops/ax-state.ts +32 -14
  127. package/src/server/operations/ops/ax-timeline.ts +32 -19
  128. package/src/server/operations/ops/ax-work.ts +54 -37
  129. package/src/server/operations/ops/batch.ts +39 -14
  130. package/src/server/operations/ops/canvas-wire.ts +91 -0
  131. package/src/server/operations/ops/edges.ts +37 -25
  132. package/src/server/operations/ops/ext-app.ts +346 -0
  133. package/src/server/operations/ops/groups.ts +49 -20
  134. package/src/server/operations/ops/intent.ts +18 -12
  135. package/src/server/operations/ops/json-render.ts +239 -98
  136. package/src/server/operations/ops/nodes.ts +298 -109
  137. package/src/server/operations/ops/query.ts +46 -28
  138. package/src/server/operations/ops/snapshots.ts +35 -26
  139. package/src/server/operations/ops/validate.ts +2 -1
  140. package/src/server/operations/ops/viewport.ts +60 -16
  141. package/src/server/operations/ops/webview.ts +44 -18
  142. package/src/server/operations/registry.ts +2 -3
  143. package/src/server/operations/types.ts +7 -5
  144. package/src/server/operations/webview-runner.ts +1 -3
  145. package/src/server/placement.ts +8 -18
  146. package/src/server/server.ts +108 -1027
  147. package/src/server/spatial-analysis.ts +39 -25
  148. package/src/server/trace-manager.ts +3 -8
  149. package/src/server/web-artifacts.ts +23 -27
  150. package/src/server/webpage-node.ts +5 -13
  151. package/src/shared/auto-arrange.ts +12 -5
  152. package/src/shared/content-height-reporter.ts +8 -6
  153. package/src/shared/ext-app-tool-result.ts +2 -6
  154. package/src/shared/placement.ts +1 -4
  155. package/src/shared/semantic-attention.ts +39 -37
  156. package/src/shared/surface.ts +8 -4
package/src/mcp/server.ts CHANGED
@@ -197,12 +197,19 @@ function compactNodePayload(node: Awaited<ReturnType<CanvasAccess['getNode']>>):
197
197
  };
198
198
  }
199
199
 
200
- function compactLayoutPayload(layout: Awaited<ReturnType<CanvasAccess['getLayout']>>, pinnedIds: string[]): Record<string, unknown> {
200
+ function compactLayoutPayload(
201
+ layout: Awaited<ReturnType<CanvasAccess['getLayout']>>,
202
+ pinnedIds: string[],
203
+ ): Record<string, unknown> {
201
204
  return {
202
205
  summary: buildSummaryFromLayout(layout, pinnedIds),
203
206
  viewport: layout.viewport,
204
- annotations: (layout.annotations ?? []).map((annotation) => summarizeCanvasAnnotationForContext(annotation, layout.nodes)),
205
- nodes: layout.nodes.map((node) => compactNodePayload(node)).filter((node): node is Record<string, unknown> => node !== null),
207
+ annotations: (layout.annotations ?? []).map((annotation) =>
208
+ summarizeCanvasAnnotationForContext(annotation, layout.nodes),
209
+ ),
210
+ nodes: layout.nodes
211
+ .map((node) => compactNodePayload(node))
212
+ .filter((node): node is Record<string, unknown> => node !== null),
206
213
  edges: layout.edges.map((edge) => ({
207
214
  id: edge.id,
208
215
  from: edge.from,
@@ -218,11 +225,16 @@ function compactLayoutPayload(layout: Awaited<ReturnType<CanvasAccess['getLayout
218
225
  function agentSafeFullLayoutPayload(layout: Awaited<ReturnType<CanvasAccess['getLayout']>>): Record<string, unknown> {
219
226
  return {
220
227
  ...serializeCanvasLayoutForAgent(layout),
221
- annotations: (layout.annotations ?? []).map((annotation) => summarizeCanvasAnnotationForContext(annotation, layout.nodes)),
228
+ annotations: (layout.annotations ?? []).map((annotation) =>
229
+ summarizeCanvasAnnotationForContext(annotation, layout.nodes),
230
+ ),
222
231
  };
223
232
  }
224
233
 
225
- function buildSummaryFromLayout(layout: Awaited<ReturnType<CanvasAccess['getLayout']>>, pinnedIds: string[]): Record<string, unknown> {
234
+ function buildSummaryFromLayout(
235
+ layout: Awaited<ReturnType<CanvasAccess['getLayout']>>,
236
+ pinnedIds: string[],
237
+ ): Record<string, unknown> {
226
238
  const pinned = new Set(pinnedIds);
227
239
  const nodesByType: Record<string, number> = {};
228
240
  const pinnedTitles: string[] = [];
@@ -235,7 +247,9 @@ function buildSummaryFromLayout(layout: Awaited<ReturnType<CanvasAccess['getLayo
235
247
  totalNodes: layout.nodes.length,
236
248
  totalEdges: layout.edges.length,
237
249
  totalAnnotations: (layout.annotations ?? []).length,
238
- annotations: (layout.annotations ?? []).map((annotation) => summarizeCanvasAnnotationForContext(annotation, layout.nodes)),
250
+ annotations: (layout.annotations ?? []).map((annotation) =>
251
+ summarizeCanvasAnnotationForContext(annotation, layout.nodes),
252
+ ),
239
253
  nodesByType,
240
254
  pinnedCount: pinned.size,
241
255
  pinnedTitles,
@@ -276,12 +290,18 @@ export async function startMcpServer(): Promise<void> {
276
290
  'canvas_ax_interaction',
277
291
  'Submit a node-originated AX interaction: a capability-gated, validated event from an eligible node that maps onto an AX operation (work item, evidence, approval, review, focus, steering, event). Returns { ok: false, code } if the node type/metadata does not allow the interaction type or the payload is invalid.',
278
292
  {
279
- type: z.enum(AX_INTERACTION_TYPES).describe('Interaction type, e.g. ax.work.create, ax.evidence.add, ax.focus.set.'),
293
+ type: z
294
+ .enum(AX_INTERACTION_TYPES)
295
+ .describe('Interaction type, e.g. ax.work.create, ax.evidence.add, ax.focus.set.'),
280
296
  sourceNodeId: z.string().describe('The node emitting the interaction.'),
281
- payload: z.record(z.string(), z.unknown()).optional().describe('Type-specific payload, e.g. {"title":"..."} for ax.work.create.'),
297
+ payload: z
298
+ .record(z.string(), z.unknown())
299
+ .optional()
300
+ .describe('Type-specific payload, e.g. {"title":"..."} for ax.work.create.'),
282
301
  sourceSurface: z.enum(['native-node', 'json-render', 'html-node', 'mcp-app', 'adapter']).optional(),
283
302
  correlationId: z.string().optional(),
284
- source: z.enum(['agent', 'api', 'browser', 'cli', 'codex', 'copilot', 'mcp', 'sdk', 'system'])
303
+ source: z
304
+ .enum(['agent', 'api', 'browser', 'cli', 'codex', 'copilot', 'mcp', 'sdk', 'system'])
285
305
  .optional()
286
306
  .describe('Optional host/source label. Defaults to mcp.'),
287
307
  },
@@ -319,29 +339,59 @@ export async function startMcpServer(): Promise<void> {
319
339
  'canvas_ingest_activity',
320
340
  'Ingest a normalized agent activity (a tool/session event your harness forwards) so the board reacts automatically — primitive A, makes AX bidirectional. Always records a timeline event; kind-driven default reactions (overridable per call via `reactions`): failure/error → work item (blocked) + review finding + evidence (logs); tool-result + outcome:"success" → evidence (tool-result); everything else (tool-start, session-*, command, note) → event only. Set any reaction to false to suppress it, or to an object to override its fields. Returns { event, workItem, evidence, review }.',
321
341
  {
322
- kind: z.enum(['tool-start', 'tool-result', 'failure', 'error', 'session-start', 'session-end', 'command', 'note']),
342
+ kind: z.enum([
343
+ 'tool-start',
344
+ 'tool-result',
345
+ 'failure',
346
+ 'error',
347
+ 'session-start',
348
+ 'session-end',
349
+ 'command',
350
+ 'note',
351
+ ]),
323
352
  title: z.string(),
324
353
  summary: z.string().optional(),
325
354
  outcome: z.enum(['success', 'failure']).optional(),
326
- ref: z.string().optional().describe('A file path, URL, or commit the activity refers to (used as the review file anchor for failures).'),
355
+ ref: z
356
+ .string()
357
+ .optional()
358
+ .describe('A file path, URL, or commit the activity refers to (used as the review file anchor for failures).'),
327
359
  nodeIds: z.array(z.string()).optional(),
328
360
  data: z.record(z.string(), z.unknown()).optional(),
329
- reactions: z.object({
330
- workItem: z.union([z.literal(false), z.object({
331
- status: z.enum(['todo', 'in-progress', 'blocked', 'done', 'cancelled']).optional(),
332
- detail: z.string().nullable().optional(),
333
- })]).optional(),
334
- evidence: z.union([z.literal(false), z.object({
335
- kind: z.enum(['logs', 'tool-result', 'screenshot', 'file', 'diff', 'test-output']).optional(),
336
- body: z.string().nullable().optional(),
337
- })]).optional(),
338
- review: z.union([z.literal(false), z.object({
339
- severity: z.enum(['info', 'warning', 'error']).optional(),
340
- kind: z.enum(['comment', 'finding']).optional(),
341
- anchorType: z.enum(['node', 'file', 'region']).optional(),
342
- nodeId: z.string().nullable().optional(),
343
- })]).optional(),
344
- }).optional().describe('Override or suppress the kind-driven default reactions.'),
361
+ reactions: z
362
+ .object({
363
+ workItem: z
364
+ .union([
365
+ z.literal(false),
366
+ z.object({
367
+ status: z.enum(['todo', 'in-progress', 'blocked', 'done', 'cancelled']).optional(),
368
+ detail: z.string().nullable().optional(),
369
+ }),
370
+ ])
371
+ .optional(),
372
+ evidence: z
373
+ .union([
374
+ z.literal(false),
375
+ z.object({
376
+ kind: z.enum(['logs', 'tool-result', 'screenshot', 'file', 'diff', 'test-output']).optional(),
377
+ body: z.string().nullable().optional(),
378
+ }),
379
+ ])
380
+ .optional(),
381
+ review: z
382
+ .union([
383
+ z.literal(false),
384
+ z.object({
385
+ severity: z.enum(['info', 'warning', 'error']).optional(),
386
+ kind: z.enum(['comment', 'finding']).optional(),
387
+ anchorType: z.enum(['node', 'file', 'region']).optional(),
388
+ nodeId: z.string().nullable().optional(),
389
+ }),
390
+ ])
391
+ .optional(),
392
+ })
393
+ .optional()
394
+ .describe('Override or suppress the kind-driven default reactions.'),
345
395
  source: z.enum(['agent', 'api', 'browser', 'cli', 'codex', 'copilot', 'mcp', 'sdk', 'system']).optional(),
346
396
  },
347
397
  async ({ kind, title, summary, outcome, ref, nodeIds, data, reactions, source }) => {
@@ -391,7 +441,10 @@ export async function startMcpServer(): Promise<void> {
391
441
  'canvas_screenshot',
392
442
  'Capture a screenshot from the active Bun.WebView automation session. Returns both an MCP image payload and JSON metadata. Requires an active automation session started via canvas_webview with action "start".',
393
443
  {
394
- format: z.enum(['png', 'jpeg', 'webp']).optional().describe('Screenshot format (default depends on Bun; png recommended)'),
444
+ format: z
445
+ .enum(['png', 'jpeg', 'webp'])
446
+ .optional()
447
+ .describe('Screenshot format (default depends on Bun; png recommended)'),
395
448
  quality: z.number().optional().describe('Optional quality for lossy formats'),
396
449
  },
397
450
  async ({ format, quality }) => {
@@ -407,27 +460,21 @@ export async function startMcpServer(): Promise<void> {
407
460
  {
408
461
  type: 'image',
409
462
  data: encodeBase64(bytes),
410
- mimeType:
411
- format === 'jpeg'
412
- ? 'image/jpeg'
413
- : format === 'webp'
414
- ? 'image/webp'
415
- : 'image/png',
463
+ mimeType: format === 'jpeg' ? 'image/jpeg' : format === 'webp' ? 'image/webp' : 'image/png',
416
464
  },
417
465
  {
418
466
  type: 'text',
419
- text: JSON.stringify({
420
- bytes: bytes.byteLength,
421
- backend: status.backend,
422
- width: status.width,
423
- height: status.height,
424
- mimeType:
425
- format === 'jpeg'
426
- ? 'image/jpeg'
427
- : format === 'webp'
428
- ? 'image/webp'
429
- : 'image/png',
430
- }, null, 2),
467
+ text: JSON.stringify(
468
+ {
469
+ bytes: bytes.byteLength,
470
+ backend: status.backend,
471
+ width: status.width,
472
+ height: status.height,
473
+ mimeType: format === 'jpeg' ? 'image/jpeg' : format === 'webp' ? 'image/webp' : 'image/png',
474
+ },
475
+ null,
476
+ 2,
477
+ ),
431
478
  },
432
479
  ],
433
480
  };
@@ -449,8 +496,7 @@ export async function startMcpServer(): Promise<void> {
449
496
  'schema',
450
497
  'canvas://schema',
451
498
  {
452
- description:
453
- `Machine-readable create schemas, canonical examples, json-render catalog details, and MCP node-type routing from the running PMX Canvas server version. Routing: ${structuredSchemaDescription()}.`,
499
+ description: `Machine-readable create schemas, canonical examples, json-render catalog details, and MCP node-type routing from the running PMX Canvas server version. Routing: ${structuredSchemaDescription()}.`,
454
500
  mimeType: 'application/json',
455
501
  },
456
502
  async () => ({
@@ -480,20 +526,20 @@ export async function startMcpServer(): Promise<void> {
480
526
  const layout = await c.getLayout();
481
527
 
482
528
  const pinnedNodes = layout.nodes.filter((n) => pinnedIds.has(n.id));
483
- const pinnedEdges = layout.edges.filter(
484
- (e) => pinnedIds.has(e.from) && pinnedIds.has(e.to),
485
- );
529
+ const pinnedEdges = layout.edges.filter((e) => pinnedIds.has(e.from) && pinnedIds.has(e.to));
486
530
 
487
531
  // Compute neighborhoods: for each pinned node, find nearby unpinned nodes
488
532
  const neighborhoods = findNeighborhoods(layout.nodes, pinnedIds);
489
533
 
490
534
  const context = {
491
535
  pinnedCount: pinnedNodes.length,
492
- nodes: pinnedNodes.map((n) => serializeNodeForAgentContext(n, {
493
- defaultTextLength: 700,
494
- webpageTextLength: 1600,
495
- includePosition: true,
496
- })),
536
+ nodes: pinnedNodes.map((n) =>
537
+ serializeNodeForAgentContext(n, {
538
+ defaultTextLength: 700,
539
+ webpageTextLength: 1600,
540
+ includePosition: true,
541
+ }),
542
+ ),
497
543
  edges: pinnedEdges.map((e) => ({
498
544
  id: e.id,
499
545
  from: e.from,
@@ -547,8 +593,7 @@ export async function startMcpServer(): Promise<void> {
547
593
  'ax-context',
548
594
  'canvas://ax-context',
549
595
  {
550
- description:
551
- 'Agent-ready PMX AX context combining pinned context, focus, and surface metadata.',
596
+ description: 'Agent-ready PMX AX context combining pinned context, focus, and surface metadata.',
552
597
  mimeType: 'application/json',
553
598
  },
554
599
  async () => {
@@ -606,14 +651,18 @@ export async function startMcpServer(): Promise<void> {
606
651
  {
607
652
  uri: 'canvas://ax-work',
608
653
  mimeType: 'application/json',
609
- text: JSON.stringify({
610
- workItems,
611
- approvalGates,
612
- reviewAnnotations: state.reviewAnnotations,
613
- elicitations: state.elicitations,
614
- modeRequests: state.modeRequests,
615
- policy: state.policy,
616
- }, null, 2),
654
+ text: JSON.stringify(
655
+ {
656
+ workItems,
657
+ approvalGates,
658
+ reviewAnnotations: state.reviewAnnotations,
659
+ elicitations: state.elicitations,
660
+ modeRequests: state.modeRequests,
661
+ policy: state.policy,
662
+ },
663
+ null,
664
+ 2,
665
+ ),
617
666
  },
618
667
  ],
619
668
  };
@@ -634,7 +683,11 @@ export async function startMcpServer(): Promise<void> {
634
683
  const pendingActivity = buildPendingAxActivity(state);
635
684
  return {
636
685
  contents: [
637
- { uri: 'canvas://ax-pending-steering', mimeType: 'application/json', text: JSON.stringify({ pending, pendingActivity }, null, 2) },
686
+ {
687
+ uri: 'canvas://ax-pending-steering',
688
+ mimeType: 'application/json',
689
+ text: JSON.stringify({ pending, pendingActivity }, null, 2),
690
+ },
638
691
  ],
639
692
  };
640
693
  },
@@ -653,7 +706,11 @@ export async function startMcpServer(): Promise<void> {
653
706
  const timeline = await c.getAxTimeline();
654
707
  return {
655
708
  contents: [
656
- { uri: 'canvas://ax-delivery', mimeType: 'application/json', text: JSON.stringify({ steering: timeline.steering }, null, 2) },
709
+ {
710
+ uri: 'canvas://ax-delivery',
711
+ mimeType: 'application/json',
712
+ text: JSON.stringify({ steering: timeline.steering }, null, 2),
713
+ },
657
714
  ],
658
715
  };
659
716
  },
@@ -732,13 +789,18 @@ export async function startMcpServer(): Promise<void> {
732
789
  'grouped together), provides reading order (top-left to bottom-right), and shows ' +
733
790
  'neighborhoods around pinned nodes (nearby unpinned nodes the human implicitly associated). ' +
734
791
  'This makes "spatial arrangement is communication" real — read this to understand the ' +
735
- 'human\'s spatial intent, not just which nodes are pinned.',
792
+ "human's spatial intent, not just which nodes are pinned.",
736
793
  mimeType: 'application/json',
737
794
  },
738
795
  async () => {
739
796
  const c = await ensureCanvas();
740
797
  const layout = await c.getLayout();
741
- const spatial = buildSpatialContext(layout.nodes, layout.edges, new Set(await c.getPinnedNodeIds()), layout.annotations ?? []);
798
+ const spatial = buildSpatialContext(
799
+ layout.nodes,
800
+ layout.edges,
801
+ new Set(await c.getPinnedNodeIds()),
802
+ layout.annotations ?? [],
803
+ );
742
804
  return {
743
805
  contents: [
744
806
  {
@@ -56,11 +56,8 @@ function summarizeWebpageData(data: Record<string, unknown>, maxLength: number):
56
56
  const status = typeof data.status === 'string' ? data.status : '';
57
57
  const statusCode = typeof data.statusCode === 'number' ? data.statusCode : null;
58
58
  const error = typeof data.error === 'string' ? data.error : '';
59
- const content = typeof data.content === 'string'
60
- ? data.content
61
- : typeof data.excerpt === 'string'
62
- ? data.excerpt
63
- : '';
59
+ const content =
60
+ typeof data.content === 'string' ? data.content : typeof data.excerpt === 'string' ? data.excerpt : '';
64
61
 
65
62
  if (url) parts.push(`URL: ${url}`);
66
63
  if (pageTitle) parts.push(`Title: ${pageTitle}`);
@@ -132,9 +129,7 @@ function summarizeWebArtifactData(data: Record<string, unknown>, maxLength: numb
132
129
  const sourceFiles = Array.isArray(data.sourceFiles)
133
130
  ? data.sourceFiles.filter((file): file is string => typeof file === 'string')
134
131
  : [];
135
- const deps = Array.isArray(data.deps)
136
- ? data.deps.filter((dep): dep is string => typeof dep === 'string')
137
- : [];
132
+ const deps = Array.isArray(data.deps) ? data.deps.filter((dep): dep is string => typeof dep === 'string') : [];
138
133
 
139
134
  if (content) parts.push(content);
140
135
  if (!content && title) parts.push(`Web artifact: ${title}`);
@@ -170,7 +165,16 @@ function metadataForNode(node: CanvasNodeState): Record<string, unknown> | undef
170
165
  switch (node.type) {
171
166
  case 'webpage': {
172
167
  const metadata: Record<string, unknown> = {};
173
- for (const key of ['url', 'pageTitle', 'description', 'imageUrl', 'fetchedAt', 'status', 'statusCode', 'contentType']) {
168
+ for (const key of [
169
+ 'url',
170
+ 'pageTitle',
171
+ 'description',
172
+ 'imageUrl',
173
+ 'fetchedAt',
174
+ 'status',
175
+ 'statusCode',
176
+ 'contentType',
177
+ ]) {
174
178
  const value = node.data[key];
175
179
  if (value !== undefined && value !== null && value !== '') metadata[key] = value;
176
180
  }
@@ -194,7 +198,19 @@ function metadataForNode(node: CanvasNodeState): Record<string, unknown> | undef
194
198
  }
195
199
  case 'html': {
196
200
  const metadata: Record<string, unknown> = {};
197
- for (const key of ['summary', 'description', 'agentSummary', 'contentSummary', 'htmlPrimitive', 'presentation', 'slideCount', 'slideTitles', 'speakerNotes', 'embeddedNodeIds', 'embeddedUrls']) {
201
+ for (const key of [
202
+ 'summary',
203
+ 'description',
204
+ 'agentSummary',
205
+ 'contentSummary',
206
+ 'htmlPrimitive',
207
+ 'presentation',
208
+ 'slideCount',
209
+ 'slideTitles',
210
+ 'speakerNotes',
211
+ 'embeddedNodeIds',
212
+ 'embeddedUrls',
213
+ ]) {
198
214
  const value = node.data[key];
199
215
  if (Array.isArray(value)) {
200
216
  if (value.length > 0) metadata[key] = value;
@@ -206,7 +222,22 @@ function metadataForNode(node: CanvasNodeState): Record<string, unknown> | undef
206
222
  }
207
223
  case 'mcp-app': {
208
224
  const metadata: Record<string, unknown> = {};
209
- for (const key of ['url', 'path', 'mode', 'hostMode', 'viewerType', 'serverName', 'toolName', 'resourceUri', 'sessionStatus', 'projectPath', 'artifactBytes', 'sourceFiles', 'sourceFileCount', 'deps']) {
225
+ for (const key of [
226
+ 'url',
227
+ 'path',
228
+ 'mode',
229
+ 'hostMode',
230
+ 'viewerType',
231
+ 'serverName',
232
+ 'toolName',
233
+ 'resourceUri',
234
+ 'sessionStatus',
235
+ 'projectPath',
236
+ 'artifactBytes',
237
+ 'sourceFiles',
238
+ 'sourceFileCount',
239
+ 'deps',
240
+ ]) {
210
241
  const value = node.data[key];
211
242
  if (Array.isArray(value)) {
212
243
  if (value.length > 0) metadata[key] = value;
@@ -221,10 +252,7 @@ function metadataForNode(node: CanvasNodeState): Record<string, unknown> | undef
221
252
  }
222
253
  }
223
254
 
224
- export function summarizeNodeForAgentContext(
225
- node: CanvasNodeState,
226
- options: AgentContextOptions = {},
227
- ): string {
255
+ export function summarizeNodeForAgentContext(node: CanvasNodeState, options: AgentContextOptions = {}): string {
228
256
  const defaultTextLength = options.defaultTextLength ?? DEFAULT_CONTEXT_TEXT_LENGTH;
229
257
  const webpageTextLength = options.webpageTextLength ?? DEFAULT_WEBPAGE_CONTEXT_TEXT_LENGTH;
230
258
 
@@ -241,9 +269,7 @@ export function summarizeNodeForAgentContext(
241
269
  if (chartCfg) {
242
270
  const chartTitle = (chartCfg.title as string) || 'Untitled chart';
243
271
  const chartType = (chartCfg.type as string) || 'unknown';
244
- const labels = Array.isArray(chartCfg.labels)
245
- ? (chartCfg.labels as string[]).join(', ')
246
- : '';
272
+ const labels = Array.isArray(chartCfg.labels) ? (chartCfg.labels as string[]).join(', ') : '';
247
273
  return truncateContextText(`Chart: ${chartTitle} (${chartType}). Labels: ${labels}`, defaultTextLength);
248
274
  }
249
275
  return summarizeMcpAppData(node.data, defaultTextLength);
@@ -263,14 +289,17 @@ export function summarizeNodeForAgentContext(
263
289
  if (typeof node.data.htmlPrimitive === 'string') {
264
290
  return summarizeHtmlPrimitiveData(node.data, defaultTextLength);
265
291
  }
266
- return stringifyContextValue({
267
- title: node.data.title,
268
- description: node.data.description,
269
- summary: node.data.summary,
270
- contentSummary: node.data.contentSummary,
271
- embeddedNodeIds: node.data.embeddedNodeIds,
272
- embeddedUrls: node.data.embeddedUrls,
273
- }, defaultTextLength);
292
+ return stringifyContextValue(
293
+ {
294
+ title: node.data.title,
295
+ description: node.data.description,
296
+ summary: node.data.summary,
297
+ contentSummary: node.data.contentSummary,
298
+ embeddedNodeIds: node.data.embeddedNodeIds,
299
+ embeddedUrls: node.data.embeddedUrls,
300
+ },
301
+ defaultTextLength,
302
+ );
274
303
  }
275
304
  case 'prompt':
276
305
  case 'response': {
@@ -279,11 +308,12 @@ export function summarizeNodeForAgentContext(
279
308
  }
280
309
  case 'file': {
281
310
  const path = typeof node.data.path === 'string' ? node.data.path : '';
282
- const fileContent = typeof node.data.fileContent === 'string'
283
- ? node.data.fileContent
284
- : typeof node.data.content === 'string'
285
- ? node.data.content
286
- : '';
311
+ const fileContent =
312
+ typeof node.data.fileContent === 'string'
313
+ ? node.data.fileContent
314
+ : typeof node.data.content === 'string'
315
+ ? node.data.content
316
+ : '';
287
317
  const prefix = path ? `Path: ${path}\n\n` : '';
288
318
  const remaining = Math.max(0, defaultTextLength - prefix.length);
289
319
  return `${prefix}${truncateContextText(fileContent, remaining)}`.trim();
@@ -309,13 +339,10 @@ export function serializeNodeForAgentContext(
309
339
  };
310
340
  }
311
341
 
312
- export function buildAgentContextPreamble(
313
- nodes: CanvasNodeState[],
314
- options: AgentContextOptions = {},
315
- ): string {
342
+ export function buildAgentContextPreamble(nodes: CanvasNodeState[], options: AgentContextOptions = {}): string {
316
343
  const sections = nodes
317
344
  .map((node) => {
318
- const title = (typeof node.data.title === 'string' && node.data.title) ? node.data.title : node.id;
345
+ const title = typeof node.data.title === 'string' && node.data.title ? node.data.title : node.id;
319
346
  const content = summarizeNodeForAgentContext(node, options);
320
347
  if (!content) return '';
321
348
  const kind = getCanvasNodeKind(node);
@@ -53,11 +53,13 @@ export function buildCanvasAxSurfaceSnapshot(): PmxAxSurfaceSnapshot {
53
53
  }
54
54
 
55
55
  function serializeNodes(nodes: CanvasNodeState[]) {
56
- return nodes.map((node) => serializeNodeForAgentContext(node, {
57
- defaultTextLength: 700,
58
- webpageTextLength: 1600,
59
- includePosition: true,
60
- }));
56
+ return nodes.map((node) =>
57
+ serializeNodeForAgentContext(node, {
58
+ defaultTextLength: 700,
59
+ webpageTextLength: 1600,
60
+ includePosition: true,
61
+ }),
62
+ );
61
63
  }
62
64
 
63
65
  export function buildCanvasAxPinnedContext(): PmxAxPinnedContext {