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.
Files changed (32) hide show
  1. package/CHANGELOG.md +151 -0
  2. package/dist/canvas/index.js +42 -42
  3. package/dist/types/client/nodes/ExtAppFrame.d.ts +2 -3
  4. package/dist/types/client/nodes/McpAppNode.d.ts +2 -1
  5. package/dist/types/client/nodes/trace-model.d.ts +9 -0
  6. package/dist/types/client/state/canvas-store.d.ts +2 -0
  7. package/dist/types/mcp/canvas-access.d.ts +1 -0
  8. package/dist/types/server/canvas-operations.d.ts +8 -0
  9. package/dist/types/server/diagram-presets.d.ts +4 -0
  10. package/dist/types/server/index.d.ts +8 -0
  11. package/dist/types/server/mcp-app-runtime.d.ts +1 -0
  12. package/dist/types/server/web-artifacts.d.ts +1 -0
  13. package/package.json +1 -1
  14. package/skills/web-artifacts-builder/scripts/init-artifact.sh +9 -8
  15. package/src/cli/agent.ts +15 -1
  16. package/src/client/canvas/ExpandedNodeOverlay.tsx +3 -3
  17. package/src/client/nodes/ExtAppFrame.tsx +10 -35
  18. package/src/client/nodes/McpAppNode.tsx +2 -2
  19. package/src/client/nodes/TraceNode.tsx +2 -6
  20. package/src/client/nodes/trace-model.ts +19 -0
  21. package/src/client/state/canvas-store.ts +5 -2
  22. package/src/client/state/sse-bridge.ts +3 -1
  23. package/src/mcp/canvas-access.ts +28 -3
  24. package/src/mcp/server.ts +51 -9
  25. package/src/server/canvas-operations.ts +36 -0
  26. package/src/server/canvas-schema.ts +11 -0
  27. package/src/server/diagram-presets.ts +44 -46
  28. package/src/server/index.ts +31 -4
  29. package/src/server/mcp-app-runtime.ts +15 -5
  30. package/src/server/server.ts +96 -50
  31. package/src/server/web-artifacts/scripts/init-artifact.sh +9 -8
  32. package/src/server/web-artifacts.ts +14 -1
@@ -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 (body.title !== undefined || body.content !== undefined || body.data || typeof body.arrangeLocked === 'boolean' || typeof body.strictSize === 'boolean') {
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 = 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
- const nodeIdSeed = toolCallId.startsWith('ext-app-') ? toolCallId : `ext-app-${toolCallId}`;
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 ?? opened.tool.title ?? opened.tool.name;
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
- const toolCallId = payload.toolCallId as string;
3411
- if (!toolCallId) return;
3412
- const payloadNodeId = typeof payload.nodeId === 'string' ? payload.nodeId : '';
3413
- const id =
3414
- (payloadNodeId && canvasState.getNode(payloadNodeId) ? payloadNodeId : null) ||
3415
- findCanvasExtAppNodeId(toolCallId) ||
3416
- (typeof payload.serverName === 'string' && typeof payload.toolName === 'string'
3417
- ? findOnlyPendingCanvasExtAppNodeId(payload.serverName, payload.toolName)
3418
- : null);
3419
- if (!id) return;
3420
- const existing = canvasState.getNode(id);
3421
- if (existing) {
3422
- canvasState.updateNode(id, { data: { ...existing.data, html: payload.html } });
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
- const toolCallId = payload.toolCallId as string;
3426
- if (!toolCallId) return;
3427
- const payloadNodeId = typeof payload.nodeId === 'string' ? payload.nodeId : '';
3428
- const id =
3429
- (payloadNodeId && canvasState.getNode(payloadNodeId) ? payloadNodeId : null) ||
3430
- findCanvasExtAppNodeId(toolCallId) ||
3431
- (typeof payload.serverName === 'string' && typeof payload.toolName === 'string'
3432
- ? findOnlyPendingCanvasExtAppNodeId(payload.serverName, payload.toolName)
3433
- : null);
3434
- if (!id) return;
3435
- if (payload.success === false) {
3436
- closeNodeAppSession(canvasState.getNode(id));
3437
- canvasState.removeNode(id);
3438
- return;
3439
- }
3440
- const existing = canvasState.getNode(id);
3441
- if (existing) {
3442
- canvasState.updateNode(id, {
3443
- data: {
3444
- ...existing.data,
3445
- toolResult: normalizeExtAppToolResult({
3446
- result: payload.result,
3447
- success: typeof payload.success === 'boolean' ? payload.success : undefined,
3448
- error: typeof payload.error === 'string' ? payload.error : undefined,
3449
- content: typeof payload.content === 'string' ? payload.content : undefined,
3450
- detailedContent:
3451
- typeof payload.detailedContent === 'string' ? payload.detailedContent : undefined,
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
- # Detect OS and set sed syntax
120
- if [[ "$OSTYPE" == "darwin"* ]]; then
121
- SED_INPLACE="sed -i ''"
122
- else
123
- SED_INPLACE="sed -i"
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
- $SED_INPLACE '/<link rel="icon".*/d' index.html
163
- $SED_INPLACE 's/<title>.*<\/title>/<title>'"$PROJECT_NAME"'<\/title>/' index.html
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
  }