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
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
} from '../../canvas-operations.js';
|
|
33
33
|
import { resolveCreateGeometry, resolvePatchGeometry, validateCanvasNodePatch } from '../../canvas-geometry.js';
|
|
34
34
|
import { normalizeNodeAxCapabilities } from '../../ax-interaction.js';
|
|
35
|
+
import { isWorkCardState } from '../../../shared/ax-kinds.js';
|
|
35
36
|
import { applyFileContentToNodeData, readFileNodeContent } from '../../file-content.js';
|
|
36
37
|
import { unwatchFileForNode, watchFileForNode } from '../../file-watcher.js';
|
|
37
38
|
import {
|
|
@@ -327,6 +328,21 @@ export function createdNodePayloadFromNode(
|
|
|
327
328
|
|
|
328
329
|
// ── Operation cores (the SDK wraps these directly) ────────────
|
|
329
330
|
|
|
331
|
+
function validateWorkCardData(data: Record<string, unknown> | undefined): void {
|
|
332
|
+
if (!data || data.workState === undefined) return;
|
|
333
|
+
if (!isWorkCardState(data.workState))
|
|
334
|
+
throw new OperationError('workState must be todo, in-progress, blocked, done or cancelled.');
|
|
335
|
+
if (data.detail !== undefined && typeof data.detail !== 'string')
|
|
336
|
+
throw new OperationError('Work card detail must be a string.');
|
|
337
|
+
if (
|
|
338
|
+
data.progress !== undefined &&
|
|
339
|
+
data.progress !== null &&
|
|
340
|
+
(typeof data.progress !== 'number' || !Number.isFinite(data.progress) || data.progress < 0 || data.progress > 100)
|
|
341
|
+
) {
|
|
342
|
+
throw new OperationError('Work card progress must be a number from 0 to 100, or null to clear it.');
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
330
346
|
/**
|
|
331
347
|
* Create a basic (non-webpage / non-group / non-primitive) node. Union of the
|
|
332
348
|
* legacy handleCanvasAddNode generic branch; the SDK passes fileMode 'path',
|
|
@@ -338,6 +354,34 @@ export function createBasicCanvasNode(
|
|
|
338
354
|
): { node: CanvasNodeState; needsCodeGraphRecompute: boolean; sizeAdjustment?: CanvasSizeAdjustment } {
|
|
339
355
|
const type = typeof body.type === 'string' ? body.type : '';
|
|
340
356
|
const extraData = isRecord(body.data) ? body.data : undefined;
|
|
357
|
+
if (type === 'status' && extraData?.workItemId !== undefined) {
|
|
358
|
+
if (body.boardId !== undefined || options.prepareOnlyNodes)
|
|
359
|
+
throw new OperationError('Live work cards must be created on the active board.');
|
|
360
|
+
const owner = extraData.workItemBoardId ?? canvasState.activeBoardId;
|
|
361
|
+
if (
|
|
362
|
+
typeof extraData.workItemId !== 'string' ||
|
|
363
|
+
(owner !== null && typeof owner !== 'string') ||
|
|
364
|
+
!(owner === null ? canvasState.getWorkItems() : canvasState.getWorkItemsOnBoard(owner)).some(
|
|
365
|
+
(item) => item.id === extraData.workItemId,
|
|
366
|
+
)
|
|
367
|
+
)
|
|
368
|
+
throw new OperationError('Live work card requires an existing work item on its owning board.');
|
|
369
|
+
if (
|
|
370
|
+
['title', 'content'].some((key) => body[key] !== undefined) ||
|
|
371
|
+
[
|
|
372
|
+
'title',
|
|
373
|
+
'content',
|
|
374
|
+
'workState',
|
|
375
|
+
'detail',
|
|
376
|
+
'progress',
|
|
377
|
+
'axWorkStatus',
|
|
378
|
+
'liveWorkAvailable',
|
|
379
|
+
'liveWorkSource',
|
|
380
|
+
].some((key) => key in extraData)
|
|
381
|
+
)
|
|
382
|
+
throw new OperationError('Live work card fields are read-only; update the work item instead.');
|
|
383
|
+
}
|
|
384
|
+
if (type === 'status') validateWorkCardData(extraData);
|
|
341
385
|
if (
|
|
342
386
|
extraData &&
|
|
343
387
|
('attachmentId' in extraData || (isRecord(extraData.source) && 'attachmentId' in extraData.source))
|
|
@@ -415,6 +459,9 @@ export function createBasicCanvasNode(
|
|
|
415
459
|
: {}),
|
|
416
460
|
...(typeof content === 'string' ? { content } : {}),
|
|
417
461
|
...(htmlMergedData && Object.keys(htmlMergedData).length > 0 ? { data: htmlMergedData } : {}),
|
|
462
|
+
...(type === 'status' && typeof extraData?.workItemId === 'string'
|
|
463
|
+
? { data: { ...extraData, workItemBoardId: extraData.workItemBoardId ?? canvasState.activeBoardId } }
|
|
464
|
+
: {}),
|
|
418
465
|
...(type === 'trace' && typeof body.toolName === 'string' ? { toolName: body.toolName } : {}),
|
|
419
466
|
...(type === 'trace' && typeof body.category === 'string' ? { category: body.category } : {}),
|
|
420
467
|
...(type === 'trace' && typeof body.status === 'string' ? { status: body.status } : {}),
|
|
@@ -446,6 +493,28 @@ export function buildNodePatch(
|
|
|
446
493
|
body: Record<string, unknown>,
|
|
447
494
|
): { patch: Partial<CanvasNodeState>; groupChildIds?: string[] } {
|
|
448
495
|
if (body.type !== undefined && body.type !== existing.type) throw new OperationError('Node type is immutable.');
|
|
496
|
+
if (existing.type === 'status') {
|
|
497
|
+
const data = isRecord(body.data) ? body.data : {};
|
|
498
|
+
if (
|
|
499
|
+
'workItemId' in data ||
|
|
500
|
+
'workItemBoardId' in data ||
|
|
501
|
+
(typeof existing.data.workItemId === 'string' &&
|
|
502
|
+
(body.title !== undefined ||
|
|
503
|
+
body.content !== undefined ||
|
|
504
|
+
[
|
|
505
|
+
'title',
|
|
506
|
+
'content',
|
|
507
|
+
'workState',
|
|
508
|
+
'detail',
|
|
509
|
+
'progress',
|
|
510
|
+
'axWorkStatus',
|
|
511
|
+
'liveWorkAvailable',
|
|
512
|
+
'liveWorkSource',
|
|
513
|
+
].some((key) => key in data)))
|
|
514
|
+
) {
|
|
515
|
+
throw new OperationError('Live work cards are read-only; create a presentation or update the work item instead.');
|
|
516
|
+
}
|
|
517
|
+
}
|
|
449
518
|
if (
|
|
450
519
|
existing.type === 'file' &&
|
|
451
520
|
typeof existing.data.attachmentId === 'string' &&
|
|
@@ -561,6 +630,7 @@ export function buildNodePatch(
|
|
|
561
630
|
}
|
|
562
631
|
patch.data = existing.type === 'trace' ? mergeTraceNodeDataFields(data, body) : data;
|
|
563
632
|
}
|
|
633
|
+
if (existing.type === 'status' && isRecord(patch.data)) validateWorkCardData(patch.data);
|
|
564
634
|
const error = validateCanvasNodePatch({
|
|
565
635
|
...(patch.position ? { position: patch.position as { x: number; y: number } } : {}),
|
|
566
636
|
...(patch.size ? { size: patch.size as { width: number; height: number } } : {}),
|
|
@@ -284,6 +284,11 @@ const contextShape = {
|
|
|
284
284
|
consumer: z.unknown().optional().describe('Stable consumer id used for the durable board-scoped cursor'),
|
|
285
285
|
since: z.unknown().optional().describe('Explicit content revision; bypasses the durable consumer cursor'),
|
|
286
286
|
budget: z.unknown().optional().describe('Maximum response size in UTF-16 code units'),
|
|
287
|
+
contextDelivery: z
|
|
288
|
+
.boolean()
|
|
289
|
+
.optional()
|
|
290
|
+
.describe('False prepares a brief without recording delivery or advancing a cursor.'),
|
|
291
|
+
includeSteering: z.boolean().optional().describe('False omits steering for hosts that deliver it separately.'),
|
|
287
292
|
};
|
|
288
293
|
const contextSchema = z.looseObject(contextShape);
|
|
289
294
|
|
|
@@ -292,8 +297,20 @@ const contextOperation = defineOperation<z.infer<typeof contextSchema>, Record<s
|
|
|
292
297
|
mutates: false,
|
|
293
298
|
input: contextSchema,
|
|
294
299
|
inputShape: contextShape,
|
|
295
|
-
http: {
|
|
300
|
+
http: {
|
|
301
|
+
method: 'GET',
|
|
302
|
+
path: '/api/canvas/context',
|
|
303
|
+
readInput: (_req, params, url) => {
|
|
304
|
+
const input: Record<string, unknown> = { ...Object.fromEntries(url.searchParams), ...params };
|
|
305
|
+
for (const key of ['contextDelivery', 'includeSteering']) {
|
|
306
|
+
if (input[key] === 'false') input[key] = false;
|
|
307
|
+
else if (input[key] === 'true') input[key] = true;
|
|
308
|
+
}
|
|
309
|
+
return input;
|
|
310
|
+
},
|
|
311
|
+
},
|
|
296
312
|
handler: (input) => {
|
|
313
|
+
canvasState.refreshContextWorkCards();
|
|
297
314
|
const active = canvasState.getActiveBoard();
|
|
298
315
|
if (!active) throw new OperationError('Open a board before requesting its context.', 409);
|
|
299
316
|
const consumer =
|
|
@@ -507,7 +524,10 @@ const contextOperation = defineOperation<z.infer<typeof contextSchema>, Record<s
|
|
|
507
524
|
})),
|
|
508
525
|
);
|
|
509
526
|
entries.push(
|
|
510
|
-
...
|
|
527
|
+
...(input.includeSteering === false
|
|
528
|
+
? []
|
|
529
|
+
: canvasState.getPendingSteering({ consumer: consumer ?? undefined, limit: 50 })
|
|
530
|
+
).map((item) => ({
|
|
511
531
|
sourceBoardId: active.id,
|
|
512
532
|
nodeId: `steering:${item.id}`,
|
|
513
533
|
reason: 'steer' as const,
|
|
@@ -553,7 +573,14 @@ const contextOperation = defineOperation<z.infer<typeof contextSchema>, Record<s
|
|
|
553
573
|
entries,
|
|
554
574
|
budget,
|
|
555
575
|
});
|
|
556
|
-
if (
|
|
576
|
+
if (
|
|
577
|
+
input.contextDelivery !== false &&
|
|
578
|
+
consumer &&
|
|
579
|
+
!explicitSince &&
|
|
580
|
+
result.document &&
|
|
581
|
+
result.nextCursor !== null &&
|
|
582
|
+
!result.invalidCursor
|
|
583
|
+
) {
|
|
557
584
|
canvasState.advanceContextBriefCursor(active.id, consumer, result.nextCursor);
|
|
558
585
|
}
|
|
559
586
|
return (result.document ?? {
|
|
@@ -634,6 +661,7 @@ const undoOperation = defineOperation<z.infer<typeof undoRedoSchema>, Record<str
|
|
|
634
661
|
count: canvasState.contextPinnedNodeIds.size,
|
|
635
662
|
nodeIds: Array.from(canvasState.contextPinnedNodeIds),
|
|
636
663
|
});
|
|
664
|
+
ctx.emit('ax-state-changed', {});
|
|
637
665
|
return { ok: true, description: `Undid: ${entry.description}` };
|
|
638
666
|
},
|
|
639
667
|
});
|
|
@@ -662,6 +690,7 @@ const redoOperation = defineOperation<z.infer<typeof undoRedoSchema>, Record<str
|
|
|
662
690
|
count: canvasState.contextPinnedNodeIds.size,
|
|
663
691
|
nodeIds: Array.from(canvasState.contextPinnedNodeIds),
|
|
664
692
|
});
|
|
693
|
+
ctx.emit('ax-state-changed', {});
|
|
665
694
|
return { ok: true, description: `Redid: ${entry.description}` };
|
|
666
695
|
},
|
|
667
696
|
});
|
|
@@ -364,6 +364,7 @@ const snapshotRestoreNodeOperation = defineOperation<
|
|
|
364
364
|
handler: ({ id, nodeId, preserveHumanEdits }) => {
|
|
365
365
|
const snapshot = canvasState.getSnapshotData(id ?? '');
|
|
366
366
|
if (!snapshot) throw new OperationError('Snapshot not found', 404);
|
|
367
|
+
canvasState.assertSnapshotRestorable(id ?? '');
|
|
367
368
|
const current = canvasState.getNode(nodeId ?? '');
|
|
368
369
|
const earlier = snapshot.nodes.find((node) => node.id === nodeId);
|
|
369
370
|
if (!current || !earlier)
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* This module must never import server.ts or index.ts.
|
|
14
14
|
*/
|
|
15
15
|
import { z } from 'zod';
|
|
16
|
-
import { tourSchema, rectSchema, cameraSchema, tourStopSchema } from '../../../shared/tour.js';
|
|
16
|
+
import { tourSchema, rectSchema, cameraSchema, tourStopSchema, tourPresenterSchema } from '../../../shared/tour.js';
|
|
17
17
|
import { canvasState } from '../../canvas-state.js';
|
|
18
18
|
import {
|
|
19
19
|
arrangeCanvasNodes,
|
|
@@ -339,6 +339,7 @@ const tourGetOperation = defineOperation({
|
|
|
339
339
|
const tourGoShape = {
|
|
340
340
|
stop: z.number().int().nonnegative().optional().describe('Zero-based stop index to go to'),
|
|
341
341
|
step: z.enum(['next', 'previous']).optional().describe('Step relative to the current stop (clamped at the ends)'),
|
|
342
|
+
presenter: tourPresenterSchema.optional().describe('Document intent order and expected board activation'),
|
|
342
343
|
present: z
|
|
343
344
|
.boolean()
|
|
344
345
|
.optional()
|
|
@@ -360,23 +361,24 @@ const tourGoOperation = defineOperation({
|
|
|
360
361
|
if ((input.stop === undefined) === (input.step === undefined)) {
|
|
361
362
|
throw new OperationError('Give exactly one of stop (index) or step ("next" | "previous").');
|
|
362
363
|
}
|
|
363
|
-
const result = goToTourStop(input.stop ?? (input.step as 'next' | 'previous'));
|
|
364
|
-
ctx.emit('canvas-tour-step', { ...result, present: input.present !== false });
|
|
364
|
+
const result = goToTourStop(input.stop ?? (input.step as 'next' | 'previous'), input.presenter);
|
|
365
|
+
if (!('superseded' in result)) ctx.emit('canvas-tour-step', { ...result, present: input.present !== false });
|
|
365
366
|
return { ok: true, ...result };
|
|
366
367
|
},
|
|
367
368
|
});
|
|
368
369
|
|
|
370
|
+
const tourExitShape = { presenter: tourGoShape.presenter };
|
|
369
371
|
const tourExitOperation = defineOperation({
|
|
370
372
|
name: 'tour.exit',
|
|
371
373
|
mutates: false,
|
|
372
|
-
input: z.object(
|
|
373
|
-
inputShape:
|
|
374
|
+
input: z.object(tourExitShape),
|
|
375
|
+
inputShape: tourExitShape,
|
|
374
376
|
http: { method: 'POST', path: '/api/canvas/tour/exit' },
|
|
375
377
|
mcp: { toolName: 'canvas_tour_exit', description: 'Leave presentation in every viewer.' },
|
|
376
|
-
handler: (
|
|
377
|
-
exitTour();
|
|
378
|
-
ctx.emit('canvas-tour-exit', {});
|
|
379
|
-
return { ok: true };
|
|
378
|
+
handler: (input, ctx) => {
|
|
379
|
+
const result = exitTour(input.presenter);
|
|
380
|
+
if (!('superseded' in result)) ctx.emit('canvas-tour-exit', { ...result });
|
|
381
|
+
return { ok: true, ...result };
|
|
380
382
|
},
|
|
381
383
|
});
|
|
382
384
|
|
|
@@ -359,6 +359,7 @@ function notePresence(
|
|
|
359
359
|
): void {
|
|
360
360
|
if (!isPresenceWrite(op) || meta.suppressAutoGhost) return;
|
|
361
361
|
const input = asRecord(rawInput);
|
|
362
|
+
if (name === 'board.move-selection' && (input.preview === true || asRecord(result).replayed === true)) return;
|
|
362
363
|
// Delivery plumbing (claim/mark) must not book an anonymous transport
|
|
363
364
|
// writer nor spam the timeline: attribute it to the consumer that marked,
|
|
364
365
|
// and record no activity row — the Steer row flipping to delivered IS the
|
|
@@ -388,11 +389,52 @@ function notePresence(
|
|
|
388
389
|
}
|
|
389
390
|
}
|
|
390
391
|
|
|
392
|
+
const operationTarget = new AsyncLocalStorage<{ boardId: string; agentId?: string }>();
|
|
393
|
+
|
|
394
|
+
/** Also used by batch's internal node path, which does not dispatch an operation. */
|
|
395
|
+
export function validateOperationTarget(name: string, input: Record<string, unknown>): string | undefined {
|
|
396
|
+
const inherited = operationTarget.getStore();
|
|
397
|
+
const target = input.targetBoardId === undefined ? inherited?.boardId : input.targetBoardId;
|
|
398
|
+
if (target === undefined) return undefined;
|
|
399
|
+
if (typeof target !== 'string' || !target.trim())
|
|
400
|
+
throw new OperationError('targetBoardId must be an existing board ID.');
|
|
401
|
+
if (
|
|
402
|
+
inherited &&
|
|
403
|
+
(target !== inherited.boardId || (input.agentId !== undefined && input.agentId !== inherited.agentId))
|
|
404
|
+
)
|
|
405
|
+
throw new OperationError('Nested operations must retain their board and agent identity.', 409);
|
|
406
|
+
if (name === 'board.update' && input.id !== target)
|
|
407
|
+
throw new OperationError('Board id conflicts with targetBoardId. Rebind before editing another board.', 409);
|
|
408
|
+
for (const key of [
|
|
409
|
+
'board',
|
|
410
|
+
'expectedBoardId',
|
|
411
|
+
'destinationBoardId',
|
|
412
|
+
...(name === 'board.move-selection' ? ['sourceBoardId'] : []),
|
|
413
|
+
]) {
|
|
414
|
+
if (input[key] !== undefined && input[key] !== target)
|
|
415
|
+
throw new OperationError(`${key} conflicts with targetBoardId. Rebind before targeting another board.`, 409);
|
|
416
|
+
}
|
|
417
|
+
if (['board.open', 'board.delete', 'backup.restore'].includes(name) || name.startsWith('webview.'))
|
|
418
|
+
throw new OperationError('This operation controls the shared view or workspace, not a scoped board.', 409);
|
|
419
|
+
return target;
|
|
420
|
+
}
|
|
421
|
+
|
|
391
422
|
export async function executeOperation(
|
|
392
423
|
name: string,
|
|
393
424
|
rawInput: unknown,
|
|
394
425
|
meta: ExecuteOperationMeta = {},
|
|
395
426
|
): Promise<unknown> {
|
|
427
|
+
const input = asRecord(rawInput);
|
|
428
|
+
const target = validateOperationTarget(name, input);
|
|
429
|
+
if (target === undefined) return executeOnCurrentBoard(name, rawInput, meta);
|
|
430
|
+
const agentId =
|
|
431
|
+
operationTarget.getStore()?.agentId ?? (typeof input.agentId === 'string' ? input.agentId : undefined);
|
|
432
|
+
return operationTarget.run({ boardId: target, agentId }, () =>
|
|
433
|
+
canvasState.withBoard(target, () => executeOnCurrentBoard(name, { ...input, agentId }, meta)),
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
async function executeOnCurrentBoard(name: string, rawInput: unknown, meta: ExecuteOperationMeta): Promise<unknown> {
|
|
396
438
|
// Check before attribution, board auto-open, ghosts, or any handler writes.
|
|
397
439
|
meta.signal?.throwIfAborted();
|
|
398
440
|
// Attribution follows this call chain across awaits and nested operations.
|
|
@@ -406,7 +448,8 @@ export async function executeOperation(
|
|
|
406
448
|
async () => {
|
|
407
449
|
const input = asRecord(rawInput);
|
|
408
450
|
const deliversContext =
|
|
409
|
-
|
|
451
|
+
!['node.get', 'context.get'].includes(name) ||
|
|
452
|
+
(input.contextDelivery !== false && input.contextDelivery !== 'false');
|
|
410
453
|
const recordRead = CONTEXT_READ_OPS.has(name) && deliversContext && !meta.fromWorkbench && !meta.proxiedRead;
|
|
411
454
|
const requestedBoard =
|
|
412
455
|
recordRead && 'board' in getOperation(name).inputShape && isString(input.board) && input.board.trim()
|
|
@@ -510,7 +553,8 @@ async function executeOperationInner(name: string, rawInput: unknown, meta: Exec
|
|
|
510
553
|
const rawExpectedBoard = asRecord(rawInput).expectedBoardId;
|
|
511
554
|
// HTTP DELETE query values are strings, including Home's JSON null.
|
|
512
555
|
const expectedBoard = rawExpectedBoard === 'null' ? null : rawExpectedBoard;
|
|
513
|
-
|
|
556
|
+
// Relocations check their durable receipt first, then their explicit source board.
|
|
557
|
+
if (name !== 'board.move-selection' && expectedBoard !== undefined && expectedBoard !== canvasState.activeBoardId) {
|
|
514
558
|
throw new OperationError('The active board changed. Refresh before retrying this action.', 409);
|
|
515
559
|
}
|
|
516
560
|
// File drops carry their captured board through the HTTP round trip. Check
|
|
@@ -638,8 +682,9 @@ async function executeOperationInner(name: string, rawInput: unknown, meta: Exec
|
|
|
638
682
|
}
|
|
639
683
|
|
|
640
684
|
const beforeBoard = canvasState.activeBoardId;
|
|
641
|
-
const beforeRevision =
|
|
642
|
-
|
|
685
|
+
const beforeRevision = ['board.create-from', 'board.delete', 'board.move-selection'].includes(name)
|
|
686
|
+
? canvasState.getContentRevision().revision
|
|
687
|
+
: null;
|
|
643
688
|
meta.signal?.throwIfAborted();
|
|
644
689
|
const result = await op.execute(rawInput, operationContext);
|
|
645
690
|
// Library writes can invalidate relations on the open board without being
|
package/src/server/server.ts
CHANGED
|
@@ -47,8 +47,8 @@ import { basename, extname, isAbsolute, join, relative, resolve, sep } from 'nod
|
|
|
47
47
|
import * as marked from 'marked';
|
|
48
48
|
import { renderWikiMarkdown, resolveWikiLinks, wikiReferences } from './wiki-links.js';
|
|
49
49
|
import { wikiLinksFromData } from '../shared/memory-graph.js';
|
|
50
|
-
import { type CanvasNodeState, IMAGE_MIME_MAP, canvasState } from './canvas-state.js';
|
|
51
|
-
import {
|
|
50
|
+
import { type CanvasNodeState, IMAGE_MIME_MAP, boardCatalogEpoch, canvasState } from './canvas-state.js';
|
|
51
|
+
import { boardsPayload } from './operations/ops/boards.js';
|
|
52
52
|
import {
|
|
53
53
|
buildAxBridge,
|
|
54
54
|
buildAxStateBridge,
|
|
@@ -72,16 +72,16 @@ import { buildAgentContextPreamble } from '../shared/agent-context.js';
|
|
|
72
72
|
import { buildCanvasAxSurfaceSnapshot } from './ax-context.js';
|
|
73
73
|
import { resolveNodeAxCapabilities } from './ax-interaction.js';
|
|
74
74
|
import { normalizeCanvasTheme, type CanvasTheme } from './canvas-db.js';
|
|
75
|
-
import {
|
|
75
|
+
import { isCanvasTheme } from '../shared/themes.js';
|
|
76
76
|
import { canOpenNodeAsSurface } from '../shared/surface.js';
|
|
77
77
|
import { validateLocalImageFile } from './image-source.js';
|
|
78
78
|
import {
|
|
79
79
|
addCanvasNode,
|
|
80
80
|
cancelCodeGraphRecompute,
|
|
81
|
-
primeCanvasRuntimeBackends,
|
|
82
81
|
setCanvasLayoutUpdateEmitter,
|
|
83
|
-
|
|
82
|
+
startBoardRuntime,
|
|
84
83
|
} from './canvas-operations.js';
|
|
84
|
+
import { createBoardLocal } from './board-local.js';
|
|
85
85
|
import { MAX_ATTACHMENT_BYTES, MAX_ATTACHMENT_NAME_LENGTH } from './document-import.js';
|
|
86
86
|
import { dispatchOperationRoute, setOperationEventEmitter } from './operations/index.js';
|
|
87
87
|
import { intentRegistry } from './intent-registry.js';
|
|
@@ -105,7 +105,10 @@ const DEFAULT_PORT = 4313;
|
|
|
105
105
|
|
|
106
106
|
let server: ReturnType<typeof Bun.serve> | null = null;
|
|
107
107
|
let activeWorkspaceRoot = resolve(process.cwd());
|
|
108
|
-
|
|
108
|
+
const boardReplay = createBoardLocal(() => ({
|
|
109
|
+
path: null as string | null,
|
|
110
|
+
contextCards: null as Record<string, unknown> | null,
|
|
111
|
+
}));
|
|
109
112
|
let primaryWorkbenchSessionId = `pmx-${Date.now().toString(36)}`;
|
|
110
113
|
let nextWorkbenchEventId = 1;
|
|
111
114
|
let nextWorkbenchSubscriberId = 1;
|
|
@@ -113,7 +116,6 @@ const workbenchSubscribers = new Map<number, ReadableStreamDefaultController<Uin
|
|
|
113
116
|
const textEncoder = new TextEncoder();
|
|
114
117
|
let primaryWorkbenchAutoOpenEnabled = true;
|
|
115
118
|
const initialCanvasThemeSetting = normalizeCanvasTheme(process.env.PMX_CANVAS_THEME);
|
|
116
|
-
let lastWorkbenchContextCardsEnvelope: Record<string, unknown> | null = null;
|
|
117
119
|
|
|
118
120
|
// Operation-registry SSE wiring (plan-005): the registry never imports this
|
|
119
121
|
// module — the workbench event emitter is injected here, mirroring the
|
|
@@ -941,6 +943,21 @@ export function onWorkbenchEvent(listener: (event: string) => void): () => void
|
|
|
941
943
|
}
|
|
942
944
|
|
|
943
945
|
function broadcastWorkbenchEvent(event: string, payload: PrimaryWorkbenchEventPayload): void {
|
|
946
|
+
if (canvasState.isBackgroundBoard) {
|
|
947
|
+
if (event === 'canvas-layout-update' || event === 'boards-changed')
|
|
948
|
+
canvasState.withVisibleBoard(() => broadcastWorkbenchEvent('boards-changed', boardsPayload()));
|
|
949
|
+
else if (event === 'ax-state-changed') {
|
|
950
|
+
const ownerBoardId = canvasState.activeBoardId;
|
|
951
|
+
canvasState.withVisibleBoard(() => {
|
|
952
|
+
const layout = canvasState.getLayout();
|
|
953
|
+
if (layout.nodes.some((node) => node.type === 'status' && node.data.workItemBoardId === ownerBoardId))
|
|
954
|
+
broadcastWorkbenchEvent('canvas-layout-update', { layout });
|
|
955
|
+
});
|
|
956
|
+
} else if (event === 'agent-presence')
|
|
957
|
+
canvasState.withVisibleBoard(() => broadcastWorkbenchEvent(event, { ...payload, ...agentPresence.snapshot() }));
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
960
|
+
payload = { ...payload, boardId: canvasState.visibleBoardId };
|
|
944
961
|
let remoteEdgesChanged = false;
|
|
945
962
|
if (event === 'canvas-layout-update') {
|
|
946
963
|
// Remote footer titles come from the catalog. Refresh it on endpoint changes
|
|
@@ -960,7 +977,7 @@ function broadcastWorkbenchEvent(event: string, payload: PrimaryWorkbenchEventPa
|
|
|
960
977
|
payload = {
|
|
961
978
|
...payload,
|
|
962
979
|
catalogEpoch: boardCatalogEpoch,
|
|
963
|
-
boardGeneration: canvasState.
|
|
980
|
+
boardGeneration: canvasState.visibleBoardGeneration,
|
|
964
981
|
boardId: canvasState.activeBoardId,
|
|
965
982
|
};
|
|
966
983
|
}
|
|
@@ -990,8 +1007,8 @@ function broadcastWorkbenchEvent(event: string, payload: PrimaryWorkbenchEventPa
|
|
|
990
1007
|
|
|
991
1008
|
function setPrimaryWorkbenchPath(safePath: string, source: string): void {
|
|
992
1009
|
const resolved = resolve(safePath);
|
|
993
|
-
if (
|
|
994
|
-
|
|
1010
|
+
if (boardReplay.current.path === resolved) return;
|
|
1011
|
+
boardReplay.current.path = resolved;
|
|
995
1012
|
broadcastWorkbenchEvent('workbench-open', {
|
|
996
1013
|
path: resolved,
|
|
997
1014
|
title: basename(resolved),
|
|
@@ -1504,44 +1521,6 @@ function handleIframeProbe(): Response {
|
|
|
1504
1521
|
);
|
|
1505
1522
|
}
|
|
1506
1523
|
|
|
1507
|
-
// ── Ext-app recovery diagnostics (0.3.2 report Finding N) ──────
|
|
1508
|
-
//
|
|
1509
|
-
// The WebKit black-tile recovery trail is otherwise trapped inside host panels
|
|
1510
|
-
// without devtools (the Copilot WKWebView). Clients mirror their bounded
|
|
1511
|
-
// recovery log here; agents/testers read it back over HTTP. In-memory only,
|
|
1512
|
-
// diagnostics-grade: no persistence, no snapshots.
|
|
1513
|
-
const EXT_APP_RECOVERY_LOG_MAX = 500;
|
|
1514
|
-
const EXT_APP_RECOVERY_FIELD_MAX = 300;
|
|
1515
|
-
const extAppRecoveryEntries: Array<Record<string, unknown>> = [];
|
|
1516
|
-
|
|
1517
|
-
/**
|
|
1518
|
-
* Clamp a reported entry to the known trail shape ({ t, nodeId, event,
|
|
1519
|
-
* visibility }) with bounded string lengths. The count trim below caps the
|
|
1520
|
-
* number of entries; this caps bytes per entry, so the in-memory log stays
|
|
1521
|
-
* diagnostics-sized no matter what a client posts.
|
|
1522
|
-
*/
|
|
1523
|
-
function clampExtAppRecoveryEntry(entry: Record<string, unknown>): Record<string, unknown> {
|
|
1524
|
-
const clamped: Record<string, unknown> = {};
|
|
1525
|
-
if (typeof entry.t === 'number' && Number.isFinite(entry.t)) clamped.t = entry.t;
|
|
1526
|
-
for (const key of ['nodeId', 'event', 'visibility'] as const) {
|
|
1527
|
-
const value = entry[key];
|
|
1528
|
-
if (typeof value === 'string') clamped[key] = value.slice(0, EXT_APP_RECOVERY_FIELD_MAX);
|
|
1529
|
-
}
|
|
1530
|
-
return clamped;
|
|
1531
|
-
}
|
|
1532
|
-
|
|
1533
|
-
function handleExtAppRecoveryReport(entries: unknown): Response {
|
|
1534
|
-
if (!Array.isArray(entries)) return responseJson({ ok: false, error: 'entries must be an array' }, 400);
|
|
1535
|
-
for (const entry of entries.slice(0, 100)) {
|
|
1536
|
-
if (!entry || typeof entry !== 'object' || Array.isArray(entry)) continue;
|
|
1537
|
-
extAppRecoveryEntries.push(clampExtAppRecoveryEntry(entry as Record<string, unknown>));
|
|
1538
|
-
}
|
|
1539
|
-
if (extAppRecoveryEntries.length > EXT_APP_RECOVERY_LOG_MAX) {
|
|
1540
|
-
extAppRecoveryEntries.splice(0, extAppRecoveryEntries.length - EXT_APP_RECOVERY_LOG_MAX);
|
|
1541
|
-
}
|
|
1542
|
-
return responseJson({ ok: true, total: extAppRecoveryEntries.length });
|
|
1543
|
-
}
|
|
1544
|
-
|
|
1545
1524
|
// ── Node surfaces ("Open as site") ─────────────────────────────
|
|
1546
1525
|
//
|
|
1547
1526
|
// One stable, node-addressable URL — /api/canvas/surface/:nodeId — that serves
|
|
@@ -1841,14 +1820,7 @@ async function handleJsonRenderView(url: URL): Promise<Response> {
|
|
|
1841
1820
|
);
|
|
1842
1821
|
|
|
1843
1822
|
const themeValue = url.searchParams.get('theme');
|
|
1844
|
-
|
|
1845
|
-
// canvas themes (midnight, sepia, …) collapse to their dark/light scheme.
|
|
1846
|
-
const theme =
|
|
1847
|
-
themeValue === 'dark' || themeValue === 'light' || themeValue === 'high-contrast'
|
|
1848
|
-
? themeValue
|
|
1849
|
-
: isCanvasTheme(themeValue)
|
|
1850
|
-
? canvasThemeScheme(themeValue)
|
|
1851
|
-
: undefined;
|
|
1823
|
+
const theme = themeValue === 'dark' || themeValue === 'light' || isCanvasTheme(themeValue) ? themeValue : 'harbor';
|
|
1852
1824
|
const title = (node.data.title as string) || node.id;
|
|
1853
1825
|
// Devtools panel is double-gated: the operator must opt in via the env flag
|
|
1854
1826
|
// AND the request must carry ?devtools=1. Off by default in all normal runs.
|
|
@@ -2118,6 +2090,7 @@ function handleRead(pathLike: string): Response {
|
|
|
2118
2090
|
|
|
2119
2091
|
function handleWorkbenchState(): Response {
|
|
2120
2092
|
const mcpAppHost = getMcpAppHostSnapshot();
|
|
2093
|
+
const primaryWorkbenchPath = boardReplay.current.path;
|
|
2121
2094
|
if (!primaryWorkbenchPath) {
|
|
2122
2095
|
return responseJson({
|
|
2123
2096
|
sessionId: primaryWorkbenchSessionId,
|
|
@@ -2130,7 +2103,7 @@ function handleWorkbenchState(): Response {
|
|
|
2130
2103
|
|
|
2131
2104
|
const safePath = resolveWorkspaceMarkdownPath(primaryWorkbenchPath);
|
|
2132
2105
|
if (!safePath || !existsSync(safePath)) {
|
|
2133
|
-
|
|
2106
|
+
boardReplay.current.path = null;
|
|
2134
2107
|
return responseJson({
|
|
2135
2108
|
sessionId: primaryWorkbenchSessionId,
|
|
2136
2109
|
path: null,
|
|
@@ -2247,6 +2220,7 @@ async function handleWorkbenchIntent(req: Request): Promise<Response> {
|
|
|
2247
2220
|
function buildWorkbenchConnectSnapshot(
|
|
2248
2221
|
requestedSessionId: string,
|
|
2249
2222
|
): Array<{ event: string; payload: Record<string, unknown> }> {
|
|
2223
|
+
const { path: primaryWorkbenchPath, contextCards: lastWorkbenchContextCardsEnvelope } = boardReplay.current;
|
|
2250
2224
|
const continuity =
|
|
2251
2225
|
requestedSessionId.length === 0
|
|
2252
2226
|
? 'fresh'
|
|
@@ -2274,6 +2248,9 @@ function buildWorkbenchConnectSnapshot(
|
|
|
2274
2248
|
timestamp: new Date().toISOString(),
|
|
2275
2249
|
},
|
|
2276
2250
|
});
|
|
2251
|
+
// Establish ownership in this ordered stream before layouts and live tour
|
|
2252
|
+
// commands. The separate catalog HTTP response may arrive after either.
|
|
2253
|
+
events.push({ event: 'boards-changed', payload: boardsPayload() });
|
|
2277
2254
|
if (primaryWorkbenchPath) {
|
|
2278
2255
|
events.push({
|
|
2279
2256
|
event: 'workbench-open',
|
|
@@ -3353,10 +3330,16 @@ export function emitPrimaryWorkbenchEvent(event: string, payload: PrimaryWorkben
|
|
|
3353
3330
|
timestamp: new Date().toISOString(),
|
|
3354
3331
|
};
|
|
3355
3332
|
if (event === 'context-cards') {
|
|
3356
|
-
|
|
3333
|
+
boardReplay.current.contextCards = { ...envelope };
|
|
3357
3334
|
}
|
|
3358
3335
|
syncEventToCanvasState(event, envelope);
|
|
3359
|
-
|
|
3336
|
+
// Events can create server state. Apply them to their producer's board before
|
|
3337
|
+
// deciding whether they belong in the human's currently visible projection.
|
|
3338
|
+
if (
|
|
3339
|
+
!canvasState.isBackgroundBoard &&
|
|
3340
|
+
primaryWorkbenchAutoOpenEnabled &&
|
|
3341
|
+
(event === 'workbench-open' || event === 'ext-app-open')
|
|
3342
|
+
) {
|
|
3360
3343
|
ensureCanvasBrowserOpen();
|
|
3361
3344
|
}
|
|
3362
3345
|
broadcastWorkbenchEvent(event, envelope);
|
|
@@ -3443,6 +3426,27 @@ export interface CanvasServerOptions {
|
|
|
3443
3426
|
allowExternalMcp?: boolean;
|
|
3444
3427
|
}
|
|
3445
3428
|
|
|
3429
|
+
// SDK/MCP producers can own cards before an HTTP listener starts.
|
|
3430
|
+
canvasState.setMoveRuntimeBlockers(() => {
|
|
3431
|
+
const blockers = new Map<string, string>();
|
|
3432
|
+
for (const id of traceManager.ownedNodeIds) blockers.set(id, 'the trace manager still owns this card');
|
|
3433
|
+
for (const [response, prompt] of serverResponseToThreadMap) {
|
|
3434
|
+
blockers.set(response, 'an active response is routed to this card');
|
|
3435
|
+
blockers.set(prompt, 'an active response is routed to this prompt');
|
|
3436
|
+
}
|
|
3437
|
+
for (const intent of pendingWorkbenchIntents) {
|
|
3438
|
+
if (intent.type !== 'canvas-prompt') continue;
|
|
3439
|
+
for (const id of [
|
|
3440
|
+
intent.payload.nodeId,
|
|
3441
|
+
intent.payload.parentNodeId,
|
|
3442
|
+
...(Array.isArray(intent.payload.contextNodeIds) ? intent.payload.contextNodeIds : []),
|
|
3443
|
+
]) {
|
|
3444
|
+
if (typeof id === 'string') blockers.set(id, 'a queued prompt still refers to this card');
|
|
3445
|
+
}
|
|
3446
|
+
}
|
|
3447
|
+
return blockers;
|
|
3448
|
+
});
|
|
3449
|
+
|
|
3446
3450
|
export function startCanvasServer(options: CanvasServerOptions = {}): string | null {
|
|
3447
3451
|
if (server) {
|
|
3448
3452
|
return typeof server.port === 'number' ? loopbackBaseUrl(server.port) : null;
|
|
@@ -3489,12 +3493,9 @@ export function startCanvasServer(options: CanvasServerOptions = {}): string | n
|
|
|
3489
3493
|
});
|
|
3490
3494
|
if (loaded) {
|
|
3491
3495
|
console.log(' Canvas state restored from .pmx-canvas/canvas.db');
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
void pending.finally(() => {
|
|
3496
|
-
emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
|
|
3497
|
-
});
|
|
3496
|
+
const rehydrate = () => startBoardRuntime({ forceRehydrateExtApps: true });
|
|
3497
|
+
if (options.allowExternalMcp === false) withoutExternalMcp(rehydrate);
|
|
3498
|
+
else rehydrate();
|
|
3498
3499
|
}
|
|
3499
3500
|
|
|
3500
3501
|
rotatePrimaryWorkbenchSessionIfNeeded();
|
|
@@ -3549,6 +3550,9 @@ export function startCanvasServer(options: CanvasServerOptions = {}): string | n
|
|
|
3549
3550
|
workspace: activeWorkspaceRoot,
|
|
3550
3551
|
pid: process.pid,
|
|
3551
3552
|
version: serverPackageVersion(),
|
|
3553
|
+
// The stamp open tabs reload on, read once per process: after a rebuild it lags
|
|
3554
|
+
// dist/ on disk, and already-open tabs keep old JS until the server restarts.
|
|
3555
|
+
bundleStamp: canvasBundleStamp(),
|
|
3552
3556
|
contracts: [CANVAS_APP_WRITE_CONTRACT],
|
|
3553
3557
|
persistence: canvasState.persistenceHealth,
|
|
3554
3558
|
});
|
|
@@ -3571,16 +3575,6 @@ export function startCanvasServer(options: CanvasServerOptions = {}): string | n
|
|
|
3571
3575
|
return handleJsonRenderView(url);
|
|
3572
3576
|
}
|
|
3573
3577
|
|
|
3574
|
-
if (url.pathname === '/api/canvas/debug/ext-app-recovery' && req.method === 'POST') {
|
|
3575
|
-
const body = await readJson(req);
|
|
3576
|
-
if (body === null) return malformedJsonResponse();
|
|
3577
|
-
return handleExtAppRecoveryReport(body.entries);
|
|
3578
|
-
}
|
|
3579
|
-
|
|
3580
|
-
if (url.pathname === '/api/canvas/debug/ext-app-recovery' && req.method === 'GET') {
|
|
3581
|
-
return responseJson({ ok: true, entries: extAppRecoveryEntries });
|
|
3582
|
-
}
|
|
3583
|
-
|
|
3584
3578
|
if (url.pathname === '/api/canvas/frame-documents' && req.method === 'POST') {
|
|
3585
3579
|
return handleCreateFrameDocument(req);
|
|
3586
3580
|
}
|