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
@@ -0,0 +1,190 @@
1
+ /**
2
+ * Server-side builder for an `html` node's standalone surface document.
3
+ *
4
+ * This is the canonical wrapper that used to live in the client (HtmlNode's
5
+ * `buildSrcDoc`). It now lives on the server so a single document definition
6
+ * backs BOTH the in-canvas iframe and the "Open as site" tab — the iframe and
7
+ * the standalone tab load the exact same URL (/api/canvas/surface/:nodeId), so
8
+ * there is one render path and no content fork.
9
+ *
10
+ * Theming: instead of inlining a token `<style>` block, the document links the
11
+ * same-origin `/canvas/surface-theme.css` stylesheet and selects a palette via
12
+ * the `<html data-theme="...">` attribute. A sandboxed (opaque-origin) document
13
+ * can still load this same-origin stylesheet, and live theme switching works by
14
+ * toggling the attribute (the theme bridge below) — no CSS payload over
15
+ * postMessage required.
16
+ */
17
+
18
+ export type SurfaceTheme = 'dark' | 'light' | 'high-contrast';
19
+
20
+ /** Path the surface document links for its theme tokens (served from dist/canvas). */
21
+ export const SURFACE_THEME_STYLESHEET = '/canvas/surface-theme.css';
22
+
23
+ /** CSP sandbox tokens for an `html`/`html-primitive` surface — scripts only, opaque origin. */
24
+ export const HTML_SURFACE_SANDBOX = 'allow-scripts';
25
+
26
+ export function normalizeSurfaceTheme(value: string | null | undefined): SurfaceTheme {
27
+ return value === 'light' || value === 'high-contrast' ? value : 'dark';
28
+ }
29
+
30
+ /**
31
+ * Restrict a caller-supplied token to a safe charset before it is embedded
32
+ * inside an inline `<script>` string. The token is a CSRF-style nonce minted by
33
+ * the client (shape `theme-<base36>-<base36>` / `presentation-<...>`), but it
34
+ * arrives as a query parameter, so it must never be trusted verbatim — anything
35
+ * outside `[A-Za-z0-9_-]` (notably `<`, `"`, backtick) could break out of the
36
+ * script context.
37
+ */
38
+ function sanitizeToken(value: string | null | undefined): string {
39
+ if (typeof value !== 'string') return '';
40
+ return value.replace(/[^A-Za-z0-9_-]/g, '').slice(0, 64);
41
+ }
42
+
43
+ /**
44
+ * Bridge that lets the parent canvas live-switch the surface theme by toggling
45
+ * the `data-theme` attribute. Validates source + type + nonce so unrelated
46
+ * windows cannot drive the attribute. No-op in a standalone tab (no parent
47
+ * posts to it), which is exactly what we want there.
48
+ */
49
+ function buildThemeBridge(themeToken: string): string {
50
+ const token = JSON.stringify(themeToken);
51
+ return `<script data-pmx-canvas-theme-bridge>
52
+ const PMX_CANVAS_THEME_TOKEN = ${token};
53
+ window.addEventListener('message', (event) => {
54
+ const message = event.data;
55
+ if (!message || message.source !== 'pmx-canvas-html-node' || message.type !== 'theme-update' || message.token !== PMX_CANVAS_THEME_TOKEN) return;
56
+ if (typeof message.theme !== 'string') return;
57
+ document.documentElement.setAttribute('data-pmx-canvas-theme', message.theme);
58
+ document.documentElement.setAttribute('data-theme', message.theme);
59
+ });
60
+ </script>`;
61
+ }
62
+
63
+ /**
64
+ * Presentation bridge (deck mode). Identical contract to the previous client
65
+ * version: Escape posts an exit message to the parent overlay, and the parent
66
+ * can forward slide keys back in. Only relevant when the surface is embedded in
67
+ * the in-canvas presentation overlay; harmless (inert) in a standalone tab.
68
+ */
69
+ function buildPresentationEscapeBridge(exitToken: string): string {
70
+ const token = JSON.stringify(exitToken);
71
+ return `<script data-pmx-canvas-presentation-bridge>
72
+ const PMX_CANVAS_PRESENTATION_EXIT_TOKEN = ${token};
73
+ document.addEventListener('keydown', (event) => {
74
+ if (event.key === 'Escape') {
75
+ window.parent.postMessage({ source: 'pmx-canvas-html-node', type: 'presentation-exit', token: PMX_CANVAS_PRESENTATION_EXIT_TOKEN }, '*');
76
+ }
77
+ }, true);
78
+ window.addEventListener('message', (event) => {
79
+ const message = event.data;
80
+ if (!message || message.source !== 'pmx-canvas-html-node' || message.type !== 'presentation-key' || message.token !== PMX_CANVAS_PRESENTATION_EXIT_TOKEN) return;
81
+ if (typeof message.key !== 'string') return;
82
+ if (typeof window.PMX_CANVAS_PRESENTATION_HANDLE_KEY === 'function') {
83
+ window.PMX_CANVAS_PRESENTATION_HANDLE_KEY(message.key);
84
+ return;
85
+ }
86
+ document.dispatchEvent(new CustomEvent('pmx-presentation-key', { detail: { key: message.key }, bubbles: true, cancelable: true }));
87
+ document.dispatchEvent(new KeyboardEvent('keydown', { key: message.key, bubbles: true, cancelable: true }));
88
+ });
89
+ </script>`;
90
+ }
91
+
92
+ function injectIntoHead(html: string, content: string): string {
93
+ if (/<head[\s>]/i.test(html)) {
94
+ return html.replace(/<head([^>]*)>/i, `<head$1>${content}`);
95
+ }
96
+ if (/<html[\s>]/i.test(html)) {
97
+ return html.replace(/<html([^>]*)>/i, `<html$1><head>${content}</head>`);
98
+ }
99
+ return html;
100
+ }
101
+
102
+ /**
103
+ * Bridge that exposes `window.PMX_AX.emit(type, payload)` to author HTML. Calls
104
+ * post a nonce-tagged message to the parent canvas, which validates the nonce +
105
+ * node id and submits the interaction through the capability-gated endpoint. Only
106
+ * injected when the node's AX capabilities are enabled (opt-in for `html`), and
107
+ * the server re-validates every interaction — so this is a convenience surface,
108
+ * not a trust boundary.
109
+ */
110
+ function buildAxBridge(axToken: string, nodeId: string): string {
111
+ const token = JSON.stringify(axToken);
112
+ const node = JSON.stringify(nodeId);
113
+ return `<script data-pmx-canvas-ax-bridge>
114
+ const PMX_AX_TOKEN = ${token};
115
+ const PMX_AX_NODE_ID = ${node};
116
+ window.PMX_AX = {
117
+ emit(type, payload) {
118
+ window.parent.postMessage({
119
+ source: 'pmx-canvas-ax',
120
+ token: PMX_AX_TOKEN,
121
+ nodeId: PMX_AX_NODE_ID,
122
+ interaction: { type: String(type), payload: payload && typeof payload === 'object' ? payload : {} },
123
+ }, '*');
124
+ },
125
+ };
126
+ </script>`;
127
+ }
128
+
129
+ /** Escape a string for safe interpolation into element text (e.g. `<title>`). */
130
+ function escapeSurfaceHtml(value: string): string {
131
+ return value.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
132
+ }
133
+
134
+ export interface HtmlSurfaceOptions {
135
+ theme: SurfaceTheme;
136
+ /**
137
+ * Tab/document title. Injected as `<title>` only when the author HTML does not
138
+ * already declare one, so a standalone "Open as site" tab shows the node title
139
+ * instead of falling back to the raw URL.
140
+ */
141
+ title?: string;
142
+ /** Client nonce that authorizes parent → iframe theme-update messages. */
143
+ themeToken?: string;
144
+ presentation?: boolean;
145
+ presentationExitToken?: string;
146
+ /** Inject window.PMX_AX.emit (only when the node's AX capabilities are enabled). */
147
+ axBridge?: boolean;
148
+ /** Nonce authorizing iframe → parent AX emits; embedded in the bridge. */
149
+ axToken?: string;
150
+ /** Node id stamped on emitted interactions. */
151
+ nodeId?: string;
152
+ }
153
+
154
+ /**
155
+ * Wrap author HTML into a complete, themed standalone document. Accepts either a
156
+ * full HTML document (injects into its `<head>`) or a fragment (wraps it).
157
+ */
158
+ export function buildHtmlSurfaceDocument(userHtml: string, options: HtmlSurfaceOptions): string {
159
+ const themeToken = sanitizeToken(options.themeToken);
160
+ const link = `<link rel="stylesheet" href="${SURFACE_THEME_STYLESHEET}">`;
161
+ const themeBridge = buildThemeBridge(themeToken);
162
+ const presentationBridge = options.presentation
163
+ ? buildPresentationEscapeBridge(sanitizeToken(options.presentationExitToken))
164
+ : '';
165
+ const axBridge = options.axBridge
166
+ ? buildAxBridge(sanitizeToken(options.axToken), sanitizeToken(options.nodeId))
167
+ : '';
168
+ const injectedHeadContent = `${link}${themeBridge}${presentationBridge}${axBridge}`;
169
+ const presentationAttr = options.presentation ? ' data-pmx-presentation-mode="present"' : '';
170
+ const trimmed = userHtml.trim();
171
+ const isFullDoc = /<html[\s>]/i.test(trimmed);
172
+ // Only supply a fallback <title> when the author HTML does not already set a
173
+ // DOCUMENT title. Strip inline <svg>/<math> first so a nested accessibility
174
+ // <title> (e.g. <svg><title>…</title></svg>) doesn't suppress the fallback.
175
+ const withoutNestedTitles = trimmed
176
+ .replace(/<svg[\s\S]*?<\/svg>/gi, '')
177
+ .replace(/<math[\s\S]*?<\/math>/gi, '');
178
+ const titleTag = options.title && !/<title[\s>]/i.test(withoutNestedTitles)
179
+ ? `<title>${escapeSurfaceHtml(options.title)}</title>`
180
+ : '';
181
+ if (isFullDoc) {
182
+ const withTheme = trimmed.replace(
183
+ /<html([^>]*)>/i,
184
+ `<html$1 data-pmx-canvas-theme="${options.theme}" data-theme="${options.theme}"${presentationAttr}>`,
185
+ );
186
+ return injectIntoHead(withTheme, `${titleTag}${injectedHeadContent}`);
187
+ }
188
+ // Fragment — wrap in a full document.
189
+ return `<!doctype html><html data-pmx-canvas-theme="${options.theme}" data-theme="${options.theme}"${presentationAttr}><head><meta charset="utf-8">${titleTag}${injectedHeadContent}</head><body>${userHtml}</body></html>`;
190
+ }
@@ -2,14 +2,20 @@ import { EventEmitter } from 'node:events';
2
2
  import { canvasState, IMAGE_MIME_MAP } from './canvas-state.js';
