pmx-canvas 0.1.29 → 0.1.31

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 (69) hide show
  1. package/CHANGELOG.md +219 -0
  2. package/Readme.md +20 -10
  3. package/dist/canvas/global.css +51 -56
  4. package/dist/canvas/index.js +80 -163
  5. package/dist/canvas/surface-theme.css +142 -0
  6. package/dist/json-render/index.js +103 -103
  7. package/dist/types/client/nodes/HtmlNode.d.ts +0 -7
  8. package/dist/types/client/nodes/ax-node-actions.d.ts +18 -0
  9. package/dist/types/client/nodes/surface-url.d.ts +22 -0
  10. package/dist/types/client/state/attention-bridge.d.ts +3 -0
  11. package/dist/types/client/state/intent-bridge.d.ts +17 -0
  12. package/dist/types/json-render/renderer/index.d.ts +2 -0
  13. package/dist/types/json-render/schema.d.ts +2 -0
  14. package/dist/types/json-render/server.d.ts +2 -0
  15. package/dist/types/mcp/canvas-access.d.ts +47 -0
  16. package/dist/types/server/ax-interaction.d.ts +210 -0
  17. package/dist/types/server/ax-state.d.ts +67 -1
  18. package/dist/types/server/canvas-db.d.ts +4 -0
  19. package/dist/types/server/canvas-serialization.d.ts +2 -0
  20. package/dist/types/server/canvas-state.d.ts +54 -2
  21. package/dist/types/server/html-surface.d.ts +46 -0
  22. package/dist/types/server/index.d.ts +63 -5
  23. package/dist/types/server/mutation-history.d.ts +1 -1
  24. package/dist/types/server/placement.d.ts +1 -1
  25. package/dist/types/server/server.d.ts +12 -0
  26. package/dist/types/shared/surface.d.ts +19 -0
  27. package/docs/cli.md +30 -0
  28. package/docs/http-api.md +55 -0
  29. package/docs/mcp.md +40 -2
  30. package/docs/node-types.md +26 -0
  31. package/docs/plans/plan-004-pmx-ax-primitives.md +623 -394
  32. package/docs/sdk.md +23 -1
  33. package/package.json +2 -2
  34. package/skills/pmx-canvas/SKILL.md +107 -9
  35. package/src/cli/agent.ts +177 -0
  36. package/src/cli/index.ts +8 -1
  37. package/src/client/canvas/CanvasNode.tsx +8 -4
  38. package/src/client/canvas/DockedNode.tsx +38 -38
  39. package/src/client/canvas/ExpandedNodeOverlay.tsx +12 -0
  40. package/src/client/nodes/ContextNode.tsx +17 -0
  41. package/src/client/nodes/ExtAppFrame.tsx +40 -3
  42. package/src/client/nodes/FileNode.tsx +26 -0
  43. package/src/client/nodes/HtmlNode.tsx +60 -188
  44. package/src/client/nodes/McpAppNode.tsx +47 -2
  45. package/src/client/nodes/StatusNode.tsx +20 -0
  46. package/src/client/nodes/ax-node-actions.ts +39 -0
  47. package/src/client/nodes/surface-url.ts +48 -0
  48. package/src/client/state/attention-bridge.ts +5 -0
  49. package/src/client/state/intent-bridge.ts +33 -0
  50. package/src/client/theme/global.css +51 -56
  51. package/src/client/theme/surface-theme.css +142 -0
  52. package/src/json-render/renderer/index.tsx +31 -0
  53. package/src/json-render/schema.ts +4 -0
  54. package/src/json-render/server.ts +13 -0
  55. package/src/mcp/canvas-access.ts +198 -1
  56. package/src/mcp/server.ts +232 -2
  57. package/src/server/ax-context.ts +3 -0
  58. package/src/server/ax-interaction.ts +549 -0
  59. package/src/server/ax-state.ts +188 -2
  60. package/src/server/canvas-db.ts +20 -0
  61. package/src/server/canvas-operations.ts +11 -0
  62. package/src/server/canvas-serialization.ts +9 -0
  63. package/src/server/canvas-state.ts +201 -26
  64. package/src/server/html-surface.ts +190 -0
  65. package/src/server/index.ts +122 -7
  66. package/src/server/mutation-history.ts +5 -0
  67. package/src/server/placement.ts +5 -1
  68. package/src/server/server.ts +360 -0
  69. package/src/shared/surface.ts +38 -0
