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
|
@@ -10,6 +10,7 @@ import { watch, existsSync, statSync } from 'node:fs';
|
|
|
10
10
|
import type { FSWatcher } from 'node:fs';
|
|
11
11
|
import { canvasState } from './canvas-state.js';
|
|
12
12
|
import { applyFileContentToNodeData, readFileNodeContent } from './file-content.js';
|
|
13
|
+
import { createBoardLocal } from './board-local.js';
|
|
13
14
|
|
|
14
15
|
function logFileWatcherWarning(action: string, error: unknown, details?: Record<string, unknown>): void {
|
|
15
16
|
console.warn(`[file-watcher] ${action}`, { error, ...(details ?? {}) });
|
|
@@ -22,7 +23,22 @@ interface WatchedFile {
|
|
|
22
23
|
lastMtime: number;
|
|
23
24
|
}
|
|
24
25
|
|
|
25
|
-
const
|
|
26
|
+
const watcherSets = new Set<Map<string, WatchedFile>>();
|
|
27
|
+
function stopWatching(watched: Map<string, WatchedFile>): void {
|
|
28
|
+
for (const entry of watched.values()) entry.watcher.close();
|
|
29
|
+
watched.clear();
|
|
30
|
+
}
|
|
31
|
+
const boardWatchers = createBoardLocal(
|
|
32
|
+
() => {
|
|
33
|
+
const watched = new Map<string, WatchedFile>();
|
|
34
|
+
watcherSets.add(watched);
|
|
35
|
+
return watched;
|
|
36
|
+
},
|
|
37
|
+
(watched) => {
|
|
38
|
+
stopWatching(watched);
|
|
39
|
+
watcherSets.delete(watched);
|
|
40
|
+
},
|
|
41
|
+
);
|
|
26
42
|
let onFileChanged: ((nodeId: string) => void) | null = null;
|
|
27
43
|
|
|
28
44
|
/** Register a callback for when a watched file changes. */
|
|
@@ -34,6 +50,7 @@ export function onFileNodeChanged(cb: (nodeId: string) => void): void {
|
|
|
34
50
|
export function watchFileForNode(nodeId: string, filePath: string): void {
|
|
35
51
|
if (!filePath || !existsSync(filePath)) return;
|
|
36
52
|
|
|
53
|
+
const watched = boardWatchers.current;
|
|
37
54
|
const existing = watched.get(filePath);
|
|
38
55
|
if (existing) {
|
|
39
56
|
existing.nodeIds.add(nodeId);
|
|
@@ -42,10 +59,14 @@ export function watchFileForNode(nodeId: string, filePath: string): void {
|
|
|
42
59
|
|
|
43
60
|
try {
|
|
44
61
|
const stat = statSync(filePath);
|
|
45
|
-
const watcher = watch(
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
62
|
+
const watcher = watch(
|
|
63
|
+
filePath,
|
|
64
|
+
{ persistent: false },
|
|
65
|
+
canvasState.bindBoard((eventType) => {
|
|
66
|
+
if (eventType !== 'change') return;
|
|
67
|
+
handleFileChange(filePath);
|
|
68
|
+
}),
|
|
69
|
+
);
|
|
49
70
|
|
|
50
71
|
watched.set(filePath, {
|
|
51
72
|
path: filePath,
|
|
@@ -60,6 +81,7 @@ export function watchFileForNode(nodeId: string, filePath: string): void {
|
|
|
60
81
|
|
|
61
82
|
/** Stop watching a file for a given node. */
|
|
62
83
|
export function unwatchFileForNode(nodeId: string, filePath?: string): void {
|
|
84
|
+
const watched = boardWatchers.current;
|
|
63
85
|
for (const [path, entry] of watched) {
|
|
64
86
|
if (filePath && path !== filePath) continue;
|
|
65
87
|
entry.nodeIds.delete(nodeId);
|
|
@@ -72,14 +94,11 @@ export function unwatchFileForNode(nodeId: string, filePath?: string): void {
|
|
|
72
94
|
|
|
73
95
|
/** Stop all watchers. */
|
|
74
96
|
export function unwatchAll(): void {
|
|
75
|
-
for (const
|
|
76
|
-
entry.watcher.close();
|
|
77
|
-
}
|
|
78
|
-
watched.clear();
|
|
97
|
+
for (const watched of watcherSets) stopWatching(watched);
|
|
79
98
|
}
|
|
80
99
|
|
|
81
100
|
export function rewatchAllFileNodes(): void {
|
|
82
|
-
|
|
101
|
+
stopWatching(boardWatchers.current);
|
|
83
102
|
for (const node of canvasState.getLayout().nodes) {
|
|
84
103
|
if (node.type === 'file' && typeof node.data.path === 'string') {
|
|
85
104
|
watchFileForNode(node.id, node.data.path);
|
|
@@ -88,6 +107,7 @@ export function rewatchAllFileNodes(): void {
|
|
|
88
107
|
}
|
|
89
108
|
|
|
90
109
|
function handleFileChange(filePath: string): void {
|
|
110
|
+
const watched = boardWatchers.current;
|
|
91
111
|
const entry = watched.get(filePath);
|
|
92
112
|
if (!entry) return;
|
|
93
113
|
|
|
@@ -812,13 +812,12 @@ function page(input: {
|
|
|
812
812
|
* { box-sizing: border-box; }
|
|
813
813
|
body { margin: 0; padding: 22px; background: radial-gradient(circle at top left, color-mix(in srgb, var(--color-accent, #4a9eff) 18%, transparent), transparent 34rem), var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font-family: var(--font-sans, ui-sans-serif, system-ui, sans-serif); }
|
|
814
814
|
.shell { max-width: 1160px; margin: 0 auto; }
|
|
815
|
-
header.hero {
|
|
815
|
+
header.hero { margin-bottom: 20px; }
|
|
816
816
|
.kicker { color: var(--color-accent, #4a9eff); text-transform: uppercase; letter-spacing: .16em; font-size: 11px; font-weight: 800; }
|
|
817
817
|
h1 { margin: 5px 0 8px; font-size: clamp(28px, 4vw, 46px); line-height: .95; letter-spacing: -.04em; }
|
|
818
818
|
h2 { margin: 0 0 12px; font-size: 19px; letter-spacing: -.02em; }
|
|
819
819
|
h3 { margin: 0 0 8px; font-size: 15px; }
|
|
820
820
|
p { color: var(--color-text-secondary, #aeb8c2); margin: 0 0 12px; }
|
|
821
|
-
.actions { display: flex; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }
|
|
822
821
|
button { border: 1px solid var(--color-border, #263241); background: var(--color-panel, #111821); color: var(--color-text, #e8edf2); border-radius: 999px; padding: 8px 12px; font: inherit; cursor: pointer; }
|
|
823
822
|
button:hover { border-color: var(--color-accent, #4a9eff); transform: translateY(-1px); }
|
|
824
823
|
table { width: 100%; border-collapse: collapse; color: var(--color-text-secondary, #aeb8c2); font-size: 13px; }
|
|
@@ -861,20 +860,20 @@ function page(input: {
|
|
|
861
860
|
.slide h2 { font-size: clamp(30px, 5vw, 58px); line-height: .95; }
|
|
862
861
|
.preview { white-space: pre-wrap; min-height: 220px; }
|
|
863
862
|
.figure svg { width: 100%; min-height: 220px; border-radius: 14px; background: color-mix(in srgb, var(--color-bg, #0b0f14) 86%, white); border: 1px solid var(--color-border, #263241); }
|
|
864
|
-
@media (max-width: 760px) { body { padding: 14px; }
|
|
863
|
+
@media (max-width: 760px) { body { padding: 14px; } .two, .three { grid-template-columns: 1fr; } .sticky { position: static; } }
|
|
865
864
|
</style>
|
|
866
865
|
</head>
|
|
867
866
|
<body>
|
|
868
867
|
<div class="shell">
|
|
869
868
|
<header class="hero">
|
|
870
869
|
<div><div class="kicker">PMX HTML primitive / ${escapeHtml(input.kind)}</div><h1>${escapeHtml(input.title)}</h1><p>${escapeHtml(input.summary)}</p></div>
|
|
871
|
-
<div class="actions"><button type="button" data-copy-json>Copy JSON</button><button type="button" data-copy-prompt>Copy prompt</button></div>
|
|
872
870
|
</header>
|
|
873
871
|
${input.body}
|
|
874
872
|
</div>
|
|
875
873
|
<script type="application/json" id="pmx-data">${safeJson(input.data)}</script>
|
|
876
874
|
<script>
|
|
877
875
|
const PMX_DATA = JSON.parse(document.getElementById('pmx-data').textContent);
|
|
876
|
+
window.__pmxGetCopyJson = () => PMX_DATA;
|
|
878
877
|
function fallbackCopy(text) {
|
|
879
878
|
const el = document.createElement('textarea');
|
|
880
879
|
el.value = text;
|
|
@@ -890,11 +889,6 @@ async function copyText(text) {
|
|
|
890
889
|
if (navigator.clipboard && window.isSecureContext) await navigator.clipboard.writeText(text);
|
|
891
890
|
else fallbackCopy(text);
|
|
892
891
|
}
|
|
893
|
-
document.querySelector('[data-copy-json]')?.addEventListener('click', () => {
|
|
894
|
-
const payload = typeof window.__pmxGetCopyJson === 'function' ? window.__pmxGetCopyJson() : PMX_DATA;
|
|
895
|
-
copyText(JSON.stringify(payload, null, 2));
|
|
896
|
-
});
|
|
897
|
-
document.querySelector('[data-copy-prompt]')?.addEventListener('click', () => copyText('Use this PMX Canvas HTML primitive output as context:\\n\\n' + JSON.stringify(PMX_DATA, null, 2)));
|
|
898
892
|
${input.script ?? ''}
|
|
899
893
|
</script>
|
|
900
894
|
</body>
|
|
@@ -61,6 +61,14 @@ const PMX_CANVAS_THEME_TOKEN = ${token};
|
|
|
61
61
|
window.addEventListener('message', (event) => {
|
|
62
62
|
const message = event.data;
|
|
63
63
|
if (event.source !== window.parent || !message || message.source !== '${HTML_SURFACE_PUSH_SOURCE}' || message.token !== PMX_CANVAS_THEME_TOKEN) return;
|
|
64
|
+
if (message.type === 'copy-json' && PMX_CANVAS_THEME_TOKEN && typeof message.requestId === 'string') {
|
|
65
|
+
let json;
|
|
66
|
+
try {
|
|
67
|
+
if (typeof window.__pmxGetCopyJson === 'function') json = JSON.stringify(window.__pmxGetCopyJson(), null, 2);
|
|
68
|
+
} catch {}
|
|
69
|
+
window.parent.postMessage({ source: '${HTML_SURFACE_PUSH_SOURCE}', type: 'copy-json-result', token: PMX_CANVAS_THEME_TOKEN, requestId: message.requestId, json }, '*');
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
64
72
|
if (message.type === 'check-ready') {
|
|
65
73
|
const reply = (type) => window.parent.postMessage({ source: '${HTML_SURFACE_PUSH_SOURCE}', type, token: PMX_CANVAS_THEME_TOKEN }, '*');
|
|
66
74
|
reply('mounted');
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import {
|
|
3
3
|
HUMAN_GRAB_TTL_MS,
|
|
4
|
+
HUMAN_NAME_MAX_LENGTH,
|
|
4
5
|
HUMAN_PRESENCE_TTL_MS,
|
|
5
6
|
type HumanPresence,
|
|
6
7
|
type HumanPresenceSnapshot,
|
|
7
8
|
MAX_HUMANS,
|
|
8
9
|
} from '../shared/human-presence.js';
|
|
9
10
|
import { OperationError } from './operations/types.js';
|
|
11
|
+
import { canvasState } from './canvas-state.js';
|
|
10
12
|
|
|
11
13
|
/**
|
|
12
14
|
* Registry of open workbench tabs (rail-chrome-v2 phase 8). Fed by the
|
|
@@ -17,7 +19,7 @@ import { OperationError } from './operations/types.js';
|
|
|
17
19
|
*/
|
|
18
20
|
export const HUMAN_PRESENCE_SHAPE = {
|
|
19
21
|
clientId: z.string().min(1).max(64),
|
|
20
|
-
name: z.string().min(1).max(
|
|
22
|
+
name: z.string().min(1).max(HUMAN_NAME_MAX_LENGTH).optional(),
|
|
21
23
|
cursor: z.object({ x: z.number().finite(), y: z.number().finite() }).nullable().optional(),
|
|
22
24
|
grabbingNodeId: z.string().max(200).nullable().optional(),
|
|
23
25
|
/** Explicit leave (tab closing): drop the presence now. */
|
|
@@ -26,6 +28,7 @@ export const HUMAN_PRESENCE_SHAPE = {
|
|
|
26
28
|
const presenceSchema = z.object(HUMAN_PRESENCE_SHAPE);
|
|
27
29
|
|
|
28
30
|
interface Stored extends HumanPresence {
|
|
31
|
+
boardId: string | null;
|
|
29
32
|
lastSeenMs: number;
|
|
30
33
|
grabSinceMs: number | null;
|
|
31
34
|
}
|
|
@@ -57,6 +60,7 @@ export class HumanPresenceRegistry {
|
|
|
57
60
|
}
|
|
58
61
|
const existing = this.humans.get(input.clientId);
|
|
59
62
|
const stored: Stored = existing ?? {
|
|
63
|
+
boardId: canvasState.activeBoardId,
|
|
60
64
|
clientId: input.clientId,
|
|
61
65
|
name: input.name ?? 'Human',
|
|
62
66
|
cursor: null,
|
|
@@ -65,6 +69,11 @@ export class HumanPresenceRegistry {
|
|
|
65
69
|
lastSeenMs: now,
|
|
66
70
|
grabSinceMs: null,
|
|
67
71
|
};
|
|
72
|
+
if (stored.boardId !== canvasState.activeBoardId) {
|
|
73
|
+
stored.boardId = canvasState.activeBoardId;
|
|
74
|
+
stored.grabbingNodeId = null;
|
|
75
|
+
stored.grabSinceMs = null;
|
|
76
|
+
}
|
|
68
77
|
stored.lastSeenMs = now;
|
|
69
78
|
stored.lastSeenAt = new Date(now).toISOString();
|
|
70
79
|
if (input.name) stored.name = input.name;
|
|
@@ -91,9 +100,10 @@ export class HumanPresenceRegistry {
|
|
|
91
100
|
}
|
|
92
101
|
|
|
93
102
|
/** Nodes a human is holding right now → that human's name. The edit lock. */
|
|
94
|
-
lockedNodes(now = Date.now()): Map<string, string> {
|
|
103
|
+
lockedNodes(now = Date.now(), boardId = canvasState.activeBoardId): Map<string, string> {
|
|
95
104
|
const locked = new Map<string, string>();
|
|
96
105
|
for (const human of this.humans.values()) {
|
|
106
|
+
if (human.boardId !== boardId) continue;
|
|
97
107
|
if (human.grabbingNodeId && human.grabSinceMs !== null && now - human.grabSinceMs <= HUMAN_GRAB_TTL_MS) {
|
|
98
108
|
locked.set(human.grabbingNodeId, human.name);
|
|
99
109
|
}
|
|
@@ -111,13 +121,16 @@ export class HumanPresenceRegistry {
|
|
|
111
121
|
}
|
|
112
122
|
|
|
113
123
|
private publicView(stored: Stored): HumanPresence {
|
|
114
|
-
const { lastSeenMs: _lastSeenMs, grabSinceMs, ...human } = stored;
|
|
124
|
+
const { lastSeenMs: _lastSeenMs, grabSinceMs, boardId, ...human } = stored;
|
|
115
125
|
// An expired grab reads as released even before the sweep drops it.
|
|
116
126
|
const grabbing =
|
|
117
|
-
|
|
127
|
+
boardId === canvasState.activeBoardId &&
|
|
128
|
+
human.grabbingNodeId &&
|
|
129
|
+
grabSinceMs !== null &&
|
|
130
|
+
Date.now() - grabSinceMs <= HUMAN_GRAB_TTL_MS
|
|
118
131
|
? human.grabbingNodeId
|
|
119
132
|
: null;
|
|
120
|
-
return { ...human, grabbingNodeId: grabbing };
|
|
133
|
+
return { ...human, cursor: boardId === canvasState.activeBoardId ? human.cursor : null, grabbingNodeId: grabbing };
|
|
121
134
|
}
|
|
122
135
|
|
|
123
136
|
private sweep(now = Date.now(), options: { emit?: boolean } = {}): void {
|
package/src/server/index.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { EventEmitter } from 'node:events';
|
|
2
|
-
import type {
|
|
2
|
+
import type { BoardMoveInput, BoardMovePreview, BoardMoveResult } from './board-move.js';
|
|
3
|
+
export type { BoardMoveInput, BoardMovePreview, BoardMoveResult } from './board-move.js';
|
|
4
|
+
import type { Camera, Tour, TourPresenter, TourStop } from '../shared/tour.js';
|
|
3
5
|
import {
|
|
4
6
|
currentTour,
|
|
5
7
|
exitTour,
|
|
@@ -8,9 +10,10 @@ import {
|
|
|
8
10
|
type TourState,
|
|
9
11
|
type TourStep,
|
|
10
12
|
type TourStepResult,
|
|
13
|
+
type TourSuperseded,
|
|
11
14
|
} from './tour-control.js';
|
|
12
|
-
export type { Tour, TourStop } from '../shared/tour.js';
|
|
13
|
-
export type { TourState, TourStep, TourStepResult } from './tour-control.js';
|
|
15
|
+
export type { Tour, TourPresenter, TourStop } from '../shared/tour.js';
|
|
16
|
+
export type { TourState, TourStep, TourStepResult, TourSuperseded } from './tour-control.js';
|
|
14
17
|
import { agentPresence } from './agent-presence.js';
|
|
15
18
|
import { checkFenceTarget, type FenceTarget } from './scope-fence.js';
|
|
16
19
|
import type { AgentPhase, AgentPresence, AgentPresenceSnapshot } from '../shared/agent-presence.js';
|
|
@@ -507,21 +510,19 @@ export class PmxCanvas extends EventEmitter {
|
|
|
507
510
|
return (await executeOperation('edge.update', { id, ...patch }, { source: 'sdk' })) as CanvasEdge;
|
|
508
511
|
}
|
|
509
512
|
|
|
510
|
-
addAnnotation(
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
return id;
|
|
513
|
+
async addAnnotation(
|
|
514
|
+
input: Pick<CanvasAnnotation, 'points'> &
|
|
515
|
+
Partial<Pick<CanvasAnnotation, 'id' | 'type' | 'color' | 'width' | 'text' | 'label'>>,
|
|
516
|
+
): Promise<string> {
|
|
517
|
+
const result = (await executeOperation('annotation.add', input, { source: 'sdk' })) as {
|
|
518
|
+
annotation: { id: string };
|
|
519
|
+
};
|
|
520
|
+
return result.annotation.id;
|
|
519
521
|
}
|
|
520
522
|
|
|
521
|
-
removeAnnotation(id: string): boolean {
|
|
522
|
-
const
|
|
523
|
-
|
|
524
|
-
return removed;
|
|
523
|
+
async removeAnnotation(id: string): Promise<boolean> {
|
|
524
|
+
const result = (await executeOperation('annotation.remove', { id }, { source: 'sdk' })) as { ok: boolean };
|
|
525
|
+
return result.ok;
|
|
525
526
|
}
|
|
526
527
|
|
|
527
528
|
removeEdge(id: string): void {
|
|
@@ -631,16 +632,25 @@ export class PmxCanvas extends EventEmitter {
|
|
|
631
632
|
}
|
|
632
633
|
|
|
633
634
|
/** Present a tour stop (index, `next` or `previous`) in the workbench. Not recorded in undo history. */
|
|
634
|
-
goToTourStop(step: TourStep, options?: { present?: boolean }): TourStepResult
|
|
635
|
-
|
|
636
|
-
|
|
635
|
+
goToTourStop(step: TourStep, options?: { present?: boolean; presenter?: undefined }): TourStepResult;
|
|
636
|
+
goToTourStop(
|
|
637
|
+
step: TourStep,
|
|
638
|
+
options: { present?: boolean; presenter?: TourPresenter },
|
|
639
|
+
): TourStepResult | TourSuperseded;
|
|
640
|
+
goToTourStop(
|
|
641
|
+
step: TourStep,
|
|
642
|
+
options?: { present?: boolean; presenter?: TourPresenter },
|
|
643
|
+
): TourStepResult | TourSuperseded {
|
|
644
|
+
const result = goToTourStop(step, options?.presenter);
|
|
645
|
+
if (!('superseded' in result))
|
|
646
|
+
emitPrimaryWorkbenchEvent('canvas-tour-step', { ...result, present: options?.present !== false });
|
|
637
647
|
return result;
|
|
638
648
|
}
|
|
639
649
|
|
|
640
650
|
/** Leave presentation in every viewer. */
|
|
641
|
-
exitTour(): void {
|
|
642
|
-
exitTour();
|
|
643
|
-
emitPrimaryWorkbenchEvent('canvas-tour-exit', {});
|
|
651
|
+
exitTour(presenter?: TourPresenter): void {
|
|
652
|
+
const result = exitTour(presenter);
|
|
653
|
+
if (!('superseded' in result)) emitPrimaryWorkbenchEvent('canvas-tour-exit', { ...result });
|
|
644
654
|
}
|
|
645
655
|
|
|
646
656
|
/** Ease the camera to a node, world rect or viewport. Not recorded in undo history. */
|
|
@@ -754,7 +764,12 @@ export class PmxCanvas extends EventEmitter {
|
|
|
754
764
|
}
|
|
755
765
|
|
|
756
766
|
/** Undelivered steering for a consumer (loop-safe; excludes consumer-originated). */
|
|
757
|
-
getPendingSteering(options?: {
|
|
767
|
+
getPendingSteering(options?: {
|
|
768
|
+
consumer?: string;
|
|
769
|
+
limit?: number;
|
|
770
|
+
addressedOnly?: boolean;
|
|
771
|
+
offset?: number;
|
|
772
|
+
}): PmxAxSteeringMessage[] {
|
|
758
773
|
return canvasState.getPendingSteering(options ?? {});
|
|
759
774
|
}
|
|
760
775
|
|
|
@@ -944,6 +959,15 @@ export class PmxCanvas extends EventEmitter {
|
|
|
944
959
|
return (await executeOperation('board.create-from', input, { source: 'sdk' })) as Record<string, unknown>;
|
|
945
960
|
}
|
|
946
961
|
|
|
962
|
+
/** Relocate identities atomically; preview is read-only and retries reuse the original receipt. */
|
|
963
|
+
async moveSelectionToNewBoard(
|
|
964
|
+
input: BoardMoveInput,
|
|
965
|
+
): Promise<{ ok: true; preview: BoardMovePreview } | (BoardMoveResult & { ok: true; replayed: boolean })> {
|
|
966
|
+
return (await executeOperation('board.move-selection', input, { source: 'sdk' })) as
|
|
967
|
+
| { ok: true; preview: BoardMovePreview }
|
|
968
|
+
| (BoardMoveResult & { ok: true; replayed: boolean });
|
|
969
|
+
}
|
|
970
|
+
|
|
947
971
|
async searchLibrary(query: string, limit?: number): Promise<Record<string, unknown>> {
|
|
948
972
|
return (await executeOperation('search', { q: query, scope: 'library', limit }, { source: 'sdk' })) as Record<
|
|
949
973
|
string,
|
|
@@ -960,7 +984,13 @@ export class PmxCanvas extends EventEmitter {
|
|
|
960
984
|
|
|
961
985
|
/** Budgeted cross-board brief. Budget is measured in UTF-16 characters. */
|
|
962
986
|
async getContextBrief(
|
|
963
|
-
options: {
|
|
987
|
+
options: {
|
|
988
|
+
consumer?: string;
|
|
989
|
+
since?: number;
|
|
990
|
+
budget?: number;
|
|
991
|
+
contextDelivery?: boolean;
|
|
992
|
+
includeSteering?: boolean;
|
|
993
|
+
} = {},
|
|
964
994
|
): Promise<Record<string, unknown>> {
|
|
965
995
|
return (await executeOperation('context.get', options, { source: 'sdk' })) as Record<string, unknown>;
|
|
966
996
|
}
|
|
@@ -25,6 +25,8 @@ import {
|
|
|
25
25
|
type PmxAxIntentKind,
|
|
26
26
|
} from '../shared/ax-intent.js';
|
|
27
27
|
import { OperationError } from './operations/types.js';
|
|
28
|
+
import { createBoardLocal } from './board-local.js';
|
|
29
|
+
import { canvasState } from './canvas-state.js';
|
|
28
30
|
|
|
29
31
|
type IntentEmitter = (event: string, payload: Record<string, unknown>) => void;
|
|
30
32
|
const MAX_VETO_TOMBSTONES = 128;
|
|
@@ -103,11 +105,33 @@ function nextIntentId(): string {
|
|
|
103
105
|
}
|
|
104
106
|
|
|
105
107
|
export class IntentRegistry {
|
|
106
|
-
private readonly
|
|
107
|
-
|
|
108
|
-
|
|
108
|
+
private readonly board = createBoardLocal(
|
|
109
|
+
() => ({
|
|
110
|
+
intents: new Map<string, PmxAxIntent>(),
|
|
111
|
+
vetoedIntentIds: new Map<string, number>(),
|
|
112
|
+
committingIntentIds: new Set<string>(),
|
|
113
|
+
sweepTimer: null as ReturnType<typeof setInterval> | null,
|
|
114
|
+
}),
|
|
115
|
+
(state) => {
|
|
116
|
+
if (state.sweepTimer) clearInterval(state.sweepTimer);
|
|
117
|
+
},
|
|
118
|
+
);
|
|
119
|
+
private get intents() {
|
|
120
|
+
return this.board.current.intents;
|
|
121
|
+
}
|
|
122
|
+
private get vetoedIntentIds() {
|
|
123
|
+
return this.board.current.vetoedIntentIds;
|
|
124
|
+
}
|
|
125
|
+
private get committingIntentIds() {
|
|
126
|
+
return this.board.current.committingIntentIds;
|
|
127
|
+
}
|
|
109
128
|
private emit: IntentEmitter = () => {};
|
|
110
|
-
private sweepTimer
|
|
129
|
+
private get sweepTimer() {
|
|
130
|
+
return this.board.current.sweepTimer;
|
|
131
|
+
}
|
|
132
|
+
private set sweepTimer(value: ReturnType<typeof setInterval> | null) {
|
|
133
|
+
this.board.current.sweepTimer = value;
|
|
134
|
+
}
|
|
111
135
|
|
|
112
136
|
/** Inject the workbench SSE emitter (server.ts wires this at module load). */
|
|
113
137
|
setEmitter(emitter: IntentEmitter | null): void {
|
|
@@ -118,6 +142,17 @@ export class IntentRegistry {
|
|
|
118
142
|
return [...this.intents.values()];
|
|
119
143
|
}
|
|
120
144
|
|
|
145
|
+
/** The first write from Home adopts its ghosts and vetoes without opening on signal. */
|
|
146
|
+
moveHomeIntentsToBoard(boardId: string): void {
|
|
147
|
+
const home = this.board.current;
|
|
148
|
+
canvasState.withBoard(boardId, () => {
|
|
149
|
+
for (const [id, intent] of home.intents) this.intents.set(id, intent);
|
|
150
|
+
for (const [id, expires] of home.vetoedIntentIds) this.vetoedIntentIds.set(id, expires);
|
|
151
|
+
this.ensureSweeper();
|
|
152
|
+
});
|
|
153
|
+
this.reset();
|
|
154
|
+
}
|
|
155
|
+
|
|
121
156
|
/** Signal a new (or replace an existing) intent. Returns the stored envelope. */
|
|
122
157
|
signal(raw: unknown): PmxAxIntent {
|
|
123
158
|
const input = parseOrThrow(intentSignalSchema, raw, 'intent');
|
|
@@ -335,7 +370,10 @@ export class IntentRegistry {
|
|
|
335
370
|
|
|
336
371
|
private ensureSweeper(): void {
|
|
337
372
|
if (this.sweepTimer || this.intents.size === 0) return;
|
|
338
|
-
this.sweepTimer = setInterval(
|
|
373
|
+
this.sweepTimer = setInterval(
|
|
374
|
+
canvasState.bindBoard(() => this.sweep()),
|
|
375
|
+
1000,
|
|
376
|
+
);
|
|
339
377
|
// Don't keep the process (or a test runner) alive just for ghost expiry.
|
|
340
378
|
(this.sweepTimer as { unref?: () => void }).unref?.();
|
|
341
379
|
}
|
|
@@ -4,6 +4,7 @@ import { normalizeBoardFolder } from '../shared/boards.js';
|
|
|
4
4
|
import { memoryKey, type MemoryTarget } from '../shared/memory-graph.js';
|
|
5
5
|
import { currentActor, type ActorAttribution } from './attribution.js';
|
|
6
6
|
import { OperationError } from './operations/types.js';
|
|
7
|
+
import { CARD_HISTORY_CTE } from './card-relocation.js';
|
|
7
8
|
import type { LifecycleQualification } from '../shared/lifecycle-status.js';
|
|
8
9
|
import type { CanvasNodeState } from './canvas-state.js';
|
|
9
10
|
import { RELATION_PRESETS, relationPresetSchema, type RelationPreset } from '../shared/relations.js';
|
|
@@ -281,15 +282,19 @@ export class KnowledgeLifecycle {
|
|
|
281
282
|
if (!targets.length) return new Map();
|
|
282
283
|
const rows = this.indexedDb()
|
|
283
284
|
.query<{ boardId: string; nodeId: string; validation: string | null; retirement: string | null }, [string]>(
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
FROM
|
|
287
|
-
|
|
288
|
-
|
|
285
|
+
`${CARD_HISTORY_CTE}
|
|
286
|
+
SELECT t.current_board AS boardId, t.node_id AS nodeId,
|
|
287
|
+
(SELECT value FROM review_records r JOIN history h
|
|
288
|
+
ON r.board_id = h.board_id AND r.node_id = h.node_id
|
|
289
|
+
AND r.sequence > h.review_after AND r.sequence <= h.review_through
|
|
290
|
+
WHERE h.current_board = t.current_board AND h.node_id = t.node_id
|
|
289
291
|
AND ${validationPredicate} ORDER BY sequence DESC LIMIT 1) AS validation,
|
|
290
|
-
(SELECT json_extract(value, '$.outcome') FROM review_records
|
|
292
|
+
(SELECT json_extract(value, '$.outcome') FROM review_records r JOIN history h
|
|
293
|
+
ON r.board_id = h.board_id AND r.node_id = h.node_id
|
|
294
|
+
AND r.sequence > h.review_after AND r.sequence <= h.review_through
|
|
295
|
+
WHERE h.current_board = t.current_board AND h.node_id = t.node_id
|
|
291
296
|
AND ${retirementPredicate} ORDER BY sequence DESC LIMIT 1) AS retirement
|
|
292
|
-
FROM targets`,
|
|
297
|
+
FROM targets t`,
|
|
293
298
|
)
|
|
294
299
|
.all(JSON.stringify(targets));
|
|
295
300
|
return new Map(
|
|
@@ -306,11 +311,29 @@ export class KnowledgeLifecycle {
|
|
|
306
311
|
);
|
|
307
312
|
}
|
|
308
313
|
|
|
314
|
+
/** Resolve a current card's location when evidence was recorded, without rewriting that evidence. */
|
|
315
|
+
locationAtReview(review: ReviewRecord, target: MemoryTarget): MemoryTarget {
|
|
316
|
+
const sequence = this.db()
|
|
317
|
+
.query<{ sequence: number }, [string, string, string]>(
|
|
318
|
+
"SELECT sequence FROM review_records WHERE board_id = ? AND node_id = ? AND json_extract(value, '$.id') = ?",
|
|
319
|
+
)
|
|
320
|
+
.get(review.boardId, review.nodeId, review.id)?.sequence;
|
|
321
|
+
if (sequence === undefined) return target;
|
|
322
|
+
const location = this.db()
|
|
323
|
+
.query<{ board_id: string }, [string, number, number]>(
|
|
324
|
+
`${CARD_HISTORY_CTE} SELECT board_id FROM history WHERE review_after < ? AND review_through >= ? LIMIT 1`,
|
|
325
|
+
)
|
|
326
|
+
.get(JSON.stringify([target]), sequence, sequence);
|
|
327
|
+
return location ? { ...target, boardId: location.board_id } : target;
|
|
328
|
+
}
|
|
329
|
+
|
|
309
330
|
reviews(boardId: string, nodeId: string) {
|
|
310
331
|
const records = (
|
|
311
332
|
this.db()
|
|
312
|
-
.query(
|
|
313
|
-
|
|
333
|
+
.query(`${CARD_HISTORY_CTE} SELECT value FROM review_records r JOIN history h
|
|
334
|
+
ON r.board_id = h.board_id AND r.node_id = h.node_id
|
|
335
|
+
AND r.sequence > h.review_after AND r.sequence <= h.review_through ORDER BY sequence`)
|
|
336
|
+
.all(JSON.stringify([{ boardId, cardId: nodeId }])) as { value: string }[]
|
|
314
337
|
).map((r): ReviewRecord => {
|
|
315
338
|
const record = JSON.parse(r.value) as ReviewRecord;
|
|
316
339
|
return { ...record, contentRevision: record.contentRevision ?? null };
|
|
@@ -11,6 +11,7 @@ const processStartedAt = Date.now();
|
|
|
11
11
|
interface Source {
|
|
12
12
|
key: string;
|
|
13
13
|
target: MemoryTarget;
|
|
14
|
+
edge?: { owner: MemoryTarget; id: string };
|
|
14
15
|
baseline?: number;
|
|
15
16
|
kind: string;
|
|
16
17
|
observedAt?: string;
|
|
@@ -179,6 +180,7 @@ export function lifecycleSnapshot(now = Date.now(), atUse: MemoryTarget[] = [],
|
|
|
179
180
|
existing.push({
|
|
180
181
|
key: JSON.stringify([board.id, edge.id, kind, memoryKey(target)]),
|
|
181
182
|
target,
|
|
183
|
+
edge: { owner: from, id: edge.id },
|
|
182
184
|
kind,
|
|
183
185
|
baseline: edge.sourceRevision,
|
|
184
186
|
});
|
|
@@ -219,13 +221,30 @@ export function lifecycleSnapshot(now = Date.now(), atUse: MemoryTarget[] = [],
|
|
|
219
221
|
const current = cards.get(memoryKey(source.target));
|
|
220
222
|
const revision = source.availability === 'unavailable' ? null : (current?.contentRevision ?? null);
|
|
221
223
|
status.observations[source.key] = revision;
|
|
222
|
-
|
|
224
|
+
let observed = validation ? validation.sourceRevisions?.[source.key] : source.baseline;
|
|
225
|
+
if (validation?.sourceRevisions && observed === undefined) {
|
|
226
|
+
const target = canvasState.knowledge.locationAtReview(validation, source.target);
|
|
227
|
+
const historicalKey = source.edge
|
|
228
|
+
? JSON.stringify([
|
|
229
|
+
canvasState.knowledge.locationAtReview(validation, source.edge.owner).boardId,
|
|
230
|
+
source.edge.id,
|
|
231
|
+
source.kind,
|
|
232
|
+
memoryKey(target),
|
|
233
|
+
])
|
|
234
|
+
: `provenance:${memoryKey(target)}`;
|
|
235
|
+
observed = validation.sourceRevisions[historicalKey];
|
|
236
|
+
}
|
|
223
237
|
const title = current ? String(current.data.title ?? current.id) : 'Unavailable source';
|
|
238
|
+
const before = status.reasons.length;
|
|
224
239
|
if (!current) status.reasons.push('A dependency source is unavailable; no fresh check.');
|
|
225
240
|
else if (source.availability === 'unavailable')
|
|
226
241
|
status.reasons.push(`${title}: source unavailable; cached content is not a fresh check.`);
|
|
227
242
|
else if (observed !== revision || (!validation && ['contradicts', 'supersedes'].includes(source.kind)))
|
|
228
243
|
status.reasons.push(`${source.kind}: ${title}${observed === undefined ? ' needs review' : ' changed'}.`);
|
|
244
|
+
if (status.reasons.length > before) {
|
|
245
|
+
status.sourceWarnings ??= {};
|
|
246
|
+
status.sourceWarnings[source.key] = status.reasons.slice(before);
|
|
247
|
+
}
|
|
229
248
|
}
|
|
230
249
|
}
|
|
231
250
|
// Fixed point, not recursive traversal: cycles cannot overflow and unrelated links never propagate.
|
|
@@ -246,6 +265,15 @@ export function lifecycleSnapshot(now = Date.now(), atUse: MemoryTarget[] = [],
|
|
|
246
265
|
}
|
|
247
266
|
}
|
|
248
267
|
for (const [key, status] of Object.entries(result)) {
|
|
268
|
+
for (const source of sources.get(key) ?? []) {
|
|
269
|
+
const upstream = result[memoryKey(source.target)];
|
|
270
|
+
if (upstream?.due || upstream?.retired) {
|
|
271
|
+
status.sourceWarnings ??= {};
|
|
272
|
+
status.sourceWarnings[source.key] ??= [
|
|
273
|
+
upstream.retired ? 'Source is retired.' : 'An explicit source needs review.',
|
|
274
|
+
];
|
|
275
|
+
}
|
|
276
|
+
}
|
|
249
277
|
const references: Source[] = [...(sources.get(key) ?? [])];
|
|
250
278
|
const boardId = portals.get(key);
|
|
251
279
|
if (boardId) {
|
|
@@ -18,6 +18,7 @@ import { EXTENSION_ID, RESOURCE_MIME_TYPE } from '@modelcontextprotocol/ext-apps
|
|
|
18
18
|
import type { McpUiClientCapabilities, McpUiResourceCsp, McpUiResourceMeta } from '@modelcontextprotocol/ext-apps';
|
|
19
19
|
import { getToolUiResourceUri } from '@modelcontextprotocol/ext-apps/app-bridge';
|
|
20
20
|
import { normalizeExtAppToolResult } from './ext-app-tool-result.js';
|
|
21
|
+
import { canvasState } from './canvas-state.js';
|
|
21
22
|
|
|
22
23
|
// A restriction follows async rehydration spawned by an embedded operation,
|
|
23
24
|
// without disabling independent requests from the local workbench.
|
|
@@ -71,10 +72,12 @@ type RuntimeTransport = StdioClientTransport | StreamableHTTPClientTransport;
|
|
|
71
72
|
|
|
72
73
|
interface McpAppSession {
|
|
73
74
|
id: string;
|
|
75
|
+
board: object;
|
|
74
76
|
serverName: string;
|
|
75
77
|
client: Client;
|
|
76
78
|
transport: RuntimeTransport;
|
|
77
79
|
tools: Tool[];
|
|
80
|
+
unregisterBoardDispose: () => void;
|
|
78
81
|
}
|
|
79
82
|
|
|
80
83
|
const uiCapabilities: McpUiClientCapabilities = {
|
|
@@ -223,21 +226,35 @@ async function createSession(
|
|
|
223
226
|
}
|
|
224
227
|
const transport = buildTransport(transportConfig);
|
|
225
228
|
const client = new Client({ name: 'pmx-canvas-app-host', version: '0.1.0' }, { capabilities: clientCapabilities });
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
const toolList = await client.listTools(undefined, requestOptions(timeoutMs));
|
|
229
|
+
const id = randomId('mcp-app-session');
|
|
230
|
+
let disposed = false;
|
|
229
231
|
const session: McpAppSession = {
|
|
230
|
-
id
|
|
232
|
+
id,
|
|
233
|
+
board: canvasState.boardIdentity,
|
|
231
234
|
serverName: normalizeServerName(serverName, transportConfig),
|
|
232
235
|
client,
|
|
233
236
|
transport,
|
|
234
|
-
tools:
|
|
237
|
+
tools: [],
|
|
238
|
+
unregisterBoardDispose: canvasState.onBoardDispose(() => {
|
|
239
|
+
disposed = true;
|
|
240
|
+
closeMcpAppSession(id);
|
|
241
|
+
}),
|
|
235
242
|
};
|
|
236
243
|
sessions.set(session.id, session);
|
|
237
|
-
|
|
244
|
+
try {
|
|
245
|
+
await client.connect(transport, requestOptions(timeoutMs));
|
|
246
|
+
const toolList = await client.listTools(undefined, requestOptions(timeoutMs));
|
|
247
|
+
if (disposed) throw new Error('The app board was deleted while connecting.');
|
|
248
|
+
session.tools = toolList.tools;
|
|
249
|
+
return session;
|
|
250
|
+
} catch (error) {
|
|
251
|
+
await closeSession(session);
|
|
252
|
+
throw error;
|
|
253
|
+
}
|
|
238
254
|
}
|
|
239
255
|
|
|
240
256
|
async function closeSession(session: McpAppSession): Promise<void> {
|
|
257
|
+
session.unregisterBoardDispose();
|
|
241
258
|
sessions.delete(session.id);
|
|
242
259
|
await session.transport.close();
|
|
243
260
|
}
|
|
@@ -468,5 +485,7 @@ export function hasMcpAppSession(sessionId: string): boolean {
|
|
|
468
485
|
}
|
|
469
486
|
|
|
470
487
|
export function listMcpAppSessionIds(): string[] {
|
|
471
|
-
return [...sessions.
|
|
488
|
+
return [...sessions.values()]
|
|
489
|
+
.filter((session) => session.board === canvasState.boardIdentity)
|
|
490
|
+
.map((session) => session.id);
|
|
472
491
|
}
|