pmx-canvas 0.3.0 → 0.3.2
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 +73 -0
- package/dist/canvas/index.js +65 -65
- package/dist/types/cli/agent.d.ts +9 -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/ExtAppFrame.d.ts +9 -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/server/canvas-schema.d.ts +1 -1
- package/dist/types/server/html-primitives.d.ts +1 -1
- package/dist/types/server/index.d.ts +4 -4
- 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/server/server.d.ts +8 -0
- package/docs/cli.md +10 -1
- package/docs/http-api.md +28 -0
- package/docs/plans/plan-009-tech-debt-backlog.md +5 -5
- package/docs/screenshot.png +0 -0
- package/docs/tech-debt-assessment-2026-07.md +2 -2
- package/package.json +5 -2
- package/skills/pmx-canvas/SKILL.md +15 -10
- package/skills/pmx-canvas/references/full-reference.md +17 -3
- package/src/cli/agent.ts +1951 -1571
- package/src/cli/daemon.ts +460 -0
- package/src/cli/index.ts +80 -323
- 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 +194 -80
- 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 +35 -21
- package/src/mcp/server.ts +132 -70
- 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 +182 -39
- package/src/server/ax-state.ts +142 -47
- package/src/server/canvas-db.ts +213 -95
- package/src/server/canvas-operations.ts +180 -123
- package/src/server/canvas-provenance.ts +1 -4
- package/src/server/canvas-schema.ts +454 -73
- package/src/server/canvas-serialization.ts +27 -35
- package/src/server/canvas-state.ts +150 -58
- package/src/server/chart-template.ts +4 -5
- package/src/server/code-graph.ts +19 -6
- package/src/server/diagram-presets.ts +28 -29
- package/src/server/ext-app-lookup.ts +3 -12
- 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 +320 -217
- package/src/server/intent-registry.ts +2 -5
- 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 +1 -3
- package/src/server/operations/http.ts +2 -3
- package/src/server/operations/index.ts +7 -1
- package/src/server/operations/invoker.ts +4 -3
- package/src/server/operations/mcp.ts +22 -30
- package/src/server/operations/ops/annotation.ts +122 -10
- package/src/server/operations/ops/app.ts +98 -73
- 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 +41 -15
- 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 +298 -109
- package/src/server/operations/ops/query.ts +46 -28
- 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 +122 -1028
- 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/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,10 +1,7 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { canvasState } from './canvas-state.js';
|
|
3
3
|
import type { CanvasAnnotation, CanvasLayout, CanvasNodeState, ViewportState } from './canvas-state.js';
|
|
4
|
-
import {
|
|
5
|
-
normalizeCanvasNodeData,
|
|
6
|
-
type CanvasNodeProvenance,
|
|
7
|
-
} from './canvas-provenance.js';
|
|
4
|
+
import { normalizeCanvasNodeData, type CanvasNodeProvenance } from './canvas-provenance.js';
|
|
8
5
|
import { getCanvasNodeKind as getSharedCanvasNodeKind } from '../shared/canvas-node-kind.js';
|
|
9
6
|
import { canOpenNodeAsSurface } from '../shared/surface.js';
|
|
10
7
|
|
|
@@ -79,34 +76,32 @@ export function getCanvasNodeKind(node: CanvasNodeState, data: Record<string, un
|
|
|
79
76
|
}
|
|
80
77
|
|
|
81
78
|
export function getCanvasNodeTitle(node: CanvasNodeState): string | null {
|
|
82
|
-
return pickString(node.data.title)
|
|
83
|
-
?? (node.type === 'webpage' ? pickString(node.data.pageTitle) : null)
|
|
84
|
-
?? null;
|
|
79
|
+
return pickString(node.data.title) ?? (node.type === 'webpage' ? pickString(node.data.pageTitle) : null) ?? null;
|
|
85
80
|
}
|
|
86
81
|
|
|
87
82
|
export function getCanvasNodeContent(node: CanvasNodeState): string | null {
|
|
88
83
|
if (node.type === 'html') {
|
|
89
84
|
const primitive = typeof node.data.htmlPrimitive === 'string' ? node.data.htmlPrimitive : null;
|
|
90
85
|
const description = pickString(node.data.description);
|
|
91
|
-
return
|
|
92
|
-
|
|
93
|
-
??
|
|
94
|
-
|
|
95
|
-
|
|
86
|
+
return (
|
|
87
|
+
pickString(node.data.agentSummary) ??
|
|
88
|
+
pickString(node.data.contentSummary) ??
|
|
89
|
+
(primitive ? (description ? `${primitive}: ${description}` : primitive) : null)
|
|
90
|
+
);
|
|
96
91
|
}
|
|
97
|
-
return
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
92
|
+
return (
|
|
93
|
+
pickString(node.data.content) ??
|
|
94
|
+
pickString(node.data.fileContent) ??
|
|
95
|
+
pickString(node.data.text) ??
|
|
96
|
+
(node.type === 'file' ? pickString(node.data.path) : null) ??
|
|
97
|
+
(node.type === 'image' ? pickString(node.data.src) : null) ??
|
|
98
|
+
(node.type === 'webpage' ? pickString(node.data.url) : null) ??
|
|
99
|
+
null
|
|
100
|
+
);
|
|
104
101
|
}
|
|
105
102
|
|
|
106
103
|
export function getCanvasNodeSurfaceUrl(node: CanvasNodeState, data: Record<string, unknown>): string | null {
|
|
107
|
-
return canOpenNodeAsSurface(node.type, data)
|
|
108
|
-
? `/api/canvas/surface/${encodeURIComponent(node.id)}`
|
|
109
|
-
: null;
|
|
104
|
+
return canOpenNodeAsSurface(node.type, data) ? `/api/canvas/surface/${encodeURIComponent(node.id)}` : null;
|
|
110
105
|
}
|
|
111
106
|
|
|
112
107
|
export function serializeCanvasNode(node: CanvasNodeState): SerializedCanvasNode {
|
|
@@ -236,22 +231,21 @@ function rectsOverlap(
|
|
|
236
231
|
a: { x: number; y: number; width: number; height: number },
|
|
237
232
|
b: { x: number; y: number; width: number; height: number },
|
|
238
233
|
): boolean {
|
|
239
|
-
return a.x <= b.x + b.width &&
|
|
240
|
-
a.x + a.width >= b.x &&
|
|
241
|
-
a.y <= b.y + b.height &&
|
|
242
|
-
a.y + a.height >= b.y;
|
|
234
|
+
return a.x <= b.x + b.width && a.x + a.width >= b.x && a.y <= b.y + b.height && a.y + a.height >= b.y;
|
|
243
235
|
}
|
|
244
236
|
|
|
245
237
|
export function summarizeCanvasAnnotationForContext(
|
|
246
238
|
annotation: CanvasAnnotation,
|
|
247
239
|
nodes: CanvasNodeState[],
|
|
248
240
|
): CanvasAnnotationContextSummary {
|
|
249
|
-
const targetNodes = nodes.filter((node) =>
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
241
|
+
const targetNodes = nodes.filter((node) =>
|
|
242
|
+
rectsOverlap(annotation.bounds, {
|
|
243
|
+
x: node.position.x,
|
|
244
|
+
y: node.position.y,
|
|
245
|
+
width: node.size.width,
|
|
246
|
+
height: node.size.height,
|
|
247
|
+
}),
|
|
248
|
+
);
|
|
255
249
|
const targetNodeTitles = targetNodes.map((node) => getCanvasNodeTitle(node) ?? node.id);
|
|
256
250
|
return {
|
|
257
251
|
id: annotation.id,
|
|
@@ -284,9 +278,7 @@ export function buildCanvasSummary(): CanvasSummary {
|
|
|
284
278
|
typeCounts[kind] = (typeCounts[kind] ?? 0) + 1;
|
|
285
279
|
}
|
|
286
280
|
|
|
287
|
-
const pinnedTitles = layout.nodes
|
|
288
|
-
.filter((n) => pinnedIds.has(n.id))
|
|
289
|
-
.map((n) => getCanvasNodeTitle(n) ?? n.id);
|
|
281
|
+
const pinnedTitles = layout.nodes.filter((n) => pinnedIds.has(n.id)).map((n) => getCanvasNodeTitle(n) ?? n.id);
|
|
290
282
|
|
|
291
283
|
return {
|
|
292
284
|
totalNodes: layout.nodes.length,
|
|
@@ -11,7 +11,16 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { createHash } from 'node:crypto';
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
existsSync,
|
|
16
|
+
readFileSync,
|
|
17
|
+
writeFileSync,
|
|
18
|
+
mkdirSync,
|
|
19
|
+
readdirSync,
|
|
20
|
+
renameSync,
|
|
21
|
+
rmSync,
|
|
22
|
+
unlinkSync,
|
|
23
|
+
} from 'node:fs';
|
|
15
24
|
import { isAbsolute, join, dirname, relative } from 'node:path';
|
|
16
25
|
import { gzipSync, gunzipSync } from 'node:zlib';
|
|
17
26
|
import { normalizeCanvasNodeData } from './canvas-provenance.js';
|
|
@@ -123,9 +132,15 @@ interface LoadFromDiskOptions {
|
|
|
123
132
|
}
|
|
124
133
|
|
|
125
134
|
export const IMAGE_MIME_MAP: Record<string, string> = {
|
|
126
|
-
png: 'image/png',
|
|
127
|
-
|
|
128
|
-
|
|
135
|
+
png: 'image/png',
|
|
136
|
+
jpg: 'image/jpeg',
|
|
137
|
+
jpeg: 'image/jpeg',
|
|
138
|
+
gif: 'image/gif',
|
|
139
|
+
svg: 'image/svg+xml',
|
|
140
|
+
webp: 'image/webp',
|
|
141
|
+
bmp: 'image/bmp',
|
|
142
|
+
ico: 'image/x-icon',
|
|
143
|
+
avif: 'image/avif',
|
|
129
144
|
};
|
|
130
145
|
|
|
131
146
|
export interface CanvasSnapshot {
|
|
@@ -235,7 +250,34 @@ export interface CanvasNodeUpdate {
|
|
|
235
250
|
export type CanvasChangeType = 'pins' | 'nodes' | 'ax' | 'ax-timeline';
|
|
236
251
|
|
|
237
252
|
export interface MutationRecordInfo {
|
|
238
|
-
operationType:
|
|
253
|
+
operationType:
|
|
254
|
+
| 'addNode'
|
|
255
|
+
| 'updateNode'
|
|
256
|
+
| 'removeNode'
|
|
257
|
+
| 'addEdge'
|
|
258
|
+
| 'removeEdge'
|
|
259
|
+
| 'addAnnotation'
|
|
260
|
+
| 'removeAnnotation'
|
|
261
|
+
| 'clear'
|
|
262
|
+
| 'restoreSnapshot'
|
|
263
|
+
| 'setPins'
|
|
264
|
+
| 'setAxFocus'
|
|
265
|
+
| 'addWorkItem'
|
|
266
|
+
| 'updateWorkItem'
|
|
267
|
+
| 'requestApproval'
|
|
268
|
+
| 'resolveApproval'
|
|
269
|
+
| 'addReviewAnnotation'
|
|
270
|
+
| 'updateReviewAnnotation'
|
|
271
|
+
| 'requestElicitation'
|
|
272
|
+
| 'respondElicitation'
|
|
273
|
+
| 'requestMode'
|
|
274
|
+
| 'resolveModeRequest'
|
|
275
|
+
| 'setPolicy'
|
|
276
|
+
| 'arrange'
|
|
277
|
+
| 'batch'
|
|
278
|
+
| 'groupNodes'
|
|
279
|
+
| 'ungroupNodes'
|
|
280
|
+
| 'viewport';
|
|
239
281
|
description: string;
|
|
240
282
|
forward: () => void;
|
|
241
283
|
inverse: () => void;
|
|
@@ -287,13 +329,15 @@ function replaceById<T extends { id: string }>(list: T[], item: T): T[] {
|
|
|
287
329
|
}
|
|
288
330
|
|
|
289
331
|
function isPersistedBlobRef(value: unknown): value is PersistedBlobRef {
|
|
290
|
-
return
|
|
332
|
+
return (
|
|
333
|
+
isRecord(value) &&
|
|
291
334
|
value.__pmxCanvasBlob === 'v1' &&
|
|
292
335
|
typeof value.path === 'string' &&
|
|
293
336
|
typeof value.sha256 === 'string' &&
|
|
294
337
|
value.encoding === 'json+gzip' &&
|
|
295
338
|
typeof value.bytes === 'number' &&
|
|
296
|
-
typeof value.jsonBytes === 'number'
|
|
339
|
+
typeof value.jsonBytes === 'number'
|
|
340
|
+
);
|
|
297
341
|
}
|
|
298
342
|
|
|
299
343
|
class CanvasStateManager {
|
|
@@ -360,7 +404,11 @@ class CanvasStateManager {
|
|
|
360
404
|
/** Run a function with mutation recording suppressed (for undo/redo replay and computed edges). */
|
|
361
405
|
withSuppressedRecording(fn: () => void): void {
|
|
362
406
|
this._suppressRecordingDepth++;
|
|
363
|
-
try {
|
|
407
|
+
try {
|
|
408
|
+
fn();
|
|
409
|
+
} finally {
|
|
410
|
+
this._suppressRecordingDepth--;
|
|
411
|
+
}
|
|
364
412
|
}
|
|
365
413
|
|
|
366
414
|
/** Create a closure that runs with recording suppressed. */
|
|
@@ -388,9 +436,8 @@ class CanvasStateManager {
|
|
|
388
436
|
bounds: { x: number; y: number; width: number; height: number },
|
|
389
437
|
existingGroups?: CanvasPlacementRect[],
|
|
390
438
|
): void {
|
|
391
|
-
const otherGroups =
|
|
392
|
-
(node) => node.id !== groupId && node.type === 'group'
|
|
393
|
-
);
|
|
439
|
+
const otherGroups =
|
|
440
|
+
existingGroups ?? Array.from(this.nodes.values()).filter((node) => node.id !== groupId && node.type === 'group');
|
|
394
441
|
const resolved = resolveGroupCollision(bounds, otherGroups);
|
|
395
442
|
const deltaX = resolved.x - bounds.x;
|
|
396
443
|
const deltaY = resolved.y - bounds.y;
|
|
@@ -472,7 +519,12 @@ class CanvasStateManager {
|
|
|
472
519
|
}
|
|
473
520
|
}
|
|
474
521
|
|
|
475
|
-
private translateGroupChildren(
|
|
522
|
+
private translateGroupChildren(
|
|
523
|
+
groupId: string,
|
|
524
|
+
deltaX: number,
|
|
525
|
+
deltaY: number,
|
|
526
|
+
skipIds: ReadonlySet<string> = new Set(),
|
|
527
|
+
): void {
|
|
476
528
|
if (deltaX === 0 && deltaY === 0) return;
|
|
477
529
|
const snapshot = this.getGroupSnapshot(groupId);
|
|
478
530
|
if (!snapshot) return;
|
|
@@ -509,9 +561,7 @@ class CanvasStateManager {
|
|
|
509
561
|
const snapshot = this.getGroupSnapshot(groupId);
|
|
510
562
|
if (!snapshot || snapshot.children.length === 0) return;
|
|
511
563
|
if (snapshot.group.data.frameMode === 'manual') {
|
|
512
|
-
const sorted = [...snapshot.children].sort(
|
|
513
|
-
(a, b) => a.position.y - b.position.y || a.position.x - b.position.x,
|
|
514
|
-
);
|
|
564
|
+
const sorted = [...snapshot.children].sort((a, b) => a.position.y - b.position.y || a.position.x - b.position.x);
|
|
515
565
|
const left = snapshot.group.position.x + GROUP_PAD;
|
|
516
566
|
const top = snapshot.group.position.y + GROUP_TITLEBAR_HEIGHT + GROUP_PAD;
|
|
517
567
|
const right = snapshot.group.position.x + snapshot.group.size.width - GROUP_PAD;
|
|
@@ -595,9 +645,10 @@ class CanvasStateManager {
|
|
|
595
645
|
}
|
|
596
646
|
|
|
597
647
|
// Keep legacy _stateFilePath for JSON migration detection
|
|
598
|
-
this._stateFilePath =
|
|
599
|
-
|
|
600
|
-
|
|
648
|
+
this._stateFilePath =
|
|
649
|
+
stateFileOverride && !stateFileOverride.endsWith('.db')
|
|
650
|
+
? stateFileOverride
|
|
651
|
+
: join(workspaceRoot, PMX_CANVAS_DIR, STATE_FILENAME);
|
|
601
652
|
|
|
602
653
|
try {
|
|
603
654
|
this._db = openCanvasDb(dbPath);
|
|
@@ -749,9 +800,7 @@ class CanvasStateManager {
|
|
|
749
800
|
private externalizePersistedStateBlobs<T extends PersistedCanvasState>(state: T): T {
|
|
750
801
|
return {
|
|
751
802
|
...state,
|
|
752
|
-
nodes: Array.isArray(state.nodes)
|
|
753
|
-
? state.nodes.map((node) => this.externalizeNodeDataBlobs(node))
|
|
754
|
-
: [],
|
|
803
|
+
nodes: Array.isArray(state.nodes) ? state.nodes.map((node) => this.externalizeNodeDataBlobs(node)) : [],
|
|
755
804
|
};
|
|
756
805
|
}
|
|
757
806
|
|
|
@@ -1160,7 +1209,9 @@ class CanvasStateManager {
|
|
|
1160
1209
|
if (!dir || !existsSync(dir)) return [];
|
|
1161
1210
|
|
|
1162
1211
|
try {
|
|
1163
|
-
const files = readdirSync(dir)
|
|
1212
|
+
const files = readdirSync(dir)
|
|
1213
|
+
.filter((f) => f.endsWith('.json'))
|
|
1214
|
+
.sort();
|
|
1164
1215
|
const snapshots: CanvasSnapshot[] = [];
|
|
1165
1216
|
for (const file of files) {
|
|
1166
1217
|
try {
|
|
@@ -1232,7 +1283,9 @@ class CanvasStateManager {
|
|
|
1232
1283
|
viewport: structuredClone(resolved.state.viewport),
|
|
1233
1284
|
nodes: Array.isArray(resolved.state.nodes) ? resolved.state.nodes.map((node) => structuredClone(node)) : [],
|
|
1234
1285
|
edges: Array.isArray(resolved.state.edges) ? resolved.state.edges.map((edge) => structuredClone(edge)) : [],
|
|
1235
|
-
annotations: Array.isArray(resolved.state.annotations)
|
|
1286
|
+
annotations: Array.isArray(resolved.state.annotations)
|
|
1287
|
+
? resolved.state.annotations.map((annotation) => structuredClone(annotation))
|
|
1288
|
+
: [],
|
|
1236
1289
|
contextPins: Array.isArray(resolved.state.contextPins) ? [...resolved.state.contextPins] : [],
|
|
1237
1290
|
ax: resolved.state.ax ? structuredClone(resolved.state.ax) : createEmptyAxState(),
|
|
1238
1291
|
};
|
|
@@ -1273,7 +1326,9 @@ class CanvasStateManager {
|
|
|
1273
1326
|
}
|
|
1274
1327
|
|
|
1275
1328
|
/** Read a snapshot's data without restoring it (for diff). Resolves by ID or name. */
|
|
1276
|
-
getSnapshotData(
|
|
1329
|
+
getSnapshotData(
|
|
1330
|
+
idOrName: string,
|
|
1331
|
+
): { name: string; nodes: CanvasNodeState[]; edges: CanvasEdge[]; annotations: CanvasAnnotation[] } | null {
|
|
1277
1332
|
const resolved = this.readResolvedSnapshot(idOrName);
|
|
1278
1333
|
if (!resolved) return null;
|
|
1279
1334
|
const state = {
|
|
@@ -1286,7 +1341,9 @@ class CanvasStateManager {
|
|
|
1286
1341
|
name: resolved.snapshot.name,
|
|
1287
1342
|
nodes: Array.isArray(state.nodes) ? state.nodes.map((node) => structuredClone(node)) : [],
|
|
1288
1343
|
edges: Array.isArray(state.edges) ? state.edges.map((edge) => structuredClone(edge)) : [],
|
|
1289
|
-
annotations: Array.isArray(state.annotations)
|
|
1344
|
+
annotations: Array.isArray(state.annotations)
|
|
1345
|
+
? state.annotations.map((annotation) => structuredClone(annotation))
|
|
1346
|
+
: [],
|
|
1290
1347
|
};
|
|
1291
1348
|
}
|
|
1292
1349
|
|
|
@@ -1344,9 +1401,10 @@ class CanvasStateManager {
|
|
|
1344
1401
|
// (including undock → dockPosition null) are respected (see normalizeNode).
|
|
1345
1402
|
// Skip during suppressed replay (undo/redo re-add) so a deliberately-undocked
|
|
1346
1403
|
// context node is restored verbatim instead of being snapped back to the dock.
|
|
1347
|
-
const seeded =
|
|
1348
|
-
|
|
1349
|
-
|
|
1404
|
+
const seeded =
|
|
1405
|
+
node.type === 'context' && node.dockPosition == null && this._suppressRecordingDepth === 0
|
|
1406
|
+
? { ...node, dockPosition: 'right' as const, collapsed: true }
|
|
1407
|
+
: node;
|
|
1350
1408
|
const cloned = structuredClone(this.normalizeNode(seeded));
|
|
1351
1409
|
this.nodes.set(node.id, cloned);
|
|
1352
1410
|
this.scheduleSave();
|
|
@@ -1372,11 +1430,7 @@ class CanvasStateManager {
|
|
|
1372
1430
|
if (!existing) return;
|
|
1373
1431
|
const oldSnapshot = structuredClone(existing);
|
|
1374
1432
|
if (existing.type === 'group' && patch.position) {
|
|
1375
|
-
this.translateGroupChildren(
|
|
1376
|
-
id,
|
|
1377
|
-
patch.position.x - existing.position.x,
|
|
1378
|
-
patch.position.y - existing.position.y,
|
|
1379
|
-
);
|
|
1433
|
+
this.translateGroupChildren(id, patch.position.x - existing.position.x, patch.position.y - existing.position.y);
|
|
1380
1434
|
}
|
|
1381
1435
|
const nextNode = this.normalizeNode({ ...existing, ...patch });
|
|
1382
1436
|
this.nodes.set(id, nextNode);
|
|
@@ -1395,7 +1449,11 @@ class CanvasStateManager {
|
|
|
1395
1449
|
operationType: 'updateNode',
|
|
1396
1450
|
description: `Updated node "${(existing.data.title as string) ?? id}"`,
|
|
1397
1451
|
forward: this.suppressed(() => this.updateNode(id, structuredClone(patch))),
|
|
1398
|
-
inverse: this.suppressed(() => {
|
|
1452
|
+
inverse: this.suppressed(() => {
|
|
1453
|
+
this.nodes.set(id, structuredClone(oldSnapshot));
|
|
1454
|
+
this.scheduleSave();
|
|
1455
|
+
this.notifyChange('nodes');
|
|
1456
|
+
}),
|
|
1399
1457
|
});
|
|
1400
1458
|
}
|
|
1401
1459
|
|
|
@@ -1448,7 +1506,8 @@ class CanvasStateManager {
|
|
|
1448
1506
|
// original deletion already recorded the note, so replaying must NOT append a
|
|
1449
1507
|
// duplicate (the timeline is append-only). `revalidateAfterNodeRemoval` above
|
|
1450
1508
|
// still runs unconditionally — only the timeline note is gated.
|
|
1451
|
-
const affected =
|
|
1509
|
+
const affected =
|
|
1510
|
+
orphaned.reanchoredIds.length > 0 || orphaned.removedReviewIds.length > 0 || orphaned.reanchoredFocus;
|
|
1452
1511
|
if (existing && this._suppressRecordingDepth === 0 && affected) {
|
|
1453
1512
|
const title = (existing.data.title as string) ?? id;
|
|
1454
1513
|
const focusNote = orphaned.reanchoredFocus ? ' (focus anchor cleared)' : '';
|
|
@@ -1611,9 +1670,7 @@ class CanvasStateManager {
|
|
|
1611
1670
|
const oldSnapshots = new Map<string, CanvasNodeState>();
|
|
1612
1671
|
const appliedUpdates: CanvasNodeUpdate[] = [];
|
|
1613
1672
|
const explicitPositionUpdateIds = new Set(
|
|
1614
|
-
updates
|
|
1615
|
-
.filter((update) => update.position !== undefined)
|
|
1616
|
-
.map((update) => update.id),
|
|
1673
|
+
updates.filter((update) => update.position !== undefined).map((update) => update.id),
|
|
1617
1674
|
);
|
|
1618
1675
|
|
|
1619
1676
|
for (const update of updates) {
|
|
@@ -1623,16 +1680,10 @@ class CanvasStateManager {
|
|
|
1623
1680
|
continue;
|
|
1624
1681
|
}
|
|
1625
1682
|
const nextPatch: Partial<CanvasNodeState> = {};
|
|
1626
|
-
if (
|
|
1627
|
-
update.position &&
|
|
1628
|
-
(update.position.x !== existing.position.x || update.position.y !== existing.position.y)
|
|
1629
|
-
) {
|
|
1683
|
+
if (update.position && (update.position.x !== existing.position.x || update.position.y !== existing.position.y)) {
|
|
1630
1684
|
nextPatch.position = update.position;
|
|
1631
1685
|
}
|
|
1632
|
-
if (
|
|
1633
|
-
update.size &&
|
|
1634
|
-
(update.size.width !== existing.size.width || update.size.height !== existing.size.height)
|
|
1635
|
-
) {
|
|
1686
|
+
if (update.size && (update.size.width !== existing.size.width || update.size.height !== existing.size.height)) {
|
|
1636
1687
|
nextPatch.size = update.size;
|
|
1637
1688
|
}
|
|
1638
1689
|
if (update.collapsed !== undefined && update.collapsed !== existing.collapsed) {
|
|
@@ -1655,10 +1706,13 @@ class CanvasStateManager {
|
|
|
1655
1706
|
explicitPositionUpdateIds,
|
|
1656
1707
|
);
|
|
1657
1708
|
}
|
|
1658
|
-
this.nodes.set(
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1709
|
+
this.nodes.set(
|
|
1710
|
+
update.id,
|
|
1711
|
+
this.normalizeNode({
|
|
1712
|
+
...existing,
|
|
1713
|
+
...nextPatch,
|
|
1714
|
+
}),
|
|
1715
|
+
);
|
|
1662
1716
|
const parentGroupId = existing.data.parentGroup as string | undefined;
|
|
1663
1717
|
if (parentGroupId) {
|
|
1664
1718
|
touchedParentGroups.add(parentGroupId);
|
|
@@ -1683,7 +1737,10 @@ class CanvasStateManager {
|
|
|
1683
1737
|
operationType: 'batch',
|
|
1684
1738
|
description: formatBatchUpdateDescription(appliedUpdates),
|
|
1685
1739
|
forward: this.suppressed(() => {
|
|
1686
|
-
this.applyUpdates(
|
|
1740
|
+
this.applyUpdates(
|
|
1741
|
+
appliedUpdates.map((update) => structuredClone(update)),
|
|
1742
|
+
options,
|
|
1743
|
+
);
|
|
1687
1744
|
}),
|
|
1688
1745
|
inverse: this.suppressed(() => {
|
|
1689
1746
|
for (const snapshot of inverseSnapshots) {
|
|
@@ -1883,7 +1940,11 @@ class CanvasStateManager {
|
|
|
1883
1940
|
}
|
|
1884
1941
|
|
|
1885
1942
|
/** Invoke a registry-gated PMX command intent — records a timeline event (no execution). */
|
|
1886
|
-
invokeCommand(
|
|
1943
|
+
invokeCommand(
|
|
1944
|
+
name: string,
|
|
1945
|
+
args: Record<string, unknown> | null = null,
|
|
1946
|
+
options: { source?: PmxAxSource } = {},
|
|
1947
|
+
): PmxAxEvent | null {
|
|
1887
1948
|
return this.ax.invokeCommand(name, args, options);
|
|
1888
1949
|
}
|
|
1889
1950
|
|
|
@@ -1905,14 +1966,27 @@ class CanvasStateManager {
|
|
|
1905
1966
|
|
|
1906
1967
|
// ── Timeline (DB-direct; NOT in _axState; NOT history-recorded) ───
|
|
1907
1968
|
recordAxEvent(
|
|
1908
|
-
input: {
|
|
1969
|
+
input: {
|
|
1970
|
+
kind: PmxAxEventKind;
|
|
1971
|
+
summary: string;
|
|
1972
|
+
detail?: string | null;
|
|
1973
|
+
nodeIds?: string[];
|
|
1974
|
+
data?: Record<string, unknown> | null;
|
|
1975
|
+
},
|
|
1909
1976
|
options: { source?: PmxAxSource } = {},
|
|
1910
1977
|
): PmxAxEvent {
|
|
1911
1978
|
return this.ax.recordAxEvent(input, options);
|
|
1912
1979
|
}
|
|
1913
1980
|
|
|
1914
1981
|
addEvidence(
|
|
1915
|
-
input: {
|
|
1982
|
+
input: {
|
|
1983
|
+
kind: PmxAxEvidenceKind;
|
|
1984
|
+
title: string;
|
|
1985
|
+
body?: string | null;
|
|
1986
|
+
ref?: string | null;
|
|
1987
|
+
nodeIds?: string[];
|
|
1988
|
+
data?: Record<string, unknown> | null;
|
|
1989
|
+
},
|
|
1916
1990
|
options: { source?: PmxAxSource } = {},
|
|
1917
1991
|
): PmxAxEvidence {
|
|
1918
1992
|
return this.ax.addEvidence(input, options);
|
|
@@ -1938,11 +2012,23 @@ class CanvasStateManager {
|
|
|
1938
2012
|
reactions?: {
|
|
1939
2013
|
workItem?: false | { status?: PmxAxWorkItemStatus; detail?: string | null };
|
|
1940
2014
|
evidence?: false | { kind?: PmxAxEvidenceKind; body?: string | null };
|
|
1941
|
-
review?:
|
|
2015
|
+
review?:
|
|
2016
|
+
| false
|
|
2017
|
+
| {
|
|
2018
|
+
severity?: PmxAxReviewSeverity;
|
|
2019
|
+
kind?: PmxAxReviewKind;
|
|
2020
|
+
anchorType?: PmxAxReviewAnchorType;
|
|
2021
|
+
nodeId?: string | null;
|
|
2022
|
+
};
|
|
1942
2023
|
};
|
|
1943
2024
|
},
|
|
1944
2025
|
options: { source?: PmxAxSource } = {},
|
|
1945
|
-
): {
|
|
2026
|
+
): {
|
|
2027
|
+
event: PmxAxEvent;
|
|
2028
|
+
workItem: PmxAxWorkItem | null;
|
|
2029
|
+
evidence: PmxAxEvidence | null;
|
|
2030
|
+
review: PmxAxReviewAnnotation | null;
|
|
2031
|
+
} {
|
|
1946
2032
|
return this.ax.ingestActivity(input, options);
|
|
1947
2033
|
}
|
|
1948
2034
|
|
|
@@ -1974,7 +2060,12 @@ class CanvasStateManager {
|
|
|
1974
2060
|
return this.ax.getAxTimelineSummary();
|
|
1975
2061
|
}
|
|
1976
2062
|
|
|
1977
|
-
getAxTimeline(q: AxTimelineQuery = {}): {
|
|
2063
|
+
getAxTimeline(q: AxTimelineQuery = {}): {
|
|
2064
|
+
events: PmxAxEvent[];
|
|
2065
|
+
evidence: PmxAxEvidence[];
|
|
2066
|
+
steering: PmxAxSteeringMessage[];
|
|
2067
|
+
summary: PmxAxTimelineSummary;
|
|
2068
|
+
} {
|
|
1978
2069
|
return this.ax.getAxTimeline(q);
|
|
1979
2070
|
}
|
|
1980
2071
|
|
|
@@ -2056,7 +2147,8 @@ class CanvasStateManager {
|
|
|
2056
2147
|
const c = this.nodes.get(id);
|
|
2057
2148
|
if (!c) continue;
|
|
2058
2149
|
const d = { ...c.data };
|
|
2059
|
-
if (oldParent) d.parentGroup = oldParent;
|
|
2150
|
+
if (oldParent) d.parentGroup = oldParent;
|
|
2151
|
+
else delete d.parentGroup;
|
|
2060
2152
|
this.nodes.set(id, { ...c, data: d });
|
|
2061
2153
|
}
|
|
2062
2154
|
this.scheduleSave();
|
|
@@ -15,15 +15,14 @@ import { createRequire } from 'node:module';
|
|
|
15
15
|
|
|
16
16
|
const require = createRequire(import.meta.url);
|
|
17
17
|
const extAppsPackageDir = dirname(require.resolve('@modelcontextprotocol/ext-apps/package.json'));
|
|
18
|
-
const extAppsRuntimeSource = readFileSync(
|
|
19
|
-
join(extAppsPackageDir, 'dist', 'src', 'app-with-deps.js'),
|
|
20
|
-
'utf-8',
|
|
21
|
-
);
|
|
18
|
+
const extAppsRuntimeSource = readFileSync(join(extAppsPackageDir, 'dist', 'src', 'app-with-deps.js'), 'utf-8');
|
|
22
19
|
const appBindingMatch = extAppsRuntimeSource.match(/([A-Za-z_$][\w$]*) as App/);
|
|
23
20
|
const transportBindingMatch = extAppsRuntimeSource.match(/([A-Za-z_$][\w$]*) as PostMessageTransport/);
|
|
24
21
|
|
|
25
22
|
if (!appBindingMatch || !transportBindingMatch) {
|
|
26
|
-
throw new Error(
|
|
23
|
+
throw new Error(
|
|
24
|
+
'Failed to locate App or PostMessageTransport export bindings in @modelcontextprotocol/ext-apps runtime',
|
|
25
|
+
);
|
|
27
26
|
}
|
|
28
27
|
|
|
29
28
|
const extAppsBootstrapSource = `${extAppsRuntimeSource}
|
package/src/server/code-graph.ts
CHANGED
|
@@ -123,9 +123,20 @@ function resolveImportPath(specifier: string, fromFilePath: string): string | nu
|
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
const RESOLVE_EXTENSIONS = [
|
|
126
|
-
'',
|
|
127
|
-
'
|
|
128
|
-
'.
|
|
126
|
+
'',
|
|
127
|
+
'.ts',
|
|
128
|
+
'.tsx',
|
|
129
|
+
'.js',
|
|
130
|
+
'.jsx',
|
|
131
|
+
'.mjs',
|
|
132
|
+
'.mts',
|
|
133
|
+
'/index.ts',
|
|
134
|
+
'/index.tsx',
|
|
135
|
+
'/index.js',
|
|
136
|
+
'/index.jsx',
|
|
137
|
+
'.py',
|
|
138
|
+
'.go',
|
|
139
|
+
'.rs',
|
|
129
140
|
];
|
|
130
141
|
|
|
131
142
|
function tryResolveWithExtensions(basePath: string): string | null {
|
|
@@ -179,7 +190,7 @@ function getFileNodes(): CanvasNodeState[] {
|
|
|
179
190
|
*/
|
|
180
191
|
function buildPathIndex(fileNodes?: CanvasNodeState[]): Map<string, string> {
|
|
181
192
|
const index = new Map<string, string>();
|
|
182
|
-
for (const node of
|
|
193
|
+
for (const node of fileNodes ?? getFileNodes()) {
|
|
183
194
|
index.set(node.data.path as string, node.id);
|
|
184
195
|
}
|
|
185
196
|
return index;
|
|
@@ -299,7 +310,7 @@ export function buildCodeGraphSummary(): CodeGraphSummary {
|
|
|
299
310
|
path: relative(process.cwd(), node.data.path as string) || (node.data.path as string),
|
|
300
311
|
title: (node.data.title as string) ?? null,
|
|
301
312
|
imports: importSpecs.get(node.id) ?? [],
|
|
302
|
-
importedBy: [...
|
|
313
|
+
importedBy: [...inc].map((id) => {
|
|
303
314
|
const path = idToPath.get(id);
|
|
304
315
|
return path ? relative(process.cwd(), path) : id;
|
|
305
316
|
}),
|
|
@@ -350,7 +361,9 @@ export function formatCodeGraph(summary: CodeGraphSummary): string {
|
|
|
350
361
|
}
|
|
351
362
|
|
|
352
363
|
if (summary.isolatedFiles.length > 0) {
|
|
353
|
-
lines.push(
|
|
364
|
+
lines.push(
|
|
365
|
+
`Isolated files (${summary.isolatedFiles.length}): ${summary.isolatedFiles.map((f) => f.title ?? f.path).join(', ')}`,
|
|
366
|
+
);
|
|
354
367
|
lines.push('');
|
|
355
368
|
}
|
|
356
369
|
|