pmx-canvas 0.2.7 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +148 -1
- package/Readme.md +13 -11
- package/dist/canvas/index.js +2 -2
- package/dist/json-render/index.css +1 -1
- 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/mcp/canvas-access.d.ts +0 -7
- package/dist/types/server/ax-state-manager.d.ts +3 -2
- package/dist/types/server/bundled-skills.d.ts +3 -3
- package/dist/types/server/canvas-schema.d.ts +1 -1
- package/dist/types/server/canvas-state.d.ts +2 -0
- package/dist/types/server/ext-app-lookup.d.ts +1 -3
- package/dist/types/server/html-primitives.d.ts +1 -1
- package/dist/types/server/index.d.ts +4 -4
- package/dist/types/server/operations/composites.d.ts +29 -23
- package/dist/types/server/operations/http.d.ts +2 -1
- 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/dist/types/shared/ax-intent.d.ts +1 -1
- package/docs/RELEASE.md +23 -8
- package/docs/ax-host-adapter-contract.md +7 -7
- package/docs/ax-state-contract.md +7 -6
- package/docs/bun-webview-integration.md +5 -5
- package/docs/cli.md +1 -1
- package/docs/http-api.md +35 -3
- package/docs/mcp.md +121 -100
- package/docs/node-types.md +45 -35
- package/docs/plans/plan-006-mcp-tool-consolidation.md +3 -1
- package/docs/plans/plan-008-registry-finish.md +2 -0
- package/docs/plans/plan-009-tech-debt-backlog.md +51 -0
- package/docs/screenshot.png +0 -0
- package/docs/tech-debt-assessment-2026-06.md +1 -1
- package/docs/tech-debt-assessment-2026-07.md +135 -0
- package/package.json +5 -2
- package/skills/data-analysis/SKILL.md +3 -3
- package/skills/frontend-design/SKILL.md +3 -2
- package/skills/json-render-mcp/SKILL.md +4 -3
- package/skills/playwright-cli/SKILL.md +1 -1
- package/skills/pmx-canvas/SKILL.md +12 -7
- package/skills/pmx-canvas/evals/evals.json +2 -2
- package/skills/pmx-canvas/references/ax-html-control-surface.md +3 -1
- package/skills/pmx-canvas/references/excalidraw-diagram-authoring.md +2 -2
- package/skills/pmx-canvas/references/full-reference.md +58 -56
- package/skills/pmx-canvas/references/html-primitives.md +3 -3
- package/skills/tufte-viz/SKILL.md +5 -4
- package/skills/web-artifacts-builder/SKILL.md +4 -4
- 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 +40 -121
- package/src/mcp/server.ts +206 -271
- 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 +185 -41
- package/src/server/ax-state.ts +142 -47
- package/src/server/bundled-skills.ts +3 -3
- 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 +500 -102
- package/src/server/canvas-serialization.ts +27 -35
- package/src/server/canvas-state.ts +155 -58
- package/src/server/chart-template.ts +4 -5
- package/src/server/code-graph.ts +20 -7
- package/src/server/diagram-presets.ts +28 -29
- package/src/server/ext-app-lookup.ts +4 -20
- 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 +321 -218
- package/src/server/intent-registry.ts +3 -6
- 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 +38 -40
- package/src/server/operations/http.ts +5 -5
- package/src/server/operations/index.ts +7 -1
- package/src/server/operations/invoker.ts +4 -3
- package/src/server/operations/mcp.ts +29 -36
- package/src/server/operations/ops/annotation.ts +122 -10
- package/src/server/operations/ops/app.ts +102 -77
- 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 +300 -111
- package/src/server/operations/ops/query.ts +62 -33
- 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 +121 -1012
- 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/ax-intent.ts +1 -1
- 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
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Annotation
|
|
2
|
+
* Annotation ops: annotation.remove (plan-008 Wave 1) and annotation.add
|
|
3
|
+
* (plan-009 C1 slice 3 — the legacy POST /api/canvas/annotation handler,
|
|
4
|
+
* wire-identical).
|
|
3
5
|
*
|
|
4
|
-
* DELETE-by-id of a human-drawn canvas annotation.
|
|
5
|
-
* auto-emits one canvas-layout-update after
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
6
|
+
* annotation.remove: DELETE-by-id of a human-drawn canvas annotation.
|
|
7
|
+
* mutates: true → the registry auto-emits one canvas-layout-update after
|
|
8
|
+
* success, matching the legacy handler. A non-existent id is a 404
|
|
9
|
+
* OperationError carrying the exact legacy message; an omitted id is a 400 so
|
|
10
|
+
* composite callers get a loud input error instead of a misleading "not
|
|
11
|
+
* found". HTTP renders errors as { ok:false, error } and MCP renders them as
|
|
12
|
+
* isError tool results.
|
|
11
13
|
*
|
|
12
14
|
* This module must never import server.ts or index.ts.
|
|
13
15
|
*/
|
|
14
16
|
import { z } from 'zod';
|
|
15
|
-
import { canvasState } from '../../canvas-state.js';
|
|
17
|
+
import { canvasState, type CanvasAnnotation } from '../../canvas-state.js';
|
|
18
|
+
import { summarizeCanvasAnnotation } from '../../canvas-serialization.js';
|
|
16
19
|
import { defineOperation, OperationError, type Operation } from '../types.js';
|
|
17
20
|
|
|
18
21
|
const annotationRemoveShape = {
|
|
@@ -57,4 +60,113 @@ const annotationRemoveOperation = defineOperation<z.infer<typeof annotationRemov
|
|
|
57
60
|
},
|
|
58
61
|
});
|
|
59
62
|
|
|
60
|
-
|
|
63
|
+
// ── annotation.add (helpers moved from server.ts) ─────────────
|
|
64
|
+
|
|
65
|
+
function annotationBounds(points: CanvasAnnotation['points']): CanvasAnnotation['bounds'] {
|
|
66
|
+
const xs = points.map((point) => point.x);
|
|
67
|
+
const ys = points.map((point) => point.y);
|
|
68
|
+
const minX = Math.min(...xs);
|
|
69
|
+
const minY = Math.min(...ys);
|
|
70
|
+
const maxX = Math.max(...xs);
|
|
71
|
+
const maxY = Math.max(...ys);
|
|
72
|
+
return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function textAnnotationBounds(
|
|
76
|
+
point: CanvasAnnotation['points'][number],
|
|
77
|
+
text: string,
|
|
78
|
+
width: number,
|
|
79
|
+
): CanvasAnnotation['bounds'] {
|
|
80
|
+
return {
|
|
81
|
+
x: point.x,
|
|
82
|
+
y: point.y - width,
|
|
83
|
+
width: Math.max(width, text.length * width * 0.62),
|
|
84
|
+
height: width * 1.2,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function parseAnnotationPoints(value: unknown): CanvasAnnotation['points'] {
|
|
89
|
+
if (!Array.isArray(value)) return [];
|
|
90
|
+
return value
|
|
91
|
+
.map((point) => {
|
|
92
|
+
if (!point || typeof point !== 'object' || Array.isArray(point)) return null;
|
|
93
|
+
const record = point as Record<string, unknown>;
|
|
94
|
+
if (typeof record.x !== 'number' || typeof record.y !== 'number') return null;
|
|
95
|
+
if (!Number.isFinite(record.x) || !Number.isFinite(record.y)) return null;
|
|
96
|
+
return { x: record.x, y: record.y };
|
|
97
|
+
})
|
|
98
|
+
.filter((point): point is CanvasAnnotation['points'][number] => point !== null);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const annotationAddShape = {
|
|
102
|
+
type: z.unknown().optional().describe("Annotation type: 'freehand' (default) or 'text'"),
|
|
103
|
+
points: z.unknown().optional().describe('Annotation points: [{ x, y }, …]'),
|
|
104
|
+
width: z.unknown().optional().describe('Stroke width (freehand) or font size (text)'),
|
|
105
|
+
color: z.unknown().optional().describe("'currentColor' or a #rrggbb hex color"),
|
|
106
|
+
label: z.unknown().optional().describe('Short annotation label'),
|
|
107
|
+
text: z.unknown().optional().describe('Text annotation content'),
|
|
108
|
+
id: z.unknown().optional().describe('Explicit annotation id'),
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const annotationAddSchema = z.looseObject(annotationAddShape);
|
|
112
|
+
|
|
113
|
+
const annotationAddOperation = defineOperation<z.infer<typeof annotationAddSchema>, Record<string, unknown>>({
|
|
114
|
+
name: 'annotation.add',
|
|
115
|
+
mutates: true,
|
|
116
|
+
input: annotationAddSchema,
|
|
117
|
+
inputShape: annotationAddShape,
|
|
118
|
+
http: {
|
|
119
|
+
method: 'POST',
|
|
120
|
+
path: '/api/canvas/annotation',
|
|
121
|
+
},
|
|
122
|
+
handler: (input) => {
|
|
123
|
+
const body: Record<string, unknown> = input;
|
|
124
|
+
const type = body.type === 'text' ? 'text' : 'freehand';
|
|
125
|
+
const points = parseAnnotationPoints(body.points);
|
|
126
|
+
if (points.length < (type === 'text' ? 1 : 2)) {
|
|
127
|
+
throw new OperationError(
|
|
128
|
+
type === 'text' ? 'Text annotation requires a valid point.' : 'Annotation requires at least two valid points.',
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const defaultWidth = type === 'text' ? 24 : 4;
|
|
133
|
+
const maxWidth = type === 'text' ? 96 : 24;
|
|
134
|
+
const width =
|
|
135
|
+
typeof body.width === 'number' && Number.isFinite(body.width)
|
|
136
|
+
? Math.min(maxWidth, Math.max(1, body.width))
|
|
137
|
+
: defaultWidth;
|
|
138
|
+
const color =
|
|
139
|
+
typeof body.color === 'string' && (body.color === 'currentColor' || /^#[0-9a-fA-F]{6}$/.test(body.color))
|
|
140
|
+
? body.color
|
|
141
|
+
: 'currentColor';
|
|
142
|
+
const label =
|
|
143
|
+
typeof body.label === 'string' && body.label.trim().length > 0 ? body.label.trim().slice(0, 160) : undefined;
|
|
144
|
+
const text =
|
|
145
|
+
type === 'text' && typeof body.text === 'string' && body.text.trim().length > 0
|
|
146
|
+
? body.text.trim().slice(0, 240)
|
|
147
|
+
: undefined;
|
|
148
|
+
if (type === 'text' && !text) {
|
|
149
|
+
throw new OperationError('Text annotation requires text.');
|
|
150
|
+
}
|
|
151
|
+
const id =
|
|
152
|
+
typeof body.id === 'string' && body.id.trim().length > 0
|
|
153
|
+
? body.id.trim().slice(0, 120)
|
|
154
|
+
: `ann-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
155
|
+
const annotation: CanvasAnnotation = {
|
|
156
|
+
id,
|
|
157
|
+
type,
|
|
158
|
+
points,
|
|
159
|
+
bounds: type === 'text' ? textAnnotationBounds(points[0]!, text!, width) : annotationBounds(points),
|
|
160
|
+
color,
|
|
161
|
+
width,
|
|
162
|
+
...(text ? { text } : {}),
|
|
163
|
+
...((label ?? text) ? { label: label ?? text } : {}),
|
|
164
|
+
createdAt: new Date().toISOString(),
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
canvasState.addAnnotation(annotation);
|
|
168
|
+
return { ok: true, annotation: summarizeCanvasAnnotation(annotation) };
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
export const annotationOperations: Operation[] = [annotationRemoveOperation, annotationAddOperation];
|
|
@@ -85,19 +85,16 @@ export interface OpenMcpAppCoreResult {
|
|
|
85
85
|
* call it. The diagram.open op delegates here after building the Excalidraw input
|
|
86
86
|
* (the SSE pair fires ONCE — diagram.open does not re-emit).
|
|
87
87
|
*/
|
|
88
|
-
export async function openMcpAppCore(
|
|
89
|
-
input: OpenMcpAppCoreInput,
|
|
90
|
-
ctx: OperationContext,
|
|
91
|
-
): Promise<OpenMcpAppCoreResult> {
|
|
88
|
+
export async function openMcpAppCore(input: OpenMcpAppCoreInput, ctx: OperationContext): Promise<OpenMcpAppCoreResult> {
|
|
92
89
|
// The canvas node is created as a side-effect of the `ext-app-open` SSE event
|
|
93
90
|
// (syncEventToCanvasState). Inside a suppressed-emit run (canvas.batch) that
|
|
94
91
|
// event is dropped, so the node would never be created and the just-opened
|
|
95
92
|
// external session would leak with a null nodeId. Reject loudly BEFORE opening
|
|
96
93
|
// any session rather than silently corrupting. mcpapp.open / diagram.open are
|
|
97
|
-
// not batchable — call
|
|
94
|
+
// not batchable — call canvas_app (action "open-mcp-app" / "diagram") instead.
|
|
98
95
|
if (isEmitSuppressed()) {
|
|
99
96
|
throw new OperationError(
|
|
100
|
-
'mcpapp.open / diagram.open cannot run inside canvas_batch: the canvas node is created from the ext-app-open SSE event, which batch suppresses. Call
|
|
97
|
+
'mcpapp.open / diagram.open cannot run inside canvas_batch: the canvas node is created from the ext-app-open SSE event, which batch suppresses. Call canvas_app with action "open-mcp-app" or "diagram" outside the batch.',
|
|
101
98
|
);
|
|
102
99
|
}
|
|
103
100
|
const targetNode = input.nodeId ? canvasState.getNode(input.nodeId) : undefined;
|
|
@@ -155,10 +152,12 @@ export async function openMcpAppCore(
|
|
|
155
152
|
success: toolResult.isError !== true,
|
|
156
153
|
result: toolResult,
|
|
157
154
|
});
|
|
158
|
-
const nodeId =
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
155
|
+
const nodeId =
|
|
156
|
+
input.nodeId ??
|
|
157
|
+
findCanvasExtAppNodeId(toolCallId, {
|
|
158
|
+
getNode: (id) => canvasState.getNode(id),
|
|
159
|
+
listNodes: () => canvasState.getLayout().nodes,
|
|
160
|
+
});
|
|
162
161
|
return {
|
|
163
162
|
ok: true,
|
|
164
163
|
...(nodeId ? { id: nodeId } : {}),
|
|
@@ -180,15 +179,10 @@ function buildOpenMcpAppInput(body: Record<string, unknown>): OpenMcpAppCoreInpu
|
|
|
180
179
|
throw new OperationError('Missing valid transport or toolName.');
|
|
181
180
|
}
|
|
182
181
|
const toolArguments = isRecord(body.toolArguments) ? body.toolArguments : undefined;
|
|
183
|
-
const serverName =
|
|
184
|
-
? body.serverName.trim()
|
|
185
|
-
|
|
186
|
-
const
|
|
187
|
-
? body.title.trim()
|
|
188
|
-
: undefined;
|
|
189
|
-
const nodeId = typeof body.nodeId === 'string' && body.nodeId.trim().length > 0
|
|
190
|
-
? body.nodeId.trim()
|
|
191
|
-
: undefined;
|
|
182
|
+
const serverName =
|
|
183
|
+
typeof body.serverName === 'string' && body.serverName.trim().length > 0 ? body.serverName.trim() : undefined;
|
|
184
|
+
const title = typeof body.title === 'string' && body.title.trim().length > 0 ? body.title.trim() : undefined;
|
|
185
|
+
const nodeId = typeof body.nodeId === 'string' && body.nodeId.trim().length > 0 ? body.nodeId.trim() : undefined;
|
|
192
186
|
return {
|
|
193
187
|
transport,
|
|
194
188
|
toolName,
|
|
@@ -216,21 +210,26 @@ const openMcpAppShape = {
|
|
|
216
210
|
y: z.number().optional().describe('Y position (auto-placed if omitted)'),
|
|
217
211
|
width: z.number().optional().describe('Width in pixels (default: 720)'),
|
|
218
212
|
height: z.number().optional().describe('Height in pixels (default: 500)'),
|
|
219
|
-
timeoutMs: z
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
213
|
+
timeoutMs: z
|
|
214
|
+
.number()
|
|
215
|
+
.optional()
|
|
216
|
+
.describe('Optional MCP request timeout in milliseconds for cold external app servers'),
|
|
217
|
+
transport: z
|
|
218
|
+
.union([
|
|
219
|
+
z.object({
|
|
220
|
+
type: z.literal('stdio'),
|
|
221
|
+
command: z.string().describe('Executable used to start the external MCP server'),
|
|
222
|
+
args: z.array(z.string()).optional().describe('Arguments for the executable'),
|
|
223
|
+
cwd: z.string().optional().describe('Optional working directory'),
|
|
224
|
+
env: z.record(z.string(), z.string()).optional().describe('Optional environment overrides'),
|
|
225
|
+
}),
|
|
226
|
+
z.object({
|
|
227
|
+
type: z.literal('http'),
|
|
228
|
+
url: z.string().describe('Streamable HTTP MCP endpoint URL'),
|
|
229
|
+
headers: z.record(z.string(), z.string()).optional().describe('Optional HTTP headers'),
|
|
230
|
+
}),
|
|
231
|
+
])
|
|
232
|
+
.describe('How PMX Canvas should connect to the external MCP server'),
|
|
234
233
|
};
|
|
235
234
|
const openMcpAppSchema = z.looseObject(openMcpAppShape);
|
|
236
235
|
|
|
@@ -246,7 +245,7 @@ const openMcpAppOperation = defineOperation<z.infer<typeof openMcpAppSchema>, Op
|
|
|
246
245
|
mcp: {
|
|
247
246
|
toolName: 'canvas_open_mcp_app',
|
|
248
247
|
description:
|
|
249
|
-
'Connect to an external MCP server that declares a ui:// app resource, call the specified tool, and open the resulting MCP App inside a canvas mcp-app node. This is a full external-MCP transport call, not the CLI kind shortcut; use
|
|
248
|
+
'Connect to an external MCP server that declares a ui:// app resource, call the specified tool, and open the resulting MCP App inside a canvas mcp-app node. This is a full external-MCP transport call, not the CLI kind shortcut; use canvas_app action "diagram" for the built-in Excalidraw preset.',
|
|
250
249
|
formatResult: (result) => ({
|
|
251
250
|
content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],
|
|
252
251
|
}),
|
|
@@ -257,17 +256,29 @@ const openMcpAppOperation = defineOperation<z.infer<typeof openMcpAppSchema>, Op
|
|
|
257
256
|
// ── diagram.open (Excalidraw preset → mcpapp.open core) ───────
|
|
258
257
|
|
|
259
258
|
const diagramShape = {
|
|
260
|
-
elements: z
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
259
|
+
elements: z
|
|
260
|
+
.union([
|
|
261
|
+
z.string().describe('JSON array string of Excalidraw elements'),
|
|
262
|
+
z.array(z.record(z.string(), z.unknown())).describe('Array of Excalidraw elements'),
|
|
263
|
+
])
|
|
264
|
+
.describe(
|
|
265
|
+
'Excalidraw elements to render. See https://github.com/excalidraw/excalidraw-mcp for the element format.',
|
|
266
|
+
),
|
|
267
|
+
nodeId: z
|
|
268
|
+
.string()
|
|
269
|
+
.optional()
|
|
270
|
+
.describe('Existing Excalidraw mcp-app node ID to update in place instead of creating a new node.'),
|
|
265
271
|
title: z.string().optional().describe('Optional canvas node title override'),
|
|
266
272
|
x: z.number().optional().describe('X position (auto-placed if omitted)'),
|
|
267
273
|
y: z.number().optional().describe('Y position (auto-placed if omitted)'),
|
|
268
274
|
width: z.number().optional().describe('Width in pixels (default: 720)'),
|
|
269
275
|
height: z.number().optional().describe('Height in pixels (default: 500)'),
|
|
270
|
-
timeoutMs: z
|
|
276
|
+
timeoutMs: z
|
|
277
|
+
.number()
|
|
278
|
+
.optional()
|
|
279
|
+
.describe(
|
|
280
|
+
'Optional MCP request timeout in milliseconds for Excalidraw cold starts. Client-side MCP hosts may still enforce their own total request timeout.',
|
|
281
|
+
),
|
|
271
282
|
};
|
|
272
283
|
const diagramSchema = z.looseObject(diagramShape);
|
|
273
284
|
|
|
@@ -283,13 +294,13 @@ const diagramOperation = defineOperation<z.infer<typeof diagramSchema>, OpenMcpA
|
|
|
283
294
|
mcp: {
|
|
284
295
|
toolName: 'canvas_add_diagram',
|
|
285
296
|
description:
|
|
286
|
-
'Draw a hand-drawn diagram on the canvas via the hosted Excalidraw MCP app. Pass an array of Excalidraw elements (rectangles, ellipses, diamonds, arrows, text). The diagram opens inside an mcp-app node that supports fullscreen editing. For other MCP apps, use
|
|
297
|
+
'Draw a hand-drawn diagram on the canvas via the hosted Excalidraw MCP app. Pass an array of Excalidraw elements (rectangles, ellipses, diamonds, arrows, text). The diagram opens inside an mcp-app node that supports fullscreen editing. For other MCP apps, use canvas_app action "open-mcp-app".',
|
|
287
298
|
formatResult: (result) => ({
|
|
288
299
|
content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],
|
|
289
300
|
}),
|
|
290
301
|
},
|
|
291
302
|
handler: (input, ctx) => {
|
|
292
|
-
let built
|
|
303
|
+
let built: ReturnType<typeof buildExcalidrawOpenMcpAppInput>;
|
|
293
304
|
try {
|
|
294
305
|
built = buildExcalidrawOpenMcpAppInput(input);
|
|
295
306
|
} catch (error) {
|
|
@@ -297,19 +308,22 @@ const diagramOperation = defineOperation<z.infer<typeof diagramSchema>, OpenMcpA
|
|
|
297
308
|
}
|
|
298
309
|
// Delegate to the shared open core (the SSE pair fires once — diagram.open
|
|
299
310
|
// does not re-emit).
|
|
300
|
-
return openMcpAppCore(
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
311
|
+
return openMcpAppCore(
|
|
312
|
+
{
|
|
313
|
+
transport: built.transport,
|
|
314
|
+
toolName: built.toolName,
|
|
315
|
+
toolArguments: built.toolArguments,
|
|
316
|
+
serverName: built.serverName,
|
|
317
|
+
...(built.nodeId ? { nodeId: built.nodeId } : {}),
|
|
318
|
+
...(built.title ? { title: built.title } : {}),
|
|
319
|
+
...(typeof built.x === 'number' ? { x: built.x } : {}),
|
|
320
|
+
...(typeof built.y === 'number' ? { y: built.y } : {}),
|
|
321
|
+
...(typeof built.width === 'number' ? { width: built.width } : {}),
|
|
322
|
+
...(typeof built.height === 'number' ? { height: built.height } : {}),
|
|
323
|
+
...(typeof built.timeoutMs === 'number' ? { timeoutMs: built.timeoutMs } : {}),
|
|
324
|
+
},
|
|
325
|
+
ctx,
|
|
326
|
+
);
|
|
313
327
|
},
|
|
314
328
|
});
|
|
315
329
|
|
|
@@ -321,23 +335,42 @@ const webArtifactShape = {
|
|
|
321
335
|
indexCss: z.string().optional().describe('Optional contents for src/index.css'),
|
|
322
336
|
mainTsx: z.string().optional().describe('Optional contents for src/main.tsx'),
|
|
323
337
|
indexHtml: z.string().optional().describe('Optional contents for index.html'),
|
|
324
|
-
files: z
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
338
|
+
files: z
|
|
339
|
+
.record(z.string(), z.string())
|
|
340
|
+
.optional()
|
|
341
|
+
.describe('Optional map of additional project-relative file paths to file contents'),
|
|
342
|
+
deps: z
|
|
343
|
+
.array(z.string())
|
|
344
|
+
.optional()
|
|
345
|
+
.describe(
|
|
346
|
+
'Optional npm dependencies to install before bundling (e.g. ["recharts", "framer-motion@^11"]). Validated against npm-name format; flags and shell metacharacters are rejected.',
|
|
347
|
+
),
|
|
348
|
+
projectPath: z
|
|
349
|
+
.string()
|
|
350
|
+
.optional()
|
|
351
|
+
.describe(
|
|
352
|
+
'Optional workspace-relative reusable project path. Defaults to .pmx-canvas/artifacts/.web-artifacts/<slug>',
|
|
353
|
+
),
|
|
354
|
+
outputPath: z
|
|
355
|
+
.string()
|
|
356
|
+
.optional()
|
|
357
|
+
.describe('Optional workspace-relative HTML output path. Defaults to .pmx-canvas/artifacts/<slug>.html'),
|
|
328
358
|
openInCanvas: z.boolean().optional().describe('Open the generated artifact in canvas after build (default true)'),
|
|
329
359
|
includeLogs: z.boolean().optional().describe('Include raw build stdout/stderr in the response (default false)'),
|
|
330
|
-
initScriptPath: z
|
|
331
|
-
|
|
360
|
+
initScriptPath: z
|
|
361
|
+
.string()
|
|
362
|
+
.optional()
|
|
363
|
+
.describe('Optional script path override for tests/debugging. Must resolve inside the workspace.'),
|
|
364
|
+
bundleScriptPath: z
|
|
365
|
+
.string()
|
|
366
|
+
.optional()
|
|
367
|
+
.describe('Optional script path override for tests/debugging. Must resolve inside the workspace.'),
|
|
332
368
|
timeoutMs: z.number().optional().describe('Optional timeout in milliseconds for init and bundle commands'),
|
|
333
369
|
};
|
|
334
370
|
const webArtifactSchema = z.looseObject(webArtifactShape);
|
|
335
371
|
|
|
336
372
|
/** Shape the byte-identical web-artifact response envelope from the build result. */
|
|
337
|
-
function webArtifactEnvelope(
|
|
338
|
-
result: WebArtifactCanvasBuildResult,
|
|
339
|
-
includeLogs: boolean,
|
|
340
|
-
): Record<string, unknown> {
|
|
373
|
+
function webArtifactEnvelope(result: WebArtifactCanvasBuildResult, includeLogs: boolean): Record<string, unknown> {
|
|
341
374
|
return {
|
|
342
375
|
ok: true,
|
|
343
376
|
path: result.filePath,
|
|
@@ -415,12 +448,8 @@ const webArtifactOperation = defineOperation<z.infer<typeof webArtifactSchema>,
|
|
|
415
448
|
// legacy HTTP handler used resolveWorkspacePath; the MCP tool used its
|
|
416
449
|
// own safeWorkspacePath — both enforce containment). resolveWorkspacePath
|
|
417
450
|
// resolves against the active canvas workspace root.
|
|
418
|
-
...(typeof input.projectPath === 'string'
|
|
419
|
-
|
|
420
|
-
: {}),
|
|
421
|
-
...(typeof input.outputPath === 'string'
|
|
422
|
-
? { outputPath: resolveWorkspacePath(input.outputPath) }
|
|
423
|
-
: {}),
|
|
451
|
+
...(typeof input.projectPath === 'string' ? { projectPath: resolveWorkspacePath(input.projectPath) } : {}),
|
|
452
|
+
...(typeof input.outputPath === 'string' ? { outputPath: resolveWorkspacePath(input.outputPath) } : {}),
|
|
424
453
|
// Script-path overrides are honored only when contained inside the
|
|
425
454
|
// workspace (enforced by resolveTrustedScriptPath in
|
|
426
455
|
// executeWebArtifactBuild), so they cannot point at an arbitrary host
|
|
@@ -440,8 +469,4 @@ const webArtifactOperation = defineOperation<z.infer<typeof webArtifactSchema>,
|
|
|
440
469
|
},
|
|
441
470
|
});
|
|
442
471
|
|
|
443
|
-
export const appOperations: Operation[] = [
|
|
444
|
-
openMcpAppOperation,
|
|
445
|
-
diagramOperation,
|
|
446
|
-
webArtifactOperation,
|
|
447
|
-
];
|
|
472
|
+
export const appOperations: Operation[] = [openMcpAppOperation, diagramOperation, webArtifactOperation];
|
|
@@ -92,7 +92,13 @@ const awaitInputSchema = z.looseObject(awaitInputShape);
|
|
|
92
92
|
|
|
93
93
|
const awaitMcpExtraShape = {
|
|
94
94
|
id: z.string(),
|
|
95
|
-
timeoutMs: z
|
|
95
|
+
timeoutMs: z
|
|
96
|
+
.number()
|
|
97
|
+
.int()
|
|
98
|
+
.min(0)
|
|
99
|
+
.max(120000)
|
|
100
|
+
.optional()
|
|
101
|
+
.describe('Max ms to block (default 30000; 0 = immediate read; capped at 120000).'),
|
|
96
102
|
};
|
|
97
103
|
|
|
98
104
|
// ── ax.approval.get (canvas_await_approval) ───────────────────
|
|
@@ -110,7 +116,8 @@ const axApprovalGetOperation = defineOperation<z.infer<typeof awaitInputSchema>,
|
|
|
110
116
|
},
|
|
111
117
|
mcp: {
|
|
112
118
|
toolName: 'canvas_await_approval',
|
|
113
|
-
description:
|
|
119
|
+
description:
|
|
120
|
+
'Block until an approval gate resolves (the human approves/rejects it in the browser) or the timeout elapses — primitive D, gates that actually gate. timeoutMs 0 = read immediately without waiting. Returns { approvalGate, pending } (pending=true → still unresolved after the wait).',
|
|
114
121
|
extraShape: awaitMcpExtraShape,
|
|
115
122
|
buildInput: buildAwaitInput,
|
|
116
123
|
formatResult: (result) => {
|
|
@@ -118,7 +125,9 @@ const axApprovalGetOperation = defineOperation<z.infer<typeof awaitInputSchema>,
|
|
|
118
125
|
const approvalGate = (body.approvalGate ?? null) as PmxAxApprovalGate | null;
|
|
119
126
|
const pending = body.pending === true;
|
|
120
127
|
return {
|
|
121
|
-
content: [
|
|
128
|
+
content: [
|
|
129
|
+
{ type: 'text' as const, text: JSON.stringify({ ok: approvalGate !== null, approvalGate, pending }) },
|
|
130
|
+
],
|
|
122
131
|
};
|
|
123
132
|
},
|
|
124
133
|
},
|
|
@@ -148,7 +157,8 @@ const axElicitationGetOperation = defineOperation<z.infer<typeof awaitInputSchem
|
|
|
148
157
|
},
|
|
149
158
|
mcp: {
|
|
150
159
|
toolName: 'canvas_await_elicitation',
|
|
151
|
-
description:
|
|
160
|
+
description:
|
|
161
|
+
'Block until an elicitation is answered (the human responds in the browser) or the timeout elapses — primitive D. timeoutMs 0 = read immediately. Returns { elicitation, pending }.',
|
|
152
162
|
extraShape: awaitMcpExtraShape,
|
|
153
163
|
buildInput: buildAwaitInput,
|
|
154
164
|
formatResult: (result) => {
|
|
@@ -186,7 +196,8 @@ const axModeGetOperation = defineOperation<z.infer<typeof awaitInputSchema>, Rec
|
|
|
186
196
|
},
|
|
187
197
|
mcp: {
|
|
188
198
|
toolName: 'canvas_await_mode',
|
|
189
|
-
description:
|
|
199
|
+
description:
|
|
200
|
+
'Block until a mode request resolves (approved/rejected in the browser) or the timeout elapses — primitive D. timeoutMs 0 = read immediately. Returns { modeRequest, pending }.',
|
|
190
201
|
extraShape: awaitMcpExtraShape,
|
|
191
202
|
buildInput: buildAwaitInput,
|
|
192
203
|
formatResult: (result) => {
|
|
@@ -209,8 +220,4 @@ const axModeGetOperation = defineOperation<z.infer<typeof awaitInputSchema>, Rec
|
|
|
209
220
|
},
|
|
210
221
|
});
|
|
211
222
|
|
|
212
|
-
export const axAwaitOperations: Operation[] = [
|
|
213
|
-
axApprovalGetOperation,
|
|
214
|
-
axElicitationGetOperation,
|
|
215
|
-
axModeGetOperation,
|
|
216
|
-
];
|
|
223
|
+
export const axAwaitOperations: Operation[] = [axApprovalGetOperation, axElicitationGetOperation, axModeGetOperation];
|