@@ -1,17 +1,28 @@
1
1
  import { EventEmitter } from 'node:events';
2
2
  import { canvasState } from './canvas-state.js';
3
3
  import type { CanvasAnnotation, CanvasNodeState, CanvasEdge, CanvasLayout } from './canvas-state.js';
4
- import type { PmxAxApprovalGate, PmxAxContext, PmxAxEvent, PmxAxEvidence, PmxAxEvidenceKind, PmxAxFocusState, PmxAxHostCapability, PmxAxReviewAnchorType, PmxAxReviewAnnotation, PmxAxReviewKind, PmxAxReviewRegion, PmxAxReviewSeverity, PmxAxReviewStatus, PmxAxSource, PmxAxState, PmxAxSteeringMessage, PmxAxWorkItem, PmxAxWorkItemStatus } from './ax-state.js';
4
+ import { type AxInteractionInput, type AxInteractionPublicResult } from './ax-interaction.js';
5
+ import type { PmxAxApprovalGate, PmxAxCommandDescriptor, PmxAxContext, PmxAxElicitation, PmxAxEvent, PmxAxEvidence, PmxAxEvidenceKind, PmxAxFocusState, PmxAxHostCapability, PmxAxMode, PmxAxModeRequest, PmxAxPolicy, PmxAxReviewAnchorType, PmxAxReviewAnnotation, PmxAxReviewKind, PmxAxReviewRegion, PmxAxReviewSeverity, PmxAxReviewStatus, PmxAxSource, PmxAxState, PmxAxSteeringMessage, PmxAxWorkItem, PmxAxWorkItemStatus } from './ax-state.js';
5
6
  import type { AxTimelineQuery } from './canvas-db.js';
6
7
  import { searchNodes } from './spatial-analysis.js';
7
8
  import { diffLayouts } from './mutation-history.js';
8
9
  import { fitCanvasView, gcCanvasSnapshots, listCanvasSnapshots } from './canvas-operations.js';
10
+ import { type SerializedCanvasNode } from './canvas-serialization.js';
9
11
  import type { HtmlPrimitiveKind } from './html-primitives.js';
10
12
  import { type WebArtifactBuildInput, type WebArtifactCanvasBuildResult } from './web-artifacts.js';
11
13
  import { type ExternalMcpTransportConfig } from './mcp-app-runtime.js';
12
14
  import { type DiagramPresetOpenInput } from './diagram-presets.js';
13
15
  import { type GraphNodeInput, type JsonRenderNodeInput, type JsonRenderSpec } from '../json-render/server.js';
14
16
  import type { CanvasAutomationWebViewOptions, CanvasAutomationWebViewStatus } from './server.js';
