pmx-canvas 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +38 -0
- package/dist/canvas/index.js +2 -2
- package/dist/types/cli/daemon.d.ts +74 -0
- package/dist/types/cli/watch.d.ts +2 -2
- package/dist/types/client/canvas/CanvasViewport.d.ts +1 -1
- package/dist/types/client/canvas/CommandPalette.d.ts +1 -1
- package/dist/types/client/canvas/Minimap.d.ts +1 -1
- package/dist/types/client/nodes/FileNode.d.ts +1 -1
- package/dist/types/client/nodes/ImageNode.d.ts +1 -1
- package/dist/types/client/nodes/InlineFormatBar.d.ts +1 -1
- package/dist/types/client/nodes/MarkdownNode.d.ts +1 -1
- package/dist/types/client/nodes/PromptNode.d.ts +1 -1
- package/dist/types/client/nodes/ResponseNode.d.ts +1 -1
- package/dist/types/server/canvas-schema.d.ts +1 -1
- package/dist/types/server/html-primitives.d.ts +1 -1
- package/dist/types/server/index.d.ts +4 -4
- package/dist/types/server/operations/index.d.ts +1 -1
- package/dist/types/server/operations/ops/ax-read.d.ts +2 -0
- package/dist/types/server/operations/ops/canvas-wire.d.ts +2 -0
- package/dist/types/server/operations/ops/ext-app.d.ts +2 -0
- package/docs/http-api.md +28 -0
- package/docs/plans/plan-009-tech-debt-backlog.md +5 -5
- package/docs/tech-debt-assessment-2026-07.md +2 -2
- package/package.json +5 -2
- package/skills/pmx-canvas/SKILL.md +3 -1
- package/skills/pmx-canvas/references/full-reference.md +10 -3
- package/src/cli/agent.ts +1861 -1548
- package/src/cli/daemon.ts +460 -0
- package/src/cli/index.ts +63 -326
- package/src/cli/watch.ts +2 -10
- package/src/client/App.tsx +48 -46
- package/src/client/canvas/AttentionHistory.tsx +11 -1
- package/src/client/canvas/CanvasNode.tsx +41 -29
- package/src/client/canvas/CanvasViewport.tsx +101 -66
- package/src/client/canvas/CommandPalette.tsx +61 -27
- package/src/client/canvas/ContextMenu.tsx +13 -20
- package/src/client/canvas/ContextPinBar.tsx +1 -5
- package/src/client/canvas/ContextPinHud.tsx +1 -6
- package/src/client/canvas/DockedNode.tsx +4 -4
- package/src/client/canvas/EdgeLayer.tsx +37 -36
- package/src/client/canvas/ExpandedNodeOverlay.tsx +69 -45
- package/src/client/canvas/FocusFieldLayer.tsx +20 -22
- package/src/client/canvas/IntentLayer.tsx +31 -14
- package/src/client/canvas/Minimap.tsx +11 -16
- package/src/client/canvas/SelectionBar.tsx +4 -11
- package/src/client/canvas/ShortcutOverlay.tsx +3 -1
- package/src/client/canvas/SnapshotPanel.tsx +77 -95
- package/src/client/canvas/auto-fit.ts +15 -14
- package/src/client/canvas/snap-guides.ts +12 -12
- package/src/client/canvas/use-node-resize.ts +1 -5
- package/src/client/canvas/use-pan-zoom.ts +25 -26
- package/src/client/ext-app/bridge.ts +3 -12
- package/src/client/icons.tsx +63 -20
- package/src/client/nodes/ContextNode.tsx +14 -25
- package/src/client/nodes/ExtAppFrame.tsx +60 -39
- package/src/client/nodes/FileNode.tsx +74 -62
- package/src/client/nodes/GroupNode.tsx +4 -6
- package/src/client/nodes/HtmlNode.tsx +76 -46
- package/src/client/nodes/ImageNode.tsx +18 -27
- package/src/client/nodes/InlineFormatBar.tsx +4 -21
- package/src/client/nodes/InlineMarkdownEditor.tsx +0 -1
- package/src/client/nodes/LedgerNode.tsx +10 -4
- package/src/client/nodes/MarkdownNode.tsx +3 -10
- package/src/client/nodes/McpAppNode.tsx +26 -22
- package/src/client/nodes/MdFormatBar.tsx +10 -7
- package/src/client/nodes/PromptNode.tsx +23 -51
- package/src/client/nodes/ResponseNode.tsx +3 -13
- package/src/client/nodes/StatusNode.tsx +5 -9
- package/src/client/nodes/StatusSummary.tsx +2 -8
- package/src/client/nodes/WebpageNode.tsx +20 -14
- package/src/client/nodes/iframe-document-url.ts +25 -16
- package/src/client/nodes/image-warnings.ts +1 -7
- package/src/client/nodes/md-format.ts +20 -5
- package/src/client/state/attention-bridge.ts +4 -9
- package/src/client/state/attention-store.ts +1 -7
- package/src/client/state/canvas-store.ts +52 -36
- package/src/client/state/intent-bridge.ts +176 -112
- package/src/client/state/intent-store.ts +4 -1
- package/src/client/state/sse-bridge.ts +53 -70
- package/src/json-render/catalog.ts +12 -16
- package/src/json-render/charts/components.tsx +16 -20
- package/src/json-render/charts/extra-components.tsx +8 -16
- package/src/json-render/charts/extra-definitions.ts +1 -2
- package/src/json-render/charts/tufte-components.tsx +37 -20
- package/src/json-render/charts/tufte-definitions.ts +8 -2
- package/src/json-render/renderer/index.tsx +42 -22
- package/src/json-render/schema.ts +6 -3
- package/src/json-render/server.ts +33 -39
- package/src/mcp/canvas-access.ts +35 -21
- package/src/mcp/server.ts +132 -70
- package/src/server/agent-context.ts +63 -36
- package/src/server/ax-context.ts +7 -5
- package/src/server/ax-interaction.ts +176 -43
- package/src/server/ax-state-manager.ts +182 -39
- package/src/server/ax-state.ts +142 -47
- package/src/server/canvas-db.ts +213 -95
- package/src/server/canvas-operations.ts +177 -120
- package/src/server/canvas-provenance.ts +1 -4
- package/src/server/canvas-schema.ts +454 -73
- package/src/server/canvas-serialization.ts +27 -35
- package/src/server/canvas-state.ts +150 -58
- package/src/server/chart-template.ts +4 -5
- package/src/server/code-graph.ts +19 -6
- package/src/server/diagram-presets.ts +28 -29
- package/src/server/ext-app-lookup.ts +3 -12
- package/src/server/html-node-summary.ts +19 -10
- package/src/server/html-primitives.ts +326 -97
- package/src/server/html-surface.ts +6 -9
- package/src/server/image-source.ts +6 -4
- package/src/server/index.ts +320 -217
- package/src/server/intent-registry.ts +2 -5
- package/src/server/mcp-app-candidate.ts +5 -10
- package/src/server/mcp-app-host.ts +14 -38
- package/src/server/mcp-app-runtime.ts +12 -20
- package/src/server/mutation-history.ts +15 -5
- package/src/server/operations/composites.ts +1 -3
- package/src/server/operations/http.ts +2 -3
- package/src/server/operations/index.ts +7 -1
- package/src/server/operations/invoker.ts +4 -3
- package/src/server/operations/mcp.ts +22 -30
- package/src/server/operations/ops/annotation.ts +122 -10
- package/src/server/operations/ops/app.ts +98 -73
- package/src/server/operations/ops/ax-await.ts +17 -10
- package/src/server/operations/ops/ax-read.ts +347 -0
- package/src/server/operations/ops/ax-shared.ts +2 -7
- package/src/server/operations/ops/ax-state.ts +32 -14
- package/src/server/operations/ops/ax-timeline.ts +32 -19
- package/src/server/operations/ops/ax-work.ts +54 -37
- package/src/server/operations/ops/batch.ts +39 -14
- package/src/server/operations/ops/canvas-wire.ts +91 -0
- package/src/server/operations/ops/edges.ts +37 -25
- package/src/server/operations/ops/ext-app.ts +346 -0
- package/src/server/operations/ops/groups.ts +49 -20
- package/src/server/operations/ops/intent.ts +18 -12
- package/src/server/operations/ops/json-render.ts +239 -98
- package/src/server/operations/ops/nodes.ts +298 -109
- package/src/server/operations/ops/query.ts +46 -28
- package/src/server/operations/ops/snapshots.ts +35 -26
- package/src/server/operations/ops/validate.ts +2 -1
- package/src/server/operations/ops/viewport.ts +60 -16
- package/src/server/operations/ops/webview.ts +44 -18
- package/src/server/operations/registry.ts +2 -3
- package/src/server/operations/types.ts +7 -5
- package/src/server/operations/webview-runner.ts +1 -3
- package/src/server/placement.ts +8 -18
- package/src/server/server.ts +108 -1027
- package/src/server/spatial-analysis.ts +39 -25
- package/src/server/trace-manager.ts +3 -8
- package/src/server/web-artifacts.ts +23 -27
- package/src/server/webpage-node.ts +5 -13
- package/src/shared/auto-arrange.ts +12 -5
- package/src/shared/content-height-reporter.ts +8 -6
- package/src/shared/ext-app-tool-result.ts +2 -6
- package/src/shared/placement.ts +1 -4
- package/src/shared/semantic-attention.ts +39 -37
- package/src/shared/surface.ts +8 -4
|
@@ -9,12 +9,7 @@
|
|
|
9
9
|
* This module must never import server.ts or index.ts.
|
|
10
10
|
*/
|
|
11
11
|
import { z } from 'zod';
|
|
12
|
-
import {
|
|
13
|
-
canvasState,
|
|
14
|
-
type CanvasAnnotation,
|
|
15
|
-
type CanvasLayout,
|
|
16
|
-
type CanvasNodeState,
|
|
17
|
-
} from '../../canvas-state.js';
|
|
12
|
+
import { canvasState, type CanvasAnnotation, type CanvasLayout, type CanvasNodeState } from '../../canvas-state.js';
|
|
18
13
|
import {
|
|
19
14
|
addCanvasNode,
|
|
20
15
|
createCanvasGroup,
|
|
@@ -122,18 +117,22 @@ function normalizeGeometryInput(body: Record<string, unknown>): {
|
|
|
122
117
|
...(pickFiniteNumber(body, 'y') !== undefined ? { y: pickFiniteNumber(body, 'y') } : {}),
|
|
123
118
|
...(pickFiniteNumber(body, 'width') !== undefined ? { width: pickFiniteNumber(body, 'width') } : {}),
|
|
124
119
|
...(pickFiniteNumber(body, 'height') !== undefined ? { height: pickFiniteNumber(body, 'height') } : {}),
|
|
125
|
-
...(position
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
120
|
+
...(position
|
|
121
|
+
? {
|
|
122
|
+
position: {
|
|
123
|
+
...(pickFiniteNumber(position, 'x') !== undefined ? { x: pickFiniteNumber(position, 'x') } : {}),
|
|
124
|
+
...(pickFiniteNumber(position, 'y') !== undefined ? { y: pickFiniteNumber(position, 'y') } : {}),
|
|
125
|
+
},
|
|
126
|
+
}
|
|
127
|
+
: {}),
|
|
128
|
+
...(size
|
|
129
|
+
? {
|
|
130
|
+
size: {
|
|
131
|
+
...(pickFiniteNumber(size, 'width') !== undefined ? { width: pickFiniteNumber(size, 'width') } : {}),
|
|
132
|
+
...(pickFiniteNumber(size, 'height') !== undefined ? { height: pickFiniteNumber(size, 'height') } : {}),
|
|
133
|
+
},
|
|
134
|
+
}
|
|
135
|
+
: {}),
|
|
137
136
|
};
|
|
138
137
|
}
|
|
139
138
|
|
|
@@ -217,8 +216,9 @@ export function setGroupChildrenFromApi(groupId: string, childIds: string[]): bo
|
|
|
217
216
|
const dataChildIds = Array.isArray(group.data.children)
|
|
218
217
|
? group.data.children.filter((id): id is string => typeof id === 'string')
|
|
219
218
|
: [];
|
|
220
|
-
const parentBackrefIds = canvasState
|
|
221
|
-
.
|
|
219
|
+
const parentBackrefIds = canvasState
|
|
220
|
+
.getLayout()
|
|
221
|
+
.nodes.filter((node) => node.id !== groupId && node.data.parentGroup === groupId)
|
|
222
222
|
.map((node) => node.id);
|
|
223
223
|
const currentChildIds = [...new Set([...dataChildIds, ...parentBackrefIds])];
|
|
224
224
|
if (currentChildIds.length > 0) {
|
|
@@ -310,7 +310,9 @@ export function buildSummaryFromLayout(layout: CanvasLayout, pinnedIds: string[]
|
|
|
310
310
|
totalNodes: layout.nodes.length,
|
|
311
311
|
totalEdges: layout.edges.length,
|
|
312
312
|
totalAnnotations: (layout.annotations ?? []).length,
|
|
313
|
-
annotations: (layout.annotations ?? []).map((annotation: CanvasAnnotation) =>
|
|
313
|
+
annotations: (layout.annotations ?? []).map((annotation: CanvasAnnotation) =>
|
|
314
|
+
summarizeCanvasAnnotationForContext(annotation, layout.nodes),
|
|
315
|
+
),
|
|
314
316
|
nodesByType,
|
|
315
317
|
pinnedCount: pinned.size,
|
|
316
318
|
pinnedTitles,
|
|
@@ -322,8 +324,12 @@ export function compactLayoutPayload(layout: CanvasLayout, pinnedIds: string[]):
|
|
|
322
324
|
return {
|
|
323
325
|
summary: buildSummaryFromLayout(layout, pinnedIds),
|
|
324
326
|
viewport: layout.viewport,
|
|
325
|
-
annotations: (layout.annotations ?? []).map((annotation) =>
|
|
326
|
-
|
|
327
|
+
annotations: (layout.annotations ?? []).map((annotation) =>
|
|
328
|
+
summarizeCanvasAnnotationForContext(annotation, layout.nodes),
|
|
329
|
+
),
|
|
330
|
+
nodes: layout.nodes
|
|
331
|
+
.map((node) => compactNodePayload(node))
|
|
332
|
+
.filter((node): node is Record<string, unknown> => node !== null),
|
|
327
333
|
edges: layout.edges.map((edge) => ({
|
|
328
334
|
id: edge.id,
|
|
329
335
|
from: edge.from,
|
|
@@ -339,7 +345,9 @@ export function compactLayoutPayload(layout: CanvasLayout, pinnedIds: string[]):
|
|
|
339
345
|
export function agentSafeFullLayoutPayload(layout: CanvasLayout): Record<string, unknown> {
|
|
340
346
|
return {
|
|
341
347
|
...serializeCanvasLayoutForAgent(layout),
|
|
342
|
-
annotations: (layout.annotations ?? []).map((annotation) =>
|
|
348
|
+
annotations: (layout.annotations ?? []).map((annotation) =>
|
|
349
|
+
summarizeCanvasAnnotationForContext(annotation, layout.nodes),
|
|
350
|
+
),
|
|
343
351
|
};
|
|
344
352
|
}
|
|
345
353
|
|
|
@@ -348,7 +356,10 @@ export function agentSafeFullLayoutPayload(layout: CanvasLayout): Record<string,
|
|
|
348
356
|
* agents using either key (or a cached schema) work — matching the
|
|
349
357
|
* external-app / web-artifact responses that already return both.
|
|
350
358
|
*/
|
|
351
|
-
export function createdNodePayloadFromNode(
|
|
359
|
+
export function createdNodePayloadFromNode(
|
|
360
|
+
node: CanvasNodeState,
|
|
361
|
+
options: Record<string, unknown> = {},
|
|
362
|
+
): Record<string, unknown> {
|
|
352
363
|
if (!wantsFullPayload(options)) {
|
|
353
364
|
return { ok: true, node: compactNodePayload(node), id: node.id, nodeId: node.id };
|
|
354
365
|
}
|
|
@@ -377,28 +388,28 @@ export function createBasicCanvasNode(
|
|
|
377
388
|
throw new OperationError('HTML node field "data.html" must be a string.');
|
|
378
389
|
}
|
|
379
390
|
}
|
|
380
|
-
const content =
|
|
381
|
-
? body.path
|
|
382
|
-
: body.content;
|
|
391
|
+
const content =
|
|
392
|
+
type === 'image' && typeof body.path === 'string' && typeof body.content !== 'string' ? body.path : body.content;
|
|
383
393
|
// For html nodes, accept top-level `html` AND `axCapabilities` and merge into data
|
|
384
394
|
// so callers can POST { type: 'html', title, html, axCapabilities } without nesting
|
|
385
395
|
// under `data` (report #53 — transport parity with MCP canvas_add_html_node). A
|
|
386
396
|
// top-level value overrides the same key under `data` (mirrors the `html` precedence).
|
|
387
397
|
const topAxCapabilities = type === 'html' ? normalizeNodeAxCapabilities(body.axCapabilities) : null;
|
|
388
|
-
const htmlMergedData =
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
398
|
+
const htmlMergedData =
|
|
399
|
+
type === 'html'
|
|
400
|
+
? {
|
|
401
|
+
...(extraData ?? {}),
|
|
402
|
+
...(typeof body.html === 'string' ? { html: resolveHtmlContent(body.html) } : {}),
|
|
403
|
+
...(typeof body.summary === 'string' ? { summary: body.summary } : {}),
|
|
404
|
+
...(typeof body.agentSummary === 'string' ? { agentSummary: body.agentSummary } : {}),
|
|
405
|
+
...(typeof body.description === 'string' ? { description: body.description } : {}),
|
|
406
|
+
...(body.presentation === true ? { presentation: true } : {}),
|
|
407
|
+
...(Array.isArray(body.slideTitles) ? { slideTitles: body.slideTitles } : {}),
|
|
408
|
+
...(Array.isArray(body.embeddedNodeIds) ? { embeddedNodeIds: body.embeddedNodeIds } : {}),
|
|
409
|
+
...(Array.isArray(body.embeddedUrls) ? { embeddedUrls: body.embeddedUrls } : {}),
|
|
410
|
+
...(topAxCapabilities ? { axCapabilities: topAxCapabilities } : {}),
|
|
411
|
+
}
|
|
412
|
+
: extraData;
|
|
402
413
|
const geometry = resolveCreateGeometry(body);
|
|
403
414
|
const defaults = defaultNodeSize(type);
|
|
404
415
|
try {
|
|
@@ -494,11 +505,12 @@ export function buildNodePatch(
|
|
|
494
505
|
const patchAxCapabilities = normalizeNodeAxCapabilities(body.axCapabilities);
|
|
495
506
|
if (patchAxCapabilities) data.axCapabilities = patchAxCapabilities;
|
|
496
507
|
if (existing.type === 'webpage') {
|
|
497
|
-
const nextUrl =
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
508
|
+
const nextUrl =
|
|
509
|
+
typeof body.url === 'string'
|
|
510
|
+
? body.url
|
|
511
|
+
: typeof (body.data as Record<string, unknown> | undefined)?.url === 'string'
|
|
512
|
+
? ((body.data as Record<string, unknown>).url as string)
|
|
513
|
+
: undefined;
|
|
502
514
|
if (typeof nextUrl === 'string' && nextUrl.trim().length > 0) {
|
|
503
515
|
try {
|
|
504
516
|
data.url = normalizeWebpageUrl(nextUrl);
|
|
@@ -507,9 +519,7 @@ export function buildNodePatch(
|
|
|
507
519
|
}
|
|
508
520
|
}
|
|
509
521
|
}
|
|
510
|
-
patch.data = existing.type === 'trace'
|
|
511
|
-
? mergeTraceNodeDataFields(data, body)
|
|
512
|
-
: data;
|
|
522
|
+
patch.data = existing.type === 'trace' ? mergeTraceNodeDataFields(data, body) : data;
|
|
513
523
|
}
|
|
514
524
|
const error = validateCanvasNodePatch({
|
|
515
525
|
...(patch.position ? { position: patch.position as { x: number; y: number } } : {}),
|
|
@@ -543,11 +553,12 @@ interface NodeAddResult {
|
|
|
543
553
|
}
|
|
544
554
|
|
|
545
555
|
async function createWebpageNode(body: Record<string, unknown>, ctx: OperationContext): Promise<NodeAddResult> {
|
|
546
|
-
const rawUrl =
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
556
|
+
const rawUrl =
|
|
557
|
+
typeof body.url === 'string' && body.url.trim().length > 0
|
|
558
|
+
? body.url
|
|
559
|
+
: typeof body.content === 'string'
|
|
560
|
+
? body.content
|
|
561
|
+
: '';
|
|
551
562
|
|
|
552
563
|
let normalizedUrl: string;
|
|
553
564
|
try {
|
|
@@ -577,9 +588,7 @@ async function createWebpageNode(body: Record<string, unknown>, ctx: OperationCo
|
|
|
577
588
|
return {
|
|
578
589
|
node: created,
|
|
579
590
|
extras: {
|
|
580
|
-
fetch: refreshed.ok
|
|
581
|
-
? { ok: true }
|
|
582
|
-
: { ok: false, error: refreshed.error ?? 'Failed to fetch webpage content.' },
|
|
591
|
+
fetch: refreshed.ok ? { ok: true } : { ok: false, error: refreshed.error ?? 'Failed to fetch webpage content.' },
|
|
583
592
|
...(refreshed.ok ? {} : { error: refreshed.error }),
|
|
584
593
|
},
|
|
585
594
|
};
|
|
@@ -639,9 +648,10 @@ function createGroupNode(body: Record<string, unknown>): NodeAddResult {
|
|
|
639
648
|
const childIds = childList.value ?? [];
|
|
640
649
|
const childError = validateGroupChildIds('', childIds);
|
|
641
650
|
if (childError) throw new OperationError(`Cannot create group: ${childError}`);
|
|
642
|
-
const childLayout =
|
|
643
|
-
|
|
644
|
-
|
|
651
|
+
const childLayout =
|
|
652
|
+
body.childLayout === 'grid' || body.childLayout === 'column' || body.childLayout === 'flow'
|
|
653
|
+
? body.childLayout
|
|
654
|
+
: undefined;
|
|
645
655
|
const { node } = createCanvasGroup({
|
|
646
656
|
...(typeof body.title === 'string' ? { title: body.title } : {}),
|
|
647
657
|
childIds,
|
|
@@ -653,20 +663,54 @@ function createGroupNode(body: Record<string, unknown>): NodeAddResult {
|
|
|
653
663
|
}
|
|
654
664
|
|
|
655
665
|
const nodeAddShape = {
|
|
656
|
-
intentId: z
|
|
666
|
+
intentId: z
|
|
667
|
+
.string()
|
|
668
|
+
.optional()
|
|
669
|
+
.catch(undefined)
|
|
670
|
+
.describe('Ghost intent id returned by canvas_intent signal. A vetoed or expired intent blocks this mutation.'),
|
|
657
671
|
type: z.string().optional().catch(undefined).describe('Node type (prefer canvas_group {action:"create"} for groups)'),
|
|
658
672
|
title: z.string().optional().catch(undefined).describe('Node title'),
|
|
659
|
-
content: z
|
|
660
|
-
|
|
661
|
-
|
|
673
|
+
content: z
|
|
674
|
+
.string()
|
|
675
|
+
.optional()
|
|
676
|
+
.catch(undefined)
|
|
677
|
+
.describe(
|
|
678
|
+
'Node content (markdown for markdown nodes, file path for file nodes, image path/URL/data-URI for image nodes, URL for webpage nodes)',
|
|
679
|
+
),
|
|
680
|
+
path: z
|
|
681
|
+
.string()
|
|
682
|
+
.optional()
|
|
683
|
+
.catch(undefined)
|
|
684
|
+
.describe('Compatibility alias for image node content. Prefer content for image paths.'),
|
|
685
|
+
url: z
|
|
686
|
+
.string()
|
|
687
|
+
.optional()
|
|
688
|
+
.catch(undefined)
|
|
689
|
+
.describe('Canonical webpage URL field for webpage nodes. Overrides content when both are provided.'),
|
|
662
690
|
x: z.number().optional().catch(undefined).describe('X position (auto-placed if omitted)'),
|
|
663
691
|
y: z.number().optional().catch(undefined).describe('Y position (auto-placed if omitted)'),
|
|
664
692
|
width: z.number().optional().catch(undefined).describe('Width in pixels (default: 720)'),
|
|
665
693
|
height: z.number().optional().catch(undefined).describe('Height in pixels (default: 600)'),
|
|
666
|
-
strictSize: z
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
694
|
+
strictSize: z
|
|
695
|
+
.boolean()
|
|
696
|
+
.optional()
|
|
697
|
+
.catch(undefined)
|
|
698
|
+
.describe('Keep explicit width/height fixed and scroll overflowing content instead of browser auto-fitting'),
|
|
699
|
+
children: z
|
|
700
|
+
.unknown()
|
|
701
|
+
.optional()
|
|
702
|
+
.describe('Group-only alias for childIds. Node IDs to include in a generic group node.'),
|
|
703
|
+
childIds: z
|
|
704
|
+
.unknown()
|
|
705
|
+
.optional()
|
|
706
|
+
.describe(
|
|
707
|
+
'Group-only field. Node IDs to include in a generic group node. Prefer canvas_group {action:"create"} for groups.',
|
|
708
|
+
),
|
|
709
|
+
childLayout: z
|
|
710
|
+
.enum(['grid', 'column', 'flow'])
|
|
711
|
+
.optional()
|
|
712
|
+
.catch(undefined)
|
|
713
|
+
.describe('Group-only optional layout for grouped children.'),
|
|
670
714
|
color: z.string().optional().catch(undefined).describe('Group-only frame accent color.'),
|
|
671
715
|
toolName: z.string().optional().catch(undefined).describe('Trace node tool or operation label'),
|
|
672
716
|
category: z.string().optional().catch(undefined).describe('Trace node category: mcp, file, subagent, or other'),
|
|
@@ -680,12 +724,25 @@ const nodeAddShape = {
|
|
|
680
724
|
kind: z.unknown().optional().describe('Alias for primitive.'),
|
|
681
725
|
summary: z.string().optional().catch(undefined).describe('Agent-readable semantic summary (html nodes).'),
|
|
682
726
|
agentSummary: z.string().optional().catch(undefined).describe('Explicit agent-readable summary (html nodes).'),
|
|
683
|
-
description: z
|
|
684
|
-
|
|
727
|
+
description: z
|
|
728
|
+
.string()
|
|
729
|
+
.optional()
|
|
730
|
+
.catch(undefined)
|
|
731
|
+
.describe('Short description included in search and pinned/spatial context (html nodes).'),
|
|
732
|
+
presentation: z
|
|
733
|
+
.boolean()
|
|
734
|
+
.optional()
|
|
735
|
+
.catch(undefined)
|
|
736
|
+
.describe('Marks an html node as a fullscreen presentation/deck.'),
|
|
685
737
|
slideTitles: z.unknown().optional().describe('Agent-readable slide titles for presentation HTML.'),
|
|
686
738
|
embeddedNodeIds: z.unknown().optional().describe('Canvas node IDs embedded or represented by this HTML surface.'),
|
|
687
739
|
embeddedUrls: z.unknown().optional().describe('URLs embedded or represented by this HTML surface.'),
|
|
688
|
-
axCapabilities: z
|
|
740
|
+
axCapabilities: z
|
|
741
|
+
.unknown()
|
|
742
|
+
.optional()
|
|
743
|
+
.describe(
|
|
744
|
+
'Opt an html node into AX interactions. Merged into node data for html nodes; clamped to the node-type ceiling server-side.',
|
|
745
|
+
),
|
|
689
746
|
position: z.unknown().optional().describe('Geometry alias: { x, y } object form.'),
|
|
690
747
|
size: z.unknown().optional().describe('Geometry alias: { width, height } object form.'),
|
|
691
748
|
};
|
|
@@ -693,7 +750,10 @@ const nodeAddShape = {
|
|
|
693
750
|
const nodeAddSchema = z.looseObject(nodeAddShape);
|
|
694
751
|
|
|
695
752
|
const fullVerboseShape = {
|
|
696
|
-
full: z
|
|
753
|
+
full: z
|
|
754
|
+
.boolean()
|
|
755
|
+
.optional()
|
|
756
|
+
.describe('Return the full created node payload. Default false returns compact metadata.'),
|
|
697
757
|
verbose: z.boolean().optional().describe('Alias for full:true.'),
|
|
698
758
|
};
|
|
699
759
|
|
|
@@ -708,22 +768,50 @@ const nodeAddOperation = defineOperation<z.infer<typeof nodeAddSchema>, NodeAddR
|
|
|
708
768
|
},
|
|
709
769
|
mcp: {
|
|
710
770
|
toolName: 'canvas_add_node',
|
|
711
|
-
description:
|
|
771
|
+
description:
|
|
772
|
+
'Add a basic node to the canvas. Returns the created node with normalized title/content and rendered geometry. Supported here: markdown, status, context, ledger, trace, file, image, webpage, mcp-app, html, group. Dedicated routes: json-render -> canvas_render {action:"add-json-render"}, graph -> canvas_render {action:"add-graph"}, web-artifact -> canvas_app {action:"build-artifact"}, external apps -> canvas_app {action:"open-mcp-app"}, groups -> canvas_group {action:"create"}. Call canvas_render {action:"describe-schema"} for the full nodeTypeRouting table.',
|
|
712
773
|
extraShape: {
|
|
713
|
-
type: z
|
|
774
|
+
type: z
|
|
775
|
+
.enum([
|
|
776
|
+
'markdown',
|
|
777
|
+
'status',
|
|
778
|
+
'context',
|
|
779
|
+
'ledger',
|
|
780
|
+
'trace',
|
|
781
|
+
'file',
|
|
782
|
+
'image',
|
|
783
|
+
'webpage',
|
|
784
|
+
'mcp-app',
|
|
785
|
+
'html',
|
|
786
|
+
'group',
|
|
787
|
+
])
|
|
714
788
|
.describe('Node type (prefer canvas_group {action:"create"} for groups)'),
|
|
715
|
-
children: z
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
789
|
+
children: z
|
|
790
|
+
.array(z.string())
|
|
791
|
+
.optional()
|
|
792
|
+
.describe('Group-only alias for childIds. Node IDs to include in a generic group node.'),
|
|
793
|
+
childIds: z
|
|
794
|
+
.array(z.string())
|
|
795
|
+
.optional()
|
|
796
|
+
.describe(
|
|
797
|
+
'Group-only field. Node IDs to include in a generic group node. Prefer canvas_group {action:"create"} for groups.',
|
|
798
|
+
),
|
|
799
|
+
axCapabilities: z
|
|
800
|
+
.object({
|
|
801
|
+
enabled: z.boolean().optional(),
|
|
802
|
+
allowed: z.array(z.string()).optional(),
|
|
803
|
+
})
|
|
804
|
+
.optional()
|
|
805
|
+
.describe(
|
|
806
|
+
'Opt an html node into AX interactions (e.g. { enabled: true, allowed: ["ax.work.create"] }) so its sandboxed UI can emit ax.* via window.PMX_AX.emit. html nodes are AX-disabled by default; merged into node data, clamped to the node-type ceiling server-side.',
|
|
807
|
+
),
|
|
721
808
|
...fullVerboseShape,
|
|
722
809
|
},
|
|
723
810
|
buildInput: (input) => {
|
|
724
811
|
if (input.type === 'webpage') {
|
|
725
|
-
const url =
|
|
726
|
-
|
|
812
|
+
const url =
|
|
813
|
+
(typeof input.url === 'string' ? input.url : undefined) ??
|
|
814
|
+
(typeof input.content === 'string' ? input.content : undefined);
|
|
727
815
|
if (!url) {
|
|
728
816
|
throw new OperationError('Webpage nodes require a page URL via "url" (preferred) or "content".');
|
|
729
817
|
}
|
|
@@ -755,13 +843,19 @@ const nodeAddOperation = defineOperation<z.infer<typeof nodeAddSchema>, NodeAddR
|
|
|
755
843
|
}
|
|
756
844
|
if (!NODE_TYPE_SET.has(type)) {
|
|
757
845
|
if (type === 'json-render') {
|
|
758
|
-
throw new OperationError(
|
|
846
|
+
throw new OperationError(
|
|
847
|
+
'Node type "json-render" is created via POST /api/canvas/json-render. See /api/canvas/schema for the required spec shape.',
|
|
848
|
+
);
|
|
759
849
|
}
|
|
760
850
|
if (type === 'graph') {
|
|
761
|
-
throw new OperationError(
|
|
851
|
+
throw new OperationError(
|
|
852
|
+
'Node type "graph" is created via POST /api/canvas/graph. See /api/canvas/schema for graphType + data fields.',
|
|
853
|
+
);
|
|
762
854
|
}
|
|
763
855
|
if (type === 'web-artifact') {
|
|
764
|
-
throw new OperationError(
|
|
856
|
+
throw new OperationError(
|
|
857
|
+
'Node type "web-artifact" is created via POST /api/canvas/web-artifact with appTsx + title.',
|
|
858
|
+
);
|
|
765
859
|
}
|
|
766
860
|
if (type === 'html-primitive') {
|
|
767
861
|
return createHtmlPrimitiveNode(body);
|
|
@@ -808,7 +902,8 @@ const nodeGetOperation = defineOperation<z.infer<typeof nodeGetSchema>, Serializ
|
|
|
808
902
|
},
|
|
809
903
|
mcp: {
|
|
810
904
|
toolName: 'canvas_get_node',
|
|
811
|
-
description:
|
|
905
|
+
description:
|
|
906
|
+
'Get a single node by ID. Defaults to compact metadata; pass full:true to include full data/tool results.',
|
|
812
907
|
extraShape: {
|
|
813
908
|
full: z.boolean().optional().describe('Include full node data, including mcp-app tool results. Default false.'),
|
|
814
909
|
verbose: z.boolean().optional().describe('Alias for full:true.'),
|
|
@@ -825,9 +920,7 @@ const nodeGetOperation = defineOperation<z.infer<typeof nodeGetSchema>, Serializ
|
|
|
825
920
|
const node = full ? canvasState.getNode(id) : canvasState.getNodeForPersistence(id);
|
|
826
921
|
if (!node) throw new OperationError(`Node "${id}" not found.`, 404);
|
|
827
922
|
const responseNode = withContextPinReadState(node);
|
|
828
|
-
return full
|
|
829
|
-
? serializeCanvasNode(responseNode)
|
|
830
|
-
: serializeCanvasNodeWithBlobSummaries(responseNode);
|
|
923
|
+
return full ? serializeCanvasNode(responseNode) : serializeCanvasNodeWithBlobSummaries(responseNode);
|
|
831
924
|
},
|
|
832
925
|
});
|
|
833
926
|
|
|
@@ -835,7 +928,11 @@ const nodeGetOperation = defineOperation<z.infer<typeof nodeGetSchema>, Serializ
|
|
|
835
928
|
|
|
836
929
|
const nodeUpdateShape = {
|
|
837
930
|
id: z.string().describe('Node ID to update'),
|
|
838
|
-
intentId: z
|
|
931
|
+
intentId: z
|
|
932
|
+
.string()
|
|
933
|
+
.optional()
|
|
934
|
+
.catch(undefined)
|
|
935
|
+
.describe('Ghost intent id returned by canvas_intent signal. A vetoed or expired intent blocks this mutation.'),
|
|
839
936
|
title: z.unknown().optional().describe('New title'),
|
|
840
937
|
content: z.unknown().optional().describe('New content'),
|
|
841
938
|
x: z.number().optional().catch(undefined).describe('New X position'),
|
|
@@ -846,7 +943,12 @@ const nodeUpdateShape = {
|
|
|
846
943
|
size: z.unknown().optional().describe('Geometry alias: { width, height } object form.'),
|
|
847
944
|
spec: z.unknown().optional().describe('New json-render spec, or a graph payload with graphType/data for graph nodes'),
|
|
848
945
|
graphType: z.unknown().optional().describe('Graph type when updating a graph node'),
|
|
849
|
-
data: z
|
|
946
|
+
data: z
|
|
947
|
+
.unknown()
|
|
948
|
+
.optional()
|
|
949
|
+
.describe(
|
|
950
|
+
'Graph dataset (array) when updating a graph node, or extra data fields (object) merged into the node data',
|
|
951
|
+
),
|
|
850
952
|
xKey: z.unknown().optional().describe('Graph x/category key'),
|
|
851
953
|
yKey: z.unknown().optional().describe('Graph y/value key'),
|
|
852
954
|
chartHeight: z.unknown().optional().describe('Graph chart content height, distinct from node height'),
|
|
@@ -857,11 +959,27 @@ const nodeUpdateShape = {
|
|
|
857
959
|
resultSummary: z.string().optional().catch(undefined).describe('Trace node result summary'),
|
|
858
960
|
error: z.string().optional().catch(undefined).describe('Trace node error message'),
|
|
859
961
|
collapsed: z.unknown().optional().describe('Collapse or expand the node'),
|
|
860
|
-
dockPosition: z
|
|
962
|
+
dockPosition: z
|
|
963
|
+
.unknown()
|
|
964
|
+
.optional()
|
|
965
|
+
.describe('Dock the node to the left/right HUD column, or pass null to return it to the canvas'),
|
|
861
966
|
pinned: z.unknown().optional().describe('Pin or unpin the node to exclude it from auto-arrange'),
|
|
862
|
-
arrangeLocked: z
|
|
863
|
-
|
|
864
|
-
|
|
967
|
+
arrangeLocked: z
|
|
968
|
+
.boolean()
|
|
969
|
+
.optional()
|
|
970
|
+
.catch(undefined)
|
|
971
|
+
.describe('Prevent auto-arrange from moving this node. Pinned nodes are also skipped.'),
|
|
972
|
+
strictSize: z
|
|
973
|
+
.boolean()
|
|
974
|
+
.optional()
|
|
975
|
+
.catch(undefined)
|
|
976
|
+
.describe('Keep explicit width/height fixed and scroll overflowing content.'),
|
|
977
|
+
axCapabilities: z
|
|
978
|
+
.unknown()
|
|
979
|
+
.optional()
|
|
980
|
+
.describe(
|
|
981
|
+
'Enable/disable AX interactions on an existing node. Merged into the node data; clamped to the node-type ceiling server-side.',
|
|
982
|
+
),
|
|
865
983
|
html: z.unknown().optional().describe('New HTML document/fragment (html nodes only).'),
|
|
866
984
|
url: z.unknown().optional().describe('New URL for webpage nodes.'),
|
|
867
985
|
refresh: z.unknown().optional().describe('Webpage nodes: pass true to re-fetch the page instead of patching fields.'),
|
|
@@ -889,21 +1007,39 @@ const nodeUpdateOperation = defineOperation<z.infer<typeof nodeUpdateSchema>, Re
|
|
|
889
1007
|
extraShape: {
|
|
890
1008
|
title: z.string().optional().describe('New title'),
|
|
891
1009
|
content: z.string().optional().describe('New content'),
|
|
892
|
-
spec: z
|
|
1010
|
+
spec: z
|
|
1011
|
+
.record(z.string(), z.unknown())
|
|
1012
|
+
.optional()
|
|
1013
|
+
.describe('New json-render spec, or a graph payload with graphType/data for graph nodes'),
|
|
893
1014
|
graphType: z.string().optional().describe('Graph type when updating a graph node'),
|
|
894
1015
|
data: z.array(z.record(z.string(), z.unknown())).optional().describe('Graph dataset when updating a graph node'),
|
|
895
1016
|
xKey: z.string().optional().describe('Graph x/category key'),
|
|
896
1017
|
yKey: z.string().optional().describe('Graph y/value key'),
|
|
897
1018
|
chartHeight: z.number().optional().describe('Graph chart content height, distinct from node height'),
|
|
898
1019
|
collapsed: z.boolean().optional().describe('Collapse or expand the node'),
|
|
899
|
-
dockPosition: z
|
|
1020
|
+
dockPosition: z
|
|
1021
|
+
.enum(['left', 'right'])
|
|
1022
|
+
.nullable()
|
|
1023
|
+
.optional()
|
|
1024
|
+
.describe('Dock the node to the left/right HUD column, or pass null to return it to the canvas'),
|
|
900
1025
|
pinned: z.boolean().optional().describe('Pin or unpin the node to exclude it from auto-arrange'),
|
|
901
|
-
arrangeLocked: z
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
1026
|
+
arrangeLocked: z
|
|
1027
|
+
.boolean()
|
|
1028
|
+
.optional()
|
|
1029
|
+
.describe('Prevent auto-arrange from moving this node. Pinned nodes are also skipped.'),
|
|
1030
|
+
axCapabilities: z
|
|
1031
|
+
.object({
|
|
1032
|
+
enabled: z.boolean().optional(),
|
|
1033
|
+
allowed: z.array(z.string()).optional(),
|
|
1034
|
+
})
|
|
1035
|
+
.optional()
|
|
1036
|
+
.describe(
|
|
1037
|
+
'Enable/disable AX interactions on an existing node (e.g. flip an html node on with { enabled: true, allowed: ["ax.work.create"] }). Merged into the node data; clamped to the node-type ceiling server-side.',
|
|
1038
|
+
),
|
|
1039
|
+
full: z
|
|
1040
|
+
.boolean()
|
|
1041
|
+
.optional()
|
|
1042
|
+
.describe('Return the full updated node payload. Default false returns compact metadata.'),
|
|
907
1043
|
verbose: z.boolean().optional().describe('Alias for full:true.'),
|
|
908
1044
|
},
|
|
909
1045
|
buildInput: (input) => {
|
|
@@ -967,7 +1103,11 @@ const nodeUpdateOperation = defineOperation<z.infer<typeof nodeUpdateSchema>, Re
|
|
|
967
1103
|
|
|
968
1104
|
const nodeRemoveShape = {
|
|
969
1105
|
id: z.string().describe('Node ID to remove'),
|
|
970
|
-
intentId: z
|
|
1106
|
+
intentId: z
|
|
1107
|
+
.string()
|
|
1108
|
+
.optional()
|
|
1109
|
+
.catch(undefined)
|
|
1110
|
+
.describe('Ghost intent id returned by canvas_intent signal. A vetoed or expired intent blocks this mutation.'),
|
|
971
1111
|
};
|
|
972
1112
|
|
|
973
1113
|
const nodeRemoveSchema = z.looseObject(nodeRemoveShape);
|
|
@@ -1018,9 +1158,13 @@ const layoutGetOperation = defineOperation<z.infer<typeof layoutGetSchema>, Reco
|
|
|
1018
1158
|
},
|
|
1019
1159
|
mcp: {
|
|
1020
1160
|
toolName: 'canvas_get_layout',
|
|
1021
|
-
description:
|
|
1161
|
+
description:
|
|
1162
|
+
'Get the canvas layout. Defaults to a compact agent-safe projection; pass full:true for full node data.',
|
|
1022
1163
|
extraShape: {
|
|
1023
|
-
full: z
|
|
1164
|
+
full: z
|
|
1165
|
+
.boolean()
|
|
1166
|
+
.optional()
|
|
1167
|
+
.describe('Return the full layout including node data. Default false keeps responses compact.'),
|
|
1024
1168
|
verbose: z.boolean().optional().describe('Alias for full:true.'),
|
|
1025
1169
|
},
|
|
1026
1170
|
buildInput: () => ({ includeBlobs: true }),
|
|
@@ -1036,8 +1180,52 @@ const layoutGetOperation = defineOperation<z.infer<typeof layoutGetSchema>, Reco
|
|
|
1036
1180
|
const full = includeBlobs === true || includeBlobs === 'true';
|
|
1037
1181
|
return (full
|
|
1038
1182
|
? serializeCanvasLayout(canvasState.getLayout())
|
|
1039
|
-
: serializeCanvasLayoutWithBlobSummaries(
|
|
1040
|
-
|
|
1183
|
+
: serializeCanvasLayoutWithBlobSummaries(
|
|
1184
|
+
withContextPinLayoutReadState(canvasState.getLayoutForPersistence()),
|
|
1185
|
+
)) as unknown as Record<string, unknown>;
|
|
1186
|
+
},
|
|
1187
|
+
});
|
|
1188
|
+
|
|
1189
|
+
// ── node.refresh (plan-009 C1 slice 3) ────────────────────────
|
|
1190
|
+
|
|
1191
|
+
const nodeRefreshShape = {
|
|
1192
|
+
id: z.string().optional().catch(undefined).describe('Webpage node ID to refresh'),
|
|
1193
|
+
url: z.unknown().optional().describe('Optional replacement page URL'),
|
|
1194
|
+
};
|
|
1195
|
+
|
|
1196
|
+
const nodeRefreshSchema = z.looseObject(nodeRefreshShape);
|
|
1197
|
+
|
|
1198
|
+
const nodeRefreshOperation = defineOperation<z.infer<typeof nodeRefreshSchema>, Record<string, unknown>>({
|
|
1199
|
+
name: 'node.refresh',
|
|
1200
|
+
mutates: false,
|
|
1201
|
+
input: nodeRefreshSchema,
|
|
1202
|
+
inputShape: nodeRefreshShape,
|
|
1203
|
+
http: {
|
|
1204
|
+
method: 'POST',
|
|
1205
|
+
path: '/api/canvas/node/:id/refresh',
|
|
1206
|
+
// Legacy wire: 200 on success, 400 with the refresh failure body otherwise.
|
|
1207
|
+
status: (result) => (isRecord(result) && result.ok === false ? 400 : 200),
|
|
1208
|
+
},
|
|
1209
|
+
handler: async (input, ctx) => {
|
|
1210
|
+
const nodeId = input.id ?? '';
|
|
1211
|
+
const existing = canvasState.getNode(nodeId);
|
|
1212
|
+
if (existing?.type !== 'webpage') {
|
|
1213
|
+
throw new OperationError(`Webpage node "${nodeId}" not found.`, 404);
|
|
1214
|
+
}
|
|
1215
|
+
const rawUrl = typeof input.url === 'string' ? input.url : undefined;
|
|
1216
|
+
let url: string | undefined;
|
|
1217
|
+
if (rawUrl && rawUrl.trim().length > 0) {
|
|
1218
|
+
try {
|
|
1219
|
+
url = normalizeWebpageUrl(rawUrl);
|
|
1220
|
+
} catch (error) {
|
|
1221
|
+
throw new OperationError(error instanceof Error ? error.message : 'Invalid webpage URL.');
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
// Legacy wire: the layout update fires after every completed refresh
|
|
1225
|
+
// attempt, success or failure (the node may have partial refresh state).
|
|
1226
|
+
const result = await refreshCanvasWebpageNode(nodeId, { ...(url ? { url } : {}) });
|
|
1227
|
+
ctx.emit('canvas-layout-update', { layout: canvasState.getLayout() });
|
|
1228
|
+
return result as unknown as Record<string, unknown>;
|
|
1041
1229
|
},
|
|
1042
1230
|
});
|
|
1043
1231
|
|
|
@@ -1047,4 +1235,5 @@ export const nodeOperations: Operation[] = [
|
|
|
1047
1235
|
nodeUpdateOperation,
|
|
1048
1236
|
nodeRemoveOperation,
|
|
1049
1237
|
layoutGetOperation,
|
|
1238
|
+
nodeRefreshOperation,
|
|
1050
1239
|
];
|