pmx-canvas 0.8.2 → 0.9.1
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/.github/extensions/pmx-canvas/runtime.json +1 -1
- package/.github/extensions/pmx-canvas/tools.json +601 -22
- package/CHANGELOG.md +59 -0
- package/Readme.md +131 -532
- package/dist/canvas/global.css +2724 -476
- package/dist/canvas/index.js +168 -107
- package/dist/canvas/mcp-app.html +2899 -596
- package/dist/json-render/index.js +20 -20
- package/dist/types/client/canvas/BarHint.d.ts +12 -2
- package/dist/types/client/canvas/BoardLinkDialog.d.ts +3 -1
- package/dist/types/client/canvas/BoardPinButton.d.ts +10 -0
- package/dist/types/client/canvas/BoardSwitcher.d.ts +1 -1
- package/dist/types/client/canvas/CanvasViewport.d.ts +1 -1
- package/dist/types/client/canvas/CommandBar.d.ts +3 -4
- package/dist/types/client/canvas/ConnectionBanner.d.ts +2 -1
- package/dist/types/client/canvas/ContextChip.d.ts +3 -0
- package/dist/types/client/canvas/CrossBoardReferences.d.ts +5 -0
- package/dist/types/client/canvas/EdgeLayer.d.ts +14 -3
- package/dist/types/client/canvas/EmptyState.d.ts +4 -4
- package/dist/types/client/canvas/HomeGraphView.d.ts +2 -0
- package/dist/types/client/canvas/HomeMapView.d.ts +1 -0
- package/dist/types/client/canvas/HomeReviewView.d.ts +6 -0
- package/dist/types/client/canvas/HomeView.d.ts +9 -7
- package/dist/types/client/canvas/KnowledgeDialog.d.ts +4 -0
- package/dist/types/client/canvas/LifecycleChrome.d.ts +5 -0
- package/dist/types/client/canvas/LifecycleFields.d.ts +7 -0
- package/dist/types/client/canvas/LifecyclePanel.d.ts +1 -0
- package/dist/types/client/canvas/MemoryInspector.d.ts +7 -0
- package/dist/types/client/canvas/NearPinLayer.d.ts +14 -0
- package/dist/types/client/canvas/NodeContextMark.d.ts +40 -7
- package/dist/types/client/canvas/RelationPresetsDialog.d.ts +4 -0
- package/dist/types/client/canvas/RelationsPanel.d.ts +4 -0
- package/dist/types/client/canvas/SessionPanel.d.ts +2 -0
- package/dist/types/client/canvas/canvas-area.d.ts +1 -1
- package/dist/types/client/canvas/graph-geometry.d.ts +27 -0
- package/dist/types/client/canvas/graph-model.d.ts +37 -0
- package/dist/types/client/canvas/memory-layout.d.ts +23 -0
- package/dist/types/client/canvas/use-chrome-height.d.ts +2 -0
- package/dist/types/client/canvas/use-focus-trap.d.ts +1 -0
- package/dist/types/client/canvas/use-memory-graph.d.ts +8 -0
- package/dist/types/client/canvas/use-pan-zoom.d.ts +4 -1
- package/dist/types/client/icons.d.ts +0 -10
- package/dist/types/client/nodes/AxStepControls.d.ts +1 -1
- package/dist/types/client/nodes/InlineMarkdownEditor.d.ts +4 -4
- package/dist/types/client/nodes/MarkdownNode.d.ts +5 -1
- package/dist/types/client/nodes/McpAppNode.d.ts +4 -4
- package/dist/types/client/nodes/viewer-runtime-state.d.ts +1 -1
- package/dist/types/client/state/boards-store.d.ts +81 -9
- package/dist/types/client/state/canvas-store.d.ts +14 -1
- package/dist/types/client/state/context-chip-store.d.ts +22 -0
- package/dist/types/client/state/context-status-store.d.ts +35 -0
- package/dist/types/client/state/human-store.d.ts +2 -1
- package/dist/types/client/state/intent-bridge.d.ts +11 -8
- package/dist/types/client/state/knowledge-store.d.ts +22 -0
- package/dist/types/client/state/near-pin-store.d.ts +44 -1
- package/dist/types/client/state/presence-store.d.ts +4 -4
- package/dist/types/client/state/presentation.d.ts +28 -0
- package/dist/types/client/state/relation-store.d.ts +10 -0
- package/dist/types/client/state/session-store.d.ts +41 -2
- package/dist/types/client/state/workspace-settings.d.ts +7 -0
- package/dist/types/client/types.d.ts +13 -1
- package/dist/types/client/utils/keyboard.d.ts +2 -0
- package/dist/types/client/utils/time-ago.d.ts +4 -0
- package/dist/types/mcp/context-uri-template.d.ts +3 -1
- package/dist/types/server/agent-presence.d.ts +10 -3
- package/dist/types/server/ax-context.d.ts +1 -1
- package/dist/types/server/canvas-db.d.ts +30 -7
- package/dist/types/server/canvas-operations.d.ts +24 -2
- package/dist/types/server/canvas-provenance.d.ts +22 -2
- package/dist/types/server/canvas-serialization.d.ts +2 -0
- package/dist/types/server/canvas-state.d.ts +93 -15
- package/dist/types/server/canvas-validation.d.ts +1 -1
- package/dist/types/server/context-brief.d.ts +94 -6
- package/dist/types/server/context-reads.d.ts +33 -7
- package/dist/types/server/edit-summary.d.ts +20 -0
- package/dist/types/server/index.d.ts +71 -10
- package/dist/types/server/knowledge-lifecycle.d.ts +221 -0
- package/dist/types/server/lifecycle-status.d.ts +10 -0
- package/dist/types/server/memory-graph.d.ts +9 -0
- package/dist/types/server/mutation-history.d.ts +2 -0
- package/dist/types/server/operations/ops/ax-shared.d.ts +1 -1
- package/dist/types/server/operations/ops/boards.d.ts +2 -1
- package/dist/types/server/operations/ops/lifecycle.d.ts +2 -0
- package/dist/types/server/operations/ops/memory-graph.d.ts +2 -0
- package/dist/types/server/operations/ops/nodes.d.ts +5 -0
- package/dist/types/server/operations/ops/query.d.ts +23 -0
- package/dist/types/server/operations/registry.d.ts +1 -0
- package/dist/types/server/placement.d.ts +7 -0
- package/dist/types/server/scope-fence.d.ts +2 -1
- package/dist/types/server/spatial-analysis.d.ts +6 -3
- package/dist/types/server/tour-control.d.ts +32 -0
- package/dist/types/server/wiki-links.d.ts +14 -0
- package/dist/types/server/workbench-auth.d.ts +2 -0
- package/dist/types/shared/agent-context.d.ts +2 -0
- package/dist/types/shared/agent-presence.d.ts +15 -12
- package/dist/types/shared/auto-arrange.d.ts +1 -0
- package/dist/types/shared/boards.d.ts +4 -4
- package/dist/types/shared/knowledge-lifecycle.d.ts +41 -0
- package/dist/types/shared/knowledge-presets.d.ts +6 -0
- package/dist/types/shared/lifecycle-status.d.ts +24 -0
- package/dist/types/shared/memory-graph.d.ts +53 -0
- package/dist/types/shared/relations.d.ts +68 -0
- package/dist/types/shared/semantic-attention.d.ts +2 -0
- package/dist/types/shared/tour.d.ts +38 -0
- package/dist/types/shared/workspace-settings.d.ts +11 -0
- package/docs/RELEASE.md +21 -3
- package/docs/adr-001-bun-only-runtime.md +7 -1
- package/docs/adr-002-shared-product-separate-runtimes.md +93 -0
- package/docs/api-reference.md +109 -0
- package/docs/ax-host-adapter-contract.md +1 -1
- package/docs/ax-state-contract.md +5 -4
- package/docs/cli.md +153 -15
- package/docs/demos/pmx-canvas-demo.mp4 +0 -0
- package/docs/design/AgentContext.dc.html +3 -3
- package/docs/design/BoardPins.dc.html +37 -22
- package/docs/design/Categories.dc.html +208 -0
- package/docs/design/CategoryEditor.dc.html +33 -0
- package/docs/design/Chrome.dc.html +52 -9
- package/docs/design/ContextChip.dc.html +224 -0
- package/docs/design/ContextChip600.dc.html +136 -0
- package/docs/design/EmptyState.dc.html +45 -0
- package/docs/design/Flows.dc.html +23 -1
- package/docs/design/HeaderMarks.dc.html +183 -0
- package/docs/design/Home.dc.html +19 -1
- package/docs/design/HomeGraph.dc.html +86 -202
- package/docs/design/HomeMap.dc.html +23 -1
- package/docs/design/HomeTeam.dc.html +32 -14
- package/docs/design/Lifecycle.dc.html +22 -0
- package/docs/design/Lifecycle600.dc.html +18 -0
- package/docs/design/LifecycleVariants.dc.html +157 -0
- package/docs/design/LinkAuthorship.dc.html +139 -0
- package/docs/design/LinksChanged.dc.html +203 -0
- package/docs/design/LinksOptions.dc.html +137 -0
- package/docs/design/LinksOptions600.dc.html +134 -0
- package/docs/design/NearPin.dc.html +9 -10
- package/docs/design/Pane600.dc.html +4 -3
- package/docs/design/Relations.dc.html +3 -2
- package/docs/design/Restore.dc.html +152 -0
- package/docs/design/SeeChange.dc.html +267 -0
- package/docs/design/Uses.dc.html +12 -6
- package/docs/design/canvas.json +146 -19
- package/docs/design.md +449 -16
- package/docs/environment.md +0 -1
- package/docs/evals/curation-effect.md +128 -0
- package/docs/http-api.md +459 -37
- package/docs/mcp.md +146 -18
- package/docs/node-types.md +58 -3
- package/docs/plans/design-gaps-2026-10-08.md +53 -0
- package/docs/plans/plan-009-tech-debt-backlog.md +379 -0
- package/docs/plans/plan-016-context-made-visible-wave-1.md +150 -5
- package/docs/plans/plan-017-knowledge-categories-and-lifecycle.md +175 -0
- package/docs/plans/plan-018-connected-memory-wave-2.md +594 -0
- package/docs/plans/plan-019-post-0.9-roadmap.md +198 -0
- package/docs/product-context-vision-2026-09.md +17 -1
- package/docs/product-vision-2026-09.md +1082 -43
- package/docs/product-vision-review-2026-09.md +13 -1
- package/docs/screenshots/workbench-live-dark.png +0 -0
- package/docs/screenshots/workbench-live-light.png +0 -0
- package/docs/sdk.md +62 -2
- package/package.json +1 -1
- package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/runtime.json +1 -1
- package/plugins/pmx-canvas/plugin.json +1 -1
- package/plugins/pmx-canvas/runtime.json +1 -1
- package/plugins/pmx-canvas/tools.json +601 -22
- package/skills/pmx-canvas/SKILL.md +216 -42
- package/skills/pmx-canvas/evals/evals.json +224 -0
- package/skills/pmx-canvas/references/full-reference.md +120 -21
- package/skills/pmx-canvas/references/github-copilot-app-adapter.md +2 -2
- package/skills/pmx-canvas/references/importing-context.md +132 -0
- package/skills/pmx-canvas/references/installing-pmx-canvas.md +6 -6
- package/skills/pmx-canvas/references/tours-and-recording.md +16 -1
- package/skills/pmx-canvas-testing/SKILL.md +20 -0
- package/src/cli/agent.ts +2 -0
- package/src/cli/commands/ax.ts +12 -0
- package/src/cli/commands/boards.ts +88 -11
- package/src/cli/commands/edges.ts +28 -0
- package/src/cli/commands/groups.ts +7 -0
- package/src/cli/commands/nodes.ts +1 -0
- package/src/cli/commands/query.ts +34 -2
- package/src/cli/commands/record.ts +199 -48
- package/src/cli/index.ts +6 -1
- package/src/client/App.tsx +177 -70
- package/src/client/canvas/AttentionHistory.tsx +0 -2
- package/src/client/canvas/BarHint.tsx +40 -2
- package/src/client/canvas/BoardCopyDialog.tsx +1 -1
- package/src/client/canvas/BoardFolderDialog.tsx +7 -7
- package/src/client/canvas/BoardLinkDialog.tsx +138 -33
- package/src/client/canvas/BoardPinButton.tsx +33 -0
- package/src/client/canvas/BoardSwitcher.tsx +26 -40
- package/src/client/canvas/CanvasNode.tsx +360 -237
- package/src/client/canvas/CanvasViewport.tsx +45 -7
- package/src/client/canvas/CommandBar.tsx +29 -23
- package/src/client/canvas/CommandPalette.tsx +5 -12
- package/src/client/canvas/ConnectionBanner.tsx +3 -3
- package/src/client/canvas/ContextChip.tsx +296 -0
- package/src/client/canvas/ContextMenu.tsx +63 -5
- package/src/client/canvas/CrossBoardReferences.tsx +60 -0
- package/src/client/canvas/EdgeLayer.tsx +339 -76
- package/src/client/canvas/EmptyState.tsx +33 -18
- package/src/client/canvas/ExpandedNodeOverlay.tsx +72 -40
- package/src/client/canvas/ExternalWriters.tsx +57 -42
- package/src/client/canvas/HomeGraphView.tsx +942 -0
- package/src/client/canvas/HomeMapView.tsx +641 -0
- package/src/client/canvas/HomeReviewView.tsx +167 -0
- package/src/client/canvas/HomeView.tsx +687 -160
- package/src/client/canvas/IntentLayer.tsx +19 -12
- package/src/client/canvas/KnowledgeDialog.tsx +488 -0
- package/src/client/canvas/LifecycleChrome.tsx +133 -0
- package/src/client/canvas/LifecycleFields.tsx +91 -0
- package/src/client/canvas/LifecyclePanel.tsx +543 -0
- package/src/client/canvas/MemoryInspector.tsx +104 -0
- package/src/client/canvas/Minimap.tsx +3 -3
- package/src/client/canvas/NearPinLayer.tsx +187 -0
- package/src/client/canvas/NodeContextMark.tsx +175 -97
- package/src/client/canvas/Presentation.tsx +23 -57
- package/src/client/canvas/RelationPresetsDialog.tsx +413 -0
- package/src/client/canvas/RelationsPanel.tsx +82 -0
- package/src/client/canvas/SelectionBar.tsx +3 -6
- package/src/client/canvas/SessionPanel.tsx +90 -50
- package/src/client/canvas/SessionReceipt.tsx +311 -39
- package/src/client/canvas/ShortcutOverlay.tsx +1 -1
- package/src/client/canvas/SnapshotPanel.tsx +27 -36
- package/src/client/canvas/ToolRail.tsx +367 -192
- package/src/client/canvas/TopBar.tsx +354 -124
- package/src/client/canvas/canvas-area.ts +10 -3
- package/src/client/canvas/graph-geometry.ts +195 -0
- package/src/client/canvas/graph-model.ts +254 -0
- package/src/client/canvas/memory-layout.ts +106 -0
- package/src/client/canvas/use-chrome-height.ts +20 -0
- package/src/client/canvas/use-focus-trap.ts +31 -5
- package/src/client/canvas/use-memory-graph.ts +58 -0
- package/src/client/canvas/use-node-drag.ts +2 -1
- package/src/client/canvas/use-pan-zoom.ts +64 -7
- package/src/client/icons.tsx +0 -63
- package/src/client/mcp-app/style.css +0 -6
- package/src/client/nodes/AxStepControls.tsx +74 -50
- package/src/client/nodes/BoardNode.tsx +5 -1
- package/src/client/nodes/ContextNode.tsx +18 -11
- package/src/client/nodes/ExtAppFrame.tsx +6 -3
- package/src/client/nodes/FileNode.tsx +46 -42
- package/src/client/nodes/HtmlNode.tsx +32 -0
- package/src/client/nodes/ImageNode.tsx +1 -1
- package/src/client/nodes/InlineFormatBar.tsx +6 -9
- package/src/client/nodes/InlineMarkdownEditor.tsx +56 -17
- package/src/client/nodes/MarkdownNode.tsx +377 -109
- package/src/client/nodes/McpAppNode.tsx +50 -20
- package/src/client/nodes/MdFormatBar.tsx +21 -18
- package/src/client/nodes/PromptNode.tsx +1 -1
- package/src/client/nodes/ResponseNode.tsx +1 -1
- package/src/client/nodes/viewer-runtime-state.ts +54 -22
- package/src/client/state/attention-bridge.ts +64 -15
- package/src/client/state/boards-store.ts +193 -39
- package/src/client/state/canvas-store.ts +99 -12
- package/src/client/state/context-chip-store.ts +70 -0
- package/src/client/state/context-status-store.ts +53 -0
- package/src/client/state/human-store.ts +13 -7
- package/src/client/state/intent-bridge.ts +51 -30
- package/src/client/state/knowledge-store.ts +43 -0
- package/src/client/state/near-pin-store.ts +115 -6
- package/src/client/state/presence-store.ts +7 -10
- package/src/client/state/presentation.ts +91 -0
- package/src/client/state/relation-store.ts +81 -0
- package/src/client/state/session-store.ts +144 -18
- package/src/client/state/sse-bridge.ts +235 -32
- package/src/client/state/workspace-settings.ts +11 -0
- package/src/client/theme/global.css +2724 -476
- package/src/client/types.ts +12 -0
- package/src/client/utils/keyboard.ts +10 -0
- package/src/client/utils/time-ago.ts +14 -0
- package/src/json-render/renderer/index.tsx +31 -0
- package/src/mcp/canvas-app.ts +12 -2
- package/src/mcp/context-uri-template.ts +7 -4
- package/src/mcp/server.ts +63 -41
- package/src/mcp/workbench-app.ts +39 -6
- package/src/server/agent-presence.ts +128 -28
- package/src/server/ax-context.ts +27 -13
- package/src/server/board-export.ts +12 -1
- package/src/server/canvas-db.ts +331 -42
- package/src/server/canvas-operations.ts +195 -83
- package/src/server/canvas-provenance.ts +53 -22
- package/src/server/canvas-serialization.ts +2 -0
- package/src/server/canvas-state.ts +950 -122
- package/src/server/canvas-validation.ts +9 -2
- package/src/server/context-brief.ts +345 -38
- package/src/server/context-reads.ts +178 -30
- package/src/server/demo-state.json +355 -24
- package/src/server/edit-summary.ts +74 -0
- package/src/server/html-surface.ts +8 -1
- package/src/server/index.ts +181 -11
- package/src/server/knowledge-lifecycle.ts +304 -0
- package/src/server/lifecycle-status.ts +232 -0
- package/src/server/memory-graph.ts +166 -0
- package/src/server/mutation-history.ts +18 -6
- package/src/server/operations/composites.ts +32 -11
- package/src/server/operations/http.ts +7 -2
- package/src/server/operations/index.ts +4 -0
- package/src/server/operations/mcp.ts +26 -1
- package/src/server/operations/ops/ax-presence.ts +1 -1
- package/src/server/operations/ops/ax-read.ts +57 -6
- package/src/server/operations/ops/ax-timeline.ts +28 -2
- package/src/server/operations/ops/backup.ts +5 -3
- package/src/server/operations/ops/batch.ts +35 -4
- package/src/server/operations/ops/boards.ts +188 -47
- package/src/server/operations/ops/canvas-wire.ts +31 -1
- package/src/server/operations/ops/edges.ts +36 -2
- package/src/server/operations/ops/groups.ts +6 -1
- package/src/server/operations/ops/lifecycle.ts +262 -0
- package/src/server/operations/ops/memory-graph.ts +45 -0
- package/src/server/operations/ops/nodes.ts +107 -24
- package/src/server/operations/ops/query.ts +281 -68
- package/src/server/operations/ops/snapshots.ts +73 -1
- package/src/server/operations/ops/validate.ts +5 -1
- package/src/server/operations/ops/viewport.ts +97 -3
- package/src/server/operations/registry.ts +74 -25
- package/src/server/placement.ts +29 -0
- package/src/server/scope-fence.ts +78 -6
- package/src/server/server.ts +115 -16
- package/src/server/spatial-analysis.ts +48 -23
- package/src/server/tour-control.ts +82 -0
- package/src/server/wiki-links.ts +137 -0
- package/src/server/workbench-auth.ts +11 -0
- package/src/shared/agent-context.ts +3 -0
- package/src/shared/agent-presence.ts +15 -15
- package/src/shared/auto-arrange.ts +2 -0
- package/src/shared/boards.ts +4 -4
- package/src/shared/knowledge-lifecycle.ts +22 -0
- package/src/shared/knowledge-presets.ts +80 -0
- package/src/shared/lifecycle-status.ts +26 -0
- package/src/shared/memory-graph.ts +74 -0
- package/src/shared/relations.ts +98 -0
- package/src/shared/semantic-attention.ts +15 -1
- package/src/shared/tour.ts +23 -5
- package/src/shared/workspace-settings.ts +13 -0
- package/dist/types/client/canvas/ContextPinBar.d.ts +0 -1
- package/dist/types/client/canvas/FocusFieldLayer.d.ts +0 -1
- package/dist/types/client/canvas/kind-colors.d.ts +0 -3
- package/dist/types/server/agent-context.d.ts +0 -22
- package/src/client/canvas/ContextPinBar.tsx +0 -25
package/src/client/types.ts
CHANGED
|
@@ -33,6 +33,8 @@ export interface CanvasNodeState {
|
|
|
33
33
|
collapsed: boolean;
|
|
34
34
|
pinned: boolean;
|
|
35
35
|
contentRevision?: number;
|
|
36
|
+
/** Revision of this node's latest link change, kept apart from its text. */
|
|
37
|
+
linksRevision?: number;
|
|
36
38
|
/** Who created / last edited the node's content (server attribution). */
|
|
37
39
|
createdBy?: NodeActor;
|
|
38
40
|
lastEditedBy?: NodeActor;
|
|
@@ -49,10 +51,20 @@ export interface CanvasEdge {
|
|
|
49
51
|
id: string;
|
|
50
52
|
from: string;
|
|
51
53
|
to: string;
|
|
54
|
+
toBoardId?: string;
|
|
52
55
|
type: 'relation' | 'depends-on' | 'flow' | 'references';
|
|
56
|
+
kind?: string;
|
|
57
|
+
reason?: string;
|
|
53
58
|
label?: string;
|
|
54
59
|
style?: 'solid' | 'dashed' | 'dotted';
|
|
55
60
|
animated?: boolean;
|
|
61
|
+
/** Board revision of this link's latest add, retype or relabel. */
|
|
62
|
+
revision?: number;
|
|
63
|
+
changedBy?: NodeActor;
|
|
64
|
+
changedAt?: string;
|
|
65
|
+
/** Who first drew the link, and when (LinkAuthorship.dc.html). */
|
|
66
|
+
createdBy?: NodeActor;
|
|
67
|
+
createdAt?: string;
|
|
56
68
|
}
|
|
57
69
|
|
|
58
70
|
export interface CanvasAnnotationPoint {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Overlays and text editors get Escape before the underlying view selection. */
|
|
2
|
+
export function isSelectionEscape(event: KeyboardEvent): boolean {
|
|
3
|
+
const target = event.target instanceof HTMLElement ? event.target : null;
|
|
4
|
+
return (
|
|
5
|
+
event.key === 'Escape' &&
|
|
6
|
+
!event.defaultPrevented &&
|
|
7
|
+
!target?.closest('input, textarea, [contenteditable="true"]') &&
|
|
8
|
+
!document.querySelector('[role="dialog"], [role="menu"], .context-menu, .shortcut-overlay-backdrop')
|
|
9
|
+
);
|
|
10
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** How long ago, compact: "just now", "5m", "3h", "2d". */
|
|
2
|
+
export function age(iso: string): string {
|
|
3
|
+
const mins = Math.max(0, Math.floor((Date.now() - new Date(iso).getTime()) / 60000));
|
|
4
|
+
if (mins < 1) return 'just now';
|
|
5
|
+
if (mins < 60) return `${mins}m`;
|
|
6
|
+
const hours = Math.floor(mins / 60);
|
|
7
|
+
return hours < 24 ? `${hours}h` : `${Math.floor(hours / 24)}d`;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** "just now", "5m ago", "3h ago", "2d ago". */
|
|
11
|
+
export function timeAgo(iso: string): string {
|
|
12
|
+
const value = age(iso);
|
|
13
|
+
return value === 'just now' ? value : `${value} ago`;
|
|
14
|
+
}
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
JSON_VIEWER_STATE_SOURCE,
|
|
15
15
|
} from '../../shared/ax-surface-protocol.js';
|
|
16
16
|
import { createRoot } from 'react-dom/client';
|
|
17
|
+
import { useLayoutEffect } from 'react';
|
|
17
18
|
import { defineRegistry, JSONUIProvider, Renderer } from '@json-render/react';
|
|
18
19
|
import { shadcnComponents } from '@json-render/shadcn';
|
|
19
20
|
import { catalog } from '../catalog';
|
|
@@ -173,6 +174,36 @@ function applyTheme(theme: unknown): void {
|
|
|
173
174
|
}
|
|
174
175
|
|
|
175
176
|
function App({ store }: { store: StateStore }) {
|
|
177
|
+
useLayoutEffect(() => {
|
|
178
|
+
const token = window.__PMX_CANVAS_UI_STATE_TOKEN__;
|
|
179
|
+
if (!token) return;
|
|
180
|
+
// Mount is runtime health, not paint. Offscreen frames may not run rAF;
|
|
181
|
+
// acknowledge the commit now so the parent never times out a healthy viewer.
|
|
182
|
+
window.parent.postMessage(
|
|
183
|
+
{
|
|
184
|
+
source: JSON_VIEWER_STATE_SOURCE,
|
|
185
|
+
type: 'mounted',
|
|
186
|
+
token,
|
|
187
|
+
nodeId: window.__PMX_CANVAS_JSON_RENDER_NODE_ID__,
|
|
188
|
+
},
|
|
189
|
+
'*',
|
|
190
|
+
);
|
|
191
|
+
// Let this document's rendering cycle advance after React's commit;
|
|
192
|
+
// parent animation frames alone do not advance a child frame's paint.
|
|
193
|
+
requestAnimationFrame(() =>
|
|
194
|
+
requestAnimationFrame(() => {
|
|
195
|
+
window.parent.postMessage(
|
|
196
|
+
{
|
|
197
|
+
source: JSON_VIEWER_STATE_SOURCE,
|
|
198
|
+
type: 'ready',
|
|
199
|
+
token,
|
|
200
|
+
nodeId: window.__PMX_CANVAS_JSON_RENDER_NODE_ID__,
|
|
201
|
+
},
|
|
202
|
+
'*',
|
|
203
|
+
);
|
|
204
|
+
}),
|
|
205
|
+
);
|
|
206
|
+
}, []);
|
|
176
207
|
const spec = window.__PMX_CANVAS_JSON_RENDER_SPEC__;
|
|
177
208
|
|
|
178
209
|
if (!spec) {
|
package/src/mcp/canvas-app.ts
CHANGED
|
@@ -187,8 +187,18 @@ export function registerCanvasApp(server: McpServer, ensureCanvas: () => Promise
|
|
|
187
187
|
position: node.position,
|
|
188
188
|
size: node.size,
|
|
189
189
|
edges: layout.edges
|
|
190
|
-
.filter(
|
|
191
|
-
|
|
190
|
+
.filter(
|
|
191
|
+
(edge) =>
|
|
192
|
+
edge.from === node.id || ((!edge.toBoardId || edge.toBoardId === input.boardId) && edge.to === node.id),
|
|
193
|
+
)
|
|
194
|
+
.map(({ id, from, to, toBoardId, type, label }) => ({
|
|
195
|
+
id,
|
|
196
|
+
from,
|
|
197
|
+
to,
|
|
198
|
+
...(toBoardId !== undefined ? { toBoardId } : {}),
|
|
199
|
+
type,
|
|
200
|
+
label: label ?? '',
|
|
201
|
+
})),
|
|
192
202
|
};
|
|
193
203
|
return { content: [{ type: 'text', text: JSON.stringify(result) }], structuredContent: result };
|
|
194
204
|
}),
|
|
@@ -2,8 +2,11 @@ import { UriTemplate } from '@modelcontextprotocol/sdk/shared/uriTemplate.js';
|
|
|
2
2
|
|
|
3
3
|
/** SDK v1 matches {?a,b} as two required, ordered parameters, unlike its expansion. */
|
|
4
4
|
export class ContextUriTemplate extends UriTemplate {
|
|
5
|
-
constructor(
|
|
6
|
-
|
|
5
|
+
constructor(
|
|
6
|
+
private readonly host = 'context',
|
|
7
|
+
private readonly parameters = ['budget', 'consumer', 'since'],
|
|
8
|
+
) {
|
|
9
|
+
super(`canvas://${host}{?${parameters.join(',')}}`);
|
|
7
10
|
}
|
|
8
11
|
|
|
9
12
|
override match(uri: string): Record<string, string> | null {
|
|
@@ -11,7 +14,7 @@ export class ContextUriTemplate extends UriTemplate {
|
|
|
11
14
|
const url = new URL(uri);
|
|
12
15
|
if (
|
|
13
16
|
url.protocol !== 'canvas:' ||
|
|
14
|
-
url.host !==
|
|
17
|
+
url.host !== this.host ||
|
|
15
18
|
url.pathname ||
|
|
16
19
|
url.hash ||
|
|
17
20
|
url.username ||
|
|
@@ -20,7 +23,7 @@ export class ContextUriTemplate extends UriTemplate {
|
|
|
20
23
|
return null;
|
|
21
24
|
const variables: Record<string, string> = {};
|
|
22
25
|
for (const [key, value] of url.searchParams) {
|
|
23
|
-
if (!
|
|
26
|
+
if (!this.parameters.includes(key) || key in variables) return null;
|
|
24
27
|
variables[key] = value;
|
|
25
28
|
}
|
|
26
29
|
return variables;
|
package/src/mcp/server.ts
CHANGED
|
@@ -32,10 +32,10 @@ import { AX_INTERACTION_TYPES } from '../server/ax-interaction.js';
|
|
|
32
32
|
import { buildPendingAxActivity } from '../server/ax-state.js';
|
|
33
33
|
import { registerOperationTools, registerCompositeTools } from '../server/operations/index.js';
|
|
34
34
|
import { agentSourceLabel } from '../server/operations/invoker.js';
|
|
35
|
-
import { contextReadFromPayload } from '../server/context-reads.js';
|
|
35
|
+
import { contextReadFromPayload, pinnedBoardReads, graphReads } from '../server/context-reads.js';
|
|
36
|
+
import type { MemoryGraph } from '../shared/memory-graph.js';
|
|
36
37
|
import { createCanvasAccess, refreshCanvasAccess, type CanvasAccess } from './canvas-access.js';
|
|
37
|
-
import {
|
|
38
|
-
import { buildSpatialContext, findNeighborhoods } from '../server/spatial-analysis.js';
|
|
38
|
+
import { buildSpatialContext } from '../server/spatial-analysis.js';
|
|
39
39
|
import {
|
|
40
40
|
getCanvasNodeTitle,
|
|
41
41
|
serializeCanvasLayoutForAgent,
|
|
@@ -89,7 +89,7 @@ function sendCanvasResourceNotifications(type: 'nodes' | 'pins' | 'ax' | 'ax-tim
|
|
|
89
89
|
const server = resourceNotificationServer;
|
|
90
90
|
if (!server) return;
|
|
91
91
|
try {
|
|
92
|
-
if (type === 'pins') {
|
|
92
|
+
if (type === 'pins' || type === 'nodes') {
|
|
93
93
|
server.server.sendResourceUpdated({ uri: 'canvas://pinned-context' });
|
|
94
94
|
}
|
|
95
95
|
if (type === 'pins' || type === 'ax') {
|
|
@@ -108,6 +108,7 @@ function sendCanvasResourceNotifications(type: 'nodes' | 'pins' | 'ax' | 'ax-tim
|
|
|
108
108
|
server.server.sendResourceUpdated({ uri: 'canvas://boards' });
|
|
109
109
|
server.server.sendResourceUpdated({ uri: 'canvas://summary' });
|
|
110
110
|
server.server.sendResourceUpdated({ uri: 'canvas://context' });
|
|
111
|
+
server.server.sendResourceUpdated({ uri: 'canvas://graph' });
|
|
111
112
|
server.server.sendResourceUpdated({ uri: 'canvas://spatial-context' });
|
|
112
113
|
server.server.sendResourceUpdated({ uri: 'canvas://history' });
|
|
113
114
|
server.server.sendResourceUpdated({ uri: 'canvas://code-graph' });
|
|
@@ -121,7 +122,8 @@ function handleRemoteSseFrame(frame: string): void {
|
|
|
121
122
|
const event = eventLine?.slice('event: '.length).trim() ?? '';
|
|
122
123
|
if (!event || event === 'connected' || event === 'ping') return;
|
|
123
124
|
sendCanvasResourceNotifications(
|
|
124
|
-
|
|
125
|
+
// boards-changed covers board pins and switches, both of which change pinned context.
|
|
126
|
+
event === 'context-pins-changed' || event === 'boards-changed'
|
|
125
127
|
? 'pins'
|
|
126
128
|
: event === 'ax-state-changed'
|
|
127
129
|
? 'ax'
|
|
@@ -508,45 +510,18 @@ export async function startMcpServer(): Promise<void> {
|
|
|
508
510
|
description:
|
|
509
511
|
'Content of all pinned nodes on the canvas. When the human pins nodes, ' +
|
|
510
512
|
'they are telling the agent "this is what matters right now." Read this ' +
|
|
511
|
-
'resource to get structured context from the canvas.'
|
|
513
|
+
'resource to get structured context from the canvas. pinnedBoards lists the ' +
|
|
514
|
+
'boards in the working set (README summary and pinned card titles); their ' +
|
|
515
|
+
'content arrives through canvas://context.',
|
|
512
516
|
mimeType: 'application/json',
|
|
513
517
|
},
|
|
514
518
|
async () => {
|
|
515
519
|
const c = await ensureCanvas();
|
|
516
520
|
const readTarget = await c.prepareContextRead();
|
|
517
521
|
const board = readTarget.boardId ?? undefined;
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
const pinnedNodes = layout.nodes.filter((n) => pinnedIds.has(n.id));
|
|
522
|
-
const pinnedEdges = layout.edges.filter((e) => pinnedIds.has(e.from) && pinnedIds.has(e.to));
|
|
523
|
-
|
|
524
|
-
// Compute neighborhoods: for each pinned node, find nearby unpinned nodes
|
|
525
|
-
const neighborhoods = findNeighborhoods(layout.nodes, pinnedIds);
|
|
526
|
-
|
|
527
|
-
const context = {
|
|
528
|
-
pinnedCount: pinnedNodes.length,
|
|
529
|
-
nodes: pinnedNodes.map((n) =>
|
|
530
|
-
serializeNodeForAgentContext(n, {
|
|
531
|
-
defaultTextLength: 700,
|
|
532
|
-
webpageTextLength: 1600,
|
|
533
|
-
includePosition: true,
|
|
534
|
-
}),
|
|
535
|
-
),
|
|
536
|
-
edges: pinnedEdges.map((e) => ({
|
|
537
|
-
id: e.id,
|
|
538
|
-
from: e.from,
|
|
539
|
-
to: e.to,
|
|
540
|
-
type: e.type,
|
|
541
|
-
label: e.label ?? null,
|
|
542
|
-
})),
|
|
543
|
-
neighborhoods: neighborhoods.map((nh) => ({
|
|
544
|
-
pinnedNodeId: nh.pinnedNodeId,
|
|
545
|
-
pinnedNodeTitle: nh.pinnedNodeTitle,
|
|
546
|
-
nearbyNodes: nh.neighbors,
|
|
547
|
-
})),
|
|
548
|
-
};
|
|
549
|
-
|
|
522
|
+
// Content and lifecycle are captured by one synchronous server operation,
|
|
523
|
+
// never joined across separate HTTP reads with different retirement state.
|
|
524
|
+
const context = await c.invoker().invoke('pinned-context.get', { board });
|
|
550
525
|
const text = JSON.stringify(context, null, 2);
|
|
551
526
|
await recordContextRead(c, 'mcp-resource', 'canvas://pinned-context', text, readTarget);
|
|
552
527
|
return {
|
|
@@ -575,19 +550,66 @@ export async function startMcpServer(): Promise<void> {
|
|
|
575
550
|
if (uri.searchParams.has('consumer')) input.consumer = uri.searchParams.get('consumer');
|
|
576
551
|
if (uri.searchParams.has('budget')) input.budget = uri.searchParams.get('budget');
|
|
577
552
|
if (uri.searchParams.has('since')) input.since = uri.searchParams.get('since');
|
|
578
|
-
|
|
553
|
+
// MCP invokers skip registry recording (proxied reads), so record here like
|
|
554
|
+
// the other context resources: the open board, then each pinned board
|
|
555
|
+
// delivered in full.
|
|
556
|
+
const readTarget = await c.prepareContextRead();
|
|
557
|
+
const document = await c.invoker().invoke('context.get', { ...input, expectedBoardId: readTarget.boardId });
|
|
558
|
+
const text = JSON.stringify(document);
|
|
559
|
+
await recordContextRead(c, 'mcp-resource', 'canvas://context', text, readTarget);
|
|
560
|
+
try {
|
|
561
|
+
const base = {
|
|
562
|
+
channel: 'mcp-resource' as const,
|
|
563
|
+
resource: 'canvas://context',
|
|
564
|
+
source: agentSourceLabel('mcp'),
|
|
565
|
+
consumer: server.server.getClientVersion()?.name ?? null,
|
|
566
|
+
agentId: null,
|
|
567
|
+
};
|
|
568
|
+
for (const read of pinnedBoardReads(base, text)) await c.recordContextRead(read);
|
|
569
|
+
} catch (error) {
|
|
570
|
+
console.error('[pmx-canvas mcp] recording a pinned-board read failed:', error);
|
|
571
|
+
}
|
|
579
572
|
return {
|
|
580
573
|
contents: [
|
|
581
574
|
{
|
|
582
575
|
uri: uri.href,
|
|
583
576
|
mimeType: 'application/json',
|
|
584
|
-
text
|
|
577
|
+
text,
|
|
585
578
|
},
|
|
586
579
|
],
|
|
587
580
|
};
|
|
588
581
|
},
|
|
589
582
|
);
|
|
590
583
|
|
|
584
|
+
server.resource(
|
|
585
|
+
'memory-graph',
|
|
586
|
+
new ResourceTemplate(new ContextUriTemplate('graph', ['focus', 'depth']), { list: undefined }),
|
|
587
|
+
{
|
|
588
|
+
description:
|
|
589
|
+
'The same connected-memory projection Home draws. Focus on a board ID, qualified card key, or pins; depth 0–4. Summaries are seen, not full reads.',
|
|
590
|
+
mimeType: 'application/json',
|
|
591
|
+
},
|
|
592
|
+
async (uri) => {
|
|
593
|
+
const c = await ensureCanvas();
|
|
594
|
+
const input: Record<string, unknown> = {};
|
|
595
|
+
if (uri.searchParams.has('focus')) input.focus = uri.searchParams.get('focus');
|
|
596
|
+
if (uri.searchParams.has('depth')) input.depth = uri.searchParams.get('depth');
|
|
597
|
+
const graph = (await c.invoker().invoke('memory-graph.get', input)) as MemoryGraph;
|
|
598
|
+
for (const read of graphReads(
|
|
599
|
+
{
|
|
600
|
+
channel: 'mcp-resource',
|
|
601
|
+
resource: 'canvas://graph',
|
|
602
|
+
source: agentSourceLabel('mcp'),
|
|
603
|
+
consumer: server.server.getClientVersion()?.name ?? null,
|
|
604
|
+
agentId: null,
|
|
605
|
+
},
|
|
606
|
+
graph,
|
|
607
|
+
))
|
|
608
|
+
await c.recordContextRead(read);
|
|
609
|
+
return { contents: [{ uri: uri.href, mimeType: 'application/json', text: JSON.stringify(graph) }] };
|
|
610
|
+
},
|
|
611
|
+
);
|
|
612
|
+
|
|
591
613
|
server.resource(
|
|
592
614
|
'ax-state',
|
|
593
615
|
'canvas://ax',
|
|
@@ -797,7 +819,7 @@ export async function startMcpServer(): Promise<void> {
|
|
|
797
819
|
'canvas://boards',
|
|
798
820
|
{
|
|
799
821
|
description:
|
|
800
|
-
'The boards in this workspace with their
|
|
822
|
+
'The boards in this workspace with their folders, most recently opened first, and which one is ' +
|
|
801
823
|
'open (activeBoardId; null = Home). Writes go to the open board; use canvas_board action "open" to switch the shared workbench.',
|
|
802
824
|
mimeType: 'application/json',
|
|
803
825
|
},
|
package/src/mcp/workbench-app.ts
CHANGED
|
@@ -10,6 +10,8 @@ const writes = new Set([
|
|
|
10
10
|
'board.create',
|
|
11
11
|
'board.open',
|
|
12
12
|
'board.update',
|
|
13
|
+
'board.pin',
|
|
14
|
+
'board.unpin',
|
|
13
15
|
'board.create-from',
|
|
14
16
|
'board.delete',
|
|
15
17
|
'node.add',
|
|
@@ -18,6 +20,14 @@ const writes = new Set([
|
|
|
18
20
|
'node.update',
|
|
19
21
|
'node.remove',
|
|
20
22
|
'node.refresh',
|
|
23
|
+
'node.lifecycle-set',
|
|
24
|
+
'node.review',
|
|
25
|
+
'node.lifecycle-restore',
|
|
26
|
+
'knowledge.category-set',
|
|
27
|
+
'knowledge.category-remove',
|
|
28
|
+
'knowledge.folder-set',
|
|
29
|
+
'knowledge.relation-preset-set',
|
|
30
|
+
'knowledge.relation-preset-remove',
|
|
21
31
|
'edge.add',
|
|
22
32
|
'edge.update',
|
|
23
33
|
'edge.remove',
|
|
@@ -28,6 +38,7 @@ const writes = new Set([
|
|
|
28
38
|
'annotation.add',
|
|
29
39
|
'annotation.remove',
|
|
30
40
|
'theme.set',
|
|
41
|
+
'settings.set',
|
|
31
42
|
'viewport.set',
|
|
32
43
|
'canvas.apply-updates',
|
|
33
44
|
'canvas.clear',
|
|
@@ -38,6 +49,8 @@ const writes = new Set([
|
|
|
38
49
|
'snapshot.restore',
|
|
39
50
|
'snapshot.delete',
|
|
40
51
|
'tour.set',
|
|
52
|
+
'tour.go',
|
|
53
|
+
'tour.exit',
|
|
41
54
|
'ax.focus.set',
|
|
42
55
|
'ax.policy.set',
|
|
43
56
|
'ax.work.create',
|
|
@@ -157,6 +170,23 @@ export function registerWorkbenchApp(server: McpServer, ensureCanvas: () => Prom
|
|
|
157
170
|
const node = await canvas.getNode(String(args.id));
|
|
158
171
|
if (node?.data.path) throw new OperationError('Edit file-linked content in the local PMX workbench.', 403);
|
|
159
172
|
}
|
|
173
|
+
if (op.name === 'settings.set') {
|
|
174
|
+
// Only this app-only UI preference request carries the workbench
|
|
175
|
+
// marker. Canvas edits still use the agent invoker below; never
|
|
176
|
+
// fetch or forward the daemon's human-author capability.
|
|
177
|
+
const response = await fetch(new URL('/api/canvas/settings', base), {
|
|
178
|
+
method: 'POST',
|
|
179
|
+
headers: {
|
|
180
|
+
'Content-Type': 'application/json',
|
|
181
|
+
'X-PMX-Workbench': '1',
|
|
182
|
+
'X-PMX-Source': 'mcp',
|
|
183
|
+
},
|
|
184
|
+
body: JSON.stringify(args),
|
|
185
|
+
redirect: 'error',
|
|
186
|
+
signal: AbortSignal.timeout(15_000),
|
|
187
|
+
});
|
|
188
|
+
return reply(response.status, await response.text());
|
|
189
|
+
}
|
|
160
190
|
const result = await canvas.invoker().invoke(op.name, args);
|
|
161
191
|
return reply(route.status ? route.status(result) : 200, JSON.stringify(result));
|
|
162
192
|
}
|
|
@@ -191,12 +221,15 @@ export function registerWorkbenchApp(server: McpServer, ensureCanvas: () => Prom
|
|
|
191
221
|
throw new OperationError('This asset is too large for the MCP connection.');
|
|
192
222
|
const contentType = response.headers.get('content-type') ?? 'application/octet-stream';
|
|
193
223
|
const text = /^(text\/|application\/(json|javascript))/.test(contentType);
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
224
|
+
let body = text ? new TextDecoder().decode(bytes) : Buffer.from(bytes).toString('base64');
|
|
225
|
+
if (target.pathname === '/api/workbench/poll') {
|
|
226
|
+
// Hosted frames act as agents; the direct workbench's human capability
|
|
227
|
+
// must not escape in private tool hydration metadata either.
|
|
228
|
+
const poll = JSON.parse(body) as { events: Array<{ payload: Record<string, unknown> }> };
|
|
229
|
+
for (const event of poll.events) delete event.payload.workbenchToken;
|
|
230
|
+
body = JSON.stringify(poll);
|
|
231
|
+
}
|
|
232
|
+
return reply(response.status, body, contentType, text ? 'text' : 'base64');
|
|
200
233
|
} catch (error) {
|
|
201
234
|
if (error instanceof OperationError)
|
|
202
235
|
return reply(error.status, JSON.stringify({ ok: false, error: error.message }));
|