17
+ /**
18
+ * Node object returned by the SDK's create/get methods. It is the fully
19
+ * serialized node (adds `surfaceUrl`, `kind`, `title`, `content`, …) plus a
20
+ * `nodeId` alias for `id`, so the SDK return shape matches the HTTP/CLI
21
+ * `node`-create responses field-for-field.
22
+ */
23
+ export type SdkCanvasNode = SerializedCanvasNode & {
24
+ nodeId: string;
25
+ };
15
26
  export declare class PmxCanvas extends EventEmitter {
16
27
  private _port;
17
28
  private _server;
@@ -53,7 +64,7 @@ export declare class PmxCanvas extends EventEmitter {
53
64
  width?: number;
54
65
  height?: number;
55
66
  strictSize?: boolean;
56
- }): CanvasNodeState;
67
+ }): SdkCanvasNode;
57
68
  addWebpageNode(input: {
58
69
  title?: string;
59
70
  url: string;
@@ -140,6 +151,19 @@ export declare class PmxCanvas extends EventEmitter {
140
151
  source?: PmxAxSource;
141
152
  }): PmxAxSteeringMessage;
142
153
  markSteeringDelivered(id: string): boolean;
154
+ /** Undelivered steering for a consumer (loop-safe; excludes consumer-originated). */
155
+ getPendingSteering(options?: {
156
+ consumer?: string;
157
+ limit?: number;
158
+ }): PmxAxSteeringMessage[];
159
+ /**
160
+ * Submit a node-originated AX interaction (plan-004 Phase 1). Validates the
161
+ * envelope + node capabilities, maps the interaction onto the matching AX
162
+ * operation, and emits the outcome + state SSE events.
163
+ */
164
+ submitAxInteraction(input: AxInteractionInput, options?: {
165
+ source?: PmxAxSource;
166
+ }): AxInteractionPublicResult;
143
167
  getAxTimeline(query?: AxTimelineQuery): ReturnType<typeof canvasState.getAxTimeline>;
144
168
  listWorkItems(): PmxAxWorkItem[];
145
169
  addWorkItem(input: {
@@ -206,6 +230,40 @@ export declare class PmxCanvas extends EventEmitter {
206
230
  reportHostCapability(input: unknown, options?: {
207
231
  source?: PmxAxSource;
208
232
  }): PmxAxHostCapability;
233
+ listElicitations(): PmxAxElicitation[];
234
+ requestElicitation(input: {
235
+ prompt: string;
236
+ fields?: string[];
237
+ nodeIds?: string[];
238
+ }, options?: {
239
+ source?: PmxAxSource;
240
+ }): PmxAxElicitation;
241
+ respondElicitation(id: string, response: Record<string, unknown>, options?: {
242
+ source?: PmxAxSource;
243
+ }): PmxAxElicitation | null;
244
+ listModeRequests(): PmxAxModeRequest[];
245
+ requestMode(input: {
246
+ mode: PmxAxMode;
247
+ reason?: string | null;
248
+ nodeIds?: string[];
249
+ }, options?: {
250
+ source?: PmxAxSource;
251
+ }): PmxAxModeRequest;
252
+ resolveModeRequest(id: string, decision: 'approved' | 'rejected', options?: {
253
+ resolution?: string;
254
+ source?: PmxAxSource;
255
+ }): PmxAxModeRequest | null;
256
+ getCommandRegistry(): PmxAxCommandDescriptor[];
257
+ invokeCommand(name: string, args?: Record<string, unknown> | null, options?: {
258
+ source?: PmxAxSource;
259
+ }): PmxAxEvent | null;
260
+ getPolicy(): PmxAxPolicy;
261
+ setPolicy(patch: {
262
+ tools?: Partial<PmxAxPolicy['tools']>;
263
+ prompt?: Partial<PmxAxPolicy['prompt']>;
264
+ }, options?: {
265
+ source?: PmxAxSource;
266
+ }): PmxAxPolicy;
209
267
  fitView(options?: {
210
268
  width?: number;
211
269
  height?: number;
@@ -214,7 +272,7 @@ export declare class PmxCanvas extends EventEmitter {
214
272
  nodeIds?: string[];
215
273
  }): ReturnType<typeof fitCanvasView>;
216
274
  getLayout(): CanvasLayout;
217
- getNode(id: string): CanvasNodeState | undefined;
275
+ getNode(id: string): SdkCanvasNode | undefined;
218
276
  search(query: string): ReturnType<typeof searchNodes>;
219
277
  getSpatialContext(): import("./spatial-analysis.js").SpatialContext;
220
278
  undo(): Promise<{
@@ -387,7 +445,7 @@ export declare class PmxCanvas extends EventEmitter {
387
445
  width?: number;
388
446
  height?: number;
389
447
  strictSize?: boolean;
390
- }): string;
448
+ }): SdkCanvasNode;
391
449
  addHtmlPrimitive(input: {
392
450
  kind: HtmlPrimitiveKind;
393
451
  title?: string;
@@ -439,5 +497,5 @@ export type { WebArtifactBuildInput, WebArtifactBuildOutput, WebArtifactCanvasBu
439
497
  export type { GraphNodeInput, JsonRenderNodeInput, JsonRenderSpec } from '../json-render/server.js';
440
498
  export type { HtmlPrimitiveKind, HtmlPrimitiveDescriptor, HtmlPrimitiveInput, HtmlPrimitiveBuildResult } from './html-primitives.js';
441
499
  export { traceManager } from './trace-manager.js';
442
- export type { PmxAxApprovalGate, PmxAxApprovalStatus, PmxAxContext, PmxAxEvent, PmxAxEventKind, PmxAxEvidence, PmxAxEvidenceKind, PmxAxFocusState, PmxAxHostCapability, PmxAxReviewAnchorType, PmxAxReviewAnnotation, PmxAxReviewKind, PmxAxReviewRegion, PmxAxReviewSeverity, PmxAxReviewStatus, PmxAxSource, PmxAxState, PmxAxSteeringMessage, PmxAxTimelineSummary, PmxAxWorkItem, PmxAxWorkItemStatus, } from './ax-state.js';
500
+ export type { PmxAxApprovalGate, PmxAxApprovalStatus, PmxAxCommandDescriptor, PmxAxContext, PmxAxEvent, PmxAxElicitation, PmxAxElicitationStatus, PmxAxEventKind, PmxAxEvidence, PmxAxEvidenceKind, PmxAxFocusState, PmxAxHostCapability, PmxAxMode, PmxAxModeRequest, PmxAxModeRequestStatus, PmxAxPolicy, PmxAxReviewAnchorType, PmxAxReviewAnnotation, PmxAxReviewKind, PmxAxReviewRegion, PmxAxReviewSeverity, PmxAxReviewStatus, PmxAxSource, PmxAxState, PmxAxSteeringMessage, PmxAxTimelineSummary, PmxAxWorkItem, PmxAxWorkItemStatus, } from './ax-state.js';
443
501
  export type { AxTimelineQuery } from './canvas-db.js';
@@ -12,7 +12,7 @@
12
12
  * - _replaying flag prevents undo/redo from recording new entries
13
13
  */
14
14
  import type { CanvasNodeState, CanvasEdge } from './canvas-state.js';
15
- export type MutationOp = 'addNode' | 'updateNode' | 'removeNode' | 'addEdge' | 'removeEdge' | 'addAnnotation' | 'removeAnnotation' | 'clear' | 'arrange' | 'restoreSnapshot' | 'setPins' | 'setAxFocus' | 'addWorkItem' | 'updateWorkItem' | 'requestApproval' | 'resolveApproval' | 'addReviewAnnotation' | 'updateReviewAnnotation' | 'batch' | 'viewport' | 'groupNodes' | 'ungroupNodes';
15
+ export type MutationOp = 'addNode' | 'updateNode' | 'removeNode' | 'addEdge' | 'removeEdge' | 'addAnnotation' | 'removeAnnotation' | 'clear' | 'arrange' | 'restoreSnapshot' | 'setPins' | 'setAxFocus' | 'addWorkItem' | 'updateWorkItem' | 'requestApproval' | 'resolveApproval' | 'addReviewAnnotation' | 'updateReviewAnnotation' | 'requestElicitation' | 'respondElicitation' | 'requestMode' | 'resolveModeRequest' | 'setPolicy' | 'batch' | 'viewport' | 'groupNodes' | 'ungroupNodes';
16
16
  export interface MutationEntry {
17
17
  id: string;
18
18
  timestamp: string;
@@ -1,6 +1,6 @@
1
1
  import { type CanvasPlacementRect } from '../shared/placement.js';
2
2
  export { findOpenCanvasPosition, type CanvasPlacementRect } from '../shared/placement.js';
3
- export declare const GROUP_PAD = 40;
3
+ export declare const GROUP_PAD = 56;
4
4
  export declare const GROUP_TITLEBAR_HEIGHT = 32;
5
5
  /**
6
6
  * Compute bounding box for a group that should contain the given child rects.
@@ -83,6 +83,18 @@ export declare function hasWorkbenchSubscribers(): boolean;
83
83
  export declare function setPrimaryWorkbenchCanvasPromptHandler(handler: PrimaryWorkbenchCanvasPromptHandler | null): void;
84
84
  export declare function buildMacBrowserOpenScript(appName: string, url: string): string;
85
85
  export declare function openUrlInExternalBrowser(url: string): boolean;
86
+ /**
87
+ * Seed the docked status (left) + context (right) widgets so a freshly opened
88
+ * canvas shows them by default — the same nodes the agent-event path creates on
89
+ * demand (`status-main`, `context-main`), just present from the start.
90
+ *
91
+ * First-run only: we bail if the workspace canvas already has persisted state,
92
+ * so we never add them to a board with content, and — because first-run state is
93
+ * persisted on save — deleting or undocking them later is respected (they are
94
+ * not re-seeded). Create-if-missing keeps it idempotent if the agent path
95
+ * already made one. Returns true if anything was seeded.
96
+ */
97
+ export declare function ensureDefaultDockedNodes(): boolean;
86
98
  export declare function emitPrimaryWorkbenchEvent(event: string, payload?: PrimaryWorkbenchEventPayload): void;
87
99
  export declare function consumePrimaryWorkbenchIntents(limit?: number): PrimaryWorkbenchIntent[];
88
100
  export declare function getPrimaryWorkbenchUrl(workspaceRoot?: string): string | null;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Cross-cutting helpers for node "surfaces" — the standalone documents served at
3
+ * /api/canvas/surface/:nodeId. Shared by the server route, the server-side node
4
+ * serialization, and the client so the openability rule lives in exactly one
5
+ * place. The authoritative dispatch (serve vs redirect) lives in the server's
6
+ * handleNodeSurface; this predicate mirrors which node types it can open.
7
+ */
8
+ /**
9
+ * Whether a node has an "Open as site" surface. Coarse by design (presence of the
10
+ * minimal field) — the route itself decides what to actually serve.
11
+ */
12
+ export declare function canOpenNodeAsSurface(type: string, data: Record<string, unknown>): boolean;
13
+ /**
14
+ * CSP sandbox tokens for a hosted ext-app surface, used both as the in-canvas
15
+ * iframe `sandbox` attribute and as the `Content-Security-Policy: sandbox` value
16
+ * when the surface route serves an ext-app standalone. All tokens are in the
17
+ * server's safe-sandbox allowlist; notably NOT allow-same-origin.
18
+ */
19
+ export declare const DEFAULT_EXT_APP_SANDBOX = "allow-scripts allow-popups allow-popups-to-escape-sandbox";
package/docs/cli.md CHANGED
@@ -157,6 +157,36 @@ pmx-canvas ax host report --host copilot --canvas --session-messaging
157
157
  pmx-canvas ax host status
158
158
  ```
159
159
 
160
+ ### AX interactions, delivery, elicitation, mode, commands & policy
161
+
162
+ ```bash
163
+ # Node interaction — one capability-gated envelope (server re-validates + scopes)
164
+ pmx-canvas ax interaction --type ax.work.create --node node-1 --payload '{"title":"Wire auth"}'
165
+
166
+ # Delivery — claim pending steering for a consumer (loop-safe), then acknowledge
167
+ pmx-canvas ax delivery list --consumer copilot --limit 20
168
+ pmx-canvas ax delivery mark <steering-id>
169
+
170
+ # Elicitation — request structured human input, then answer
171
+ pmx-canvas ax elicitation request --prompt "Who owns this migration?" --fields owner
172
+ pmx-canvas ax elicitation respond <id> --response '{"owner":"alice"}'
173
+ pmx-canvas ax elicitation list
174
+
175
+ # Mode — request a plan/execute/autonomous transition, then resolve
176
+ pmx-canvas ax mode request --mode execute --reason "plan approved"
177
+ pmx-canvas ax mode resolve <id> --decision approved
178
+ pmx-canvas ax mode list
179
+
180
+ # Commands — list the registry, invoke a registry-gated command
181
+ pmx-canvas ax command list
182
+ pmx-canvas ax command invoke pmx.plan
183
+ pmx-canvas ax command invoke pmx.promote-context --args '{"nodeIds":["n1"]}'
184
+
185
+ # Policy — read / patch the tool/prompt policy (stored by PMX, enforced by adapters)
186
+ pmx-canvas ax policy get
187
+ pmx-canvas ax policy set --excluded-tools shell,write --mode concise
188
+ ```
189
+
160
190
  ## Copilot adapter
161
191
 
162
192
  Install the bundled GitHub Copilot extension adapter into a repo. The adapter
package/docs/http-api.md CHANGED
@@ -181,6 +181,61 @@ Validation: `/ax/event` requires a valid `kind` + `summary` (400 otherwise);
181
181
  and `PATCH /ax/review/:id` return 404 for unknown IDs; approval resolve returns
182
182
  404 if the gate is missing or already resolved.
183
183
 
184
+ ## AX interactions, delivery, elicitation, mode, commands & policy
185
+
186
+ Node interactions are one normalized, capability-gated envelope that maps onto an
187
+ AX operation. The server re-validates every interaction against the source node's
188
+ effective capabilities and clamps sandboxed surfaces (`html-node`, `mcp-app`,
189
+ `json-render`) to their own node.
190
+
191
+ ```bash
192
+ # Node interaction — one envelope, validated + mapped to the matching AX op
193
+ curl -X POST http://localhost:4313/api/canvas/ax/interaction \
194
+ -H "Content-Type: application/json" \
195
+ -d '{"type":"ax.work.create","sourceNodeId":"node-1","payload":{"title":"Wire auth"}}'
196
+
197
+ # Delivery — claim pending steering for a consumer (loop-safe), then mark delivered
198
+ curl "http://localhost:4313/api/canvas/ax/delivery/pending?consumer=copilot&limit=20"
199
+ curl -X POST http://localhost:4313/api/canvas/ax/delivery/<steering-id>/mark \
200
+ -H "Content-Type: application/json" \
201
+ -d '{"consumer":"copilot"}'
202
+
203
+ # Elicitation — request structured human input, then respond
204
+ curl -X POST http://localhost:4313/api/canvas/ax/elicitation \
205
+ -H "Content-Type: application/json" \
206
+ -d '{"prompt":"Who owns this migration?","fields":["owner"],"source":"api"}'
207
+ curl -X POST http://localhost:4313/api/canvas/ax/elicitation/<id>/respond \
208
+ -H "Content-Type: application/json" \
209
+ -d '{"response":{"owner":"alice"}}'
210
+ curl http://localhost:4313/api/canvas/ax/elicitation
211
+
212
+ # Mode — request a plan/execute/autonomous transition, then resolve
213
+ curl -X POST http://localhost:4313/api/canvas/ax/mode \
214
+ -H "Content-Type: application/json" \
215
+ -d '{"mode":"plan","reason":"scope the change first","source":"api"}'
216
+ curl -X POST http://localhost:4313/api/canvas/ax/mode/<id>/resolve \
217
+ -H "Content-Type: application/json" \
218
+ -d '{"decision":"approved"}'
219
+ curl http://localhost:4313/api/canvas/ax/mode
220
+
221
+ # Commands — list the registry, invoke a command (records a `command` agent-event)
222
+ curl http://localhost:4313/api/canvas/ax/command
223
+ curl -X POST http://localhost:4313/api/canvas/ax/command \
224
+ -H "Content-Type: application/json" \
225
+ -d '{"name":"pmx.plan","args":{"note":"draft a plan"},"source":"api"}'
226
+
227
+ # Policy — read / patch the canvas-bound tool/prompt policy (patches merge)
228
+ curl http://localhost:4313/api/canvas/ax/policy
229
+ curl -X POST http://localhost:4313/api/canvas/ax/policy \
230
+ -H "Content-Type: application/json" \
231
+ -d '{"tools":{"excluded":["shell"]},"prompt":{"mode":"concise"},"source":"api"}'
232
+ ```
233
+
234
+ Validation: `/ax/interaction` returns `{ ok: false, code }` (403 `ax-disabled` /
235
+ `not-allowed`, 400 `invalid-payload` / `unknown-command`, 404 `unknown-node`);
236
+ `/ax/command` rejects an unknown command name with 400; `/ax/elicitation/:id/respond`
237
+ and `/ax/mode/:id/resolve` return 404 for unknown IDs.
238
+
184
239
  ## Diagrams (Excalidraw preset)
185
240
 
186
241
  ```bash
package/docs/mcp.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # MCP reference
2
2
 
3
- PMX Canvas ships an MCP stdio server with **56 tools** + **12 core resources**,
3
+ PMX Canvas ships an MCP stdio server with **65 tools** + **14 core resources**,
4
4
  plus per-skill resources at `canvas://skills/<name>`. The server emits
5
5
  `notifications/resources/updated` when canvas state changes — humans pin
6
6
  nodes in the browser, agents are notified immediately.
@@ -65,6 +65,15 @@ searchable and readable in pinned/spatial context.
65
65
  | `canvas_add_evidence` | Record an `evidence-item` on the timeline (logs/tool-result/screenshot/file/diff/test-output) |
66
66
  | `canvas_add_review_annotation` | Add a canvas-bound `review-annotation` (comment/finding) anchored to a node, file, or region |
67
67
  | `canvas_report_host_capability` | Report a host/session `host-capability` for diagnostics |
68
+ | `canvas_ax_interaction` | Submit one capability-gated AX interaction envelope (`{ type, sourceNodeId, payload }`) that maps onto an AX operation; the server re-validates and clamps sandboxed surfaces to their own node |
69
+ | `canvas_claim_ax_delivery` | Claim undelivered steering messages for an adapterless consumer (loop-safe — never returns steering the consumer originated) |
70
+ | `canvas_mark_ax_delivery` | Mark a steering message delivered for a consumer |
71
+ | `canvas_request_elicitation` | Request structured human input via a canvas-bound `elicitation` (pending) |
72
+ | `canvas_respond_elicitation` | Respond to / resolve a pending elicitation |
73
+ | `canvas_request_mode` | Request a workflow `mode-request` transition (plan/execute/autonomous) |
74
+ | `canvas_resolve_mode` | Resolve a pending mode request |
75
+ | `canvas_invoke_command` | Invoke a registry command (`pmx.plan`, `pmx.execute`, `pmx.promote-context`, `pmx.summarize`, `pmx.review`); records a `command` agent-event, unknown names rejected |
76
+ | `canvas_set_ax_policy` | Patch the canvas-bound tool/prompt policy (`tools.allowed\|excluded\|approvalRequired`, `prompt.systemAppend\|mode`); patches merge and are normalized |
68
77
  | `canvas_pin_nodes` | Pin nodes to include in agent context |
69
78
  | `canvas_clear` | Clear all nodes and edges |
70
79
  | `canvas_snapshot` | Save current canvas as a named snapshot |
@@ -96,8 +105,10 @@ Individual bundled skills are also readable at `canvas://skills/<name>`.
96
105
  | `canvas://pinned-context` | Content of pinned nodes + nearby unpinned neighbors |
97
106
  | `canvas://ax` | PMX AX state: focus, work items, approval gates, review annotations |
98
107
  | `canvas://ax-context` | Agent-readable pinned and focused AX context, plus timeline summary and host capability |
99
- | `canvas://ax-work` | Canvas-bound AX work: work items, approval gates, review annotations |
108
+ | `canvas://ax-work` | Canvas-bound AX work: work items, approval gates, review annotations, elicitations, mode requests, and tool/prompt policy |
100
109
  | `canvas://ax-timeline` | Bounded AX timeline: recent agent-events, evidence, and steering messages |
110
+ | `canvas://ax-pending-steering` | Undelivered steering an adapterless MCP client can claim, act on, and mark delivered |
111
+ | `canvas://ax-delivery` | Steering delivery state (delivered flag) for diagnostics |
101
112
  | `canvas://schema` | Running-server create schemas and json-render catalog metadata |
102
113
  | `canvas://layout` | Full canvas state (all nodes, edges, viewport) |
103
114
  | `canvas://summary` | Compact overview: counts, pinned titles, viewport |
@@ -106,6 +117,33 @@ Individual bundled skills are also readable at `canvas://skills/<name>`.
106
117
  | `canvas://code-graph` | Auto-detected file dependency graph (JS/TS, Python, Go, Rust) |
107
118
  | `canvas://skills` | Index of bundled agent skills + per-skill content at `canvas://skills/<name>` |
108
119
 
120
+ ## Node interactions (capability-gated)
121
+
122
+ Eligible nodes emit one normalized, validated interaction envelope
123
+ (`{ type, sourceNodeId, payload, sourceSurface }`) via `canvas_ax_interaction`
124
+ (HTTP `POST /api/canvas/ax/interaction`) that maps onto an AX operation — work
125
+ item, evidence, approval, review, focus, steering, event, elicitation, mode, or
126
+ command. The server is the single trust boundary and re-validates every
127
+ interaction against the node's effective capabilities.
128
+
129
+ - **Capabilities:** each node type has a default capability set (a ceiling). A
130
+ node may opt in or narrow via `data.axCapabilities` (`{ enabled, allowed }`),
131
+ clamped to the ceiling — a node can never escalate beyond its type's ceiling.
132
+ `html` / `html-primitive`, `mcp-app`, and internal `prompt` / `response` are
133
+ disabled by default.
134
+ - **Scoping:** sandboxed/opaque-origin iframe surfaces (`html-node`, `mcp-app`,
135
+ `json-render`) are clamped to their own node — caller-supplied `nodeIds` are
136
+ forced to the source node. Trusted surfaces (`native-node`, `adapter`) may
137
+ target explicit nodeIds.
138
+ - **Transports:** native node controls call the endpoint directly; sandboxed
139
+ `html` / `mcp-app` nodes call `window.PMX_AX.emit(type, payload)`; the
140
+ `json-render` / `graph` viewer forwards a spec action named after an AX type
141
+ (e.g. `on.press → { action: "ax.work.create", params }`). All postMessage
142
+ transports are nonce-validated by the parent canvas before submission.
143
+ - **Commands:** `canvas_invoke_command` runs a registry command (`pmx.plan`,
144
+ `pmx.execute`, `pmx.promote-context`, `pmx.summarize`, `pmx.review`); unknown
145
+ names are rejected and a successful call records a `command` agent-event.
146
+
109
147
  ## Change notifications
110
148
 
111
149
  The MCP server emits `notifications/resources/updated` whenever canvas state
@@ -138,6 +138,32 @@ canvas_add_json_render_node({
138
138
  `outline`. Older saved specs using `label` or status variants such as
139
139
  `success`/`warning` are normalized during validation.
140
140
 
141
+ Elements may carry an `on` map (`on.press`, `on.change`, …) binding events to
142
+ actions (`{ action, params }`) — built-in actions (`setState`, `pushState`, …) or
143
+ host-provided handlers. PMX wires AX handlers named after interaction types, so a
144
+ spec action named `ax.*` becomes a capability-gated AX interaction:
145
+
146
+ ```ts
147
+ canvas_add_json_render_node({
148
+ title: 'Approve plan',
149
+ spec: {
150
+ root: 'btn',
151
+ elements: {
152
+ btn: {
153
+ type: 'Button',
154
+ props: { label: 'Track as work', variant: 'primary' },
155
+ on: { press: { action: 'ax.work.create', params: { title: 'Ship the plan' } } },
156
+ },
157
+ },
158
+ },
159
+ });
160
+ ```
161
+
162
+ The viewer forwards the emit to the parent canvas, which validates it (iframe
163
+ source + per-viewer nonce + node id) and submits it server-side; `json-render` /
164
+ `graph` viewers are sandboxed surfaces, so caller-supplied `nodeIds` are clamped
165
+ to the node's own id. See the [MCP reference](mcp.md#node-interactions-capability-gated).
166
+
141
167
  Use `canvas_describe_schema` / `canvas_validate_spec` to introspect the
142
168
  component catalog before building a spec.
143
169