3
3
  import type { CanvasAnnotation, CanvasNodeState, CanvasEdge, CanvasLayout, ViewportState } from './canvas-state.js';
4
4
  import { buildCanvasAxContext } from './ax-context.js';
5
+ import { applyAxInteraction, type AxInteractionInput, type AxInteractionPublicResult } from './ax-interaction.js';
5
6
  import type {
6
7
  PmxAxApprovalGate,
8
+ PmxAxCommandDescriptor,
7
9
  PmxAxContext,
10
+ PmxAxElicitation,
8
11
  PmxAxEvent,
9
12
  PmxAxEvidence,
10
13
  PmxAxEvidenceKind,
11
14
  PmxAxFocusState,
12
15
  PmxAxHostCapability,
16
+ PmxAxMode,
17
+ PmxAxModeRequest,
18
+ PmxAxPolicy,
13
19
  PmxAxReviewAnchorType,
14
20
  PmxAxReviewAnnotation,
15
21
  PmxAxReviewKind,
@@ -68,6 +74,7 @@ import {
68
74
  } from './canvas-operations.js';
69
75
  import { validateCanvasLayout } from './canvas-validation.js';
70
76
  import { describeCanvasSchema, validateStructuredCanvasPayload } from './canvas-schema.js';
77
+ import { serializeCanvasNode, type SerializedCanvasNode } from './canvas-serialization.js';
71
78
  import { buildHtmlPrimitive, getHtmlPrimitiveSemanticMetadata, isHtmlPrimitiveKind, listHtmlPrimitiveDescriptors } from './html-primitives.js';
72
79
  import type { HtmlPrimitiveKind } from './html-primitives.js';
73
80
  import {
@@ -118,6 +125,19 @@ import type {
118
125
  PrimaryWorkbenchIntent,
119
126
  } from './server.js';
120
127
 
128
+ /**
129
+ * Node object returned by the SDK's create/get methods. It is the fully
130
+ * serialized node (adds `surfaceUrl`, `kind`, `title`, `content`, …) plus a
131
+ * `nodeId` alias for `id`, so the SDK return shape matches the HTTP/CLI
132
+ * `node`-create responses field-for-field.
133
+ */
134
+ export type SdkCanvasNode = SerializedCanvasNode & { nodeId: string };
135
+
136
+ /** Enrich a raw canvas node into the SDK return shape (surfaceUrl + nodeId). */
137
+ function toSdkNode(node: CanvasNodeState): SdkCanvasNode {
138
+ return { ...serializeCanvasNode(node), nodeId: node.id };
139
+ }
140
+
121
141
  export class PmxCanvas extends EventEmitter {
122
142
  private _port: number;
123
143
  private _server: string | null = null;
@@ -218,7 +238,7 @@ export class PmxCanvas extends EventEmitter {
218
238
  width?: number;
219
239
  height?: number;
220
240
  strictSize?: boolean;
221
- }): CanvasNodeState {
241
+ }): SdkCanvasNode {
222
242
  if (input.type === 'webpage') {
223
243
  throw new Error('Use addWebpageNode for webpage nodes so page content is fetched and cached on the server.');
224
244
  }
@@ -235,7 +255,7 @@ export class PmxCanvas extends EventEmitter {
235
255
  });
236
256
  const groupNode = canvasState.getNode(groupId);
237
257
  if (!groupNode) throw new Error(`Group node "${groupId}" was not created.`);
238
- return groupNode;
258
+ return toSdkNode(groupNode);
239
259
  }
