pmx-canvas 0.1.36 → 0.2.0
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 +409 -0
- package/Readme.md +2 -2
- package/dist/json-render/index.js +89 -334
- package/dist/types/mcp/canvas-access.d.ts +5 -171
- package/dist/types/server/ax-state-manager.d.ts +256 -0
- package/dist/types/server/ax-state.d.ts +1 -1
- package/dist/types/server/canvas-operations.d.ts +1 -12
- package/dist/types/server/canvas-state.d.ts +3 -23
- package/dist/types/server/index.d.ts +6 -24
- package/dist/types/server/operations/composites.d.ts +121 -0
- package/dist/types/server/operations/http.d.ts +7 -0
- package/dist/types/server/operations/index.d.ts +8 -0
- package/dist/types/server/operations/invoker.d.ts +13 -0
- package/dist/types/server/operations/mcp.d.ts +15 -0
- package/dist/types/server/operations/ops/annotation.d.ts +2 -0
- package/dist/types/server/operations/ops/app.d.ts +33 -0
- package/dist/types/server/operations/ops/ax-await.d.ts +2 -0
- package/dist/types/server/operations/ops/ax-shared.d.ts +31 -0
- package/dist/types/server/operations/ops/ax-state.d.ts +2 -0
- package/dist/types/server/operations/ops/ax-timeline.d.ts +2 -0
- package/dist/types/server/operations/ops/ax-work.d.ts +2 -0
- package/dist/types/server/operations/ops/batch.d.ts +19 -0
- package/dist/types/server/operations/ops/edges.d.ts +2 -0
- package/dist/types/server/operations/ops/groups.d.ts +2 -0
- package/dist/types/server/operations/ops/json-render.d.ts +31 -0
- package/dist/types/server/operations/ops/nodes.d.ts +62 -0
- package/dist/types/server/operations/ops/query.d.ts +2 -0
- package/dist/types/server/operations/ops/snapshots.d.ts +2 -0
- package/dist/types/server/operations/ops/validate.d.ts +2 -0
- package/dist/types/server/operations/ops/viewport.d.ts +2 -0
- package/dist/types/server/operations/ops/webview.d.ts +2 -0
- package/dist/types/server/operations/registry.d.ts +15 -0
- package/dist/types/server/operations/types.d.ts +116 -0
- package/dist/types/server/operations/webview-runner.d.ts +69 -0
- package/docs/RELEASE.md +5 -0
- package/docs/adr-001-bun-only-runtime.md +46 -0
- package/docs/api-stability.md +57 -0
- package/docs/ax-state-contract.md +72 -0
- package/docs/mcp.md +60 -11
- package/docs/plans/plan-005-operation-registry.md +84 -0
- package/docs/plans/plan-006-mcp-tool-consolidation.md +109 -0
- package/docs/plans/plan-007-ax-domain.md +99 -0
- package/docs/plans/plan-008-registry-finish.md +91 -0
- package/docs/tech-debt-assessment-2026-06.md +90 -0
- package/package.json +3 -3
- package/skills/pmx-canvas/SKILL.md +192 -186
- package/skills/pmx-canvas/evals/evals.json +3 -3
- package/skills/pmx-canvas/references/codex-app-adapter.md +13 -14
- package/skills/pmx-canvas/references/github-copilot-app-adapter.md +4 -5
- package/src/cli/agent.ts +52 -31
- package/src/mcp/canvas-access.ts +30 -830
- package/src/mcp/server.ts +162 -2014
- package/src/server/ax-state-manager.ts +808 -0
- package/src/server/ax-state.ts +2 -2
- package/src/server/canvas-operations.ts +2 -328
- package/src/server/canvas-schema.ts +2 -2
- package/src/server/canvas-state.ts +95 -465
- package/src/server/index.ts +54 -190
- package/src/server/operations/composites.ts +355 -0
- package/src/server/operations/http.ts +103 -0
- package/src/server/operations/index.ts +65 -0
- package/src/server/operations/invoker.ts +87 -0
- package/src/server/operations/mcp.ts +221 -0
- package/src/server/operations/ops/annotation.ts +60 -0
- package/src/server/operations/ops/app.ts +447 -0
- package/src/server/operations/ops/ax-await.ts +216 -0
- package/src/server/operations/ops/ax-shared.ts +38 -0
- package/src/server/operations/ops/ax-state.ts +249 -0
- package/src/server/operations/ops/ax-timeline.ts +381 -0
- package/src/server/operations/ops/ax-work.ts +635 -0
- package/src/server/operations/ops/batch.ts +365 -0
- package/src/server/operations/ops/edges.ts +166 -0
- package/src/server/operations/ops/groups.ts +176 -0
- package/src/server/operations/ops/json-render.ts +691 -0
- package/src/server/operations/ops/nodes.ts +1047 -0
- package/src/server/operations/ops/query.ts +281 -0
- package/src/server/operations/ops/snapshots.ts +366 -0
- package/src/server/operations/ops/validate.ts +37 -0
- package/src/server/operations/ops/viewport.ts +219 -0
- package/src/server/operations/ops/webview.ts +339 -0
- package/src/server/operations/registry.ts +79 -0
- package/src/server/operations/types.ts +150 -0
- package/src/server/operations/webview-runner.ts +77 -0
- package/src/server/server.ts +158 -2255
- package/src/server/web-artifacts.ts +6 -2
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slice 2 operations (plan-005): group.create / group.add / group.remove
|
|
3
|
+
* (ungroup). Names match the canvas_batch op names.
|
|
4
|
+
*
|
|
5
|
+
* Note: the legacy POST /api/canvas/group handler only rejected MISSING child
|
|
6
|
+
* IDs (lowercase "missing child node ID…" message) — unlike the node.add
|
|
7
|
+
* group branch, it does not reject group-typed children. Replicated as-is.
|
|
8
|
+
*
|
|
9
|
+
* This module must never import server.ts or index.ts.
|
|
10
|
+
*/
|
|
11
|
+
import { z } from 'zod';
|
|
12
|
+
import { canvasState, type CanvasNodeState } from '../../canvas-state.js';
|
|
13
|
+
import {
|
|
14
|
+
createCanvasGroup,
|
|
15
|
+
groupCanvasNodes,
|
|
16
|
+
ungroupCanvasNodes,
|
|
17
|
+
} from '../../canvas-operations.js';
|
|
18
|
+
import { defineOperation, OperationError, type Operation } from '../types.js';
|
|
19
|
+
import { buildNodeResponse, createdNodePayloadFromNode, isRecord } from './nodes.js';
|
|
20
|
+
|
|
21
|
+
function pickChildLayout(value: unknown): 'grid' | 'column' | 'flow' | undefined {
|
|
22
|
+
return value === 'grid' || value === 'column' || value === 'flow' ? value : undefined;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// ── group.create ──────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
const groupCreateShape = {
|
|
28
|
+
title: z.string().optional().catch(undefined).describe('Group title (default: "Group")'),
|
|
29
|
+
childIds: z.unknown().optional().describe('Node IDs to include in the group. Group auto-sizes to fit them.'),
|
|
30
|
+
color: z.string().optional().catch(undefined).describe('Group accent color (CSS color string, e.g. "#4a9eff")'),
|
|
31
|
+
x: z.number().optional().catch(undefined).describe('X position (auto-computed from children if omitted)'),
|
|
32
|
+
y: z.number().optional().catch(undefined).describe('Y position (auto-computed from children if omitted)'),
|
|
33
|
+
width: z.number().optional().catch(undefined).describe('Width (auto-computed from children if omitted)'),
|
|
34
|
+
height: z.number().optional().catch(undefined).describe('Height (auto-computed from children if omitted)'),
|
|
35
|
+
childLayout: z.enum(['grid', 'column', 'flow']).optional().catch(undefined).describe('Optional child auto-layout. Omit to preserve current child positions.'),
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const groupCreateSchema = z.looseObject(groupCreateShape);
|
|
39
|
+
|
|
40
|
+
const groupCreateOperation = defineOperation<z.infer<typeof groupCreateSchema>, CanvasNodeState>({
|
|
41
|
+
name: 'group.create',
|
|
42
|
+
mutates: true,
|
|
43
|
+
input: groupCreateSchema,
|
|
44
|
+
inputShape: groupCreateShape,
|
|
45
|
+
http: {
|
|
46
|
+
method: 'POST',
|
|
47
|
+
path: '/api/canvas/group',
|
|
48
|
+
},
|
|
49
|
+
mcp: {
|
|
50
|
+
toolName: 'canvas_create_group',
|
|
51
|
+
description: 'Create a group (frame) on the canvas that visually contains other nodes. Groups are spatial containers — they communicate "these nodes belong together." If childIds are provided, grouping preserves child positions by default; pass childLayout to auto-pack them. You can also provide an explicit frame (x/y/width/height) and auto-arrange children inside it.',
|
|
52
|
+
extraShape: {
|
|
53
|
+
childIds: z.array(z.string()).optional().describe('Node IDs to include in the group. Group auto-sizes to fit them.'),
|
|
54
|
+
full: z.boolean().optional().describe('Return the full created group payload. Default false returns compact metadata.'),
|
|
55
|
+
verbose: z.boolean().optional().describe('Alias for full:true.'),
|
|
56
|
+
},
|
|
57
|
+
formatResult: (result, input) => {
|
|
58
|
+
const body = isRecord(result) ? result : {};
|
|
59
|
+
const node = body.node as CanvasNodeState | undefined;
|
|
60
|
+
const payload = node ? createdNodePayloadFromNode(node, input) : { ok: true };
|
|
61
|
+
return { content: [{ type: 'text' as const, text: JSON.stringify(payload, null, 2) }] };
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
handler: (input) => {
|
|
65
|
+
const body: Record<string, unknown> = input;
|
|
66
|
+
const title = typeof body.title === 'string' ? body.title : 'Group';
|
|
67
|
+
const childIds = Array.isArray(body.childIds)
|
|
68
|
+
? body.childIds.filter((id): id is string => typeof id === 'string')
|
|
69
|
+
: [];
|
|
70
|
+
const color = typeof body.color === 'string' ? body.color : undefined;
|
|
71
|
+
const x = typeof body.x === 'number' ? body.x : undefined;
|
|
72
|
+
const y = typeof body.y === 'number' ? body.y : undefined;
|
|
73
|
+
const width = typeof body.width === 'number' ? body.width : undefined;
|
|
74
|
+
const height = typeof body.height === 'number' ? body.height : undefined;
|
|
75
|
+
const childLayout = pickChildLayout(body.childLayout);
|
|
76
|
+
if (childIds.length > 0) {
|
|
77
|
+
const missingChildIds = childIds.filter((id) => !canvasState.getNode(id));
|
|
78
|
+
if (missingChildIds.length > 0) {
|
|
79
|
+
throw new OperationError(
|
|
80
|
+
`Cannot create group: missing child node ID${missingChildIds.length === 1 ? '' : 's'}: ${missingChildIds.join(', ')}.`,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const { node } = createCanvasGroup({ title, childIds, color, x, y, width, height, ...(childLayout ? { childLayout } : {}) });
|
|
85
|
+
return node;
|
|
86
|
+
},
|
|
87
|
+
serialize: (node) => buildNodeResponse(node),
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// ── group.add ─────────────────────────────────────────────────
|
|
91
|
+
|
|
92
|
+
const groupAddShape = {
|
|
93
|
+
groupId: z.string().optional().catch(undefined).describe('The group node ID'),
|
|
94
|
+
childIds: z.unknown().optional().describe('Node IDs to add to the group'),
|
|
95
|
+
childLayout: z.enum(['grid', 'column', 'flow']).optional().catch(undefined).describe('Optional child layout to apply while grouping'),
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const groupAddSchema = z.looseObject(groupAddShape);
|
|
99
|
+
|
|
100
|
+
const groupAddOperation = defineOperation<z.infer<typeof groupAddSchema>, Record<string, unknown>>({
|
|
101
|
+
name: 'group.add',
|
|
102
|
+
mutates: true,
|
|
103
|
+
input: groupAddSchema,
|
|
104
|
+
inputShape: groupAddShape,
|
|
105
|
+
http: {
|
|
106
|
+
method: 'POST',
|
|
107
|
+
path: '/api/canvas/group/add',
|
|
108
|
+
},
|
|
109
|
+
mcp: {
|
|
110
|
+
toolName: 'canvas_group_nodes',
|
|
111
|
+
description: 'Add nodes to an existing group. The nodes will be visually contained within the group frame.',
|
|
112
|
+
extraShape: {
|
|
113
|
+
groupId: z.string().describe('The group node ID'),
|
|
114
|
+
childIds: z.array(z.string()).describe('Node IDs to add to the group'),
|
|
115
|
+
},
|
|
116
|
+
formatResult: (result) => ({
|
|
117
|
+
content: [{ type: 'text' as const, text: JSON.stringify(result) }],
|
|
118
|
+
}),
|
|
119
|
+
},
|
|
120
|
+
handler: (input) => {
|
|
121
|
+
const body: Record<string, unknown> = input;
|
|
122
|
+
const groupId = typeof body.groupId === 'string' ? body.groupId : '';
|
|
123
|
+
const childIds = Array.isArray(body.childIds)
|
|
124
|
+
? body.childIds.filter((id): id is string => typeof id === 'string')
|
|
125
|
+
: [];
|
|
126
|
+
const childLayout = pickChildLayout(body.childLayout);
|
|
127
|
+
if (!groupId || childIds.length === 0) {
|
|
128
|
+
throw new OperationError('Missing groupId or childIds.');
|
|
129
|
+
}
|
|
130
|
+
const { ok } = groupCanvasNodes(groupId, childIds, childLayout ? { childLayout } : {});
|
|
131
|
+
if (!ok) throw new OperationError('Group not found or no valid children.');
|
|
132
|
+
return { ok: true, groupId };
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// ── group.remove (ungroup) ────────────────────────────────────
|
|
137
|
+
|
|
138
|
+
const groupRemoveShape = {
|
|
139
|
+
groupId: z.string().optional().catch(undefined).describe('The group node ID to ungroup'),
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const groupRemoveSchema = z.looseObject(groupRemoveShape);
|
|
143
|
+
|
|
144
|
+
const groupRemoveOperation = defineOperation<z.infer<typeof groupRemoveSchema>, Record<string, unknown>>({
|
|
145
|
+
name: 'group.remove',
|
|
146
|
+
mutates: true,
|
|
147
|
+
input: groupRemoveSchema,
|
|
148
|
+
inputShape: groupRemoveShape,
|
|
149
|
+
http: {
|
|
150
|
+
method: 'POST',
|
|
151
|
+
path: '/api/canvas/group/ungroup',
|
|
152
|
+
},
|
|
153
|
+
mcp: {
|
|
154
|
+
toolName: 'canvas_ungroup',
|
|
155
|
+
description: 'Remove all children from a group, releasing them as independent nodes. The group node itself remains (delete it separately with canvas_remove_node if desired).',
|
|
156
|
+
extraShape: {
|
|
157
|
+
groupId: z.string().describe('The group node ID to ungroup'),
|
|
158
|
+
},
|
|
159
|
+
formatResult: (result) => ({
|
|
160
|
+
content: [{ type: 'text' as const, text: JSON.stringify(result) }],
|
|
161
|
+
}),
|
|
162
|
+
},
|
|
163
|
+
handler: (input) => {
|
|
164
|
+
const groupId = typeof input.groupId === 'string' ? input.groupId : '';
|
|
165
|
+
if (!groupId) throw new OperationError('Missing groupId.');
|
|
166
|
+
const { ok } = ungroupCanvasNodes(groupId);
|
|
167
|
+
if (!ok) throw new OperationError('Group not found or empty.');
|
|
168
|
+
return { ok: true, groupId };
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
export const groupOperations: Operation[] = [
|
|
173
|
+
groupCreateOperation,
|
|
174
|
+
groupAddOperation,
|
|
175
|
+
groupRemoveOperation,
|
|
176
|
+
];
|