pmx-canvas 0.1.6 → 0.1.7
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 +49 -1
- package/dist/types/server/server.d.ts +1 -0
- package/package.json +1 -1
- package/skills/pmx-canvas/SKILL.md +23 -2
- package/src/cli/agent.ts +3 -3
- package/src/mcp/server.ts +4 -4
- package/src/server/server.ts +7 -3
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,55 @@
|
|
|
3
3
|
All notable changes to `pmx-canvas` are documented here. This project follows
|
|
4
4
|
[Semantic Versioning](https://semver.org/).
|
|
5
5
|
|
|
6
|
+
## [0.1.7] - 2026-04-26
|
|
7
|
+
|
|
8
|
+
Small retest-driven follow-up to 0.1.6. Three agent-facing ergonomics:
|
|
9
|
+
`canvas_evaluate` now accepts top-level `await`, snapshot responses gain
|
|
10
|
+
a flat `id` alias for add-style consistency, and the PMX Canvas skill
|
|
11
|
+
documents real DOM selectors plus several quirks an agent would
|
|
12
|
+
otherwise have to discover by trial and error.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- **Snapshot save responses include a flat `id` alias.** Both
|
|
17
|
+
`canvas_snapshot` and `POST /api/canvas/snapshots` still return the
|
|
18
|
+
nested `snapshot` object, and now also include `id: snapshot.id` at
|
|
19
|
+
the top level — same shape as every other add-style response in the
|
|
20
|
+
canvas API. HTTP and MCP surfaces are aligned.
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- **`canvas_evaluate` script mode supports top-level `await`.** Both
|
|
25
|
+
MCP and HTTP WebView script mode wrap multi-statement bodies in an
|
|
26
|
+
async IIFE and serialize the resolved return value, so an agent can
|
|
27
|
+
write `const r = await fetch(...); return r.json();` directly without
|
|
28
|
+
scaffolding the wrapper itself. WebView script documentation now
|
|
29
|
+
describes the async behavior explicitly.
|
|
30
|
+
- **PMX Canvas skill docs now ship a defensive ID extractor pattern.**
|
|
31
|
+
The skill recommends `r.id ?? r.nodeId ?? r.snapshot?.id` so agents
|
|
32
|
+
pull the right id field across add-style, web-artifact, and snapshot
|
|
33
|
+
responses without branching per tool.
|
|
34
|
+
- **PMX Canvas skill docs name the real WebView CSS selectors.** The
|
|
35
|
+
bundled skill calls out `.canvas-node`, `.hud-layer`,
|
|
36
|
+
`.canvas-toolbar`, `.connection-dot`, and related classes, and is
|
|
37
|
+
explicit that nodes do **not** expose stable `data-node-id`
|
|
38
|
+
attributes — agents driving the canvas via `canvas_evaluate` no
|
|
39
|
+
longer have to discover selectors by trial and error.
|
|
40
|
+
- **PMX Canvas skill edge docs list the valid edge types.** `flow`,
|
|
41
|
+
`depends-on`, `relation`, `references` — same as the rest of the
|
|
42
|
+
surface but now explicit in the skill so the agent doesn't guess.
|
|
43
|
+
- **PMX Canvas skill diagram docs clarify
|
|
44
|
+
`canvas_add_diagram.elements`.** The field expects Excalidraw element
|
|
45
|
+
objects (rectangles, ellipses, arrows with bindings, labels), not
|
|
46
|
+
Mermaid / DOT / Graphviz source text or any other diagram DSL.
|
|
47
|
+
|
|
48
|
+
### Internal
|
|
49
|
+
|
|
50
|
+
- Regression coverage for snapshot flat-`id` aliases on both MCP and
|
|
51
|
+
HTTP surfaces, plus async / top-level-`await` WebView script bodies.
|
|
52
|
+
|
|
53
|
+
[0.1.7]: https://github.com/pskoett/pmx-canvas/releases/tag/v0.1.7
|
|
54
|
+
|
|
6
55
|
## [0.1.6] - 2026-04-26
|
|
7
56
|
|
|
8
57
|
CLI/MCP regression cleanup after the 0.1.5 coverage pass. This release tightens
|
|
@@ -50,7 +99,6 @@ drift without guessing.
|
|
|
50
99
|
omitted from the response instead of being `undefined`. Consumers can now
|
|
51
100
|
reliably use `'id' in response` to detect the build-only case. `nodeId` is
|
|
52
101
|
always present and remains the canonical identifier.
|
|
53
|
-
|
|
54
102
|
### Fixed
|
|
55
103
|
|
|
56
104
|
- **`pmx-canvas graph add` no longer starts a rogue server.** The top-level
|
|
@@ -60,6 +60,7 @@ export declare function getCanvasAutomationWebViewStatus(): CanvasAutomationWebV
|
|
|
60
60
|
export declare function stopCanvasAutomationWebView(): Promise<boolean>;
|
|
61
61
|
export declare function startCanvasAutomationWebView(url: string, options?: CanvasAutomationWebViewOptions): Promise<CanvasAutomationWebViewStatus>;
|
|
62
62
|
export declare function evaluateCanvasAutomationWebView(expression: string): Promise<unknown>;
|
|
63
|
+
export declare function wrapCanvasAutomationScript(script: string): string;
|
|
63
64
|
export declare function resizeCanvasAutomationWebView(width: number, height: number): Promise<CanvasAutomationWebViewStatus>;
|
|
64
65
|
export declare function screenshotCanvasAutomationWebView(options?: Record<string, unknown>): Promise<Uint8Array>;
|
|
65
66
|
export interface PrimaryWorkbenchIntent {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pmx-canvas",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "Spatial canvas workbench for coding agents — infinite 2D canvas with agent-native CLI, MCP integration, nodes, edges, file watching, and snapshots",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/server/index.ts",
|
|
@@ -332,6 +332,10 @@ If a node type is rejected by `canvas_add_node`, call `canvas_describe_schema` a
|
|
|
332
332
|
- Cold builds commonly take 45-60 seconds; use a long client timeout such as 300000 ms or more
|
|
333
333
|
- Returns both `id` and `nodeId` for the created artifact node when `openInCanvas` is true
|
|
334
334
|
|
|
335
|
+
ID extraction for mixed tool responses:
|
|
336
|
+
- Most add-style tools return a flat `id`; web artifacts return `id` plus `nodeId`; snapshots return `id` plus nested `snapshot.id`.
|
|
337
|
+
- Defensive extractor: `const getId = (r) => r.id ?? r.nodeId ?? r.snapshot?.id;`
|
|
338
|
+
|
|
335
339
|
**`canvas_open_mcp_app`** — Open a tool-backed external MCP app node
|
|
336
340
|
- Required: `toolName`, `transport`
|
|
337
341
|
- `transport` is either `{ type: "stdio", command, args?, cwd?, env? }` or `{ type: "http", url, headers? }`
|
|
@@ -366,7 +370,7 @@ If a node type is rejected by `canvas_add_node`, call `canvas_describe_schema` a
|
|
|
366
370
|
- `from`, `to` (required): source and target node IDs
|
|
367
371
|
- `fromSearch`, `toSearch`: optional search-based selectors when you do not have IDs. Each search
|
|
368
372
|
query must resolve to exactly one node or the edge creation fails with an ambiguity error.
|
|
369
|
-
- `type`:
|
|
373
|
+
- `type`: `flow`, `depends-on`, `relation`, or `references` (default: `relation`)
|
|
370
374
|
- `label`: descriptive relationship label
|
|
371
375
|
- `style`: `solid`, `dashed`, or `dotted`
|
|
372
376
|
- `animated`: boolean for visual emphasis
|
|
@@ -465,6 +469,7 @@ Current product caveats for grouped comparison boards:
|
|
|
465
469
|
**`canvas_redo`** — Redo the last undone mutation
|
|
466
470
|
**`canvas_snapshot`** — Save a named snapshot to disk
|
|
467
471
|
- `name` (required): descriptive snapshot name (e.g., "before-refactor")
|
|
472
|
+
- Returns `{ ok, id, snapshot }`; the flat `id` is an alias for `snapshot.id`
|
|
468
473
|
**`canvas_restore`** — Restore canvas from a saved snapshot
|
|
469
474
|
- `id`: snapshot to restore
|
|
470
475
|
**`canvas_diff`** — Compare current canvas against a saved snapshot
|
|
@@ -502,7 +507,8 @@ tools below operate on the live canvas state.
|
|
|
502
507
|
**`canvas_webview_stop`** — Tear down the automation session
|
|
503
508
|
|
|
504
509
|
**`canvas_evaluate`** — Run JavaScript inside the workbench page and return the result
|
|
505
|
-
- Required: `expression` (
|
|
510
|
+
- Required: exactly one of `expression` (single JS expression) or `script` (multi-statement body)
|
|
511
|
+
- `script` is wrapped in an async IIFE, so top-level `await` works inside script bodies
|
|
506
512
|
- Useful for asserting DOM state after a sequence of canvas mutations
|
|
507
513
|
- Example: read the count of rendered `.canvas-node` elements:
|
|
508
514
|
|
|
@@ -510,6 +516,20 @@ tools below operate on the live canvas state.
|
|
|
510
516
|
canvas_evaluate({ expression: 'document.querySelectorAll(".canvas-node").length' })
|
|
511
517
|
```
|
|
512
518
|
|
|
519
|
+
Useful workbench selectors:
|
|
520
|
+
- Nodes: `.canvas-node`, `.canvas-node.active`, `.canvas-node.context-pinned`, `.canvas-node.group-node`
|
|
521
|
+
- Node internals: `.node-title`, `.node-titlebar`, `.node-body`, `.node-type-badge`, `.node-controls`
|
|
522
|
+
- Canvas chrome: `.hud-layer`, `.canvas-toolbar`, `.connection-dot`, `.canvas-bootstrap-card`
|
|
523
|
+
- Nodes do not expose stable `data-node-id` attributes. Use `canvas_get_layout`, `canvas_search`, or MCP resource data for exact node IDs.
|
|
524
|
+
|
|
525
|
+
Async script example:
|
|
526
|
+
|
|
527
|
+
```typescript
|
|
528
|
+
canvas_evaluate({
|
|
529
|
+
script: 'const title = await Promise.resolve(document.title); return title;',
|
|
530
|
+
})
|
|
531
|
+
```
|
|
532
|
+
|
|
513
533
|
**`canvas_resize`** — Change the WebView viewport
|
|
514
534
|
- Required: `width`, `height`
|
|
515
535
|
- Use before `canvas_screenshot` when the human needs a specific aspect ratio
|
|
@@ -535,6 +555,7 @@ canvas_webview_stop();
|
|
|
535
555
|
[Excalidraw MCP app](https://github.com/excalidraw/excalidraw-mcp)
|
|
536
556
|
- Required: `elements` — an array of Excalidraw elements (rectangles, ellipses, diamonds, arrows,
|
|
537
557
|
text). Can also be a JSON-array string.
|
|
558
|
+
- `elements` must be Excalidraw element objects, not Mermaid/DOT/source-text diagrams. Convert source diagrams to Excalidraw elements first or use a markdown/web-artifact node.
|
|
538
559
|
- Optional: `title`, `x`, `y`, `width`, `height`
|
|
539
560
|
- The diagram opens inside an `mcp-app` node with fullscreen editing and draw-on animations
|
|
540
561
|
- CLI equivalent: `pmx-canvas external-app add --kind excalidraw --title "Diagram"`
|
package/src/cli/agent.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import { readFileSync, writeFileSync } from 'node:fs';
|
|
15
|
-
import { openUrlInExternalBrowser } from '../server/server.js';
|
|
15
|
+
import { openUrlInExternalBrowser, wrapCanvasAutomationScript } from '../server/server.js';
|
|
16
16
|
import { DEFAULT_EXCALIDRAW_ELEMENTS } from '../server/diagram-presets.js';
|
|
17
17
|
import {
|
|
18
18
|
ALL_SEMANTIC_WATCH_EVENT_TYPES,
|
|
@@ -1856,9 +1856,9 @@ cmd('webview evaluate', 'Evaluate JavaScript in the active Bun.WebView automatio
|
|
|
1856
1856
|
'pmx-canvas webview evaluate --file ./probe.js',
|
|
1857
1857
|
);
|
|
1858
1858
|
}
|
|
1859
|
-
expression =
|
|
1859
|
+
expression = wrapCanvasAutomationScript(script);
|
|
1860
1860
|
} else if (typeof flags.script === 'string') {
|
|
1861
|
-
expression =
|
|
1861
|
+
expression = wrapCanvasAutomationScript(flags.script);
|
|
1862
1862
|
} else {
|
|
1863
1863
|
expression = requireFlag(
|
|
1864
1864
|
flags,
|
package/src/mcp/server.ts
CHANGED
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
type PmxCanvas,
|
|
33
33
|
} from '../server/index.js';
|
|
34
34
|
import { serializeNodeForAgentContext } from '../server/agent-context.js';
|
|
35
|
-
import { emitPrimaryWorkbenchEvent } from '../server/server.js';
|
|
35
|
+
import { emitPrimaryWorkbenchEvent, wrapCanvasAutomationScript } from '../server/server.js';
|
|
36
36
|
import { searchNodes, buildSpatialContext, findNeighborhoods } from '../server/spatial-analysis.js';
|
|
37
37
|
import { mutationHistory, diffLayouts, formatDiff } from '../server/mutation-history.js';
|
|
38
38
|
import { buildCodeGraphSummary, formatCodeGraph } from '../server/code-graph.js';
|
|
@@ -899,7 +899,7 @@ export async function startMcpServer(): Promise<void> {
|
|
|
899
899
|
'Evaluate JavaScript in the active Bun.WebView automation session for the workbench page. Use this to inspect rendered browser state. Requires an active automation session started via canvas_webview_start.',
|
|
900
900
|
{
|
|
901
901
|
expression: z.string().optional().describe('JavaScript expression to evaluate in the page context'),
|
|
902
|
-
script: z.string().optional().describe('Multi-statement JavaScript body. The MCP server wraps it in an IIFE and evaluates the return value.'),
|
|
902
|
+
script: z.string().optional().describe('Multi-statement JavaScript body. The MCP server wraps it in an async IIFE and evaluates the resolved return value.'),
|
|
903
903
|
},
|
|
904
904
|
async ({ expression, script }) => {
|
|
905
905
|
const c = await ensureCanvas();
|
|
@@ -910,7 +910,7 @@ export async function startMcpServer(): Promise<void> {
|
|
|
910
910
|
};
|
|
911
911
|
}
|
|
912
912
|
|
|
913
|
-
const source = script ?
|
|
913
|
+
const source = script ? wrapCanvasAutomationScript(script) : expression!;
|
|
914
914
|
try {
|
|
915
915
|
const value = await c.evaluateAutomationWebView(source);
|
|
916
916
|
return {
|
|
@@ -1401,7 +1401,7 @@ export async function startMcpServer(): Promise<void> {
|
|
|
1401
1401
|
if (!snapshot) {
|
|
1402
1402
|
return { content: [{ type: 'text', text: JSON.stringify({ ok: false, error: 'Failed to save snapshot' }) }] };
|
|
1403
1403
|
}
|
|
1404
|
-
return { content: [{ type: 'text', text: JSON.stringify({ ok: true, snapshot }) }] };
|
|
1404
|
+
return { content: [{ type: 'text', text: JSON.stringify({ ok: true, id: snapshot.id, snapshot }) }] };
|
|
1405
1405
|
},
|
|
1406
1406
|
);
|
|
1407
1407
|
|
package/src/server/server.ts
CHANGED
|
@@ -473,6 +473,10 @@ export async function evaluateCanvasAutomationWebView(expression: string): Promi
|
|
|
473
473
|
));
|
|
474
474
|
}
|
|
475
475
|
|
|
476
|
+
export function wrapCanvasAutomationScript(script: string): string {
|
|
477
|
+
return `(async () => {\n${script}\n})()`;
|
|
478
|
+
}
|
|
479
|
+
|
|
476
480
|
export async function resizeCanvasAutomationWebView(
|
|
477
481
|
width: number,
|
|
478
482
|
height: number,
|
|
@@ -2389,10 +2393,10 @@ async function handleWorkbenchWebViewEvaluate(req: Request): Promise<Response> {
|
|
|
2389
2393
|
if ((expression ? 1 : 0) + (script ? 1 : 0) !== 1) {
|
|
2390
2394
|
return responseJson({
|
|
2391
2395
|
ok: false,
|
|
2392
|
-
error: 'Pass exactly one of "expression" (single JS expression) or "script" (multi-statement body, wrapped in an IIFE).',
|
|
2396
|
+
error: 'Pass exactly one of "expression" (single JS expression) or "script" (multi-statement body, wrapped in an async IIFE).',
|
|
2393
2397
|
}, 400);
|
|
2394
2398
|
}
|
|
2395
|
-
const source = script ?
|
|
2399
|
+
const source = script ? wrapCanvasAutomationScript(script) : expression;
|
|
2396
2400
|
|
|
2397
2401
|
try {
|
|
2398
2402
|
const value = await evaluateCanvasAutomationWebView(source);
|
|
@@ -2899,7 +2903,7 @@ async function handleSnapshotSave(req: Request): Promise<Response> {
|
|
|
2899
2903
|
if (!name) return responseText('Missing snapshot name', 400);
|
|
2900
2904
|
const snapshot = saveCanvasSnapshot(name);
|
|
2901
2905
|
if (!snapshot) return responseText('Failed to save snapshot', 500);
|
|
2902
|
-
return responseJson({ ok: true, snapshot });
|
|
2906
|
+
return responseJson({ ok: true, id: snapshot.id, snapshot });
|
|
2903
2907
|
}
|
|
2904
2908
|
|
|
2905
2909
|
async function handleContextPinsUpdate(req: Request): Promise<Response> {
|