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.
- package/CHANGELOG.md +219 -0
- package/Readme.md +20 -10
- package/dist/canvas/global.css +51 -56
- package/dist/canvas/index.js +80 -163
- package/dist/canvas/surface-theme.css +142 -0
- package/dist/json-render/index.js +103 -103
- package/dist/types/client/nodes/HtmlNode.d.ts +0 -7
- package/dist/types/client/nodes/ax-node-actions.d.ts +18 -0
- package/dist/types/client/nodes/surface-url.d.ts +22 -0
- package/dist/types/client/state/attention-bridge.d.ts +3 -0
- package/dist/types/client/state/intent-bridge.d.ts +17 -0
- package/dist/types/json-render/renderer/index.d.ts +2 -0
- package/dist/types/json-render/schema.d.ts +2 -0
- package/dist/types/json-render/server.d.ts +2 -0
- package/dist/types/mcp/canvas-access.d.ts +47 -0
- package/dist/types/server/ax-interaction.d.ts +210 -0
- package/dist/types/server/ax-state.d.ts +67 -1
- package/dist/types/server/canvas-db.d.ts +4 -0
- package/dist/types/server/canvas-serialization.d.ts +2 -0
- package/dist/types/server/canvas-state.d.ts +54 -2
- package/dist/types/server/html-surface.d.ts +46 -0
- package/dist/types/server/index.d.ts +63 -5
- package/dist/types/server/mutation-history.d.ts +1 -1
- package/dist/types/server/placement.d.ts +1 -1
- package/dist/types/server/server.d.ts +12 -0
- package/dist/types/shared/surface.d.ts +19 -0
- package/docs/cli.md +30 -0
- package/docs/http-api.md +55 -0
- package/docs/mcp.md +40 -2
- package/docs/node-types.md +26 -0
- package/docs/plans/plan-004-pmx-ax-primitives.md +623 -394
- package/docs/sdk.md +23 -1
- package/package.json +2 -2
- package/skills/pmx-canvas/SKILL.md +107 -9
- package/src/cli/agent.ts +177 -0
- package/src/cli/index.ts +8 -1
- package/src/client/canvas/CanvasNode.tsx +8 -4
- package/src/client/canvas/DockedNode.tsx +38 -38
- package/src/client/canvas/ExpandedNodeOverlay.tsx +12 -0
- package/src/client/nodes/ContextNode.tsx +17 -0
- package/src/client/nodes/ExtAppFrame.tsx +40 -3
- package/src/client/nodes/FileNode.tsx +26 -0
- package/src/client/nodes/HtmlNode.tsx +60 -188
- package/src/client/nodes/McpAppNode.tsx +47 -2
- package/src/client/nodes/StatusNode.tsx +20 -0
- package/src/client/nodes/ax-node-actions.ts +39 -0
- package/src/client/nodes/surface-url.ts +48 -0
- package/src/client/state/attention-bridge.ts +5 -0
- package/src/client/state/intent-bridge.ts +33 -0
- package/src/client/theme/global.css +51 -56
- package/src/client/theme/surface-theme.css +142 -0
- package/src/json-render/renderer/index.tsx +31 -0
- package/src/json-render/schema.ts +4 -0
- package/src/json-render/server.ts +13 -0
- package/src/mcp/canvas-access.ts +198 -1
- package/src/mcp/server.ts +232 -2
- package/src/server/ax-context.ts +3 -0
- package/src/server/ax-interaction.ts +549 -0
- package/src/server/ax-state.ts +188 -2
- package/src/server/canvas-db.ts +20 -0
- package/src/server/canvas-operations.ts +11 -0
- package/src/server/canvas-serialization.ts +9 -0
- package/src/server/canvas-state.ts +201 -26
- package/src/server/html-surface.ts +190 -0
- package/src/server/index.ts +122 -7
- package/src/server/mutation-history.ts +5 -0
- package/src/server/placement.ts +5 -1
- package/src/server/server.ts +360 -0
- 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, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
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
|
+
}
|
package/src/server/index.ts
CHANGED
|
@@ -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
|
-
}):
|
|
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):
|
|
648
|
-
|
|
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
|
-
}):
|
|
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
|
-
|
|
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'
|
package/src/server/placement.ts
CHANGED
|
@@ -7,7 +7,11 @@ import {
|
|
|
7
7
|
|
|
8
8
|
export { findOpenCanvasPosition, type CanvasPlacementRect } from '../shared/placement.js';
|
|
9
9
|
|
|
10
|
-
|
|
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;
|