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
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { canvasState } from './canvas-state.js';
|
|
2
|
+
import { summarizeNodeForAgentContext } from '../shared/agent-context.js';
|
|
3
|
+
import {
|
|
4
|
+
memoryKey,
|
|
5
|
+
wikiLinksFromData,
|
|
6
|
+
type MemoryGraph,
|
|
7
|
+
type MemoryGraphEdge,
|
|
8
|
+
type MemoryGraphNode,
|
|
9
|
+
type MemoryTarget,
|
|
10
|
+
} from '../shared/memory-graph.js';
|
|
11
|
+
|
|
12
|
+
/** One library projection for Home, backlinks, brief discovery and agent exploration. No writes. */
|
|
13
|
+
export function memoryGraph(projection: 'full' | 'boards' = 'full'): MemoryGraph {
|
|
14
|
+
const boards = canvasState.listBoards();
|
|
15
|
+
const reads = boards.map((board) => ({ board, read: canvasState.readBoard(board.id, false) }));
|
|
16
|
+
const keys = new Set<string>();
|
|
17
|
+
const nodes: MemoryGraphNode[] = [];
|
|
18
|
+
const edges: MemoryGraphEdge[] = [];
|
|
19
|
+
const unresolvedLinks: MemoryGraph['unresolvedLinks'] = [];
|
|
20
|
+
const add = (
|
|
21
|
+
kind: MemoryGraphEdge['kind'],
|
|
22
|
+
id: string,
|
|
23
|
+
source: MemoryTarget,
|
|
24
|
+
target: MemoryTarget,
|
|
25
|
+
label?: string,
|
|
26
|
+
type?: string,
|
|
27
|
+
relationKind?: string,
|
|
28
|
+
reason?: string,
|
|
29
|
+
) => {
|
|
30
|
+
edges.push({
|
|
31
|
+
id,
|
|
32
|
+
kind,
|
|
33
|
+
from: memoryKey(source),
|
|
34
|
+
to: memoryKey(target),
|
|
35
|
+
source,
|
|
36
|
+
target,
|
|
37
|
+
missing: false,
|
|
38
|
+
...(label ? { label } : {}),
|
|
39
|
+
...(type ? { type } : {}),
|
|
40
|
+
...(relationKind ? { relationKind } : {}),
|
|
41
|
+
...(reason ? { reason } : {}),
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
for (const { board, read } of reads) {
|
|
45
|
+
const readme = read?.layout.nodes.find((node) => node.id === board.readmeNodeId);
|
|
46
|
+
const summary = (node: NonNullable<typeof read>['layout']['nodes'][number]) =>
|
|
47
|
+
summarizeNodeForAgentContext(node, { defaultTextLength: 280, webpageTextLength: 280 }).slice(0, 280);
|
|
48
|
+
nodes.push({
|
|
49
|
+
key: memoryKey({ boardId: board.id }),
|
|
50
|
+
boardId: board.id,
|
|
51
|
+
kind: 'board',
|
|
52
|
+
title: board.name,
|
|
53
|
+
summary: readme ? summary(readme) : '',
|
|
54
|
+
folder: board.folder,
|
|
55
|
+
inContext: !!board.pin,
|
|
56
|
+
summaryOnly: true,
|
|
57
|
+
nodeCount: board.nodeCount,
|
|
58
|
+
...(board.mapPosition ? { position: board.mapPosition } : {}),
|
|
59
|
+
});
|
|
60
|
+
keys.add(memoryKey({ boardId: board.id }));
|
|
61
|
+
if (board.previousBoardId)
|
|
62
|
+
add(
|
|
63
|
+
'previous',
|
|
64
|
+
`previous:${board.id}`,
|
|
65
|
+
{ boardId: board.id },
|
|
66
|
+
{ boardId: board.previousBoardId },
|
|
67
|
+
'previous board',
|
|
68
|
+
);
|
|
69
|
+
for (const node of read?.layout.nodes ?? []) {
|
|
70
|
+
const source = { boardId: board.id, cardId: node.id };
|
|
71
|
+
keys.add(memoryKey(source));
|
|
72
|
+
if (projection === 'full')
|
|
73
|
+
nodes.push({
|
|
74
|
+
...source,
|
|
75
|
+
key: memoryKey(source),
|
|
76
|
+
kind: 'card',
|
|
77
|
+
title: typeof node.data.title === 'string' ? node.data.title : node.id,
|
|
78
|
+
summary: summary(node),
|
|
79
|
+
folder: board.folder,
|
|
80
|
+
type: node.type,
|
|
81
|
+
summaryOnly: true,
|
|
82
|
+
linksRevision: node.linksRevision ?? 0,
|
|
83
|
+
inContext: !!board.pin || !!read?.state.contextPins.includes(node.id),
|
|
84
|
+
});
|
|
85
|
+
if (projection === 'full') add('member', `member:${memoryKey(source)}`, { boardId: board.id }, source);
|
|
86
|
+
if (node.type === 'board' && typeof node.data.boardId === 'string') {
|
|
87
|
+
add('portal', `portal:${memoryKey(source)}`, source, { boardId: node.data.boardId });
|
|
88
|
+
}
|
|
89
|
+
for (const link of node.type === 'markdown' ? wikiLinksFromData(node.data) : []) {
|
|
90
|
+
if (link.target) add('wiki', `wiki:${memoryKey(source)}:${link.ref}`, source, link.target, link.ref);
|
|
91
|
+
else unresolvedLinks.push({ source, ref: link.ref });
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
for (const edge of read?.layout.edges ?? []) {
|
|
95
|
+
add(
|
|
96
|
+
'card',
|
|
97
|
+
`card:${board.id}:${edge.id}`,
|
|
98
|
+
{ boardId: board.id, cardId: edge.from },
|
|
99
|
+
{ boardId: edge.toBoardId ?? board.id, cardId: edge.to },
|
|
100
|
+
edge.label,
|
|
101
|
+
edge.type,
|
|
102
|
+
edge.kind,
|
|
103
|
+
edge.reason,
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
for (const edge of edges) edge.missing = !keys.has(edge.from) || !keys.has(edge.to);
|
|
108
|
+
const graph = { nodes, edges, unresolvedLinks, focus: null, depth: 2 };
|
|
109
|
+
return projection === 'boards' ? boardMemoryGraph(graph) : graph;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Keep original qualified endpoints and missing evidence, without card summaries or membership. */
|
|
113
|
+
export function boardMemoryGraph(graph: MemoryGraph): MemoryGraph {
|
|
114
|
+
return {
|
|
115
|
+
...graph,
|
|
116
|
+
nodes: graph.nodes.filter((node) => node.kind === 'board'),
|
|
117
|
+
edges: graph.edges.filter(
|
|
118
|
+
(edge) => edge.kind !== 'member' && (edge.missing || edge.source.boardId !== edge.target.boardId),
|
|
119
|
+
),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Board-level connections are a collapse of the SAME edges, not another link scanner. */
|
|
124
|
+
export function boardConnections(graph: MemoryGraph): MemoryGraphEdge[] {
|
|
125
|
+
return graph.edges.filter((edge) => edge.kind !== 'member' && edge.source.boardId !== edge.target.boardId);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Undirected exploration. Membership is a hop too; focus accepts a board ID, qualified key, or pins. */
|
|
129
|
+
export function localMemoryGraph(graph: MemoryGraph, focus: string | null, depth: number): MemoryGraph {
|
|
130
|
+
if (!focus) return { ...graph, depth };
|
|
131
|
+
const seed =
|
|
132
|
+
focus === 'pins'
|
|
133
|
+
? graph.nodes.filter((node) => node.inContext).map((node) => node.key)
|
|
134
|
+
: graph.nodes
|
|
135
|
+
.filter((node) => node.key === focus || (node.kind === 'board' && node.boardId === focus))
|
|
136
|
+
.map((node) => node.key);
|
|
137
|
+
const selected = new Set(seed);
|
|
138
|
+
let frontier = new Set(seed);
|
|
139
|
+
for (let level = 0; level < depth; level++) {
|
|
140
|
+
const next = new Set<string>();
|
|
141
|
+
for (const edge of graph.edges) {
|
|
142
|
+
if (edge.missing) continue;
|
|
143
|
+
if (frontier.has(edge.from) && !selected.has(edge.to)) next.add(edge.to);
|
|
144
|
+
if (frontier.has(edge.to) && !selected.has(edge.from)) next.add(edge.from);
|
|
145
|
+
}
|
|
146
|
+
for (const key of next) selected.add(key);
|
|
147
|
+
frontier = next;
|
|
148
|
+
}
|
|
149
|
+
const edges = graph.edges.filter((edge) => selected.has(edge.from) && (selected.has(edge.to) || edge.missing));
|
|
150
|
+
const deliveredEdges = new Set(edges.map((edge) => edge.id));
|
|
151
|
+
return {
|
|
152
|
+
nodes: graph.nodes
|
|
153
|
+
.filter((node) => selected.has(node.key))
|
|
154
|
+
.map((node) => ({
|
|
155
|
+
...node,
|
|
156
|
+
linksComplete: graph.edges.every(
|
|
157
|
+
(edge) =>
|
|
158
|
+
edge.kind === 'member' || (edge.from !== node.key && edge.to !== node.key) || deliveredEdges.has(edge.id),
|
|
159
|
+
),
|
|
160
|
+
})),
|
|
161
|
+
edges,
|
|
162
|
+
unresolvedLinks: graph.unresolvedLinks.filter((link) => selected.has(memoryKey(link.source))),
|
|
163
|
+
focus,
|
|
164
|
+
depth,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
@@ -84,8 +84,8 @@ export interface SnapshotDiffResult {
|
|
|
84
84
|
title: string | null;
|
|
85
85
|
changes: string[];
|
|
86
86
|
}[];
|
|
87
|
-
addedEdges: { id: string; from: string; to: string; type: string }[];
|
|
88
|
-
removedEdges: { id: string; from: string; to: string; type: string }[];
|
|
87
|
+
addedEdges: { id: string; from: string; to: string; toBoardId?: string; type: string }[];
|
|
88
|
+
removedEdges: { id: string; from: string; to: string; toBoardId?: string; type: string }[];
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
function comparableNodeData(data: Record<string, unknown>): Record<string, unknown> {
|
|
@@ -335,12 +335,24 @@ export function diffLayouts(
|
|
|
335
335
|
|
|
336
336
|
for (const [id, edge] of curEdges) {
|
|
337
337
|
if (!snapEdges.has(id)) {
|
|
338
|
-
addedEdges.push({
|
|
338
|
+
addedEdges.push({
|
|
339
|
+
id,
|
|
340
|
+
from: edge.from,
|
|
341
|
+
to: edge.to,
|
|
342
|
+
...(edge.toBoardId !== undefined ? { toBoardId: edge.toBoardId } : {}),
|
|
343
|
+
type: edge.type,
|
|
344
|
+
});
|
|
339
345
|
}
|
|
340
346
|
}
|
|
341
347
|
for (const [id, edge] of snapEdges) {
|
|
342
348
|
if (!curEdges.has(id)) {
|
|
343
|
-
removedEdges.push({
|
|
349
|
+
removedEdges.push({
|
|
350
|
+
id,
|
|
351
|
+
from: edge.from,
|
|
352
|
+
to: edge.to,
|
|
353
|
+
...(edge.toBoardId !== undefined ? { toBoardId: edge.toBoardId } : {}),
|
|
354
|
+
type: edge.type,
|
|
355
|
+
});
|
|
344
356
|
}
|
|
345
357
|
}
|
|
346
358
|
|
|
@@ -395,7 +407,7 @@ export function formatDiff(diff: SnapshotDiffResult): string {
|
|
|
395
407
|
if (diff.addedEdges.length > 0) {
|
|
396
408
|
lines.push(`Added edges (${diff.addedEdges.length}):`);
|
|
397
409
|
for (const e of diff.addedEdges) {
|
|
398
|
-
lines.push(` + ${e.type}: ${e.from} → ${e.to}`);
|
|
410
|
+
lines.push(` + ${e.type}: ${e.from} → ${e.toBoardId ? `${e.toBoardId}/` : ''}${e.to}`);
|
|
399
411
|
}
|
|
400
412
|
lines.push('');
|
|
401
413
|
}
|
|
@@ -403,7 +415,7 @@ export function formatDiff(diff: SnapshotDiffResult): string {
|
|
|
403
415
|
if (diff.removedEdges.length > 0) {
|
|
404
416
|
lines.push(`Removed edges (${diff.removedEdges.length}):`);
|
|
405
417
|
for (const e of diff.removedEdges) {
|
|
406
|
-
lines.push(` - ${e.type}: ${e.from} → ${e.to}`);
|
|
418
|
+
lines.push(` - ${e.type}: ${e.from} → ${e.toBoardId ? `${e.toBoardId}/` : ''}${e.to}`);
|
|
407
419
|
}
|
|
408
420
|
lines.push('');
|
|
409
421
|
}
|
|
@@ -137,12 +137,13 @@ export const compositeToolDefinitions: CompositeToolDefinition[] = [
|
|
|
137
137
|
// compositeFoldedOpNames and frees the name in the same change.
|
|
138
138
|
toolName: 'canvas_snapshot',
|
|
139
139
|
description:
|
|
140
|
-
'Canvas snapshots: save the current state, list saved snapshots, restore one, delete one, garbage-collect old ones, or diff the live canvas against one. Action "save" stores a named snapshot; "list" returns saved snapshots (newest first); "restore" replaces the live canvas with a snapshot; "delete" removes one; "gc" keeps the newest N and deletes the rest (dryRun to preview); "diff" shows added/removed/modified nodes and edges versus a snapshot (by name or id).',
|
|
141
|
-
actionSummary: 'save | list | restore | delete | gc | diff',
|
|
140
|
+
'Canvas snapshots: save the current state, list saved snapshots, restore one, delete one, garbage-collect old ones, or diff the live canvas against one. Action "save" stores a named snapshot; "list" returns saved snapshots (newest first); "restore" replaces the live canvas with a snapshot; "delete" removes one; "gc" keeps the newest N and deletes the rest (dryRun to preview); "diff" shows added/removed/modified nodes and edges versus a snapshot (by name or id); "restore-node" puts one card (nodeId) back to its content in a snapshot, leaving the rest of the board as it is.',
|
|
141
|
+
actionSummary: 'save | list | restore | restore-node | delete | gc | diff',
|
|
142
142
|
actions: {
|
|
143
143
|
save: 'snapshot.save',
|
|
144
144
|
list: 'snapshot.list',
|
|
145
145
|
restore: 'snapshot.restore',
|
|
146
|
+
'restore-node': 'snapshot.restore-node',
|
|
146
147
|
delete: 'snapshot.delete',
|
|
147
148
|
gc: 'snapshot.gc',
|
|
148
149
|
diff: 'snapshot.diff',
|
|
@@ -151,8 +152,10 @@ export const compositeToolDefinitions: CompositeToolDefinition[] = [
|
|
|
151
152
|
{
|
|
152
153
|
toolName: 'canvas_board',
|
|
153
154
|
description:
|
|
154
|
-
'
|
|
155
|
-
|
|
155
|
+
'Knowledge data: knowledge-list reads categories/folders/shared relation presets; category-set replaces category (id/name/glyph/decayType/rate/triggers/revalidationMethod); category-remove deletes an unused category by id (explicit folder assignments, including empty folders, refuse deletion); relation-preset-set saves preset (id/name/vocabulary); relation-preset-remove deletes a template without changing existing board vocabularies; folder-set replaces path/categoryId/lifecycleOverride (null inherits). These workspace actions never open a board and are refused under agent scope fences. ' +
|
|
156
|
+
'Boards in this workspace — the library of the human\'s and your working memory, filed in folders on Home. Your writes always go to the open board (activeBoardId); on Home (no board open) your first write opens the most recent board, or a new one when there are none. Action "list" returns every board (with its folder), most recently opened first; "get" reads one (default: the open board); "create" makes a new empty board WITHOUT opening it (optional folder); "open" explicitly switches the server active board to id (null or omitted opens Home), changing the subsequent write target. Humans browsing Map/Graph stay in the library until they explicitly open a board/card; do not infer activeBoardId from their visible library. After creating a board, open its returned id and verify activeBoardId before writing; another participant can switch the board later. "update" renames a board, files it under a folder (folder "" removes it), or designates its README via readmeNodeId. As part of normal board authoring, read the board metadata; if readmeNodeId is null, choose or create a concise markdown introduction explaining its purpose and key context, then designate that node with "update". Preserve an existing README unless the human asks to change it; humans can override the designation manually. "pin" adds a board to the working set (its README and pinned cards reach your brief whichever board is open; optional reason), "unpin" removes it; a pinned board is read-only context, never a write target. Only the human deletes boards.',
|
|
157
|
+
actionSummary:
|
|
158
|
+
'list | get | create | create-from | open | update | pin | unpin | knowledge-list | category-set | category-remove | relation-preset-set | relation-preset-remove | folder-set',
|
|
156
159
|
actions: {
|
|
157
160
|
list: 'board.list',
|
|
158
161
|
get: 'board.get',
|
|
@@ -160,18 +163,31 @@ export const compositeToolDefinitions: CompositeToolDefinition[] = [
|
|
|
160
163
|
'create-from': 'board.create-from',
|
|
161
164
|
open: 'board.open',
|
|
162
165
|
update: 'board.update',
|
|
166
|
+
'knowledge-list': 'knowledge.list',
|
|
167
|
+
'category-set': 'knowledge.category-set',
|
|
168
|
+
'category-remove': 'knowledge.category-remove',
|
|
169
|
+
'relation-preset-set': 'knowledge.relation-preset-set',
|
|
170
|
+
'relation-preset-remove': 'knowledge.relation-preset-remove',
|
|
171
|
+
'folder-set': 'knowledge.folder-set',
|
|
172
|
+
pin: 'board.pin',
|
|
173
|
+
unpin: 'board.unpin',
|
|
163
174
|
},
|
|
164
175
|
},
|
|
165
176
|
{
|
|
166
177
|
toolName: 'canvas_node',
|
|
167
178
|
description:
|
|
168
|
-
'
|
|
169
|
-
|
|
179
|
+
'Lifecycle data: lifecycle-get reads effective inheritance, due/source qualification and review history (optional board); lifecycle-set sets full lifecycleOverride or null; review requires outcome retain/revise/externalize/retire/unresolved, evidence and applied. Applied retain/revise/externalize with nonempty evidence also requires expectedContentRevision and expectedSources from the content and sources actually checked; stale reviews fail. Records keep those observations and persist content before evidence. lifecycle-restore requires evidence and explicitly restores retirement, never validation. Retired cards are excluded from active briefs by default. Writes accept expectedBoardId and obey edit locks/scope. ' +
|
|
180
|
+
'Create, read, update, or remove a canvas node. One tool for node CRUD: action "add" creates a node (requires type — markdown, board with data.boardId for a portal, status, context, ledger, trace, file, image, webpage, html, group, etc.); "get" reads one node by id; "update" patches an existing node (title, content, position, size, data); "remove" deletes a node by id. For spec-driven content (json-render, graph) use canvas_render; for external/built apps use canvas_app (actions: open-mcp-app, diagram, build-artifact).',
|
|
181
|
+
actionSummary: 'add | get | update | remove | lifecycle-get | lifecycle-set | review | lifecycle-restore',
|
|
170
182
|
actions: {
|
|
171
183
|
add: 'node.add',
|
|
172
184
|
get: 'node.get',
|
|
173
185
|
update: 'node.update',
|
|
174
186
|
remove: 'node.remove',
|
|
187
|
+
'lifecycle-get': 'node.lifecycle-get',
|
|
188
|
+
'lifecycle-set': 'node.lifecycle-set',
|
|
189
|
+
review: 'node.review',
|
|
190
|
+
'lifecycle-restore': 'node.lifecycle-restore',
|
|
175
191
|
},
|
|
176
192
|
},
|
|
177
193
|
{
|
|
@@ -223,11 +239,15 @@ export const compositeToolDefinitions: CompositeToolDefinition[] = [
|
|
|
223
239
|
{
|
|
224
240
|
toolName: 'canvas_view',
|
|
225
241
|
description:
|
|
226
|
-
'Canvas viewport and layout control. Action "arrange" auto-lays-out nodes (grid/columns/etc.); "focus" pans/zooms the viewport to a node; "fit" zooms to fit all nodes in view; "clear" removes every node and edge from the canvas; "remove-annotation" deletes a human-drawn annotation by id.',
|
|
227
|
-
actionSummary:
|
|
242
|
+
'Canvas viewport and layout control. Action "arrange" auto-lays-out nodes (grid/columns/etc.); "focus" pans/zooms the viewport to a node; "fit" zooms to fit all nodes in view; "clear" removes every node and edge from the canvas; "remove-annotation" deletes a human-drawn annotation by id; "get-tour"/"set-tour" read or persist the presentation tour; "tour-go" presents a stop (stop index or step next/previous); "tour-exit" leaves presentation; "move" eases the camera to a nodeId, world rect or viewport over a duration. Camera actions never enter undo history.',
|
|
243
|
+
actionSummary:
|
|
244
|
+
'arrange | focus | fit | clear | remove-annotation | get-tour | set-tour | tour-go | tour-exit | move',
|
|
228
245
|
actions: {
|
|
229
246
|
'get-tour': 'tour.get',
|
|
230
247
|
'set-tour': 'tour.set',
|
|
248
|
+
'tour-go': 'tour.go',
|
|
249
|
+
'tour-exit': 'tour.exit',
|
|
250
|
+
move: 'camera.move',
|
|
231
251
|
arrange: 'arrange',
|
|
232
252
|
focus: 'node.focus',
|
|
233
253
|
fit: 'view.fit',
|
|
@@ -238,10 +258,11 @@ export const compositeToolDefinitions: CompositeToolDefinition[] = [
|
|
|
238
258
|
{
|
|
239
259
|
toolName: 'canvas_query',
|
|
240
260
|
description:
|
|
241
|
-
'Read the board cheapest-first. Action "context" returns a budgeted cross-board brief with a durable consumer cursor (budget is UTF-16 characters); "search" finds nodes by title/content keywords; "layout" returns the full layout; "validate" checks geometry and endpoints.',
|
|
242
|
-
actionSummary: 'context | search | layout | validate',
|
|
261
|
+
'Read the board cheapest-first. Action "context" returns a budgeted cross-board brief with a durable consumer cursor (budget is UTF-16 characters); "graph" explores the library relation graph (focus: board ID, qualified node key, or pins; depth: 0–4), summaries only; "search" finds nodes by title/content keywords; "layout" returns the full layout; "validate" checks geometry and endpoints.',
|
|
262
|
+
actionSummary: 'context | graph | search | layout | validate',
|
|
243
263
|
actions: {
|
|
244
264
|
context: 'context.get',
|
|
265
|
+
graph: 'memory-graph.get',
|
|
245
266
|
search: 'search',
|
|
246
267
|
layout: 'layout.get',
|
|
247
268
|
validate: 'validate.get',
|
|
@@ -275,7 +296,7 @@ export const compositeToolDefinitions: CompositeToolDefinition[] = [
|
|
|
275
296
|
{
|
|
276
297
|
toolName: 'canvas_ax_state',
|
|
277
298
|
description:
|
|
278
|
-
'Read or set host-agnostic PMX AX state. Action "get" reads the AX state + agent-ready context (pinned/focused context, focus field); "set-focus" places node IDs in the AX focus field (nodeIds); "set-policy" patches the tool/prompt policy (tools.allowed|excluded|approvalRequired, prompt.systemAppend|mode — merges with existing); "report-capability" records a host/session capability for diagnostics; "presence" reads agent presence (who is writing, phase, attached session
|
|
299
|
+
'Read or set host-agnostic PMX AX state. Action "get" reads the AX state + agent-ready context (pinned/focused context, focus field); "set-focus" places node IDs in the AX focus field (nodeIds); "set-policy" patches the tool/prompt policy (tools.allowed|excluded|approvalRequired, prompt.systemAppend|mode — merges with existing); "report-capability" records a host/session capability for diagnostics; "presence" reads agent presence (who is writing, phase, attached session); "set-presence" updates this caller\'s presence (phase idle|thinking|tooling|waiting-approval, detail, focusNodeId, cursor, attached — attached:true starts a session, false ends it; contextUsage {used,total} reports the agent\'s real token usage, which the board\'s context panel names). Adapters may pass a source label (e.g. codex).',
|
|
279
300
|
actionSummary: 'get | set-focus | set-policy | report-capability | presence | set-presence',
|
|
280
301
|
actions: {
|
|
281
302
|
get: 'ax.get',
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import { executeOperation, listOperations } from './registry.js';
|
|
10
10
|
import { SOURCE_LABEL_RE } from '../agent-presence.js';
|
|
11
11
|
import { OperationError } from './types.js';
|
|
12
|
-
import { isWorkbenchToken } from '../workbench-auth.js';
|
|
12
|
+
import { isWorkbenchToken, refuseStaleWorkbenchWrite } from '../workbench-auth.js';
|
|
13
13
|
|
|
14
14
|
function responseJson(data: unknown, status = 200): Response {
|
|
15
15
|
return Response.json(data, {
|
|
@@ -90,7 +90,12 @@ export async function dispatchOperationRoute(req: Request, url: URL): Promise<Re
|
|
|
90
90
|
// the browser is not agent activity, so it never synthesizes an
|
|
91
91
|
// auto-ghost. Everything else (CLI, scripts, adapters) counts as agent.
|
|
92
92
|
const workbenchMarked = req.headers.get('x-pmx-workbench') === '1';
|
|
93
|
-
const
|
|
93
|
+
const token = req.headers.get('x-pmx-workbench-token');
|
|
94
|
+
const humanAuthor = isWorkbenchToken(token);
|
|
95
|
+
// Before reconnect delivers the rotated capability, do not silently
|
|
96
|
+
// downgrade an old tab's human save to an agent edit.
|
|
97
|
+
const staleWrite = refuseStaleWorkbenchWrite(req);
|
|
98
|
+
if (staleWrite) return staleWrite;
|
|
94
99
|
// Presence writer label: adapters/CLI may identify themselves; plain
|
|
95
100
|
// HTTP callers read as 'api'.
|
|
96
101
|
const sourceHeader = req.headers.get('x-pmx-source');
|
|
@@ -12,6 +12,7 @@ import { validateOperations } from './ops/validate.js';
|
|
|
12
12
|
import { annotationOperations } from './ops/annotation.js';
|
|
13
13
|
import { snapshotOperations } from './ops/snapshots.js';
|
|
14
14
|
import { boardOperations } from './ops/boards.js';
|
|
15
|
+
import { memoryGraphOperations } from './ops/memory-graph.js';
|
|
15
16
|
import { backupOperations } from './ops/backup.js';
|
|
16
17
|
import { exportOperations } from './ops/export.js';
|
|
17
18
|
import { jsonRenderOperations } from './ops/json-render.js';
|
|
@@ -29,8 +30,10 @@ import { axPresenceOperations } from './ops/ax-presence.js';
|
|
|
29
30
|
import { humanPresenceOperations } from './ops/human-presence.js';
|
|
30
31
|
import { canvasWireOperations } from './ops/canvas-wire.js';
|
|
31
32
|
import { importOperations } from './ops/imports.js';
|
|
33
|
+
import { lifecycleOperations } from './ops/lifecycle.js';
|
|
32
34
|
|
|
33
35
|
for (const op of [
|
|
36
|
+
...lifecycleOperations,
|
|
34
37
|
...nodeOperations,
|
|
35
38
|
...edgeOperations,
|
|
36
39
|
...viewportOperations,
|
|
@@ -40,6 +43,7 @@ for (const op of [
|
|
|
40
43
|
...annotationOperations,
|
|
41
44
|
...snapshotOperations,
|
|
42
45
|
...boardOperations,
|
|
46
|
+
...memoryGraphOperations,
|
|
43
47
|
...backupOperations,
|
|
44
48
|
...exportOperations,
|
|
45
49
|
...jsonRenderOperations,
|
|
@@ -12,7 +12,14 @@ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
|
12
12
|
import { getOperation, listOperations } from './registry.js';
|
|
13
13
|
import { agentSourceLabel, type OperationInvoker } from './invoker.js';
|
|
14
14
|
import { OperationError, type Operation, type OperationMcpToolHost } from './types.js';
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
CONTEXT_READ_OPS,
|
|
17
|
+
contextReadFromPayload,
|
|
18
|
+
pinnedBoardReads,
|
|
19
|
+
graphReads,
|
|
20
|
+
type ContextReadInput,
|
|
21
|
+
} from '../context-reads.js';
|
|
22
|
+
import type { MemoryGraph } from '../../shared/memory-graph.js';
|
|
16
23
|
import { compositeFoldedOpNames, compositeToolDefinitions, type CompositeToolDefinition } from './composites.js';
|
|
17
24
|
|
|
18
25
|
export interface OperationToolHost extends OperationMcpToolHost {
|
|
@@ -50,6 +57,14 @@ async function invokeMcpOperation(
|
|
|
50
57
|
if (node.data === undefined && !node.content) return formatted;
|
|
51
58
|
}
|
|
52
59
|
try {
|
|
60
|
+
if (op.name === 'memory-graph.get') {
|
|
61
|
+
for (const read of graphReads(
|
|
62
|
+
{ channel: 'operation', resource: op.name, source: agentSourceLabel('mcp'), consumer, agentId: null },
|
|
63
|
+
JSON.parse(text) as MemoryGraph,
|
|
64
|
+
))
|
|
65
|
+
await host.recordContextRead(read);
|
|
66
|
+
return formatted;
|
|
67
|
+
}
|
|
53
68
|
await host.recordContextRead(
|
|
54
69
|
contextReadFromPayload(
|
|
55
70
|
{
|
|
@@ -64,6 +79,16 @@ async function invokeMcpOperation(
|
|
|
64
79
|
text,
|
|
65
80
|
),
|
|
66
81
|
);
|
|
82
|
+
if (op.name === 'context.get') {
|
|
83
|
+
const base = {
|
|
84
|
+
channel: 'operation' as const,
|
|
85
|
+
resource: op.name,
|
|
86
|
+
source: agentSourceLabel('mcp'),
|
|
87
|
+
consumer,
|
|
88
|
+
agentId: null,
|
|
89
|
+
};
|
|
90
|
+
for (const read of pinnedBoardReads(base, text)) await host.recordContextRead(read);
|
|
91
|
+
}
|
|
67
92
|
} catch (error) {
|
|
68
93
|
console.error('[pmx-canvas mcp] recording a context read failed:', error);
|
|
69
94
|
}
|
|
@@ -39,7 +39,7 @@ const presenceSetShape = {
|
|
|
39
39
|
'true attaches a session; false detaches it. Read presence first and reuse your host adapter session instead of attaching another. Only detach sessions you own.',
|
|
40
40
|
),
|
|
41
41
|
contextUsage: PRESENCE_SET_SHAPE.contextUsage.describe(
|
|
42
|
-
"The agent's real token
|
|
42
|
+
"The agent's real token usage as {used,total} — the board's context panel names how many tokens its whole context holds (null clears it)",
|
|
43
43
|
),
|
|
44
44
|
};
|
|
45
45
|
const presenceSetSchema = z.looseObject(presenceSetShape);
|
|
@@ -13,7 +13,11 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import { agentPresence } from '../../agent-presence.js';
|
|
15
15
|
import { z } from 'zod';
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
buildAgentContextPreamble,
|
|
18
|
+
serializeNodeForAgentContext,
|
|
19
|
+
summarizeNodeForAgentContext,
|
|
20
|
+
} from '../../../shared/agent-context.js';
|
|
17
21
|
import { buildCanvasAxContext, buildCanvasAxSurfaceSnapshot } from '../../ax-context.js';
|
|
18
22
|
import { applyAxInteraction } from '../../ax-interaction.js';
|
|
19
23
|
import {
|
|
@@ -31,6 +35,9 @@ import { defineOperation, OperationError, type Operation, type OperationContext
|
|
|
31
35
|
import { readTargetBoard } from './boards.js';
|
|
32
36
|
import { normalizeAxNodeIds, normalizeAxSource } from './ax-shared.js';
|
|
33
37
|
import { isRecord } from './nodes.js';
|
|
38
|
+
import { lifecycleQualifications } from '../../lifecycle-status.js';
|
|
39
|
+
import { memoryKey } from '../../../shared/memory-graph.js';
|
|
40
|
+
import { findNeighborhoods } from '../../spatial-analysis.js';
|
|
34
41
|
|
|
35
42
|
// ── Activity-reaction validation (moved from server.ts) ───────
|
|
36
43
|
|
|
@@ -167,20 +174,64 @@ const pinnedContextOperation = defineOperation<z.infer<typeof pinnedContextSchem
|
|
|
167
174
|
},
|
|
168
175
|
handler: ({ board }) => {
|
|
169
176
|
const target = readTargetBoard(board);
|
|
170
|
-
const
|
|
177
|
+
const qualifications = lifecycleQualifications(Date.now(), target.boardId ? [{ boardId: target.boardId }] : []);
|
|
178
|
+
const qualification = (id: string, boardId = target.boardId) =>
|
|
179
|
+
boardId ? qualifications[memoryKey({ boardId, cardId: id })] : undefined;
|
|
180
|
+
const pinnedIds = Array.from(target.pinnedNodeIds).filter((id) => !qualification(id)?.retired);
|
|
171
181
|
const nodes = pinnedIds
|
|
172
182
|
.map((id) => target.layout.nodes.find((node) => node.id === id))
|
|
173
183
|
.filter((node): node is CanvasNodeState => node !== undefined);
|
|
174
184
|
const preamble =
|
|
175
185
|
pinnedIds.length > 0 ? buildAgentContextPreamble(nodes, { defaultTextLength: 700, webpageTextLength: 1600 }) : '';
|
|
176
|
-
const serialized = nodes.map((node) =>
|
|
177
|
-
serializeNodeForAgentContext(node, {
|
|
186
|
+
const serialized = nodes.map((node) => ({
|
|
187
|
+
...serializeNodeForAgentContext(node, {
|
|
178
188
|
defaultTextLength: 700,
|
|
179
189
|
webpageTextLength: 1600,
|
|
180
190
|
includePosition: true,
|
|
181
191
|
}),
|
|
182
|
-
|
|
183
|
-
|
|
192
|
+
qualification: qualification(node.id),
|
|
193
|
+
}));
|
|
194
|
+
const activeNodes = target.layout.nodes.filter((node) => !qualification(node.id)?.retired);
|
|
195
|
+
return {
|
|
196
|
+
preamble,
|
|
197
|
+
nodeIds: pinnedIds,
|
|
198
|
+
count: pinnedIds.length,
|
|
199
|
+
pinnedCount: pinnedIds.length,
|
|
200
|
+
nodes: serialized,
|
|
201
|
+
edges: target.layout.edges.filter(
|
|
202
|
+
(edge) =>
|
|
203
|
+
pinnedIds.includes(edge.from) &&
|
|
204
|
+
(!edge.toBoardId || edge.toBoardId === target.boardId) &&
|
|
205
|
+
pinnedIds.includes(edge.to),
|
|
206
|
+
),
|
|
207
|
+
neighborhoods: findNeighborhoods(activeNodes, new Set(pinnedIds)).map((neighborhood) => ({
|
|
208
|
+
pinnedNodeId: neighborhood.pinnedNodeId,
|
|
209
|
+
pinnedNodeTitle: neighborhood.pinnedNodeTitle,
|
|
210
|
+
nearbyNodes: neighborhood.neighbors.map((node) => ({ ...node, qualification: qualification(node.id) })),
|
|
211
|
+
})),
|
|
212
|
+
pinnedBoards: canvasState
|
|
213
|
+
.listBoards()
|
|
214
|
+
.filter((board) => board.pin)
|
|
215
|
+
.map((board) => {
|
|
216
|
+
const read = canvasState.readBoard(board.id, true);
|
|
217
|
+
const cards = (read?.layout.nodes ?? []).filter((node) => !qualification(node.id, board.id)?.retired);
|
|
218
|
+
const readme = cards.find((node) => node.id === board.readmeNodeId && node.type === 'markdown');
|
|
219
|
+
return {
|
|
220
|
+
boardId: board.id,
|
|
221
|
+
name: board.name,
|
|
222
|
+
pin: board.pin,
|
|
223
|
+
summary: readme ? summarizeNodeForAgentContext(readme, { defaultTextLength: 1000 }) : null,
|
|
224
|
+
readmeQualification: readme ? qualification(readme.id, board.id) : null,
|
|
225
|
+
pinnedTitles: cards
|
|
226
|
+
.filter((node) => read?.state.contextPins.includes(node.id))
|
|
227
|
+
.map((node) => ({
|
|
228
|
+
nodeId: node.id,
|
|
229
|
+
title: String(node.data.title ?? node.id),
|
|
230
|
+
qualification: qualification(node.id, board.id),
|
|
231
|
+
})),
|
|
232
|
+
};
|
|
233
|
+
}),
|
|
234
|
+
};
|
|
184
235
|
},
|
|
185
236
|
});
|
|
186
237
|
|
|
@@ -506,6 +506,14 @@ const axReadsRecordShape = {
|
|
|
506
506
|
.array(z.string())
|
|
507
507
|
.optional()
|
|
508
508
|
.describe('Ids of every serialized node in what the agent received; defaults to deliveredNodeIds.'),
|
|
509
|
+
seenNodeIds: z
|
|
510
|
+
.array(z.string())
|
|
511
|
+
.optional()
|
|
512
|
+
.describe('Ids of every node that arrived with its relations, summaries included; defaults to readNodeIds.'),
|
|
513
|
+
seenLinkRevisions: z
|
|
514
|
+
.record(z.string(), z.number().int().nonnegative())
|
|
515
|
+
.optional()
|
|
516
|
+
.describe('Exact link revisions captured in the delivered graph or brief before transport work.'),
|
|
509
517
|
bytes: z.number().int().nonnegative().describe('Size of what the agent received.'),
|
|
510
518
|
boardId: z.string().nullable().optional().describe('Board that was read, captured before delivery.'),
|
|
511
519
|
};
|
|
@@ -532,6 +540,8 @@ const axReadsRecordOperation = defineOperation<z.infer<typeof axReadsRecordSchem
|
|
|
532
540
|
pinnedNodeIds: input.pinnedNodeIds ?? [...canvasState.contextPinnedNodeIds],
|
|
533
541
|
deliveredNodeIds: input.deliveredNodeIds,
|
|
534
542
|
...(input.readNodeIds ? { readNodeIds: input.readNodeIds } : {}),
|
|
543
|
+
...(input.seenNodeIds ? { seenNodeIds: input.seenNodeIds } : {}),
|
|
544
|
+
...(input.seenLinkRevisions ? { seenLinkRevisions: input.seenLinkRevisions } : {}),
|
|
535
545
|
bytes: input.bytes,
|
|
536
546
|
},
|
|
537
547
|
input.boardId === undefined ? canvasState.activeBoardId : input.boardId,
|
|
@@ -542,12 +552,17 @@ const axReadsRecordOperation = defineOperation<z.infer<typeof axReadsRecordSchem
|
|
|
542
552
|
|
|
543
553
|
const axReadsStatusShape = {
|
|
544
554
|
board: z.string().optional().describe('Board id; defaults to the open board.'),
|
|
555
|
+
boards: z
|
|
556
|
+
.string()
|
|
557
|
+
.optional()
|
|
558
|
+
.describe("Comma-separated board ids: also return each one's latest agent read (pinned boards, in one request)."),
|
|
545
559
|
};
|
|
546
560
|
const axReadsStatusSchema = z.looseObject(axReadsStatusShape);
|
|
547
561
|
|
|
548
562
|
/**
|
|
549
563
|
* Per node: when an agent last read its content, who, and at which revision
|
|
550
|
-
* (vs the node's revision now);
|
|
564
|
+
* (vs the node's revision now); per node, the newest links revision a read
|
|
565
|
+
* carried (connection marks); and per pin: who pinned it, when and why.
|
|
551
566
|
*/
|
|
552
567
|
const axReadsStatusOperation = defineOperation<z.infer<typeof axReadsStatusSchema>, Record<string, unknown>>({
|
|
553
568
|
name: 'ax.reads.status',
|
|
@@ -567,7 +582,18 @@ const axReadsStatusOperation = defineOperation<z.infer<typeof axReadsStatusSchem
|
|
|
567
582
|
: boardId
|
|
568
583
|
? (canvasState.readBoard(boardId, false)?.state.contextPinMeta ?? {})
|
|
569
584
|
: {};
|
|
570
|
-
|
|
585
|
+
const others = typeof input.boards === 'string' ? input.boards.split(',').map((id) => id.trim()) : [];
|
|
586
|
+
return {
|
|
587
|
+
ok: true,
|
|
588
|
+
boardId,
|
|
589
|
+
board: canvasState.getBoardLastRead(boardId),
|
|
590
|
+
...(others.length > 0
|
|
591
|
+
? { boards: Object.fromEntries(others.filter(Boolean).map((id) => [id, canvasState.getBoardLastRead(id)])) }
|
|
592
|
+
: {}),
|
|
593
|
+
nodes: canvasState.getNodeReadStatus(boardId),
|
|
594
|
+
links: canvasState.getSeenLinks(boardId),
|
|
595
|
+
pins,
|
|
596
|
+
};
|
|
571
597
|
},
|
|
572
598
|
});
|
|
573
599
|
|
|
@@ -120,9 +120,11 @@ const backupRestoreOperation = defineOperation<z.infer<typeof restoreSchema>, Re
|
|
|
120
120
|
}
|
|
121
121
|
const previous = `${canvasState.databasePath}.before-restore`;
|
|
122
122
|
restoreCanvasLibrary(file);
|
|
123
|
-
//
|
|
124
|
-
|
|
125
|
-
|
|
123
|
+
// Keep the reload marker in later catalogs too, if a GET overtakes this event.
|
|
124
|
+
const catalog = boardsPayload(true);
|
|
125
|
+
ctx.emit('boards-changed', catalog);
|
|
126
|
+
ctx.emit('settings-changed', { workspaceSettings: canvasState.getWorkspaceSettings() });
|
|
127
|
+
return { ok: true, restored: file, previous, ...catalog };
|
|
126
128
|
},
|
|
127
129
|
});
|
|
128
130
|
|