pmx-canvas 0.10.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/extensions/pmx-canvas/extension.mjs +3 -1
- package/.github/extensions/pmx-canvas/runtime.json +1 -1
- package/.github/extensions/pmx-canvas/tools.json +688 -128
- package/CHANGELOG.md +69 -0
- package/dist/amp-plugin/pmx-canvas/LICENSE +21 -0
- package/dist/amp-plugin/pmx-canvas/THIRD_PARTY_LICENSES.txt +331 -0
- package/dist/amp-plugin/pmx-canvas/index.js +5445 -0
- package/dist/amp-plugin/pmx-canvas/runtime.json +3 -0
- package/dist/amp-plugin/pmx-canvas/shared-runtime.mjs +194 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/SKILL.md +251 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/evals/evals.json +1086 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/evals/fixtures/code-exploration/src/auth/jwt.ts +17 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/evals/fixtures/code-exploration/src/auth/login.ts +12 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/evals/fixtures/code-exploration/src/auth/middleware.ts +13 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/evals/fixtures/code-exploration/src/routes/auth.ts +13 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/evals/fixtures/investigation-board/src/handlers/users.ts +27 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/mcp.json +36 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/ax-html-control-surface.md +101 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/codex-app-adapter.md +151 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/excalidraw-diagram-authoring.md +150 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/full-reference.md +1785 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/github-copilot-app-adapter.md +223 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/html-primitives.md +139 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/importing-context.md +132 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/installing-pmx-canvas.md +251 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/tours-and-recording.md +111 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/workbench-workflows.md +900 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas-orchestration/SKILL.md +141 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas-testing/SKILL.md +266 -0
- package/dist/amp-plugin/pmx-canvas/start-mcp.mjs +14857 -0
- package/dist/amp-plugin/pmx-canvas/tools.json +10963 -0
- package/dist/canvas/chunks/abnfDiagram-N423BO3Z-cwtsds5d.js +1 -0
- package/dist/canvas/chunks/architecture-TIHT7OUA-x63cwejs.js +1 -0
- package/dist/canvas/chunks/architectureDiagram-T3A2C74G-b4xc9tpz.js +36 -0
- package/dist/canvas/chunks/blockDiagram-VBNYF7ZC-ahp3yxxs.js +132 -0
- package/dist/canvas/chunks/c4Diagram-5PPSVZJV-nappnay7.js +10 -0
- package/dist/canvas/chunks/classDiagram-JCYQIIEL-k1j2xc1t.js +1 -0
- package/dist/canvas/chunks/classDiagram-v2-OCEON4UE-33x08xf2.js +1 -0
- package/dist/canvas/chunks/cose-bilkent-JH36ORCC-a428syda.js +1 -0
- package/dist/canvas/chunks/cynefin-VYW2F7L2-8v6jxfz8.js +1 -0
- package/dist/canvas/chunks/cynefinDiagram-MW4NZA55-nspsqebj.js +62 -0
- package/dist/canvas/chunks/dagre-VZM6K2ZE-8y6177fh.js +4 -0
- package/dist/canvas/chunks/diagram-7IWD3JNH-vym028wn.js +30 -0
- package/dist/canvas/chunks/diagram-B4RE2ZJO-jddqb8sy.js +3 -0
- package/dist/canvas/chunks/diagram-LBJQPF4R-bjwbpkrn.js +24 -0
- package/dist/canvas/chunks/diagram-Q27KOJAE-j6ph6hcx.js +24 -0
- package/dist/canvas/chunks/diagram-UB23O5K3-p09w8jej.js +41 -0
- package/dist/canvas/chunks/ebnfDiagram-BXEA7PRR-j975ycq5.js +1 -0
- package/dist/canvas/chunks/erDiagram-JOGREHBK-sxp6hjn8.js +85 -0
- package/dist/canvas/chunks/eventmodeling-45OFAUF4-xzfgn6xg.js +1 -0
- package/dist/canvas/chunks/flowDiagram-UKHOOZJN-2nsmkskb.js +1 -0
- package/dist/canvas/chunks/ganttDiagram-PKOTCBZU-4tv230y2.js +292 -0
- package/dist/canvas/chunks/gitGraph-TEB2WS4Q-macbhand.js +1 -0
- package/dist/canvas/chunks/gitGraphDiagram-DS77QQ5N-mqgmnw9g.js +106 -0
- package/dist/canvas/chunks/index-037z5dz2.js +232 -0
- package/dist/canvas/chunks/index-081n3har.js +207 -0
- package/dist/canvas/chunks/index-5n03dmb9.js +2 -0
- package/dist/canvas/chunks/index-5tv8v1hk.js +1 -0
- package/dist/canvas/chunks/index-60pgzdqe.js +157 -0
- package/dist/canvas/chunks/index-6ceya556.js +332 -0
- package/dist/canvas/chunks/index-8g1vb57n.js +2 -0
- package/dist/canvas/chunks/index-8rjes9r6.js +2 -0
- package/dist/canvas/chunks/index-9022d1n9.js +16 -0
- package/dist/canvas/chunks/index-9b02042t.js +2 -0
- package/dist/canvas/chunks/index-ap4q4vez.js +2 -0
- package/dist/canvas/chunks/index-dyp2nhd3.js +2 -0
- package/dist/canvas/chunks/index-frvta4tm.js +2 -0
- package/dist/canvas/chunks/index-gf4e0et4.js +2 -0
- package/dist/canvas/chunks/index-h9ycj8n2.js +2 -0
- package/dist/canvas/chunks/index-hj99nz1f.js +1 -0
- package/dist/canvas/chunks/index-mergmj97.js +2 -0
- package/dist/canvas/chunks/index-nwsya6dj.js +301 -0
- package/dist/canvas/chunks/index-nz8qbd1p.js +2 -0
- package/dist/canvas/chunks/index-qhfrazv3.js +2 -0
- package/dist/canvas/chunks/index-v119nznk.js +4 -0
- package/dist/canvas/chunks/index-vpt37m3d.js +89 -0
- package/dist/canvas/chunks/index-y9ma6azy.js +2 -0
- package/dist/canvas/chunks/index-zx0j3a2z.js +179 -0
- package/dist/canvas/chunks/info-DKCQHKI2-a0ykzs9z.js +1 -0
- package/dist/canvas/chunks/infoDiagram-6WML65LV-qv97qq6a.js +2 -0
- package/dist/canvas/chunks/ishikawaDiagram-WSZJBQD7-p9306gh0.js +70 -0
- package/dist/canvas/chunks/journeyDiagram-NVQOT4AX-sv67zats.js +139 -0
- package/dist/canvas/chunks/kanban-definition-27J2QSJJ-k2pfam6p.js +89 -0
- package/dist/canvas/chunks/katex-dmg8k4zm.js +257 -0
- package/dist/canvas/chunks/mermaid.core-83r0fdce.js +12 -0
- package/dist/canvas/chunks/mindmap-definition-FAOFIHXS-vg2newm1.js +96 -0
- package/dist/canvas/chunks/packet-7NZHBO7P-tqnabcdv.js +1 -0
- package/dist/canvas/chunks/pegDiagram-VL7TDLO6-k2h7zc5n.js +1 -0
- package/dist/canvas/chunks/pie-RZYD4A2V-wke6gyhe.js +1 -0
- package/dist/canvas/chunks/pieDiagram-7S7Q4E2Y-hg169p1v.js +39 -0
- package/dist/canvas/chunks/quadrantDiagram-CIZ2JOQS-bxtvsq0b.js +7 -0
- package/dist/canvas/chunks/radar-I7S5WNFK-w7ka0drw.js +1 -0
- package/dist/canvas/chunks/railroad-3IZDKUUU-y3vg318x.js +1 -0
- package/dist/canvas/chunks/railroad-abnf-AHOZXSZD-6hqvcz3c.js +1 -0
- package/dist/canvas/chunks/railroad-ebnf-EBAXGLYW-vcae260m.js +1 -0
- package/dist/canvas/chunks/railroad-peg-LSFZ7HO6-0dbyme23.js +1 -0
- package/dist/canvas/chunks/railroadDiagram-AXF67PYL-njyhcp4p.js +1 -0
- package/dist/canvas/chunks/requirementDiagram-LRYGKXZP-2z6v09gm.js +84 -0
- package/dist/canvas/chunks/sankeyDiagram-W5VNT64P-15mzam0e.js +40 -0
- package/dist/canvas/chunks/sequenceDiagram-SI44F4Z6-rbshyd2c.js +162 -0
- package/dist/canvas/chunks/sizeCapture-X5ZJPWSS-sg26xwts.js +1 -0
- package/dist/canvas/chunks/stateDiagram-OKZ733FA-6eahp98e.js +1 -0
- package/dist/canvas/chunks/stateDiagram-v2-UEYNNEHI-tty0ggn5.js +1 -0
- package/dist/canvas/chunks/swimlanes-SLNWSIFB-ze5db10t.js +1 -0
- package/dist/canvas/chunks/swimlanesDiagram-ULZ7WXOC-thze75gq.js +8 -0
- package/dist/canvas/chunks/timeline-definition-Z64GVDOM-vnp7jyy8.js +120 -0
- package/dist/canvas/chunks/treeView-QDETBFTQ-6ysffnm6.js +1 -0
- package/dist/canvas/chunks/treemap-6X3UGDF4-m3h9p066.js +1 -0
- package/dist/canvas/chunks/vennDiagram-T6HMQDX7-0qrnb7mj.js +34 -0
- package/dist/canvas/chunks/wardley-OPB4EBWU-dp37drwx.js +1 -0
- package/dist/canvas/chunks/wardleyDiagram-T6FBY63Y-8nxg8wqt.js +78 -0
- package/dist/canvas/chunks/xychartDiagram-ELKLHX3M-nj921e7s.js +7 -0
- package/dist/canvas/global.css +483 -230
- package/dist/canvas/index.js +158 -219
- package/dist/canvas/mcp-app.html +4301 -548
- package/dist/canvas/mermaid-entry.js +338 -338
- package/dist/canvas/surface-theme.css +6 -6
- package/dist/json-render/index.css +1 -1
- package/dist/json-render/index.js +49 -49
- package/dist/types/client/canvas/BarHint.d.ts +5 -3
- package/dist/types/client/canvas/BoardMoveDialog.d.ts +2 -0
- package/dist/types/client/canvas/CanvasNode.d.ts +0 -11
- package/dist/types/client/canvas/EdgeEditor.d.ts +19 -0
- package/dist/types/client/canvas/EdgeLayer.d.ts +1 -2
- package/dist/types/client/canvas/GroupNavigation.d.ts +2 -0
- package/dist/types/client/canvas/TextPrompt.d.ts +4 -0
- package/dist/types/client/canvas/auto-fit.d.ts +4 -5
- package/dist/types/client/mermaid-entry.d.ts +1 -2
- package/dist/types/client/mermaid-render.d.ts +1 -0
- package/dist/types/client/nodes/BoardNode.d.ts +1 -1
- package/dist/types/client/nodes/ExtAppFrame.d.ts +7 -90
- package/dist/types/client/nodes/McpAppNode.d.ts +1 -25
- package/dist/types/client/nodes/MermaidNode.d.ts +1 -8
- package/dist/types/client/nodes/PromptNode.d.ts +1 -0
- package/dist/types/client/nodes/ViewerFrame.d.ts +29 -0
- package/dist/types/client/nodes/ViewerHost.d.ts +6 -0
- package/dist/types/client/nodes/WorkCard.d.ts +7 -0
- package/dist/types/client/nodes/iframe-document-url.d.ts +3 -9
- package/dist/types/client/nodes/primitive-copy.d.ts +5 -0
- package/dist/types/client/nodes/use-ax-surface-bridge.d.ts +1 -0
- package/dist/types/client/nodes/use-iframe-content-height.d.ts +1 -1
- package/dist/types/client/nodes/use-surface-frame.d.ts +4 -6
- package/dist/types/client/nodes/viewer-runtime-state.d.ts +2 -1
- package/dist/types/client/state/canvas-store.d.ts +18 -1
- package/dist/types/client/state/human-store.d.ts +2 -0
- package/dist/types/client/state/intent-bridge.d.ts +5 -2
- package/dist/types/client/state/presentation.d.ts +17 -6
- package/dist/types/client/state/relation-store.d.ts +11 -0
- package/dist/types/client/types.d.ts +1 -1
- package/dist/types/json-render/catalog.d.ts +2 -2
- package/dist/types/json-render/server.d.ts +2 -1
- package/dist/types/server/agent-presence.d.ts +1 -0
- package/dist/types/server/ax-interaction.d.ts +1 -0
- package/dist/types/server/ax-state-manager.d.ts +10 -2
- package/dist/types/server/board-local.d.ts +4 -0
- package/dist/types/server/board-move.d.ts +38 -0
- package/dist/types/server/canvas-db.d.ts +12 -2
- package/dist/types/server/canvas-operations.d.ts +5 -6
- package/dist/types/server/canvas-provenance.d.ts +1 -1
- package/dist/types/server/canvas-serialization.d.ts +1 -0
- package/dist/types/server/canvas-state.d.ts +86 -20
- package/dist/types/server/card-relocation.d.ts +6 -0
- package/dist/types/server/context-reads.d.ts +3 -1
- package/dist/types/server/human-presence.d.ts +1 -1
- package/dist/types/server/index.d.ts +26 -10
- package/dist/types/server/intent-registry.d.ts +8 -4
- package/dist/types/server/knowledge-lifecycle.d.ts +4 -2
- package/dist/types/server/mutation-history.d.ts +7 -5
- package/dist/types/server/operations/ops/boards.d.ts +0 -1
- package/dist/types/server/operations/registry.d.ts +2 -0
- package/dist/types/server/tour-control.d.ts +13 -3
- package/dist/types/server/trace-manager.d.ts +1 -0
- package/dist/types/shared/ax-kinds.d.ts +6 -0
- package/dist/types/shared/ax-surface-protocol.d.ts +8 -9
- package/dist/types/shared/human-presence.d.ts +1 -0
- package/dist/types/shared/lifecycle-status.d.ts +2 -0
- package/dist/types/shared/tour.d.ts +8 -0
- package/docs/RELEASE.md +21 -14
- package/docs/agent-plugin.md +16 -2
- package/docs/amp-plugin.md +271 -0
- package/docs/cli.md +42 -3
- package/docs/design/Chrome.dc.html +32 -6
- package/docs/design/GuestName.dc.html +50 -0
- package/docs/design/PluginLogo.svg +7 -0
- package/docs/design/Types1.dc.html +3 -2
- package/docs/design/Types2.dc.html +1 -1
- package/docs/design/Types3.dc.html +1 -1
- package/docs/design/WorkCards.dc.html +39 -0
- package/docs/design/Zoom.dc.html +37 -60
- package/docs/design/canvas.json +17 -1
- package/docs/design/plugin-logo.png +0 -0
- package/docs/design.md +280 -12
- package/docs/evals/agents-md-sections.md +38 -0
- package/docs/http-api.md +179 -11
- package/docs/mcp.md +69 -9
- package/docs/node-types.md +109 -4
- package/docs/plans/plan-009-tech-debt-backlog.md +3 -3
- package/docs/plans/plan-019-post-0.9-roadmap.md +327 -31
- package/docs/plans/plan-020-wave-5-work-planning.md +553 -0
- package/docs/product-vision-2026-09.md +42 -17
- package/docs/sdk.md +18 -0
- package/package.json +7 -4
- package/plugins/pmx-canvas/assets/preview.png +0 -0
- package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/extension.mjs +3 -1
- package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/runtime.json +1 -1
- package/plugins/pmx-canvas/plugin.json +2 -2
- package/plugins/pmx-canvas/runtime.json +1 -1
- package/plugins/pmx-canvas/tools.json +688 -128
- package/skills/data-analysis/SKILL.md +13 -26
- package/skills/frontend-design/LICENSE.txt +177 -0
- package/skills/frontend-design/SKILL.md +3 -0
- package/skills/pmx-canvas/SKILL.md +232 -849
- package/skills/pmx-canvas/evals/evals.json +26 -3
- package/skills/pmx-canvas/references/full-reference.md +89 -24
- package/skills/pmx-canvas/references/html-primitives.md +7 -0
- package/skills/pmx-canvas/references/installing-pmx-canvas.md +6 -6
- package/skills/pmx-canvas/references/tours-and-recording.md +29 -2
- package/skills/pmx-canvas/references/workbench-workflows.md +900 -0
- package/skills/pmx-canvas-testing/SKILL.md +7 -5
- package/skills/published-consumer-e2e/SKILL.md +25 -8
- package/skills/published-consumer-e2e/scripts/run-published-consumer-e2e.sh +51 -77
- package/skills/web-artifacts-builder/LICENSE.txt +202 -0
- package/skills/web-artifacts-builder/SKILL.md +6 -0
- package/skills/web-artifacts-builder/scripts/init-artifact.sh +2 -2
- package/src/cli/commands/apps.ts +1 -0
- package/src/cli/commands/boards.ts +30 -0
- package/src/client/App.tsx +20 -4
- package/src/client/canvas/AttentionHistory.tsx +10 -2
- package/src/client/canvas/BarHint.tsx +6 -3
- package/src/client/canvas/BoardMoveDialog.tsx +225 -0
- package/src/client/canvas/CanvasNode.tsx +79 -49
- package/src/client/canvas/CanvasViewport.tsx +42 -14
- package/src/client/canvas/ContextMenu.tsx +97 -15
- package/src/client/canvas/EdgeEditor.tsx +149 -0
- package/src/client/canvas/EdgeLayer.tsx +441 -174
- package/src/client/canvas/ExpandedNodeOverlay.tsx +5 -2
- package/src/client/canvas/GroupNavigation.tsx +85 -0
- package/src/client/canvas/Presentation.tsx +53 -3
- package/src/client/canvas/RelationsPanel.tsx +9 -0
- package/src/client/canvas/SelectionBar.tsx +21 -0
- package/src/client/canvas/SessionPanel.tsx +35 -1
- package/src/client/canvas/TextPrompt.tsx +18 -1
- package/src/client/canvas/TopBar.tsx +54 -9
- package/src/client/canvas/auto-fit.ts +9 -11
- package/src/client/canvas/canvas-area.ts +2 -2
- package/src/client/canvas/use-focus-trap.ts +6 -1
- package/src/client/mermaid-entry.ts +6 -20
- package/src/client/mermaid-render.ts +39 -0
- package/src/client/nodes/AxStepControls.tsx +45 -69
- package/src/client/nodes/BoardNode.tsx +65 -9
- package/src/client/nodes/ContextNode.tsx +88 -396
- package/src/client/nodes/ExtAppFrame.tsx +302 -1465
- package/src/client/nodes/FileNode.tsx +92 -81
- package/src/client/nodes/HtmlNode.tsx +8 -36
- package/src/client/nodes/LedgerNode.tsx +31 -17
- package/src/client/nodes/MarkdownNode.tsx +14 -24
- package/src/client/nodes/McpAppNode.tsx +48 -154
- package/src/client/nodes/MermaidNode.tsx +80 -69
- package/src/client/nodes/PromptNode.tsx +44 -17
- package/src/client/nodes/ResponseNode.tsx +8 -6
- package/src/client/nodes/StatusNode.tsx +34 -7
- package/src/client/nodes/StatusSummary.tsx +16 -3
- package/src/client/nodes/TraceNode.tsx +8 -8
- package/src/client/nodes/ViewerFrame.tsx +273 -0
- package/src/client/nodes/ViewerHost.tsx +41 -0
- package/src/client/nodes/WebpageNode.tsx +22 -24
- package/src/client/nodes/WorkCard.tsx +175 -0
- package/src/client/nodes/iframe-document-url.ts +20 -27
- package/src/client/nodes/primitive-copy.ts +97 -0
- package/src/client/nodes/use-ax-surface-bridge.ts +18 -14
- package/src/client/nodes/use-iframe-content-height.ts +34 -10
- package/src/client/nodes/use-surface-frame.ts +22 -11
- package/src/client/nodes/viewer-runtime-state.ts +41 -35
- package/src/client/state/attention-bridge.ts +5 -0
- package/src/client/state/attention-store.ts +1 -0
- package/src/client/state/canvas-store.ts +202 -26
- package/src/client/state/human-store.ts +22 -2
- package/src/client/state/intent-bridge.ts +20 -5
- package/src/client/state/presentation.ts +135 -15
- package/src/client/state/relation-store.ts +127 -1
- package/src/client/state/sse-bridge.ts +18 -19
- package/src/client/theme/global.css +483 -230
- package/src/client/theme/surface-theme.css +6 -6
- package/src/client/types.ts +1 -0
- package/src/json-render/charts/tufte-components.tsx +44 -2
- package/src/json-render/renderer/index.css +62 -2
- package/src/json-render/renderer/index.tsx +31 -7
- package/src/json-render/server.ts +3 -2
- package/src/mcp/server.ts +13 -3
- package/src/server/agent-presence.ts +43 -23
- package/src/server/ax-flow-loop.ts +6 -0
- package/src/server/ax-gate-ttl.ts +8 -1
- package/src/server/ax-interaction.ts +16 -10
- package/src/server/ax-state-manager.ts +17 -4
- package/src/server/ax-wait.ts +20 -6
- package/src/server/board-export.ts +54 -30
- package/src/server/board-local.ts +20 -0
- package/src/server/board-move.ts +297 -0
- package/src/server/canvas-db.ts +249 -158
- package/src/server/canvas-geometry.ts +1 -0
- package/src/server/canvas-operations.ts +139 -109
- package/src/server/canvas-schema.ts +21 -0
- package/src/server/canvas-serialization.ts +2 -0
- package/src/server/canvas-state.ts +797 -159
- package/src/server/card-relocation.ts +53 -0
- package/src/server/code-graph.ts +4 -3
- package/src/server/context-reads.ts +83 -55
- package/src/server/demo-state.json +140 -114
- package/src/server/file-watcher.ts +30 -10
- package/src/server/html-primitives.ts +3 -9
- package/src/server/html-surface.ts +8 -0
- package/src/server/human-presence.ts +18 -5
- package/src/server/index.ts +54 -24
- package/src/server/intent-registry.ts +43 -5
- package/src/server/knowledge-lifecycle.ts +32 -9
- package/src/server/lifecycle-status.ts +29 -1
- package/src/server/mcp-app-runtime.ts +26 -7
- package/src/server/mutation-history.ts +37 -8
- package/src/server/operations/composites.ts +6 -3
- package/src/server/operations/mcp-contracts.ts +32 -2
- package/src/server/operations/mcp.ts +36 -9
- package/src/server/operations/ops/annotation.ts +22 -1
- package/src/server/operations/ops/ax-await.ts +1 -0
- package/src/server/operations/ops/ax-timeline.ts +28 -2
- package/src/server/operations/ops/batch.ts +25 -11
- package/src/server/operations/ops/boards.ts +45 -6
- package/src/server/operations/ops/nodes.ts +70 -0
- package/src/server/operations/ops/query.ts +32 -3
- package/src/server/operations/ops/snapshots.ts +1 -0
- package/src/server/operations/ops/viewport.ts +11 -9
- package/src/server/operations/registry.ts +49 -4
- package/src/server/server.ts +69 -75
- package/src/server/tour-control.ts +97 -12
- package/src/server/trace-manager.ts +4 -0
- package/src/server/web-artifacts/scripts/init-artifact.sh +2 -2
- package/src/shared/agent-context.ts +7 -1
- package/src/shared/auto-arrange.ts +5 -4
- package/src/shared/ax-kinds.ts +13 -0
- package/src/shared/ax-surface-protocol.ts +20 -9
- package/src/shared/content-height-reporter.ts +4 -3
- package/src/shared/human-presence.ts +1 -0
- package/src/shared/lifecycle-status.ts +2 -0
- package/src/shared/tour.ts +8 -0
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
import type { CanvasNodeState, CanvasEdge, CanvasLayout } from './canvas-state.js';
|
|
16
16
|
import { currentActor as getActor, setCurrentActor } from './attribution.js';
|
|
17
|
+
import { createBoardLocal } from './board-local.js';
|
|
17
18
|
|
|
18
19
|
// ── Types ────────────────────────────────────────────────────────────
|
|
19
20
|
|
|
@@ -88,11 +89,25 @@ export interface SnapshotDiffResult {
|
|
|
88
89
|
removedEdges: { id: string; from: string; to: string; toBoardId?: string; type: string }[];
|
|
89
90
|
}
|
|
90
91
|
|
|
91
|
-
function comparableNodeData(data: Record<string, unknown>):
|
|
92
|
+
function comparableNodeData(data: Record<string, unknown>): string {
|
|
92
93
|
const comparable = { ...data };
|
|
93
94
|
delete comparable.title;
|
|
94
95
|
delete comparable.content;
|
|
95
|
-
|
|
96
|
+
// Match content-revision semantics: fetching the same page again is not a
|
|
97
|
+
// content change. Keep authored timestamps elsewhere, including nested data.
|
|
98
|
+
delete comparable.fetchedAt;
|
|
99
|
+
if (comparable.provenance && typeof comparable.provenance === 'object') {
|
|
100
|
+
const provenance = { ...(comparable.provenance as Record<string, unknown>) };
|
|
101
|
+
delete provenance.syncedAt;
|
|
102
|
+
comparable.provenance = provenance;
|
|
103
|
+
}
|
|
104
|
+
// Canonical JSON preserves array order and the persisted JSON semantics for
|
|
105
|
+
// undefined properties, while ignoring object-key insertion order at any depth.
|
|
106
|
+
return JSON.stringify(comparable, (_key, value: unknown) =>
|
|
107
|
+
value && typeof value === 'object' && !Array.isArray(value)
|
|
108
|
+
? Object.fromEntries(Object.entries(value).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)))
|
|
109
|
+
: value,
|
|
110
|
+
);
|
|
96
111
|
}
|
|
97
112
|
|
|
98
113
|
// ── Ring Buffer ──────────────────────────────────────────────────────
|
|
@@ -100,11 +115,25 @@ function comparableNodeData(data: Record<string, unknown>): Record<string, unkno
|
|
|
100
115
|
const MAX_ENTRIES = 200;
|
|
101
116
|
|
|
102
117
|
class MutationHistory {
|
|
103
|
-
private entries:
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
private
|
|
118
|
+
private readonly board = createBoardLocal(() => ({ entries: [] as MutationEntry[], cursor: -1, replaying: false }));
|
|
119
|
+
private get entries() {
|
|
120
|
+
return this.board.current.entries;
|
|
121
|
+
}
|
|
122
|
+
private set entries(value: MutationEntry[]) {
|
|
123
|
+
this.board.current.entries = value;
|
|
124
|
+
}
|
|
125
|
+
private get cursor() {
|
|
126
|
+
return this.board.current.cursor;
|
|
127
|
+
}
|
|
128
|
+
private set cursor(value: number) {
|
|
129
|
+
this.board.current.cursor = value;
|
|
130
|
+
}
|
|
131
|
+
private get _replaying() {
|
|
132
|
+
return this.board.current.replaying;
|
|
133
|
+
}
|
|
134
|
+
private set _replaying(value: boolean) {
|
|
135
|
+
this.board.current.replaying = value;
|
|
136
|
+
}
|
|
108
137
|
|
|
109
138
|
get isReplaying(): boolean {
|
|
110
139
|
return this._replaying;
|
|
@@ -315,7 +344,7 @@ export function diffLayouts(
|
|
|
315
344
|
changes.push(`content changed (${lenDiff >= 0 ? '+' : ''}${lenDiff} chars)`);
|
|
316
345
|
}
|
|
317
346
|
|
|
318
|
-
if (
|
|
347
|
+
if (comparableNodeData(snapNode.data) !== comparableNodeData(curNode.data)) {
|
|
319
348
|
changes.push('data changed');
|
|
320
349
|
}
|
|
321
350
|
|
|
@@ -152,15 +152,17 @@ export const compositeToolDefinitions: CompositeToolDefinition[] = [
|
|
|
152
152
|
{
|
|
153
153
|
toolName: 'canvas_board',
|
|
154
154
|
description:
|
|
155
|
+
'"move-selection" previews or atomically relocates selected cards and group descendants to a new board, preserves identity and leaves a portal. Supply sourceBoardId, nodeIds, name, expectedRevision and requestId; preview:true reports blockers and the Undo/snapshot boundary. Retry identical input with the same requestId. It never opens the destination. ' +
|
|
155
156
|
'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
157
|
'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
158
|
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',
|
|
159
|
+
'list | get | create | create-from | move-selection | open | update | pin | unpin | knowledge-list | category-set | category-remove | relation-preset-set | relation-preset-remove | folder-set',
|
|
159
160
|
actions: {
|
|
160
161
|
list: 'board.list',
|
|
161
162
|
get: 'board.get',
|
|
162
163
|
create: 'board.create',
|
|
163
164
|
'create-from': 'board.create-from',
|
|
165
|
+
'move-selection': 'board.move-selection',
|
|
164
166
|
open: 'board.open',
|
|
165
167
|
update: 'board.update',
|
|
166
168
|
'knowledge-list': 'knowledge.list',
|
|
@@ -239,9 +241,9 @@ export const compositeToolDefinitions: CompositeToolDefinition[] = [
|
|
|
239
241
|
{
|
|
240
242
|
toolName: 'canvas_view',
|
|
241
243
|
description:
|
|
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"
|
|
244
|
+
'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; "add-annotation" draws freehand points or text in world coordinates; "remove-annotation" erases a whole mark 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
245
|
actionSummary:
|
|
244
|
-
'arrange | focus | fit | clear | remove-annotation | get-tour | set-tour | tour-go | tour-exit | move',
|
|
246
|
+
'arrange | focus | fit | clear | add-annotation | remove-annotation | get-tour | set-tour | tour-go | tour-exit | move',
|
|
245
247
|
actions: {
|
|
246
248
|
'get-tour': 'tour.get',
|
|
247
249
|
'set-tour': 'tour.set',
|
|
@@ -252,6 +254,7 @@ export const compositeToolDefinitions: CompositeToolDefinition[] = [
|
|
|
252
254
|
focus: 'node.focus',
|
|
253
255
|
fit: 'view.fit',
|
|
254
256
|
clear: 'canvas.clear',
|
|
257
|
+
'add-annotation': 'annotation.add',
|
|
255
258
|
'remove-annotation': 'annotation.remove',
|
|
256
259
|
},
|
|
257
260
|
},
|
|
@@ -11,6 +11,7 @@ import { tourSchema, tourStopSchema } from '../../shared/tour.js';
|
|
|
11
11
|
const id = z.string().min(1);
|
|
12
12
|
const count = z.number().int().nonnegative();
|
|
13
13
|
const ok = z.looseObject({ ok: z.boolean() });
|
|
14
|
+
const tourSuperseded = z.object({ ok: z.literal(true), superseded: z.literal(true) });
|
|
14
15
|
const identified = z.looseObject({ id });
|
|
15
16
|
const message = z.strictObject({ message: z.string() });
|
|
16
17
|
const node = z.looseObject({
|
|
@@ -85,6 +86,20 @@ const payloads: Record<string, ZodType> = {
|
|
|
85
86
|
board,
|
|
86
87
|
z.looseObject({ ok: z.boolean(), sourceBoardId: id, cards: z.array(node), edges: z.array(edge) }),
|
|
87
88
|
]),
|
|
89
|
+
'board.move-selection': z.union([
|
|
90
|
+
z.looseObject({
|
|
91
|
+
ok: z.boolean(),
|
|
92
|
+
preview: z.looseObject({ sourceBoardId: id, nodeIds: z.array(id), blockers: z.array(z.string()) }),
|
|
93
|
+
}),
|
|
94
|
+
z.looseObject({
|
|
95
|
+
ok: z.boolean(),
|
|
96
|
+
sourceBoardId: id,
|
|
97
|
+
destinationBoardId: id,
|
|
98
|
+
portalId: id,
|
|
99
|
+
nodeIds: z.array(id),
|
|
100
|
+
replayed: z.boolean(),
|
|
101
|
+
}),
|
|
102
|
+
]),
|
|
88
103
|
'board.open': boards,
|
|
89
104
|
'board.update': boards,
|
|
90
105
|
'board.pin': boards,
|
|
@@ -123,13 +138,28 @@ const payloads: Record<string, ZodType> = {
|
|
|
123
138
|
'canvas.redo': history,
|
|
124
139
|
'tour.get': z.looseObject({ tour: tourSchema, derived: z.boolean(), position: count.nullable() }),
|
|
125
140
|
'tour.set': ok,
|
|
126
|
-
'tour.go': z.
|
|
127
|
-
|
|
141
|
+
'tour.go': z.union([
|
|
142
|
+
z.looseObject({
|
|
143
|
+
ok: z.boolean(),
|
|
144
|
+
catalogEpoch: z.string(),
|
|
145
|
+
boardGeneration: count,
|
|
146
|
+
sequence: count,
|
|
147
|
+
index: count,
|
|
148
|
+
total: count,
|
|
149
|
+
stop: tourStopSchema,
|
|
150
|
+
}),
|
|
151
|
+
tourSuperseded,
|
|
152
|
+
]),
|
|
153
|
+
'tour.exit': z.union([
|
|
154
|
+
z.looseObject({ ok: z.boolean(), catalogEpoch: z.string(), boardGeneration: count, sequence: count }),
|
|
155
|
+
tourSuperseded,
|
|
156
|
+
]),
|
|
128
157
|
'camera.move': z.looseObject({ ok: z.boolean(), stop: tourStopSchema, viewport }),
|
|
129
158
|
arrange: z.looseObject({ ok: z.boolean(), layout: z.enum(['grid', 'column', 'flow']) }),
|
|
130
159
|
'node.focus': z.looseObject({ ok: z.boolean(), focused: id, panned: z.boolean() }),
|
|
131
160
|
'view.fit': z.looseObject({ ok: z.boolean(), viewport }),
|
|
132
161
|
'canvas.clear': z.looseObject({ ok: z.boolean(), cleared: z.literal(true) }),
|
|
162
|
+
'annotation.add': entity('annotation'),
|
|
133
163
|
'annotation.remove': ok,
|
|
134
164
|
'context.get': z.looseObject({ version: z.literal(1), budgetUnit: z.literal('utf16-code-units') }),
|
|
135
165
|
'memory-graph.get': z.looseObject({
|
|
@@ -36,6 +36,17 @@ export interface OperationToolHost extends OperationMcpToolHost {
|
|
|
36
36
|
recordContextRead(read: ContextReadInput): Promise<void>;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
const routingShape = {
|
|
40
|
+
targetBoardId: z
|
|
41
|
+
.string()
|
|
42
|
+
.min(1)
|
|
43
|
+
.optional()
|
|
44
|
+
.describe(
|
|
45
|
+
'Execute on this board without changing the shared workbench view. Nested operations retain this target.',
|
|
46
|
+
),
|
|
47
|
+
agentId: z.string().min(1).optional().describe('Stable agent/thread identity for attribution and scope fences.'),
|
|
48
|
+
};
|
|
49
|
+
|
|
39
50
|
function executionOptions(extra: RequestHandlerExtra<ServerRequest, ServerNotification>): OperationExecutionOptions {
|
|
40
51
|
const progressToken = extra._meta?.progressToken;
|
|
41
52
|
return {
|
|
@@ -58,9 +69,23 @@ async function invokeMcpOperation(
|
|
|
58
69
|
options: OperationExecutionOptions,
|
|
59
70
|
) {
|
|
60
71
|
options.signal?.throwIfAborted();
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
72
|
+
// buildInput may project operation-specific fields; routing belongs to the
|
|
73
|
+
// invocation and must also cover formatter follow-up reads and accounting.
|
|
74
|
+
const routing = {
|
|
75
|
+
...(formatInput.targetBoardId !== undefined ? { targetBoardId: formatInput.targetBoardId } : {}),
|
|
76
|
+
...(formatInput.agentId !== undefined ? { agentId: formatInput.agentId } : {}),
|
|
77
|
+
};
|
|
78
|
+
opInput = { ...opInput, ...routing };
|
|
79
|
+
if (typeof routing.agentId === 'string') consumer = routing.agentId;
|
|
80
|
+
const board =
|
|
81
|
+
typeof routing.targetBoardId === 'string'
|
|
82
|
+
? routing.targetBoardId
|
|
83
|
+
: typeof opInput.board === 'string' && opInput.board.trim()
|
|
84
|
+
? opInput.board.trim()
|
|
85
|
+
: undefined;
|
|
86
|
+
const preparesContext = op.name === 'context.get' && opInput.contextDelivery === false;
|
|
87
|
+
const readTarget = CONTEXT_READ_OPS.has(op.name) && !preparesContext ? await host.prepareContextRead(board) : null;
|
|
88
|
+
if (readTarget?.boardId && 'board' in op.inputShape && opInput.board === undefined) {
|
|
64
89
|
opInput = { ...opInput, board: readTarget.boardId };
|
|
65
90
|
formatInput = { ...formatInput, board: readTarget.boardId };
|
|
66
91
|
}
|
|
@@ -70,8 +95,10 @@ async function invokeMcpOperation(
|
|
|
70
95
|
// Preserve the host receiver, and bind options to formatter follow-up reads
|
|
71
96
|
// (history flags, snapshot summary, streaming node metadata).
|
|
72
97
|
const formatterHost: OperationMcpToolHost = {
|
|
73
|
-
getPinnedNodeIds: (target) => host.getPinnedNodeIds(target),
|
|
74
|
-
invoker: () => ({
|
|
98
|
+
getPinnedNodeIds: (target) => host.getPinnedNodeIds(target ?? board),
|
|
99
|
+
invoker: () => ({
|
|
100
|
+
invoke: (name, input, followUp) => invoker.invoke(name, { ...input, ...routing }, { ...options, ...followUp }),
|
|
101
|
+
}),
|
|
75
102
|
};
|
|
76
103
|
const formatted = structuredOperationResult(
|
|
77
104
|
op.name,
|
|
@@ -151,7 +178,7 @@ export function registerOperationTools(server: McpServer, getHost: () => Promise
|
|
|
151
178
|
// successful over MCP while the same HTTP call 400'd (0.5.1 Amp
|
|
152
179
|
// finding A). Loose parsing forwards unknown keys to the op layer,
|
|
153
180
|
// the single authority on dead-field rejection, HTTP and MCP alike.
|
|
154
|
-
inputSchema: z.looseObject({ ...op.inputShape, ...(tool.extraShape ?? {}) }),
|
|
181
|
+
inputSchema: z.looseObject({ ...op.inputShape, ...(tool.extraShape ?? {}), ...routingShape }),
|
|
155
182
|
outputSchema: operationOutputSchema([op.name]),
|
|
156
183
|
annotations: operationToolAnnotations(tool.toolName),
|
|
157
184
|
},
|
|
@@ -376,8 +403,8 @@ export function registerOperationHelpTool(server: McpServer): void {
|
|
|
376
403
|
const composite = compositeToolDefinitions.find((def) => compositeMemberOps(def).includes(name));
|
|
377
404
|
const publicName = (field: string) =>
|
|
378
405
|
Object.entries(composite?.fieldRemap ?? {}).find(([, original]) => original === field)?.[0] ?? field;
|
|
379
|
-
const shape = Object.fromEntries(
|
|
380
|
-
Object.entries({ ...op.inputShape, ...op.mcp?.extraShape }).map(([field, schema]) => [
|
|
406
|
+
const shape: Record<string, ZodTypeAny> = Object.fromEntries(
|
|
407
|
+
Object.entries({ ...op.inputShape, ...op.mcp?.extraShape, ...routingShape }).map(([field, schema]) => [
|
|
381
408
|
publicName(field),
|
|
382
409
|
schema,
|
|
383
410
|
]),
|
|
@@ -444,7 +471,7 @@ export function registerCompositeTools(
|
|
|
444
471
|
def.toolName,
|
|
445
472
|
{
|
|
446
473
|
description: def.description,
|
|
447
|
-
inputSchema: z.looseObject(buildCompositeShape(def)),
|
|
474
|
+
inputSchema: z.looseObject({ ...buildCompositeShape(def), ...routingShape }),
|
|
448
475
|
outputSchema: operationOutputSchema(compositeMemberOps(def)),
|
|
449
476
|
annotations: operationToolAnnotations(def.toolName),
|
|
450
477
|
},
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
*
|
|
14
14
|
* This module must never import server.ts or index.ts.
|
|
15
15
|
*/
|
|
16
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
16
17
|
import { z } from 'zod';
|
|
17
18
|
import { canvasState, type CanvasAnnotation } from '../../canvas-state.js';
|
|
18
19
|
import { summarizeCanvasAnnotation } from '../../canvas-serialization.js';
|
|
@@ -35,7 +36,7 @@ const annotationRemoveOperation = defineOperation<z.infer<typeof annotationRemov
|
|
|
35
36
|
},
|
|
36
37
|
mcp: {
|
|
37
38
|
toolName: 'canvas_remove_annotation',
|
|
38
|
-
description: '
|
|
39
|
+
description: 'Erase a whole canvas mark by ID, whether drawn by a human or agent.',
|
|
39
40
|
extraShape: {
|
|
40
41
|
id: z.string().describe('Annotation ID to remove'),
|
|
41
42
|
},
|
|
@@ -119,6 +120,19 @@ const annotationAddOperation = defineOperation<z.infer<typeof annotationAddSchem
|
|
|
119
120
|
method: 'POST',
|
|
120
121
|
path: '/api/canvas/annotation',
|
|
121
122
|
},
|
|
123
|
+
mcp: {
|
|
124
|
+
toolName: 'canvas_add_annotation',
|
|
125
|
+
description: 'Draw canvas ink or write a text note in world coordinates. No session required.',
|
|
126
|
+
extraShape: {
|
|
127
|
+
type: z.enum(['freehand', 'text']).optional(),
|
|
128
|
+
points: z.array(z.object({ x: z.number(), y: z.number() })).min(1),
|
|
129
|
+
width: z.number().optional(),
|
|
130
|
+
color: z.string().optional(),
|
|
131
|
+
label: z.string().optional(),
|
|
132
|
+
text: z.string().optional(),
|
|
133
|
+
id: z.string().optional(),
|
|
134
|
+
},
|
|
135
|
+
},
|
|
122
136
|
handler: (input) => {
|
|
123
137
|
const body: Record<string, unknown> = input;
|
|
124
138
|
const type = body.type === 'text' ? 'text' : 'freehand';
|
|
@@ -164,6 +178,13 @@ const annotationAddOperation = defineOperation<z.infer<typeof annotationAddSchem
|
|
|
164
178
|
createdAt: new Date().toISOString(),
|
|
165
179
|
};
|
|
166
180
|
|
|
181
|
+
const existing = canvasState.getAnnotations().find((entry) => entry.id === id);
|
|
182
|
+
if (existing) {
|
|
183
|
+
if (!isDeepStrictEqual(existing, { ...annotation, createdAt: existing.createdAt })) {
|
|
184
|
+
throw new OperationError(`Annotation "${id}" already exists with different content.`, 409);
|
|
185
|
+
}
|
|
186
|
+
return { ok: true, annotation: summarizeCanvasAnnotation(existing) };
|
|
187
|
+
}
|
|
167
188
|
canvasState.addAnnotation(annotation);
|
|
168
189
|
return { ok: true, annotation: summarizeCanvasAnnotation(annotation) };
|
|
169
190
|
},
|
|
@@ -69,6 +69,7 @@ function clampTimeoutMs(value: unknown): number {
|
|
|
69
69
|
/** Accept the operation's timeoutMs as well as the direct HTTP waitMs spelling. */
|
|
70
70
|
function readWaitMsInput(_req: Request, params: Record<string, string>, url: URL): Record<string, unknown> {
|
|
71
71
|
return {
|
|
72
|
+
...Object.fromEntries(url.searchParams),
|
|
72
73
|
...params,
|
|
73
74
|
timeoutMs: clampTimeoutMs(url.searchParams.get('timeoutMs') ?? url.searchParams.get('waitMs') ?? ''),
|
|
74
75
|
};
|
|
@@ -289,7 +289,17 @@ const axTimelineGetOperation = defineOperation<z.infer<typeof axTimelineGetSchem
|
|
|
289
289
|
const axDeliveryPendingShape = {
|
|
290
290
|
consumer: z.unknown().optional().describe('Consumer/source label to exclude from results (e.g. copilot, mcp).'),
|
|
291
291
|
limit: z.unknown().optional().describe('Max steering messages to return.'),
|
|
292
|
+
offset: z.coerce
|
|
293
|
+
.number()
|
|
294
|
+
.int()
|
|
295
|
+
.nonnegative()
|
|
296
|
+
.optional()
|
|
297
|
+
.describe('Skip this many matching pending messages. Restart at zero after an empty page.'),
|
|
292
298
|
order: z.unknown().optional().describe('"oldest" (FIFO, default) or "newest" first.'),
|
|
299
|
+
addressedOnly: z
|
|
300
|
+
.boolean()
|
|
301
|
+
.optional()
|
|
302
|
+
.describe('Only messages addressed to consumer, excluding broadcasts before applying the limit.'),
|
|
293
303
|
timeoutMs: z
|
|
294
304
|
.unknown()
|
|
295
305
|
.optional()
|
|
@@ -310,10 +320,13 @@ const axDeliveryPendingOperation = defineOperation<z.infer<typeof axDeliveryPend
|
|
|
310
320
|
// consumer arrives or the timeout elapses. Custom readInput replaces the
|
|
311
321
|
// default query merge, so re-read the standard params here too.
|
|
312
322
|
readInput: (_req, params, url) => {
|
|
313
|
-
const raw = Number(url.searchParams.get('waitMs') ?? '');
|
|
323
|
+
const raw = Number(url.searchParams.get('timeoutMs') ?? url.searchParams.get('waitMs') ?? '');
|
|
314
324
|
return {
|
|
315
325
|
...Object.fromEntries(url.searchParams),
|
|
316
326
|
...params,
|
|
327
|
+
...(url.searchParams.has('addressedOnly')
|
|
328
|
+
? { addressedOnly: url.searchParams.get('addressedOnly') === 'true' }
|
|
329
|
+
: {}),
|
|
317
330
|
timeoutMs: Number.isFinite(raw) && raw > 0 ? raw : 0,
|
|
318
331
|
};
|
|
319
332
|
},
|
|
@@ -353,7 +366,13 @@ const axDeliveryPendingOperation = defineOperation<z.infer<typeof axDeliveryPend
|
|
|
353
366
|
// can't bury the human's current action behind stale undelivered rows. Both
|
|
354
367
|
// queries apply the same loop-safe consumer filter before the limit.
|
|
355
368
|
const newest = input.order === 'newest';
|
|
356
|
-
|
|
369
|
+
if (input.addressedOnly && !consumer) throw new OperationError('addressedOnly requires consumer.', 400);
|
|
370
|
+
const scope = {
|
|
371
|
+
...(consumer ? { consumer } : {}),
|
|
372
|
+
...(limit ? { limit } : {}),
|
|
373
|
+
addressedOnly: input.addressedOnly,
|
|
374
|
+
offset: input.offset,
|
|
375
|
+
};
|
|
357
376
|
const readPending = () =>
|
|
358
377
|
newest ? canvasState.getPendingSteeringForContext(scope) : canvasState.getPendingSteering(scope);
|
|
359
378
|
let pending = readPending();
|
|
@@ -495,6 +514,12 @@ const axReadsListOperation = defineOperation<z.infer<typeof axReadsListSchema>,
|
|
|
495
514
|
});
|
|
496
515
|
|
|
497
516
|
const axReadsRecordShape = {
|
|
517
|
+
deliveryId: z
|
|
518
|
+
.string()
|
|
519
|
+
.min(1)
|
|
520
|
+
.max(200)
|
|
521
|
+
.optional()
|
|
522
|
+
.describe('Idempotent adapter receipt ID, scoped to source, consumer and board.'),
|
|
498
523
|
channel: z.enum(CONTEXT_READ_CHANNELS).describe('How the read reached the agent.'),
|
|
499
524
|
resource: z.string().min(1).describe('Resource URI or operation name that was read.'),
|
|
500
525
|
consumer: z.string().nullable().optional().describe('Reader identity (MCP client name, adapter host).'),
|
|
@@ -532,6 +557,7 @@ const axReadsRecordOperation = defineOperation<z.infer<typeof axReadsRecordSchem
|
|
|
532
557
|
handler: (input) => {
|
|
533
558
|
const read = canvasState.recordContextRead(
|
|
534
559
|
{
|
|
560
|
+
deliveryId: input.deliveryId,
|
|
535
561
|
channel: input.channel,
|
|
536
562
|
resource: input.resource,
|
|
537
563
|
source: input.source ?? 'api',
|
|
@@ -26,7 +26,8 @@ import { z } from 'zod';
|
|
|
26
26
|
import { canvasState } from '../../canvas-state.js';
|
|
27
27
|
import { serializeCanvasNode, serializeCanvasNodeCompact } from '../../canvas-serialization.js';
|
|
28
28
|
import { addCanvasNode } from '../../canvas-operations.js';
|
|
29
|
-
import {
|
|
29
|
+
import { currentActor } from '../../attribution.js';
|
|
30
|
+
import { executeOperation, runWithSuppressedEmits, validateOperationTarget } from '../registry.js';
|
|
30
31
|
import { defineOperation, type Operation, type OperationContext } from '../types.js';
|
|
31
32
|
import { readJsonValue } from '../http.js';
|
|
32
33
|
import { isRecord } from './nodes.js';
|
|
@@ -57,6 +58,8 @@ const SUPPORTED_BATCH_OPS = new Set([
|
|
|
57
58
|
'group.create',
|
|
58
59
|
'group.add',
|
|
59
60
|
'group.remove',
|
|
61
|
+
'annotation.add',
|
|
62
|
+
'annotation.remove',
|
|
60
63
|
'pin.set',
|
|
61
64
|
'pin.add',
|
|
62
65
|
'pin.remove',
|
|
@@ -224,6 +227,7 @@ function shapeBatchEntry(op: string, result: unknown, creationReceipt = false):
|
|
|
224
227
|
async function runBatch(operations: BatchEntry[], ctx: OperationContext): Promise<BatchEnvelope> {
|
|
225
228
|
const refs: Record<string, unknown> = {};
|
|
226
229
|
const results: Array<Record<string, unknown>> = [];
|
|
230
|
+
const actor = currentActor('api');
|
|
227
231
|
|
|
228
232
|
for (let index = 0; index < operations.length; index++) {
|
|
229
233
|
const operation = operations[index]!;
|
|
@@ -233,6 +237,7 @@ async function runBatch(operations: BatchEntry[], ctx: OperationContext): Promis
|
|
|
233
237
|
}
|
|
234
238
|
try {
|
|
235
239
|
ctx.signal?.throwIfAborted();
|
|
240
|
+
validateOperationTarget(operation.op, args);
|
|
236
241
|
if (ctx.onProgress) {
|
|
237
242
|
const boardGeneration = canvasState.boardGeneration;
|
|
238
243
|
await ctx.onProgress({ progress: index, total: operations.length, message: operation.op });
|
|
@@ -268,14 +273,19 @@ async function runBatch(operations: BatchEntry[], ctx: OperationContext): Promis
|
|
|
268
273
|
// Batch churn is exempt from auto-ghosts (matches the skill's explicit
|
|
269
274
|
// ghost-cursor exemption for batch operations). No `fromWorkbench`: the
|
|
270
275
|
// browser never issues batches, so inner writes are agent writes and the
|
|
271
|
-
// scope fence applies to each of them.
|
|
272
|
-
const pending = executeOperation(
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
:
|
|
278
|
-
|
|
276
|
+
// scope fence applies to each of them using the original writer identity.
|
|
277
|
+
const pending = executeOperation(
|
|
278
|
+
dispatch.name,
|
|
279
|
+
{ agentId: actor.agentId, ...dispatch.args },
|
|
280
|
+
{
|
|
281
|
+
source: actor.source,
|
|
282
|
+
suppressAutoGhost: true,
|
|
283
|
+
signal: ctx.signal,
|
|
284
|
+
onProgress: ctx.onProgress
|
|
285
|
+
? (progress) => ctx.onProgress!({ progress: index, total: operations.length, message: progress.message })
|
|
286
|
+
: undefined,
|
|
287
|
+
},
|
|
288
|
+
);
|
|
279
289
|
// The first write may synchronously open a board from Home. Capture after
|
|
280
290
|
// dispatch, then refuse to continue a partially completed batch elsewhere.
|
|
281
291
|
const boardGeneration = canvasState.boardGeneration;
|
|
@@ -319,6 +329,7 @@ const batchShape = {
|
|
|
319
329
|
}),
|
|
320
330
|
)
|
|
321
331
|
.describe('Ordered array of batch operations'),
|
|
332
|
+
agentId: z.string().optional().describe('Default writer identity for entries without their own agentId'),
|
|
322
333
|
full: z
|
|
323
334
|
.boolean()
|
|
324
335
|
.optional()
|
|
@@ -404,13 +415,16 @@ const batchOperation = defineOperation<z.infer<typeof batchSchema>, BatchEnvelop
|
|
|
404
415
|
: isRecord(body) && Array.isArray(body.operations)
|
|
405
416
|
? body.operations
|
|
406
417
|
: [];
|
|
407
|
-
return {
|
|
418
|
+
return {
|
|
419
|
+
operations,
|
|
420
|
+
...(isRecord(body) ? { agentId: body.agentId, targetBoardId: body.targetBoardId } : {}),
|
|
421
|
+
};
|
|
408
422
|
},
|
|
409
423
|
},
|
|
410
424
|
mcp: {
|
|
411
425
|
toolName: 'canvas_batch',
|
|
412
426
|
description:
|
|
413
|
-
'Run a non-atomic batch of canvas operations with optional assigned references. Use assign to name a result, then reference it later as "$name" for the created node id or "$name.id" for a specific result field. On failure, earlier successful operations remain applied and the response includes ok:false, failedIndex, error, results, and refs. Supports node.add, node.update, node.remove, graph.add, edge.add, edge.remove, group.create, group.add, group.remove, pin.set/add/remove, snapshot.save, and arrange.',
|
|
427
|
+
'Run a non-atomic batch of canvas operations with optional assigned references. Use assign to name a result, then reference it later as "$name" for the created node id or "$name.id" for a specific result field. On failure, earlier successful operations remain applied and the response includes ok:false, failedIndex, error, results, and refs. Supports node.add, node.update, node.remove, graph.add, edge.add, edge.remove, group.create, group.add, group.remove, annotation.add, annotation.remove, pin.set/add/remove, snapshot.save, and arrange.',
|
|
414
428
|
formatResult: (result, input) => {
|
|
415
429
|
const envelope = result as BatchEnvelope;
|
|
416
430
|
const payload = wantsFullBatch(input) ? envelope : compactBatchResult(envelope);
|
|
@@ -12,12 +12,12 @@
|
|
|
12
12
|
*
|
|
13
13
|
* This module must never import server.ts or index.ts.
|
|
14
14
|
*/
|
|
15
|
-
import { randomUUID } from 'node:crypto';
|
|
16
15
|
import { z } from 'zod';
|
|
17
16
|
import { normalizeBoardFolder } from '../../../shared/boards.js';
|
|
18
17
|
import { relationVocabularySchema } from '../../../shared/relations.js';
|
|
19
|
-
import { canvasState, type CanvasLayout } from '../../canvas-state.js';
|
|
18
|
+
import { boardCatalogEpoch, canvasState, type CanvasLayout } from '../../canvas-state.js';
|
|
20
19
|
import { openCanvasBoard } from '../../canvas-operations.js';
|
|
20
|
+
import { rewatchAllFileNodes } from '../../file-watcher.js';
|
|
21
21
|
import { summarizeNodeForAgentContext } from '../../../shared/agent-context.js';
|
|
22
22
|
import { BRIEF_SUMMARY_LENGTH } from '../../context-brief.js';
|
|
23
23
|
import { memoryGraph, boardConnections } from '../../memory-graph.js';
|
|
@@ -25,7 +25,6 @@ import { defineOperation, OperationError, type Operation, type OperationContext
|
|
|
25
25
|
|
|
26
26
|
/** The most cards a Home thumbnail draws (the largest first); the rest are noise at that size. */
|
|
27
27
|
const PREVIEW_CARDS = 24;
|
|
28
|
-
export const boardCatalogEpoch = randomUUID();
|
|
29
28
|
let catalogRevision = 0;
|
|
30
29
|
let reloadRevision = 0;
|
|
31
30
|
|
|
@@ -68,8 +67,8 @@ export function boardsPayload(reloaded = false): Record<string, unknown> {
|
|
|
68
67
|
catalogEpoch: boardCatalogEpoch,
|
|
69
68
|
catalogRevision: revision,
|
|
70
69
|
reloadRevision,
|
|
71
|
-
boardGeneration: canvasState.
|
|
72
|
-
activeBoardId: canvasState.
|
|
70
|
+
boardGeneration: canvasState.visibleBoardGeneration,
|
|
71
|
+
activeBoardId: canvasState.visibleBoardId,
|
|
73
72
|
boards: boards.map((board) => {
|
|
74
73
|
const read = canvasState.readBoard(board.id, true);
|
|
75
74
|
const readme = read?.layout.nodes.find((node) => node.id === board.readmeNodeId && node.type === 'markdown');
|
|
@@ -218,7 +217,8 @@ const boardGetOperation = defineOperation<z.infer<typeof getSchema>, Record<stri
|
|
|
218
217
|
const id = typeof input.id === 'string' && input.id.trim() ? input.id.trim() : canvasState.activeBoardId;
|
|
219
218
|
const board = id ? canvasState.listBoards().find((entry) => entry.id === id) : undefined;
|
|
220
219
|
if (!board) throw new OperationError(id ? `Board "${id}" not found.` : 'No board is open.', 404);
|
|
221
|
-
|
|
220
|
+
const contentRevision = canvasState.readBoard(board.id, false)!.state.revisionState?.revision ?? 0;
|
|
221
|
+
return { ok: true, board, contentRevision, open: board.id === canvasState.activeBoardId };
|
|
222
222
|
},
|
|
223
223
|
});
|
|
224
224
|
|
|
@@ -406,6 +406,44 @@ const boardCreateFromOperation = defineOperation<z.infer<typeof fromSchema>, Rec
|
|
|
406
406
|
},
|
|
407
407
|
});
|
|
408
408
|
|
|
409
|
+
const moveShape = {
|
|
410
|
+
sourceBoardId: z.string().min(1),
|
|
411
|
+
nodeIds: z.array(z.string().min(1)).min(1),
|
|
412
|
+
name: z.string().trim().min(1).max(120),
|
|
413
|
+
expectedRevision: z.number().int().nonnegative(),
|
|
414
|
+
requestId: z.string().trim().min(1).max(200),
|
|
415
|
+
preview: z.boolean().optional(),
|
|
416
|
+
};
|
|
417
|
+
const moveSchema = z.object(moveShape);
|
|
418
|
+
const boardMoveOperation = defineOperation<z.infer<typeof moveSchema>, Record<string, unknown>>({
|
|
419
|
+
name: 'board.move-selection',
|
|
420
|
+
// Own validation precedes activation, and preview must never emit mutation/intent events.
|
|
421
|
+
mutates: false,
|
|
422
|
+
input: moveSchema,
|
|
423
|
+
inputShape: moveShape,
|
|
424
|
+
http: { method: 'POST', path: '/api/canvas/boards/move-selection' },
|
|
425
|
+
mcp: {
|
|
426
|
+
toolName: 'canvas_move_selection_to_board',
|
|
427
|
+
description:
|
|
428
|
+
'Preview or atomically move selected cards/group descendants to a new board, preserving identities and leaving a portal. Requires explicit sourceBoardId, expectedRevision and a durable requestId. Retry with identical input; never opens the destination. Preview lists unsafe bindings and the Undo/snapshot boundary.',
|
|
429
|
+
formatResult: jsonResult,
|
|
430
|
+
},
|
|
431
|
+
handler: (input, ctx) => {
|
|
432
|
+
const outcome = canvasState.moveSelectionToNewBoard(input);
|
|
433
|
+
if ('preview' in outcome) return { ok: true, ...outcome };
|
|
434
|
+
if (!outcome.replayed) {
|
|
435
|
+
rewatchAllFileNodes();
|
|
436
|
+
emitBoardsChanged(ctx);
|
|
437
|
+
ctx.emit('context-pins-changed', {
|
|
438
|
+
count: canvasState.contextPinnedNodeIds.size,
|
|
439
|
+
nodeIds: [...canvasState.contextPinnedNodeIds],
|
|
440
|
+
});
|
|
441
|
+
ctx.emit('ax-state-changed', {});
|
|
442
|
+
}
|
|
443
|
+
return { ok: true, ...outcome.result, replayed: outcome.replayed };
|
|
444
|
+
},
|
|
445
|
+
});
|
|
446
|
+
|
|
409
447
|
// ── board.pin / board.unpin ───────────────────────────────────
|
|
410
448
|
// A pinned board joins the agent's working set: its README and pinned cards
|
|
411
449
|
// travel in the brief whichever board is open. Pinning is context, never a
|
|
@@ -492,6 +530,7 @@ export const boardOperations: Operation[] = [
|
|
|
492
530
|
boardGetOperation,
|
|
493
531
|
boardCreateOperation,
|
|
494
532
|
boardCreateFromOperation,
|
|
533
|
+
boardMoveOperation,
|
|
495
534
|
boardUpdateOperation,
|
|
496
535
|
boardPinOperation,
|
|
497
536
|
boardUnpinOperation,
|