pmx-canvas 0.1.12 โ 0.1.14
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 +151 -0
- package/dist/canvas/index.js +42 -42
- package/dist/types/client/nodes/ExtAppFrame.d.ts +2 -3
- package/dist/types/client/nodes/McpAppNode.d.ts +2 -1
- package/dist/types/client/nodes/trace-model.d.ts +9 -0
- package/dist/types/client/state/canvas-store.d.ts +2 -0
- package/dist/types/mcp/canvas-access.d.ts +1 -0
- package/dist/types/server/canvas-operations.d.ts +8 -0
- package/dist/types/server/diagram-presets.d.ts +4 -0
- package/dist/types/server/index.d.ts +8 -0
- package/dist/types/server/mcp-app-runtime.d.ts +1 -0
- package/dist/types/server/web-artifacts.d.ts +1 -0
- package/package.json +1 -1
- package/skills/web-artifacts-builder/scripts/init-artifact.sh +9 -8
- package/src/cli/agent.ts +15 -1
- package/src/client/canvas/ExpandedNodeOverlay.tsx +3 -3
- package/src/client/nodes/ExtAppFrame.tsx +10 -35
- package/src/client/nodes/McpAppNode.tsx +2 -2
- package/src/client/nodes/TraceNode.tsx +2 -6
- package/src/client/nodes/trace-model.ts +19 -0
- package/src/client/state/canvas-store.ts +5 -2
- package/src/client/state/sse-bridge.ts +3 -1
- package/src/mcp/canvas-access.ts +28 -3
- package/src/mcp/server.ts +51 -9
- package/src/server/canvas-operations.ts +36 -0
- package/src/server/canvas-schema.ts +11 -0
- package/src/server/diagram-presets.ts +44 -46
- package/src/server/index.ts +31 -4
- package/src/server/mcp-app-runtime.ts +15 -5
- package/src/server/server.ts +96 -50
- package/src/server/web-artifacts/scripts/init-artifact.sh +9 -8
- package/src/server/web-artifacts.ts +14 -1
package/src/server/server.ts
CHANGED
|
@@ -96,6 +96,8 @@ import {
|
|
|
96
96
|
ungroupCanvasNodes,
|
|
97
97
|
validateCanvasNodePatch,
|
|
98
98
|
hasStructuredNodeUpdateFields,
|
|
99
|
+
hasTraceNodeDataFields,
|
|
100
|
+
mergeTraceNodeDataFields,
|
|
99
101
|
} from './canvas-operations.js';
|
|
100
102
|
import { validateCanvasLayout } from './canvas-validation.js';
|
|
101
103
|
import { describeCanvasSchema, validateStructuredCanvasPayload } from './canvas-schema.js';
|
|
@@ -1331,6 +1333,12 @@ async function handleCanvasAddNode(req: Request): Promise<Response> {
|
|
|
1331
1333
|
...(typeof body.title === 'string' ? { title: body.title } : {}),
|
|
1332
1334
|
...(typeof content === 'string' ? { content } : {}),
|
|
1333
1335
|
...(extraData ? { data: extraData } : {}),
|
|
1336
|
+
...(type === 'trace' && typeof body.toolName === 'string' ? { toolName: body.toolName } : {}),
|
|
1337
|
+
...(type === 'trace' && typeof body.category === 'string' ? { category: body.category } : {}),
|
|
1338
|
+
...(type === 'trace' && typeof body.status === 'string' ? { status: body.status } : {}),
|
|
1339
|
+
...(type === 'trace' && typeof body.duration === 'string' ? { duration: body.duration } : {}),
|
|
1340
|
+
...(type === 'trace' && typeof body.resultSummary === 'string' ? { resultSummary: body.resultSummary } : {}),
|
|
1341
|
+
...(type === 'trace' && typeof body.error === 'string' ? { error: body.error } : {}),
|
|
1334
1342
|
...(body.strictSize === true ? { strictSize: true } : {}),
|
|
1335
1343
|
...geometry,
|
|
1336
1344
|
defaultWidth: 360,
|
|
@@ -1495,7 +1503,14 @@ async function handleCanvasUpdateNode(nodeId: string, req: Request): Promise<Res
|
|
|
1495
1503
|
} catch (error) {
|
|
1496
1504
|
return responseJson({ ok: false, error: error instanceof Error ? error.message : String(error) }, 400);
|
|
1497
1505
|
}
|
|
1498
|
-
} else if (
|
|
1506
|
+
} else if (
|
|
1507
|
+
body.title !== undefined ||
|
|
1508
|
+
body.content !== undefined ||
|
|
1509
|
+
body.data ||
|
|
1510
|
+
typeof body.arrangeLocked === 'boolean' ||
|
|
1511
|
+
typeof body.strictSize === 'boolean' ||
|
|
1512
|
+
(existing.type === 'trace' && hasTraceNodeDataFields(body))
|
|
1513
|
+
) {
|
|
1499
1514
|
const data = { ...existing.data };
|
|
1500
1515
|
if (body.title !== undefined) {
|
|
1501
1516
|
data.title = String(body.title);
|
|
@@ -1524,7 +1539,9 @@ async function handleCanvasUpdateNode(nodeId: string, req: Request): Promise<Res
|
|
|
1524
1539
|
}
|
|
1525
1540
|
}
|
|
1526
1541
|
}
|
|
1527
|
-
patch.data =
|
|
1542
|
+
patch.data = existing.type === 'trace'
|
|
1543
|
+
? mergeTraceNodeDataFields(data, body)
|
|
1544
|
+
: data;
|
|
1528
1545
|
}
|
|
1529
1546
|
const error = validateCanvasNodePatch({
|
|
1530
1547
|
...(patch.position ? { position: patch.position as { x: number; y: number } } : {}),
|
|
@@ -1639,6 +1656,7 @@ async function handleCanvasBuildWebArtifact(req: Request): Promise<Response> {
|
|
|
1639
1656
|
bytes: result.fileSize,
|
|
1640
1657
|
projectPath: result.projectPath,
|
|
1641
1658
|
openedInCanvas: result.openedInCanvas,
|
|
1659
|
+
completedAt: result.completedAt,
|
|
1642
1660
|
// `id` is the canvas node id alias used by every other add-style
|
|
1643
1661
|
// response. It is only present when a canvas node was actually
|
|
1644
1662
|
// created (i.e. openInCanvas was not explicitly disabled). When
|
|
@@ -2110,29 +2128,44 @@ interface RunAndEmitOpenMcpAppParams {
|
|
|
2110
2128
|
transport: ExternalMcpTransportConfig;
|
|
2111
2129
|
toolName: string;
|
|
2112
2130
|
toolArguments?: Record<string, unknown>;
|
|
2131
|
+
nodeId?: string;
|
|
2113
2132
|
serverName?: string;
|
|
2114
2133
|
title?: string;
|
|
2115
2134
|
x?: number;
|
|
2116
2135
|
y?: number;
|
|
2117
2136
|
width?: number;
|
|
2118
2137
|
height?: number;
|
|
2138
|
+
timeoutMs?: number;
|
|
2119
2139
|
}
|
|
2120
2140
|
|
|
2121
2141
|
async function runAndEmitOpenMcpApp(params: RunAndEmitOpenMcpAppParams): Promise<Response> {
|
|
2122
2142
|
try {
|
|
2143
|
+
const targetNode = params.nodeId ? canvasState.getNode(params.nodeId) : undefined;
|
|
2144
|
+
if (params.nodeId && !targetNode) {
|
|
2145
|
+
return responseJson({ ok: false, error: `Node "${params.nodeId}" not found.` }, 404);
|
|
2146
|
+
}
|
|
2147
|
+
if (targetNode && (targetNode.type !== 'mcp-app' || targetNode.data.mode !== 'ext-app')) {
|
|
2148
|
+
return responseJson({ ok: false, error: `Node "${params.nodeId}" is not an external app node.` }, 400);
|
|
2149
|
+
}
|
|
2150
|
+
|
|
2123
2151
|
const opened = await openMcpApp({
|
|
2124
2152
|
transport: params.transport,
|
|
2125
2153
|
toolName: params.toolName,
|
|
2126
2154
|
...(params.toolArguments ? { toolArguments: params.toolArguments } : {}),
|
|
2127
2155
|
...(params.serverName ? { serverName: params.serverName } : {}),
|
|
2156
|
+
...(typeof params.timeoutMs === 'number' ? { timeoutMs: params.timeoutMs } : {}),
|
|
2128
2157
|
});
|
|
2129
2158
|
|
|
2130
2159
|
const toolCallId = randomExtAppToolCallId();
|
|
2131
|
-
|
|
2160
|
+
if (params.nodeId) closeNodeAppSession(targetNode);
|
|
2161
|
+
const nodeIdSeed = params.nodeId ?? (toolCallId.startsWith('ext-app-') ? toolCallId : `ext-app-${toolCallId}`);
|
|
2132
2162
|
const toolResult = isExcalidrawCreateView(opened.serverName, opened.toolName)
|
|
2133
2163
|
? ensureExcalidrawCheckpointId(opened.toolResult, nodeIdSeed)
|
|
2134
2164
|
: opened.toolResult;
|
|
2135
|
-
const nodeTitle = params.title
|
|
2165
|
+
const nodeTitle = params.title
|
|
2166
|
+
?? (typeof targetNode?.data.title === 'string' ? targetNode.data.title : undefined)
|
|
2167
|
+
?? opened.tool.title
|
|
2168
|
+
?? opened.tool.name;
|
|
2136
2169
|
|
|
2137
2170
|
emitPrimaryWorkbenchEvent('ext-app-open', {
|
|
2138
2171
|
toolCallId,
|
|
@@ -2162,7 +2195,7 @@ async function runAndEmitOpenMcpApp(params: RunAndEmitOpenMcpAppParams): Promise
|
|
|
2162
2195
|
success: toolResult.isError !== true,
|
|
2163
2196
|
result: toolResult,
|
|
2164
2197
|
});
|
|
2165
|
-
const nodeId = findCanvasExtAppNodeId(toolCallId);
|
|
2198
|
+
const nodeId = params.nodeId ?? findCanvasExtAppNodeId(toolCallId);
|
|
2166
2199
|
|
|
2167
2200
|
return responseJson({
|
|
2168
2201
|
ok: true,
|
|
@@ -2201,17 +2234,22 @@ async function handleCanvasOpenMcpApp(req: Request): Promise<Response> {
|
|
|
2201
2234
|
const requestedServerName = typeof body.serverName === 'string' && body.serverName.trim().length > 0
|
|
2202
2235
|
? body.serverName.trim()
|
|
2203
2236
|
: undefined;
|
|
2237
|
+
const requestedNodeId = typeof body.nodeId === 'string' && body.nodeId.trim().length > 0
|
|
2238
|
+
? body.nodeId.trim()
|
|
2239
|
+
: undefined;
|
|
2204
2240
|
|
|
2205
2241
|
return runAndEmitOpenMcpApp({
|
|
2206
2242
|
transport,
|
|
2207
2243
|
toolName,
|
|
2208
2244
|
...(toolArguments ? { toolArguments } : {}),
|
|
2245
|
+
...(requestedNodeId ? { nodeId: requestedNodeId } : {}),
|
|
2209
2246
|
...(requestedServerName ? { serverName: requestedServerName } : {}),
|
|
2210
2247
|
...(requestedTitle ? { title: requestedTitle } : {}),
|
|
2211
2248
|
...(typeof body.x === 'number' ? { x: body.x } : {}),
|
|
2212
2249
|
...(typeof body.y === 'number' ? { y: body.y } : {}),
|
|
2213
2250
|
...(typeof body.width === 'number' ? { width: body.width } : {}),
|
|
2214
2251
|
...(typeof body.height === 'number' ? { height: body.height } : {}),
|
|
2252
|
+
...(typeof body.timeoutMs === 'number' ? { timeoutMs: body.timeoutMs } : {}),
|
|
2215
2253
|
});
|
|
2216
2254
|
}
|
|
2217
2255
|
|
|
@@ -2221,11 +2259,13 @@ async function handleCanvasAddDiagram(req: Request): Promise<Response> {
|
|
|
2221
2259
|
try {
|
|
2222
2260
|
built = buildExcalidrawOpenMcpAppInput({
|
|
2223
2261
|
elements: body.elements,
|
|
2262
|
+
...(typeof body.nodeId === 'string' ? { nodeId: body.nodeId } : {}),
|
|
2224
2263
|
...(typeof body.title === 'string' ? { title: body.title } : {}),
|
|
2225
2264
|
...(typeof body.x === 'number' ? { x: body.x } : {}),
|
|
2226
2265
|
...(typeof body.y === 'number' ? { y: body.y } : {}),
|
|
2227
2266
|
...(typeof body.width === 'number' ? { width: body.width } : {}),
|
|
2228
2267
|
...(typeof body.height === 'number' ? { height: body.height } : {}),
|
|
2268
|
+
...(typeof body.timeoutMs === 'number' ? { timeoutMs: body.timeoutMs } : {}),
|
|
2229
2269
|
});
|
|
2230
2270
|
} catch (error) {
|
|
2231
2271
|
return responseJson({
|
|
@@ -2238,11 +2278,13 @@ async function handleCanvasAddDiagram(req: Request): Promise<Response> {
|
|
|
2238
2278
|
toolName: built.toolName,
|
|
2239
2279
|
toolArguments: built.toolArguments,
|
|
2240
2280
|
serverName: built.serverName,
|
|
2281
|
+
...(built.nodeId ? { nodeId: built.nodeId } : {}),
|
|
2241
2282
|
...(built.title ? { title: built.title } : {}),
|
|
2242
2283
|
...(typeof built.x === 'number' ? { x: built.x } : {}),
|
|
2243
2284
|
...(typeof built.y === 'number' ? { y: built.y } : {}),
|
|
2244
2285
|
...(typeof built.width === 'number' ? { width: built.width } : {}),
|
|
2245
2286
|
...(typeof built.height === 'number' ? { height: built.height } : {}),
|
|
2287
|
+
...(typeof built.timeoutMs === 'number' ? { timeoutMs: built.timeoutMs } : {}),
|
|
2246
2288
|
});
|
|
2247
2289
|
}
|
|
2248
2290
|
|
|
@@ -3407,52 +3449,56 @@ function syncEventToCanvasState(event: string, payload: PrimaryWorkbenchEventPay
|
|
|
3407
3449
|
});
|
|
3408
3450
|
}
|
|
3409
3451
|
} else if (event === 'ext-app-update') {
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3452
|
+
canvasState.withSuppressedRecording(() => {
|
|
3453
|
+
const toolCallId = payload.toolCallId as string;
|
|
3454
|
+
if (!toolCallId) return;
|
|
3455
|
+
const payloadNodeId = typeof payload.nodeId === 'string' ? payload.nodeId : '';
|
|
3456
|
+
const id =
|
|
3457
|
+
(payloadNodeId && canvasState.getNode(payloadNodeId) ? payloadNodeId : null) ||
|
|
3458
|
+
findCanvasExtAppNodeId(toolCallId) ||
|
|
3459
|
+
(typeof payload.serverName === 'string' && typeof payload.toolName === 'string'
|
|
3460
|
+
? findOnlyPendingCanvasExtAppNodeId(payload.serverName, payload.toolName)
|
|
3461
|
+
: null);
|
|
3462
|
+
if (!id) return;
|
|
3463
|
+
const existing = canvasState.getNode(id);
|
|
3464
|
+
if (existing) {
|
|
3465
|
+
canvasState.updateNode(id, { data: { ...existing.data, html: payload.html } });
|
|
3466
|
+
}
|
|
3467
|
+
});
|
|
3424
3468
|
} else if (event === 'ext-app-result') {
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3469
|
+
canvasState.withSuppressedRecording(() => {
|
|
3470
|
+
const toolCallId = payload.toolCallId as string;
|
|
3471
|
+
if (!toolCallId) return;
|
|
3472
|
+
const payloadNodeId = typeof payload.nodeId === 'string' ? payload.nodeId : '';
|
|
3473
|
+
const id =
|
|
3474
|
+
(payloadNodeId && canvasState.getNode(payloadNodeId) ? payloadNodeId : null) ||
|
|
3475
|
+
findCanvasExtAppNodeId(toolCallId) ||
|
|
3476
|
+
(typeof payload.serverName === 'string' && typeof payload.toolName === 'string'
|
|
3477
|
+
? findOnlyPendingCanvasExtAppNodeId(payload.serverName, payload.toolName)
|
|
3478
|
+
: null);
|
|
3479
|
+
if (!id) return;
|
|
3480
|
+
if (payload.success === false) {
|
|
3481
|
+
closeNodeAppSession(canvasState.getNode(id));
|
|
3482
|
+
canvasState.removeNode(id);
|
|
3483
|
+
return;
|
|
3484
|
+
}
|
|
3485
|
+
const existing = canvasState.getNode(id);
|
|
3486
|
+
if (existing) {
|
|
3487
|
+
canvasState.updateNode(id, {
|
|
3488
|
+
data: {
|
|
3489
|
+
...existing.data,
|
|
3490
|
+
toolResult: normalizeExtAppToolResult({
|
|
3491
|
+
result: payload.result,
|
|
3492
|
+
success: typeof payload.success === 'boolean' ? payload.success : undefined,
|
|
3493
|
+
error: typeof payload.error === 'string' ? payload.error : undefined,
|
|
3494
|
+
content: typeof payload.content === 'string' ? payload.content : undefined,
|
|
3495
|
+
detailedContent:
|
|
3496
|
+
typeof payload.detailedContent === 'string' ? payload.detailedContent : undefined,
|
|
3497
|
+
}),
|
|
3498
|
+
},
|
|
3499
|
+
});
|
|
3500
|
+
}
|
|
3501
|
+
});
|
|
3456
3502
|
} else if (event === 'context-cards') {
|
|
3457
3503
|
syncContextNodeToCanvasState(
|
|
3458
3504
|
{ cards: Array.isArray(payload.cards) ? payload.cards : [] },
|
|
@@ -116,12 +116,13 @@ else
|
|
|
116
116
|
echo "โ
Using Vite $VITE_VERSION (Node 18 compatible)"
|
|
117
117
|
fi
|
|
118
118
|
|
|
119
|
-
|
|
120
|
-
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
121
|
-
|
|
122
|
-
else
|
|
123
|
-
|
|
124
|
-
fi
|
|
119
|
+
function sed_in_place() {
|
|
120
|
+
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
121
|
+
sed -i '' "$@"
|
|
122
|
+
else
|
|
123
|
+
sed -i "$@"
|
|
124
|
+
fi
|
|
125
|
+
}
|
|
125
126
|
|
|
126
127
|
declare -a PNPM_CMD
|
|
127
128
|
configure_pnpm
|
|
@@ -159,8 +160,8 @@ fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n');
|
|
|
159
160
|
"
|
|
160
161
|
|
|
161
162
|
echo "๐งน Cleaning up Vite template..."
|
|
162
|
-
|
|
163
|
-
|
|
163
|
+
sed_in_place '/<link rel="icon".*/d' index.html
|
|
164
|
+
sed_in_place 's/<title>.*<\/title>/<title>'"$PROJECT_NAME"'<\/title>/' index.html
|
|
164
165
|
|
|
165
166
|
echo "๐ฆ Installing base dependencies..."
|
|
166
167
|
run_pnpm_quiet install
|
|
@@ -2,9 +2,11 @@ import { spawn } from 'node:child_process';
|
|
|
2
2
|
import {
|
|
3
3
|
copyFileSync,
|
|
4
4
|
existsSync,
|
|
5
|
+
readdirSync,
|
|
5
6
|
mkdirSync,
|
|
6
7
|
readFileSync,
|
|
7
8
|
statSync,
|
|
9
|
+
unlinkSync,
|
|
8
10
|
writeFileSync,
|
|
9
11
|
} from 'node:fs';
|
|
10
12
|
import { basename, delimiter, dirname, isAbsolute, join, relative, resolve } from 'node:path';
|
|
@@ -71,6 +73,7 @@ export interface WebArtifactCanvasBuildResult extends WebArtifactBuildOutput {
|
|
|
71
73
|
openedInCanvas: boolean;
|
|
72
74
|
nodeId?: string;
|
|
73
75
|
url?: string;
|
|
76
|
+
completedAt: string;
|
|
74
77
|
}
|
|
75
78
|
|
|
76
79
|
function currentWorkspaceRoot(): string {
|
|
@@ -300,6 +303,14 @@ function writeProjectFiles(
|
|
|
300
303
|
}
|
|
301
304
|
}
|
|
302
305
|
|
|
306
|
+
function removeLiteralSedBackupFiles(projectPath: string): void {
|
|
307
|
+
for (const entry of readdirSync(projectPath, { withFileTypes: true })) {
|
|
308
|
+
if (entry.isFile() && entry.name.endsWith("''")) {
|
|
309
|
+
unlinkSync(join(projectPath, entry.name));
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
303
314
|
function ensurePackageManagerBoundary(dirPath: string): void {
|
|
304
315
|
const packageJsonPath = join(dirPath, 'package.json');
|
|
305
316
|
mkdirSync(dirPath, { recursive: true });
|
|
@@ -401,6 +412,7 @@ export async function executeWebArtifactBuild(
|
|
|
401
412
|
});
|
|
402
413
|
stdout = [stdout, initResult.stdout].filter(Boolean).join('\n');
|
|
403
414
|
stderr = [stderr, initResult.stderr].filter(Boolean).join('\n');
|
|
415
|
+
removeLiteralSedBackupFiles(projectPath);
|
|
404
416
|
}
|
|
405
417
|
|
|
406
418
|
writeProjectFiles(projectPath, input);
|
|
@@ -508,7 +520,7 @@ export async function buildWebArtifactOnCanvas(input: WebArtifactBuildInput & {
|
|
|
508
520
|
}): Promise<WebArtifactCanvasBuildResult> {
|
|
509
521
|
const build = await executeWebArtifactBuild(input);
|
|
510
522
|
if (input.openInCanvas === false) {
|
|
511
|
-
return { ...build, openedInCanvas: false };
|
|
523
|
+
return { ...build, openedInCanvas: false, completedAt: new Date().toISOString() };
|
|
512
524
|
}
|
|
513
525
|
const opened = openWebArtifactInCanvas({
|
|
514
526
|
title: input.title,
|
|
@@ -519,5 +531,6 @@ export async function buildWebArtifactOnCanvas(input: WebArtifactBuildInput & {
|
|
|
519
531
|
openedInCanvas: true,
|
|
520
532
|
nodeId: opened.nodeId,
|
|
521
533
|
url: opened.url,
|
|
534
|
+
completedAt: new Date().toISOString(),
|
|
522
535
|
};
|
|
523
536
|
}
|