pmx-canvas 0.2.7 → 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 (189) hide show
  1. package/CHANGELOG.md +148 -1
  2. package/Readme.md +13 -11
  3. package/dist/canvas/index.js +2 -2
  4. package/dist/json-render/index.css +1 -1
  5. package/dist/types/cli/daemon.d.ts +74 -0
  6. package/dist/types/cli/watch.d.ts +2 -2
  7. package/dist/types/client/canvas/CanvasViewport.d.ts +1 -1
  8. package/dist/types/client/canvas/CommandPalette.d.ts +1 -1
  9. package/dist/types/client/canvas/Minimap.d.ts +1 -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/mcp/canvas-access.d.ts +0 -7
  17. package/dist/types/server/ax-state-manager.d.ts +3 -2
  18. package/dist/types/server/bundled-skills.d.ts +3 -3
  19. package/dist/types/server/canvas-schema.d.ts +1 -1
  20. package/dist/types/server/canvas-state.d.ts +2 -0
  21. package/dist/types/server/ext-app-lookup.d.ts +1 -3
  22. package/dist/types/server/html-primitives.d.ts +1 -1
  23. package/dist/types/server/index.d.ts +4 -4
  24. package/dist/types/server/operations/composites.d.ts +29 -23
  25. package/dist/types/server/operations/http.d.ts +2 -1
  26. package/dist/types/server/operations/index.d.ts +1 -1
  27. package/dist/types/server/operations/ops/ax-read.d.ts +2 -0
  28. package/dist/types/server/operations/ops/canvas-wire.d.ts +2 -0
  29. package/dist/types/server/operations/ops/ext-app.d.ts +2 -0
  30. package/dist/types/shared/ax-intent.d.ts +1 -1
  31. package/docs/RELEASE.md +23 -8
  32. package/docs/ax-host-adapter-contract.md +7 -7
  33. package/docs/ax-state-contract.md +7 -6
  34. package/docs/bun-webview-integration.md +5 -5
  35. package/docs/cli.md +1 -1
  36. package/docs/http-api.md +35 -3
  37. package/docs/mcp.md +121 -100
  38. package/docs/node-types.md +45 -35
  39. package/docs/plans/plan-006-mcp-tool-consolidation.md +3 -1
  40. package/docs/plans/plan-008-registry-finish.md +2 -0
  41. package/docs/plans/plan-009-tech-debt-backlog.md +51 -0
  42. package/docs/screenshot.png +0 -0
  43. package/docs/tech-debt-assessment-2026-06.md +1 -1
  44. package/docs/tech-debt-assessment-2026-07.md +135 -0
  45. package/package.json +5 -2
  46. package/skills/data-analysis/SKILL.md +3 -3
  47. package/skills/frontend-design/SKILL.md +3 -2
  48. package/skills/json-render-mcp/SKILL.md +4 -3
  49. package/skills/playwright-cli/SKILL.md +1 -1
  50. package/skills/pmx-canvas/SKILL.md +12 -7
  51. package/skills/pmx-canvas/evals/evals.json +2 -2
  52. package/skills/pmx-canvas/references/ax-html-control-surface.md +3 -1
  53. package/skills/pmx-canvas/references/excalidraw-diagram-authoring.md +2 -2
  54. package/skills/pmx-canvas/references/full-reference.md +58 -56
  55. package/skills/pmx-canvas/references/html-primitives.md +3 -3
  56. package/skills/tufte-viz/SKILL.md +5 -4
  57. package/skills/web-artifacts-builder/SKILL.md +4 -4
  58. package/src/cli/agent.ts +1861 -1548
  59. package/src/cli/daemon.ts +460 -0
  60. package/src/cli/index.ts +63 -326
  61. package/src/cli/watch.ts +2 -10
  62. package/src/client/App.tsx +48 -46
  63. package/src/client/canvas/AttentionHistory.tsx +11 -1
  64. package/src/client/canvas/CanvasNode.tsx +41 -29
  65. package/src/client/canvas/CanvasViewport.tsx +101 -66
  66. package/src/client/canvas/CommandPalette.tsx +61 -27
  67. package/src/client/canvas/ContextMenu.tsx +13 -20
  68. package/src/client/canvas/ContextPinBar.tsx +1 -5
  69. package/src/client/canvas/ContextPinHud.tsx +1 -6
  70. package/src/client/canvas/DockedNode.tsx +4 -4
  71. package/src/client/canvas/EdgeLayer.tsx +37 -36
  72. package/src/client/canvas/ExpandedNodeOverlay.tsx +69 -45
  73. package/src/client/canvas/FocusFieldLayer.tsx +20 -22
  74. package/src/client/canvas/IntentLayer.tsx +31 -14
  75. package/src/client/canvas/Minimap.tsx +11 -16
  76. package/src/client/canvas/SelectionBar.tsx +4 -11
  77. package/src/client/canvas/ShortcutOverlay.tsx +3 -1
  78. package/src/client/canvas/SnapshotPanel.tsx +77 -95
  79. package/src/client/canvas/auto-fit.ts +15 -14
  80. package/src/client/canvas/snap-guides.ts +12 -12
  81. package/src/client/canvas/use-node-resize.ts +1 -5
  82. package/src/client/canvas/use-pan-zoom.ts +25 -26
  83. package/src/client/ext-app/bridge.ts +3 -12
  84. package/src/client/icons.tsx +63 -20
  85. package/src/client/nodes/ContextNode.tsx +14 -25
  86. package/src/client/nodes/ExtAppFrame.tsx +60 -39
  87. package/src/client/nodes/FileNode.tsx +74 -62
  88. package/src/client/nodes/GroupNode.tsx +4 -6
  89. package/src/client/nodes/HtmlNode.tsx +76 -46
  90. package/src/client/nodes/ImageNode.tsx +18 -27
  91. package/src/client/nodes/InlineFormatBar.tsx +4 -21
  92. package/src/client/nodes/InlineMarkdownEditor.tsx +0 -1
  93. package/src/client/nodes/LedgerNode.tsx +10 -4
  94. package/src/client/nodes/MarkdownNode.tsx +3 -10
  95. package/src/client/nodes/McpAppNode.tsx +26 -22
  96. package/src/client/nodes/MdFormatBar.tsx +10 -7
  97. package/src/client/nodes/PromptNode.tsx +23 -51
  98. package/src/client/nodes/ResponseNode.tsx +3 -13
  99. package/src/client/nodes/StatusNode.tsx +5 -9
  100. package/src/client/nodes/StatusSummary.tsx +2 -8
  101. package/src/client/nodes/WebpageNode.tsx +20 -14
  102. package/src/client/nodes/iframe-document-url.ts +25 -16
  103. package/src/client/nodes/image-warnings.ts +1 -7
  104. package/src/client/nodes/md-format.ts +20 -5
  105. package/src/client/state/attention-bridge.ts +4 -9
  106. package/src/client/state/attention-store.ts +1 -7
  107. package/src/client/state/canvas-store.ts +52 -36
  108. package/src/client/state/intent-bridge.ts +176 -112
  109. package/src/client/state/intent-store.ts +4 -1
  110. package/src/client/state/sse-bridge.ts +53 -70
  111. package/src/json-render/catalog.ts +12 -16
  112. package/src/json-render/charts/components.tsx +16 -20
  113. package/src/json-render/charts/extra-components.tsx +8 -16
  114. package/src/json-render/charts/extra-definitions.ts +1 -2
  115. package/src/json-render/charts/tufte-components.tsx +37 -20
  116. package/src/json-render/charts/tufte-definitions.ts +8 -2
  117. package/src/json-render/renderer/index.tsx +42 -22
  118. package/src/json-render/schema.ts +6 -3
  119. package/src/json-render/server.ts +33 -39
  120. package/src/mcp/canvas-access.ts +40 -121
  121. package/src/mcp/server.ts +206 -271
  122. package/src/server/agent-context.ts +63 -36
  123. package/src/server/ax-context.ts +7 -5
  124. package/src/server/ax-interaction.ts +176 -43
  125. package/src/server/ax-state-manager.ts +185 -41
  126. package/src/server/ax-state.ts +142 -47
  127. package/src/server/bundled-skills.ts +3 -3
  128. package/src/server/canvas-db.ts +213 -95
  129. package/src/server/canvas-operations.ts +177 -120
  130. package/src/server/canvas-provenance.ts +1 -4
  131. package/src/server/canvas-schema.ts +500 -102
  132. package/src/server/canvas-serialization.ts +27 -35
  133. package/src/server/canvas-state.ts +155 -58
  134. package/src/server/chart-template.ts +4 -5
  135. package/src/server/code-graph.ts +20 -7
  136. package/src/server/diagram-presets.ts +28 -29
  137. package/src/server/ext-app-lookup.ts +4 -20
  138. package/src/server/html-node-summary.ts +19 -10
  139. package/src/server/html-primitives.ts +326 -97
  140. package/src/server/html-surface.ts +6 -9
  141. package/src/server/image-source.ts +6 -4
  142. package/src/server/index.ts +321 -218
  143. package/src/server/intent-registry.ts +3 -6
  144. package/src/server/mcp-app-candidate.ts +5 -10
  145. package/src/server/mcp-app-host.ts +14 -38
  146. package/src/server/mcp-app-runtime.ts +12 -20
  147. package/src/server/mutation-history.ts +15 -5
  148. package/src/server/operations/composites.ts +38 -40
  149. package/src/server/operations/http.ts +5 -5
  150. package/src/server/operations/index.ts +7 -1
  151. package/src/server/operations/invoker.ts +4 -3
  152. package/src/server/operations/mcp.ts +29 -36
  153. package/src/server/operations/ops/annotation.ts +122 -10
  154. package/src/server/operations/ops/app.ts +102 -77
  155. package/src/server/operations/ops/ax-await.ts +17 -10
  156. package/src/server/operations/ops/ax-read.ts +347 -0
  157. package/src/server/operations/ops/ax-shared.ts +2 -7
  158. package/src/server/operations/ops/ax-state.ts +32 -14
  159. package/src/server/operations/ops/ax-timeline.ts +32 -19
  160. package/src/server/operations/ops/ax-work.ts +54 -37
  161. package/src/server/operations/ops/batch.ts +39 -14
  162. package/src/server/operations/ops/canvas-wire.ts +91 -0
  163. package/src/server/operations/ops/edges.ts +37 -25
  164. package/src/server/operations/ops/ext-app.ts +346 -0
  165. package/src/server/operations/ops/groups.ts +49 -20
  166. package/src/server/operations/ops/intent.ts +18 -12
  167. package/src/server/operations/ops/json-render.ts +239 -98
  168. package/src/server/operations/ops/nodes.ts +300 -111
  169. package/src/server/operations/ops/query.ts +62 -33
  170. package/src/server/operations/ops/snapshots.ts +35 -26
  171. package/src/server/operations/ops/validate.ts +2 -1
  172. package/src/server/operations/ops/viewport.ts +60 -16
  173. package/src/server/operations/ops/webview.ts +44 -18
  174. package/src/server/operations/registry.ts +2 -3
  175. package/src/server/operations/types.ts +7 -5
  176. package/src/server/operations/webview-runner.ts +1 -3
  177. package/src/server/placement.ts +8 -18
  178. package/src/server/server.ts +121 -1012
  179. package/src/server/spatial-analysis.ts +39 -25
  180. package/src/server/trace-manager.ts +3 -8
  181. package/src/server/web-artifacts.ts +23 -27
  182. package/src/server/webpage-node.ts +5 -13
  183. package/src/shared/auto-arrange.ts +12 -5
  184. package/src/shared/ax-intent.ts +1 -1
  185. package/src/shared/content-height-reporter.ts +8 -6
  186. package/src/shared/ext-app-tool-result.ts +2 -6
  187. package/src/shared/placement.ts +1 -4
  188. package/src/shared/semantic-attention.ts +39 -37
  189. package/src/shared/surface.ts +8 -4