240
260
  const { id, needsCodeGraphRecompute } = addCanvasNode({
241
261
  ...input,
@@ -271,7 +291,7 @@ export class PmxCanvas extends EventEmitter {
271
291
 
272
292
  const node = canvasState.getNode(id);
273
293
  if (!node) throw new Error(`Node "${id}" was not created.`);
274
- return node;
294
+ return toSdkNode(node);
275
295
  }
276
296
 
277
297
  async addWebpageNode(input: {
@@ -523,6 +543,22 @@ export class PmxCanvas extends EventEmitter {
523
543
  return ok;
524
544
  }
525
545
 
546
+ /** Undelivered steering for a consumer (loop-safe; excludes consumer-originated). */
547
+ getPendingSteering(options?: { consumer?: string; limit?: number }): PmxAxSteeringMessage[] {
548
+ return canvasState.getPendingSteering(options ?? {});
549
+ }
550
+
551
+ /**
552
+ * Submit a node-originated AX interaction (plan-004 Phase 1). Validates the
553
+ * envelope + node capabilities, maps the interaction onto the matching AX
554
+ * operation, and emits the outcome + state SSE events.
555
+ */
556
+ submitAxInteraction(input: AxInteractionInput, options?: { source?: PmxAxSource }): AxInteractionPublicResult {
557
+ const { result, events } = applyAxInteraction(canvasState, input, options?.source ?? 'sdk');
558
+ for (const e of events) emitPrimaryWorkbenchEvent(e.event, e.payload);
559
+ return result;
560
+ }
561
+
526
562
  getAxTimeline(query?: AxTimelineQuery): ReturnType<typeof canvasState.getAxTimeline> {
527
563
  return canvasState.getAxTimeline(query);
528
564
  }
@@ -628,6 +664,75 @@ export class PmxCanvas extends EventEmitter {
628
664
  return host;
629
665
  }
630
666
 
667
+ listElicitations(): PmxAxElicitation[] {
668
+ return canvasState.getElicitations();
669
+ }
670
+
671
+ requestElicitation(
672
+ input: { prompt: string; fields?: string[]; nodeIds?: string[] },
673
+ options?: { source?: PmxAxSource },
674
+ ): PmxAxElicitation {
675
+ const elicitation = canvasState.requestElicitation(input, { source: options?.source ?? 'sdk' });
676
+ emitPrimaryWorkbenchEvent('ax-state-changed', { elicitation });
677
+ return elicitation;
678
+ }
679
+
680
+ respondElicitation(
681
+ id: string,
682
+ response: Record<string, unknown>,
683
+ options?: { source?: PmxAxSource },
684
+ ): PmxAxElicitation | null {
685
+ const elicitation = canvasState.respondElicitation(id, response, { source: options?.source ?? 'sdk' });
686
+ if (elicitation) emitPrimaryWorkbenchEvent('ax-state-changed', { elicitation });
687
+ return elicitation;
688
+ }
689
+
690
+ listModeRequests(): PmxAxModeRequest[] {
691
+ return canvasState.getModeRequests();
692
+ }
693
+
694
+ requestMode(
695
+ input: { mode: PmxAxMode; reason?: string | null; nodeIds?: string[] },
696
+ options?: { source?: PmxAxSource },
697
+ ): PmxAxModeRequest {
698
+ const modeRequest = canvasState.requestMode(input, { source: options?.source ?? 'sdk' });
699
+ emitPrimaryWorkbenchEvent('ax-state-changed', { modeRequest });
700
+ return modeRequest;
701
+ }
702
+
703
+ resolveModeRequest(
704
+ id: string,
705
+ decision: 'approved' | 'rejected',
706
+ options?: { resolution?: string; source?: PmxAxSource },
707
+ ): PmxAxModeRequest | null {
708
+ const modeRequest = canvasState.resolveModeRequest(id, decision, { ...(options ?? {}), source: options?.source ?? 'sdk' });
709
+ if (modeRequest) emitPrimaryWorkbenchEvent('ax-state-changed', { modeRequest });
710
+ return modeRequest;
711
+ }
712
+
713
+ getCommandRegistry(): PmxAxCommandDescriptor[] {
714
+ return canvasState.getCommandRegistry();
715
+ }
716
+
717
+ invokeCommand(name: string, args?: Record<string, unknown> | null, options?: { source?: PmxAxSource }): PmxAxEvent | null {
718
+ const event = canvasState.invokeCommand(name, args ?? null, { source: options?.source ?? 'sdk' });
719
+ if (event) emitPrimaryWorkbenchEvent('ax-event-created', { event });
720
+ return event;
721
+ }
722
+
723
+ getPolicy(): PmxAxPolicy {
724
+ return canvasState.getPolicy();
725
+ }
726
+
727
+ setPolicy(
728
+ patch: { tools?: Partial<PmxAxPolicy['tools']>; prompt?: Partial<PmxAxPolicy['prompt']> },
729
+ options?: { source?: PmxAxSource },
730
+ ): PmxAxPolicy {
731
+ const policy = canvasState.setPolicy(patch, { source: options?.source ?? 'sdk' });
732
+ emitPrimaryWorkbenchEvent('ax-state-changed', { policy });
733
+ return policy;
734
+ }
735
+
631
736
  fitView(options?: {
632
737
  width?: number;
633
738
  height?: number;
@@ -644,8 +749,9 @@ export class PmxCanvas extends EventEmitter {
644
749
  return canvasState.getLayout();
645
750
  }
646
751
 
647
- getNode(id: string): CanvasNodeState | undefined {
648
- return canvasState.getNode(id);
752
+ getNode(id: string): SdkCanvasNode | undefined {
753
+ const node = canvasState.getNode(id);
754
+ return node ? toSdkNode(node) : undefined;
649
755
  }
650
756
 
651
757
  search(query: string): ReturnType<typeof searchNodes> {
@@ -936,7 +1042,7 @@ export class PmxCanvas extends EventEmitter {
936
1042
  width?: number;
937
1043
  height?: number;
938
1044
  strictSize?: boolean;
939
- }): string {
1045
+ }): SdkCanvasNode {
940
1046
  const { id } = addCanvasNode({
941
1047
  type: 'html',
942
1048
  ...(typeof input.title === 'string' ? { title: input.title } : {}),
@@ -959,7 +1065,9 @@ export class PmxCanvas extends EventEmitter {
959
1065
  defaultHeight: 640,
960
1066
  });
961
1067
  emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
962
- return id;
1068
+ const node = canvasState.getNode(id);
1069
+ if (!node) throw new Error(`HTML node "${id}" was not created.`);
1070
+ return toSdkNode(node);
963
1071
  }
964
1072
 
965
1073
  addHtmlPrimitive(input: {
@@ -1112,13 +1220,20 @@ export { traceManager } from './trace-manager.js';
1112
1220
  export type {
1113
1221
  PmxAxApprovalGate,
1114
1222
  PmxAxApprovalStatus,
1223
+ PmxAxCommandDescriptor,
1115
1224
  PmxAxContext,
1116
1225
  PmxAxEvent,
1226
+ PmxAxElicitation,
1227
+ PmxAxElicitationStatus,
1117
1228
  PmxAxEventKind,
1118
1229
  PmxAxEvidence,
1119
1230
  PmxAxEvidenceKind,
1120
1231
  PmxAxFocusState,
1121
1232
  PmxAxHostCapability,
1233
+ PmxAxMode,
1234
+ PmxAxModeRequest,
1235
+ PmxAxModeRequestStatus,
1236
+ PmxAxPolicy,
1122
1237
  PmxAxReviewAnchorType,
1123
1238
  PmxAxReviewAnnotation,
1124
1239
  PmxAxReviewKind,
@@ -35,6 +35,11 @@ export type MutationOp =
35
35
  | 'resolveApproval'
36
36
  | 'addReviewAnnotation'
37
37
  | 'updateReviewAnnotation'
38
+ | 'requestElicitation'
39
+ | 'respondElicitation'
40
+ | 'requestMode'
41
+ | 'resolveModeRequest'
42
+ | 'setPolicy'
38
43
  | 'batch'
39
44
  | 'viewport'
40
45
  | 'groupNodes'
@@ -7,7 +7,11 @@ import {
7
7
 
8
8
  export { findOpenCanvasPosition, type CanvasPlacementRect } from '../shared/placement.js';
9
9
 
10
- export const GROUP_PAD = 40;
10
+ // Margin between a group frame and the children it contains. Kept generous so
11
+ // the auto-fit frame stays visibly larger than its children — leaving room for
12
+ // the node-count/type badge in the header that would otherwise sit under the
13
+ // top-left child.
14
+ export const GROUP_PAD = 56;
11
15
  export const GROUP_TITLEBAR_HEIGHT = 32;
12
16
  const GROUP_LAYOUT_GAP_X = 32;
13
17
  const GROUP_LAYOUT_GAP_Y = 32;