@@ -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 {
@@ -86,11 +86,29 @@ function caps(
86
86
  * a node can anchor AX state but only eligible types may EMIT interactions.
87
87
  */
88
88
  export const DEFAULT_NODE_AX_CAPABILITIES: Record<CanvasNodeType, NodeAxCapabilities> = {
89
- markdown: caps(true, ['ax.steer', 'ax.work.create', 'ax.evidence.add', 'ax.command.invoke', 'ax.event.record'], 'notify-agent'),
90
- context: caps(true, ['ax.focus.set', 'ax.steer', 'ax.evidence.add', 'ax.command.invoke', 'ax.event.record'], 'notify-agent'),
91
- status: caps(true, ['ax.work.create', 'ax.work.update', 'ax.approval.request', 'ax.mode.request', 'ax.event.record'], 'notify-agent'),
89
+ markdown: caps(
90
+ true,
91
+ ['ax.steer', 'ax.work.create', 'ax.evidence.add', 'ax.command.invoke', 'ax.event.record'],
92
+ 'notify-agent',
93
+ ),
94
+ context: caps(
95
+ true,
96
+ ['ax.focus.set', 'ax.steer', 'ax.evidence.add', 'ax.command.invoke', 'ax.event.record'],
97
+ 'notify-agent',
98
+ ),
99
+ status: caps(
100
+ true,
101
+ ['ax.work.create', 'ax.work.update', 'ax.approval.request', 'ax.mode.request', 'ax.event.record'],
102
+ 'notify-agent',
103
+ ),
92
104
  file: caps(true, ['ax.evidence.add', 'ax.review.add', 'ax.focus.set', 'ax.event.record']),
93
- 'json-render': caps(true, ['ax.work.create', 'ax.work.update', 'ax.evidence.add', 'ax.elicitation.request', 'ax.event.record']),
105
+ 'json-render': caps(true, [
106
+ 'ax.work.create',
107
+ 'ax.work.update',
108
+ 'ax.evidence.add',
109
+ 'ax.elicitation.request',
110
+ 'ax.event.record',
111
+ ]),
94
112
  graph: caps(true, ['ax.evidence.add', 'ax.focus.set', 'ax.event.record']),
95
113
  ledger: caps(true, ['ax.evidence.add', 'ax.event.record']),
96
114
  trace: caps(true, ['ax.evidence.add', 'ax.event.record']),
@@ -123,8 +141,12 @@ export const DEFAULT_NODE_AX_CAPABILITIES: Record<CanvasNodeType, NodeAxCapabili
123
141
  // itself, and request human input. Excludes higher-trust types (steer, approval,
124
142
  // review, command, mode) which stay native-control / adapter only.
125
143
  'mcp-app': caps(false, [
126
- 'ax.event.record', 'ax.evidence.add', 'ax.work.create', 'ax.work.update',
127
- 'ax.focus.set', 'ax.elicitation.request',
144
+ 'ax.event.record',
145
+ 'ax.evidence.add',
146
+ 'ax.work.create',
147
+ 'ax.work.update',
148
+ 'ax.focus.set',
149
+ 'ax.elicitation.request',
128
150
  ]),
129
151
  // Internal thread nodes — anchor only, no human-facing emission by default.
130
152
  prompt: caps(false, ['ax.event.record']),
@@ -141,11 +163,13 @@ export function normalizeNodeAxCapabilities(value: unknown): Partial<NodeAxCapab
141
163
  if (typeof v.enabled === 'boolean') out.enabled = v.enabled;
142
164
  if (Array.isArray(v.allowed)) {
143
165
  out.allowed = v.allowed.filter((a): a is AxInteractionType =>
144
- AX_INTERACTION_TYPES.includes(a as AxInteractionType));
166
+ AX_INTERACTION_TYPES.includes(a as AxInteractionType),
167
+ );
145
168
  }
146
169
  if (Array.isArray(v.requiresApproval)) {
147
170
  out.requiresApproval = v.requiresApproval.filter((a): a is AxInteractionType =>
148
- AX_INTERACTION_TYPES.includes(a as AxInteractionType));
171
+ AX_INTERACTION_TYPES.includes(a as AxInteractionType),
172
+ );
149
173
  }
150
174
  if (v.delivery === 'record-only' || v.delivery === 'notify-agent' || v.delivery === 'send-to-agent') {
151
175
  out.delivery = v.delivery;
@@ -172,7 +196,15 @@ export function resolveNodeAxCapabilities(node: CanvasNodeState): NodeAxCapabili
172
196
 
173
197
  // ── Envelope + payload validation ──────────────────────────────
174
198
 
175
- const EVENT_KINDS = ['prompt', 'assistant-message', 'tool-start', 'tool-result', 'failure', 'approval', 'steering'] as const;
199
+ const EVENT_KINDS = [
200
+ 'prompt',
201
+ 'assistant-message',
202
+ 'tool-start',
203
+ 'tool-result',
204
+ 'failure',
205
+ 'approval',
206
+ 'steering',
207
+ ] as const;
176
208
  const EVIDENCE_KINDS = ['logs', 'tool-result', 'screenshot', 'file', 'diff', 'test-output'] as const;
177
209
  const WORK_STATUSES = ['todo', 'in-progress', 'blocked', 'done', 'cancelled'] as const;
178
210
  const REVIEW_KINDS = ['comment', 'finding'] as const;
@@ -284,7 +316,13 @@ const PAYLOAD_SCHEMAS: Record<string, z.ZodType> = {
284
316
  export interface AxInteractionManager {
285
317
  getNode(id: string): CanvasNodeState | undefined;
286
318
  recordAxEvent(
287
- input: { kind: PmxAxEventKind; summary: string; detail?: string | null; nodeIds?: string[]; data?: Record<string, unknown> | null },
319
+ input: {
320
+ kind: PmxAxEventKind;
321
+ summary: string;
322
+ detail?: string | null;
323
+ nodeIds?: string[];
324
+ data?: Record<string, unknown> | null;
325
+ },
288
326
  options?: { source?: PmxAxSource },
289
327
  ): PmxAxEvent;
290
328
  recordSteeringMessage(message: string, options?: { source?: PmxAxSource }): PmxAxSteeringMessage;
@@ -298,7 +336,14 @@ export interface AxInteractionManager {
298
336
  options?: { source?: PmxAxSource },
299
337
  ): PmxAxWorkItem | null;
300
338
  addEvidence(
301
- input: { kind: PmxAxEvidenceKind; title: string; body?: string | null; ref?: string | null; nodeIds?: string[]; data?: Record<string, unknown> | null },
339
+ input: {
340
+ kind: PmxAxEvidenceKind;
341
+ title: string;
342
+ body?: string | null;
343
+ ref?: string | null;
344
+ nodeIds?: string[];
345
+ data?: Record<string, unknown> | null;
346
+ },
302
347
  options?: { source?: PmxAxSource },
303
348
  ): PmxAxEvidence;
304
349
  requestApproval(
@@ -332,7 +377,11 @@ export interface AxInteractionManager {
332
377
  input: { mode: PmxAxMode; reason?: string | null; nodeIds?: string[] },
333
378
  options?: { source?: PmxAxSource },
334
379
  ): PmxAxModeRequest;
335
- invokeCommand(name: string, args?: Record<string, unknown> | null, options?: { source?: PmxAxSource }): PmxAxEvent | null;
380
+ invokeCommand(
381
+ name: string,
382
+ args?: Record<string, unknown> | null,
383
+ options?: { source?: PmxAxSource },
384
+ ): PmxAxEvent | null;
336
385
  }
337
386
 
338
387
  export interface AxInteractionEvent {
@@ -353,13 +402,7 @@ function outcomeEvent(extra: Record<string, unknown>): AxInteractionEvent {
353
402
  return { event: 'ax-interaction', payload: extra };
354
403
  }
355
404
 
356
- function reject(
357
- type: string,
358
- sourceNodeId: string,
359
- status: number,
360
- code: string,
361
- error: string,
362
- ): AxInteractionResult {
405
+ function reject(type: string, sourceNodeId: string, status: number, code: string, error: string): AxInteractionResult {
363
406
  return {
364
407
  result: { ok: false, status, code, error },
365
408
  events: [outcomeEvent({ ok: false, type, sourceNodeId, code, error })],
@@ -375,10 +418,7 @@ function accept(
375
418
  ): AxInteractionResult {
376
419
  return {
377
420
  result: { ok: true, type, sourceNodeId, primitive },
378
- events: [
379
- outcomeEvent({ ok: true, type, sourceNodeId }),
380
- { event: stateEvent, payload: statePayload },
381
- ],
421
+ events: [outcomeEvent({ ok: true, type, sourceNodeId }), { event: stateEvent, payload: statePayload }],
382
422
  };
383
423
  }
384
424
 
@@ -396,10 +436,14 @@ export function applyAxInteraction(
396
436
  const parsed = InteractionEnvelopeSchema.safeParse(rawBody);
397
437
  if (!parsed.success) {
398
438
  const error = parsed.error.issues.map((i) => `${i.path.join('.') || 'envelope'}: ${i.message}`).join('; ');
399
- const type = typeof (rawBody as { type?: unknown })?.type === 'string' ? String((rawBody as { type?: unknown }).type) : 'unknown';
400
- const sourceNodeId = typeof (rawBody as { sourceNodeId?: unknown })?.sourceNodeId === 'string'
401
- ? String((rawBody as { sourceNodeId?: unknown }).sourceNodeId)
402
- : '';
439
+ const type =
440
+ typeof (rawBody as { type?: unknown })?.type === 'string'
441
+ ? String((rawBody as { type?: unknown }).type)
442
+ : 'unknown';
443
+ const sourceNodeId =
444
+ typeof (rawBody as { sourceNodeId?: unknown })?.sourceNodeId === 'string'
445
+ ? String((rawBody as { sourceNodeId?: unknown }).sourceNodeId)
446
+ : '';
403
447
  return reject(type, sourceNodeId, 400, 'invalid-envelope', error);
404
448
  }
405
449
  const interaction = parsed.data;
@@ -410,7 +454,13 @@ export function applyAxInteraction(
410
454
 
411
455
  const capabilities = resolveNodeAxCapabilities(node);
412
456
  if (!capabilities.enabled) {
413
- return reject(type, sourceNodeId, 403, 'ax-disabled', `AX interactions are not enabled for node "${sourceNodeId}".`);
457
+ return reject(
458
+ type,
459
+ sourceNodeId,
460
+ 403,
461
+ 'ax-disabled',
462
+ `AX interactions are not enabled for node "${sourceNodeId}".`,
463
+ );
414
464
  }
415
465
  if (!capabilities.allowed.includes(type)) {
416
466
  return reject(type, sourceNodeId, 403, 'not-allowed', `Node type "${node.type}" cannot emit "${type}".`);
@@ -418,7 +468,13 @@ export function applyAxInteraction(
418
468
  // Fail closed: approval-gated interaction types are rejected until approval
419
469
  // routing lands, rather than dispatched without the gate they require.
420
470
  if (capabilities.requiresApproval.includes(type)) {
421
- return reject(type, sourceNodeId, 403, 'requires-approval', `"${type}" requires approval routing, which is not yet available.`);
471
+ return reject(
472
+ type,
473
+ sourceNodeId,
474
+ 403,
475
+ 'requires-approval',
476
+ `"${type}" requires approval routing, which is not yet available.`,
477
+ );
422
478
  }
423
479
 
424
480
  const schema = PAYLOAD_SCHEMAS[type];
@@ -446,9 +502,21 @@ export function applyAxInteraction(
446
502
 
447
503
  switch (type) {
448
504
  case 'ax.event.record': {
449
- const p = payloadParsed.data as { kind: PmxAxEventKind; summary: string; detail?: string | null; nodeIds?: string[]; data?: Record<string, unknown> | null };
505
+ const p = payloadParsed.data as {
506
+ kind: PmxAxEventKind;
507
+ summary: string;
508
+ detail?: string | null;
509
+ nodeIds?: string[];
510
+ data?: Record<string, unknown> | null;
511
+ };
450
512
  const event = manager.recordAxEvent(
451
- { kind: p.kind, summary: p.summary, detail: p.detail ?? null, nodeIds: scopedNodeIds(p.nodeIds), data: p.data ?? null },
513
+ {
514
+ kind: p.kind,
515
+ summary: p.summary,
516
+ detail: p.detail ?? null,
517
+ nodeIds: scopedNodeIds(p.nodeIds),
518
+ data: p.data ?? null,
519
+ },
452
520
  opts,
453
521
  );
454
522
  return accept(type, sourceNodeId, event, 'ax-event-created', { event });
@@ -459,15 +527,31 @@ export function applyAxInteraction(
459
527
  return accept(type, sourceNodeId, steering, 'ax-event-created', { steering });
460
528
  }
461
529
  case 'ax.work.create': {
462
- const p = payloadParsed.data as { title: string; status?: PmxAxWorkItemStatus; detail?: string | null; nodeIds?: string[] };
530
+ const p = payloadParsed.data as {
531
+ title: string;
532
+ status?: PmxAxWorkItemStatus;
533
+ detail?: string | null;
534
+ nodeIds?: string[];
535
+ };
463
536
  const workItem = manager.addWorkItem(
464
- { title: p.title, ...(p.status ? { status: p.status } : {}), ...(p.detail !== undefined ? { detail: p.detail } : {}), nodeIds: scopedNodeIds(p.nodeIds) },
537
+ {
538
+ title: p.title,
539
+ ...(p.status ? { status: p.status } : {}),
540
+ ...(p.detail !== undefined ? { detail: p.detail } : {}),
541
+ nodeIds: scopedNodeIds(p.nodeIds),
542
+ },
465
543
  opts,
466
544
  );
467
545
  return accept(type, sourceNodeId, workItem, 'ax-state-changed', { workItem });
468
546
  }
469
547
  case 'ax.work.update': {
470
- const p = payloadParsed.data as { id: string; title?: string; status?: PmxAxWorkItemStatus; detail?: string | null; nodeIds?: string[] };
548
+ const p = payloadParsed.data as {
549
+ id: string;
550
+ title?: string;
551
+ status?: PmxAxWorkItemStatus;
552
+ detail?: string | null;
553
+ nodeIds?: string[];
554
+ };
471
555
  const { id, ...patch } = p;
472
556
  if (scoped && patch.nodeIds !== undefined) patch.nodeIds = [sourceNodeId];
473
557
  const workItem = manager.updateWorkItem(id, patch, opts);
@@ -475,29 +559,71 @@ export function applyAxInteraction(
475
559
  return accept(type, sourceNodeId, workItem, 'ax-state-changed', { workItem });
476
560
  }
477
561
  case 'ax.evidence.add': {
478
- const p = payloadParsed.data as { kind: PmxAxEvidenceKind; title: string; body?: string | null; ref?: string | null; nodeIds?: string[]; data?: Record<string, unknown> | null };
562
+ const p = payloadParsed.data as {
563
+ kind: PmxAxEvidenceKind;
564
+ title: string;
565
+ body?: string | null;
566
+ ref?: string | null;
567
+ nodeIds?: string[];
568
+ data?: Record<string, unknown> | null;
569
+ };
479
570
  const evidence = manager.addEvidence(
480
- { kind: p.kind, title: p.title, body: p.body ?? null, ref: p.ref ?? null, nodeIds: scopedNodeIds(p.nodeIds), data: p.data ?? null },
571
+ {
572
+ kind: p.kind,
573
+ title: p.title,
574
+ body: p.body ?? null,
575
+ ref: p.ref ?? null,
576
+ nodeIds: scopedNodeIds(p.nodeIds),
577
+ data: p.data ?? null,
578
+ },
481
579
  opts,
482
580
  );
483
581
  return accept(type, sourceNodeId, evidence, 'ax-event-created', { evidence });
484
582
  }
485
583
  case 'ax.approval.request': {
486
- const p = payloadParsed.data as { title: string; detail?: string | null; action?: string | null; nodeIds?: string[] };
584
+ const p = payloadParsed.data as {
585
+ title: string;
586
+ detail?: string | null;
587
+ action?: string | null;
588
+ nodeIds?: string[];
589
+ };
487
590
  const approvalGate = manager.requestApproval(
488
- { title: p.title, ...(p.detail !== undefined ? { detail: p.detail } : {}), ...(p.action !== undefined ? { action: p.action } : {}), nodeIds: scopedNodeIds(p.nodeIds) },
591
+ {
592
+ title: p.title,
593
+ ...(p.detail !== undefined ? { detail: p.detail } : {}),
594
+ ...(p.action !== undefined ? { action: p.action } : {}),
595
+ nodeIds: scopedNodeIds(p.nodeIds),
596
+ },
489
597
  opts,
490
598
  );
491
599
  return accept(type, sourceNodeId, approvalGate, 'ax-state-changed', { approvalGate });
492
600
  }
493
601
  case 'ax.approval.resolve': {
494
602
  const p = payloadParsed.data as { id: string; decision: 'approved' | 'rejected'; resolution?: string };
495
- const approvalGate = manager.resolveApproval(p.id, p.decision, { ...(p.resolution !== undefined ? { resolution: p.resolution } : {}), source });
496
- if (!approvalGate) return reject(type, sourceNodeId, 404, 'approval-not-found', `Approval "${p.id}" not found or already resolved.`);
603
+ const approvalGate = manager.resolveApproval(p.id, p.decision, {
604
+ ...(p.resolution !== undefined ? { resolution: p.resolution } : {}),
605
+ source,
606
+ });
607
+ if (!approvalGate)
608
+ return reject(
609
+ type,
610
+ sourceNodeId,
611
+ 404,
612
+ 'approval-not-found',
613
+ `Approval "${p.id}" not found or already resolved.`,
614
+ );
497
615
  return accept(type, sourceNodeId, approvalGate, 'ax-state-changed', { approvalGate });
498
616
  }
499
617
  case 'ax.review.add': {
500
- const p = payloadParsed.data as { body: string; kind?: PmxAxReviewKind; severity?: PmxAxReviewSeverity; anchorType?: PmxAxReviewAnchorType; nodeId?: string; file?: string; author?: string };
618
+ const p = payloadParsed.data as {
619
+ body: string;
620
+ kind?: PmxAxReviewKind;
621
+ severity?: PmxAxReviewSeverity;
622
+ anchorType?: PmxAxReviewAnchorType;
623
+ nodeId?: string;
624
+ file?: string;
625
+ author?: string;
626
+ };
501
627
  // Sandboxed surfaces may only review their own node; trusted surfaces may
502
628
  // anchor to a file/region or another node.
503
629
  // A node-interaction review carries a sourceNodeId, so it defaults to a node
@@ -510,13 +636,20 @@ export function applyAxInteraction(
510
636
  ...(p.kind ? { kind: p.kind } : {}),
511
637
  ...(p.severity ? { severity: p.severity } : {}),
512
638
  anchorType,
513
- nodeId: scoped ? sourceNodeId : (anchorType === 'node' ? (p.nodeId ?? sourceNodeId) : (p.nodeId ?? null)),
639
+ nodeId: scoped ? sourceNodeId : anchorType === 'node' ? (p.nodeId ?? sourceNodeId) : (p.nodeId ?? null),
514
640
  ...(!scoped && p.file !== undefined ? { file: p.file } : {}),
515
641
  ...(p.author !== undefined ? { author: p.author } : {}),
516
642
  },
517
643
  opts,
518
644
  );
519
- if (!reviewAnnotation) return reject(type, sourceNodeId, 400, 'invalid-review-anchor', 'Node-anchored review requires a nodeId that exists on the canvas.');
645
+ if (!reviewAnnotation)
646
+ return reject(
647
+ type,
648
+ sourceNodeId,
649
+ 400,
650
+ 'invalid-review-anchor',
651
+ 'Node-anchored review requires a nodeId that exists on the canvas.',
652
+ );
520
653
  return accept(type, sourceNodeId, reviewAnnotation, 'ax-state-changed', { reviewAnnotation });
521
654
  }
522
655
  case 'ax.focus.set': {