pmx-canvas 0.9.3 → 0.11.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/runtime.json +1 -1
- package/.github/extensions/pmx-canvas/tools.json +9418 -2016
- package/CHANGELOG.md +54 -1
- package/dist/canvas/chunks/abnfDiagram-N423BO3Z-p8dtp6va.js +1 -0
- package/dist/canvas/chunks/architecture-TIHT7OUA-9tffvpsv.js +1 -0
- package/dist/canvas/chunks/architectureDiagram-T3A2C74G-5jhmxrzf.js +36 -0
- package/dist/canvas/chunks/blockDiagram-VBNYF7ZC-pw1fa9yb.js +132 -0
- package/dist/canvas/chunks/c4Diagram-5PPSVZJV-x07nq0tt.js +10 -0
- package/dist/canvas/chunks/classDiagram-JCYQIIEL-nc6g5x1r.js +1 -0
- package/dist/canvas/chunks/classDiagram-v2-OCEON4UE-0qnbpwvh.js +1 -0
- package/dist/canvas/chunks/cose-bilkent-JH36ORCC-nm39npq0.js +1 -0
- package/dist/canvas/chunks/cynefin-VYW2F7L2-ys8p46ts.js +1 -0
- package/dist/canvas/chunks/cynefinDiagram-MW4NZA55-vdxrwahz.js +62 -0
- package/dist/canvas/chunks/dagre-VZM6K2ZE-bqdm7wpd.js +4 -0
- package/dist/canvas/chunks/diagram-7IWD3JNH-gs5gpr93.js +30 -0
- package/dist/canvas/chunks/diagram-B4RE2ZJO-cgwjg47s.js +3 -0
- package/dist/canvas/chunks/diagram-LBJQPF4R-w3vahpav.js +24 -0
- package/dist/canvas/chunks/diagram-Q27KOJAE-42837z6h.js +24 -0
- package/dist/canvas/chunks/diagram-UB23O5K3-4fz83q59.js +41 -0
- package/dist/canvas/chunks/ebnfDiagram-BXEA7PRR-zphtgegj.js +1 -0
- package/dist/canvas/chunks/erDiagram-JOGREHBK-zvbbdxp4.js +85 -0
- package/dist/canvas/chunks/eventmodeling-45OFAUF4-v9q6dn4c.js +1 -0
- package/dist/canvas/chunks/flowDiagram-UKHOOZJN-yrr5z8tc.js +1 -0
- package/dist/canvas/chunks/ganttDiagram-PKOTCBZU-282jzycm.js +292 -0
- package/dist/canvas/chunks/gitGraph-TEB2WS4Q-kdwb8m0a.js +1 -0
- package/dist/canvas/chunks/gitGraphDiagram-DS77QQ5N-vqqy6p1b.js +106 -0
- package/dist/canvas/chunks/index-0rk4gp1g.js +2 -0
- package/dist/canvas/chunks/index-194m3hc3.js +2 -0
- package/dist/canvas/chunks/index-1r72hdzb.js +301 -0
- package/dist/canvas/chunks/index-353m7b0p.js +2 -0
- package/dist/canvas/chunks/index-3cy1bzwh.js +2 -0
- package/dist/canvas/chunks/index-5tv8v1hk.js +1 -0
- package/dist/canvas/chunks/index-6ceya556.js +332 -0
- package/dist/canvas/chunks/index-6qmsnxwy.js +4 -0
- package/dist/canvas/chunks/index-d3b3gqe9.js +2 -0
- package/dist/canvas/chunks/index-fjtdggjn.js +2 -0
- package/dist/canvas/chunks/index-g3pwnqeb.js +2 -0
- package/dist/canvas/chunks/index-gf4e0et4.js +2 -0
- package/dist/canvas/chunks/index-hj99nz1f.js +1 -0
- package/dist/canvas/chunks/index-nprm96xp.js +16 -0
- package/dist/canvas/chunks/index-qj8fxwsf.js +2 -0
- package/dist/canvas/chunks/index-r2fyc9w1.js +89 -0
- package/dist/canvas/chunks/index-rft2hcz5.js +2 -0
- package/dist/canvas/chunks/index-rh7nv68n.js +2 -0
- package/dist/canvas/chunks/index-s4j678gj.js +2 -0
- package/dist/canvas/chunks/index-tb1wf525.js +179 -0
- package/dist/canvas/chunks/index-vv56h019.js +2 -0
- package/dist/canvas/chunks/index-w2mra17t.js +207 -0
- package/dist/canvas/chunks/index-wgv7en21.js +232 -0
- package/dist/canvas/chunks/index-yw8h9vha.js +157 -0
- package/dist/canvas/chunks/info-DKCQHKI2-wpzs6sz3.js +1 -0
- package/dist/canvas/chunks/infoDiagram-6WML65LV-v1yvggqj.js +2 -0
- package/dist/canvas/chunks/ishikawaDiagram-WSZJBQD7-5hnq1a9p.js +70 -0
- package/dist/canvas/chunks/journeyDiagram-NVQOT4AX-zjcffveh.js +139 -0
- package/dist/canvas/chunks/kanban-definition-27J2QSJJ-dakqtfp1.js +89 -0
- package/dist/canvas/chunks/katex-dmg8k4zm.js +257 -0
- package/dist/canvas/chunks/mermaid.core-j2geswrd.js +12 -0
- package/dist/canvas/chunks/mindmap-definition-FAOFIHXS-142xhr3k.js +96 -0
- package/dist/canvas/chunks/packet-7NZHBO7P-xn439f0s.js +1 -0
- package/dist/canvas/chunks/pegDiagram-VL7TDLO6-5wvk7vsd.js +1 -0
- package/dist/canvas/chunks/pie-RZYD4A2V-0kf16rwh.js +1 -0
- package/dist/canvas/chunks/pieDiagram-7S7Q4E2Y-ztdhwycc.js +39 -0
- package/dist/canvas/chunks/quadrantDiagram-CIZ2JOQS-c5p9gmg6.js +7 -0
- package/dist/canvas/chunks/radar-I7S5WNFK-0g2pz36t.js +1 -0
- package/dist/canvas/chunks/railroad-3IZDKUUU-fcaran6j.js +1 -0
- package/dist/canvas/chunks/railroad-abnf-AHOZXSZD-wgdya41q.js +1 -0
- package/dist/canvas/chunks/railroad-ebnf-EBAXGLYW-gy07a4ka.js +1 -0
- package/dist/canvas/chunks/railroad-peg-LSFZ7HO6-ewk6wtkh.js +1 -0
- package/dist/canvas/chunks/railroadDiagram-AXF67PYL-xkmqkjwf.js +1 -0
- package/dist/canvas/chunks/requirementDiagram-LRYGKXZP-9wm2dyaw.js +84 -0
- package/dist/canvas/chunks/sankeyDiagram-W5VNT64P-2w19etgb.js +40 -0
- package/dist/canvas/chunks/sequenceDiagram-SI44F4Z6-0jn0vwmz.js +162 -0
- package/dist/canvas/chunks/sizeCapture-X5ZJPWSS-x75epwdq.js +1 -0
- package/dist/canvas/chunks/stateDiagram-OKZ733FA-schjpy6d.js +1 -0
- package/dist/canvas/chunks/stateDiagram-v2-UEYNNEHI-k8sfp2e7.js +1 -0
- package/dist/canvas/chunks/swimlanes-SLNWSIFB-gkw4xce3.js +1 -0
- package/dist/canvas/chunks/swimlanesDiagram-ULZ7WXOC-nqysw67j.js +8 -0
- package/dist/canvas/chunks/timeline-definition-Z64GVDOM-x7nkcy01.js +120 -0
- package/dist/canvas/chunks/treeView-QDETBFTQ-cesw6gp0.js +1 -0
- package/dist/canvas/chunks/treemap-6X3UGDF4-zdv4knp9.js +1 -0
- package/dist/canvas/chunks/vennDiagram-T6HMQDX7-x8dqn9yf.js +34 -0
- package/dist/canvas/chunks/wardley-OPB4EBWU-44pdddez.js +1 -0
- package/dist/canvas/chunks/wardleyDiagram-T6FBY63Y-t1j52t9r.js +78 -0
- package/dist/canvas/chunks/xychartDiagram-ELKLHX3M-38t16pme.js +7 -0
- package/dist/canvas/global.css +389 -229
- package/dist/canvas/index.js +141 -211
- package/dist/canvas/mcp-app.html +4205 -554
- 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/CanvasNode.d.ts +0 -11
- package/dist/types/client/canvas/EdgeLayer.d.ts +1 -2
- 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/ViewerFrame.d.ts +29 -0
- package/dist/types/client/nodes/ViewerHost.d.ts +6 -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-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 +3 -1
- package/dist/types/client/state/intent-bridge.d.ts +1 -0
- package/dist/types/client/state/presentation.d.ts +17 -6
- package/dist/types/json-render/server.d.ts +2 -1
- package/dist/types/mcp/canvas-access.d.ts +1 -12
- package/dist/types/mcp/resource-subscriptions.d.ts +7 -0
- package/dist/types/mcp/server.d.ts +2 -0
- package/dist/types/server/ax-wait.d.ts +1 -0
- package/dist/types/server/canvas-operations.d.ts +1 -0
- package/dist/types/server/canvas-state.d.ts +2 -0
- package/dist/types/server/index.d.ts +11 -6
- package/dist/types/server/operations/http.d.ts +10 -0
- package/dist/types/server/operations/index.d.ts +2 -2
- package/dist/types/server/operations/invoker.d.ts +4 -3
- package/dist/types/server/operations/mcp-contracts.d.ts +20 -0
- package/dist/types/server/operations/mcp.d.ts +2 -0
- package/dist/types/server/operations/ops/batch.d.ts +1 -0
- package/dist/types/server/operations/ops/boards.d.ts +0 -1
- package/dist/types/server/operations/registry.d.ts +2 -2
- package/dist/types/server/operations/types.d.ts +12 -2
- package/dist/types/server/server.d.ts +2 -0
- package/dist/types/server/tour-control.d.ts +13 -3
- package/dist/types/server/webpage-node.d.ts +1 -0
- package/dist/types/shared/ax-surface-protocol.d.ts +0 -9
- package/dist/types/shared/tour.d.ts +8 -0
- package/docs/api-reference.md +2 -2
- package/docs/cli.md +1 -1
- package/docs/design/Chrome.dc.html +32 -6
- 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/Zoom.dc.html +37 -60
- package/docs/design/canvas.json +1 -1
- package/docs/design.md +169 -4
- package/docs/http-api.md +82 -1
- package/docs/mcp.md +101 -21
- package/docs/node-types.md +38 -3
- package/docs/plans/plan-009-tech-debt-backlog.md +2 -2
- package/docs/plans/plan-019-post-0.9-roadmap.md +402 -13
- package/docs/product-vision-2026-09.md +9 -0
- package/docs/sdk.md +5 -2
- package/package.json +2 -2
- package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/runtime.json +1 -1
- package/plugins/pmx-canvas/plugin.json +1 -1
- package/plugins/pmx-canvas/runtime.json +1 -1
- package/plugins/pmx-canvas/tools.json +9418 -2016
- package/skills/pmx-canvas/SKILL.md +19 -21
- package/skills/pmx-canvas/references/full-reference.md +44 -6
- package/skills/pmx-canvas/references/installing-pmx-canvas.md +7 -7
- package/skills/published-consumer-e2e/scripts/run-published-consumer-e2e.sh +3 -5
- package/src/client/canvas/BarHint.tsx +6 -3
- package/src/client/canvas/CanvasNode.tsx +34 -47
- package/src/client/canvas/CanvasViewport.tsx +6 -7
- package/src/client/canvas/ContextMenu.tsx +63 -3
- package/src/client/canvas/EdgeLayer.tsx +365 -169
- package/src/client/canvas/ExpandedNodeOverlay.tsx +1 -2
- package/src/client/canvas/Presentation.tsx +53 -3
- package/src/client/canvas/ToolRail.tsx +2 -0
- package/src/client/canvas/TopBar.tsx +24 -9
- package/src/client/canvas/auto-fit.ts +9 -11
- package/src/client/canvas/canvas-area.ts +1 -1
- 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 +283 -1462
- 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 +8 -17
- package/src/client/nodes/McpAppNode.tsx +48 -154
- package/src/client/nodes/MermaidNode.tsx +80 -69
- package/src/client/nodes/PromptNode.tsx +12 -14
- package/src/client/nodes/ResponseNode.tsx +8 -6
- package/src/client/nodes/StatusNode.tsx +22 -7
- package/src/client/nodes/StatusSummary.tsx +5 -3
- package/src/client/nodes/TraceNode.tsx +8 -8
- package/src/client/nodes/ViewerFrame.tsx +262 -0
- package/src/client/nodes/ViewerHost.tsx +41 -0
- package/src/client/nodes/WebpageNode.tsx +22 -24
- package/src/client/nodes/iframe-document-url.ts +20 -27
- package/src/client/nodes/primitive-copy.ts +97 -0
- 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/canvas-store.ts +18 -12
- package/src/client/state/intent-bridge.ts +1 -0
- package/src/client/state/presentation.ts +135 -15
- package/src/client/state/sse-bridge.ts +18 -19
- package/src/client/theme/global.css +389 -229
- package/src/client/theme/surface-theme.css +6 -6
- 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/canvas-access.ts +6 -47
- package/src/mcp/resource-subscriptions.ts +181 -0
- package/src/mcp/server.ts +185 -219
- package/src/server/ax-wait.ts +21 -6
- package/src/server/board-export.ts +35 -24
- package/src/server/canvas-geometry.ts +1 -0
- package/src/server/canvas-operations.ts +6 -2
- package/src/server/canvas-state.ts +5 -0
- package/src/server/demo-state.json +106 -100
- package/src/server/html-primitives.ts +3 -9
- package/src/server/html-surface.ts +8 -0
- package/src/server/index.ts +30 -12
- package/src/server/operations/http.ts +69 -21
- package/src/server/operations/index.ts +8 -1
- package/src/server/operations/invoker.ts +53 -5
- package/src/server/operations/mcp-contracts.ts +354 -0
- package/src/server/operations/mcp.ts +153 -18
- package/src/server/operations/ops/ax-await.ts +18 -10
- package/src/server/operations/ops/ax-read.ts +2 -0
- package/src/server/operations/ops/ax-timeline.ts +2 -2
- package/src/server/operations/ops/batch.ts +27 -8
- package/src/server/operations/ops/boards.ts +1 -3
- package/src/server/operations/ops/nodes.ts +18 -3
- package/src/server/operations/ops/viewport.ts +11 -9
- package/src/server/operations/registry.ts +24 -4
- package/src/server/operations/types.ts +16 -2
- package/src/server/server.ts +17 -59
- package/src/server/tour-control.ts +66 -6
- package/src/server/webpage-node.ts +9 -4
- package/src/shared/ax-surface-protocol.ts +0 -11
- package/src/shared/content-height-reporter.ts +4 -3
- package/src/shared/tour.ts +8 -0
|
@@ -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');
|
package/src/server/index.ts
CHANGED
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
import { EventEmitter } from 'node:events';
|
|
2
|
-
import type { Camera, Tour, TourStop } from '../shared/tour.js';
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
import type { Camera, Tour, TourPresenter, TourStop } from '../shared/tour.js';
|
|
3
|
+
import {
|
|
4
|
+
currentTour,
|
|
5
|
+
exitTour,
|
|
6
|
+
goToTourStop,
|
|
7
|
+
moveCamera,
|
|
8
|
+
type TourState,
|
|
9
|
+
type TourStep,
|
|
10
|
+
type TourStepResult,
|
|
11
|
+
type TourSuperseded,
|
|
12
|
+
} from './tour-control.js';
|
|
13
|
+
export type { Tour, TourPresenter, TourStop } from '../shared/tour.js';
|
|
14
|
+
export type { TourState, TourStep, TourStepResult, TourSuperseded } from './tour-control.js';
|
|
6
15
|
import { agentPresence } from './agent-presence.js';
|
|
7
16
|
import { checkFenceTarget, type FenceTarget } from './scope-fence.js';
|
|
8
17
|
import type { AgentPhase, AgentPresence, AgentPresenceSnapshot } from '../shared/agent-presence.js';
|
|
@@ -612,8 +621,8 @@ export class PmxCanvas extends EventEmitter {
|
|
|
612
621
|
);
|
|
613
622
|
}
|
|
614
623
|
|
|
615
|
-
getTour():
|
|
616
|
-
return currentTour()
|
|
624
|
+
getTour(): TourState {
|
|
625
|
+
return currentTour();
|
|
617
626
|
}
|
|
618
627
|
|
|
619
628
|
setTour(tour: Tour | null): void {
|
|
@@ -623,16 +632,25 @@ export class PmxCanvas extends EventEmitter {
|
|
|
623
632
|
}
|
|
624
633
|
|
|
625
634
|
/** Present a tour stop (index, `next` or `previous`) in the workbench. Not recorded in undo history. */
|
|
626
|
-
goToTourStop(step: TourStep, options?: { present?: boolean }): TourStepResult
|
|
627
|
-
|
|
628
|
-
|
|
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 });
|
|
629
647
|
return result;
|
|
630
648
|
}
|
|
631
649
|
|
|
632
650
|
/** Leave presentation in every viewer. */
|
|
633
|
-
exitTour(): void {
|
|
634
|
-
exitTour();
|
|
635
|
-
emitPrimaryWorkbenchEvent('canvas-tour-exit', {});
|
|
651
|
+
exitTour(presenter?: TourPresenter): void {
|
|
652
|
+
const result = exitTour(presenter);
|
|
653
|
+
if (!('superseded' in result)) emitPrimaryWorkbenchEvent('canvas-tour-exit', { ...result });
|
|
636
654
|
}
|
|
637
655
|
|
|
638
656
|
/** Ease the camera to a node, world rect or viewport. Not recorded in undo history. */
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { executeOperation, listOperations } from './registry.js';
|
|
10
10
|
import { SOURCE_LABEL_RE } from '../agent-presence.js';
|
|
11
|
-
import { OperationError } from './types.js';
|
|
11
|
+
import { OperationError, type OperationExecutionOptions, type OperationProgress } from './types.js';
|
|
12
12
|
import { isWorkbenchToken, refuseStaleWorkbenchWrite } from '../workbench-auth.js';
|
|
13
13
|
|
|
14
14
|
function responseJson(data: unknown, status = 200): Response {
|
|
@@ -20,6 +20,57 @@ function responseJson(data: unknown, status = 200): Response {
|
|
|
20
20
|
});
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
/** Opt-in NDJSON; the terminal frame retains the ordinary HTTP status/body. */
|
|
24
|
+
export type OperationStreamFrame =
|
|
25
|
+
| { type: 'progress'; progress: OperationProgress }
|
|
26
|
+
| { type: 'result'; status: number; body: unknown };
|
|
27
|
+
|
|
28
|
+
function operationFailure(name: string, error: unknown, signal: AbortSignal): Response {
|
|
29
|
+
if (signal.aborted) return responseJson({ ok: false, cancelled: true, error: 'Operation cancelled.' }, 499);
|
|
30
|
+
if (error instanceof OperationError) {
|
|
31
|
+
return responseJson({ ok: false, error: error.message, ...(error.details ?? {}) }, error.status);
|
|
32
|
+
}
|
|
33
|
+
// Keep unexpected errors server-side, never leak a Bun development overlay.
|
|
34
|
+
console.error(`[operation] unhandled error dispatching ${name}:`, error);
|
|
35
|
+
return responseJson({ ok: false, error: 'Internal error processing the request.' }, 500);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function progressResponse(
|
|
39
|
+
req: Request,
|
|
40
|
+
name: string,
|
|
41
|
+
run: (options: OperationExecutionOptions) => Promise<Response>,
|
|
42
|
+
): Response {
|
|
43
|
+
const cancelled = new AbortController();
|
|
44
|
+
const signal = AbortSignal.any([req.signal, cancelled.signal]);
|
|
45
|
+
const encoder = new TextEncoder();
|
|
46
|
+
let closed = false;
|
|
47
|
+
const stream = new ReadableStream<Uint8Array>({
|
|
48
|
+
async start(controller) {
|
|
49
|
+
const send = (frame: OperationStreamFrame): void => {
|
|
50
|
+
if (!closed) controller.enqueue(encoder.encode(`${JSON.stringify(frame)}\n`));
|
|
51
|
+
};
|
|
52
|
+
let response: Response;
|
|
53
|
+
try {
|
|
54
|
+
response = await run({ signal, onProgress: (progress) => send({ type: 'progress', progress }) });
|
|
55
|
+
} catch (error) {
|
|
56
|
+
response = operationFailure(name, error, signal);
|
|
57
|
+
}
|
|
58
|
+
if (!closed) {
|
|
59
|
+
send({ type: 'result', status: response.status, body: await response.json() });
|
|
60
|
+
closed = true;
|
|
61
|
+
controller.close();
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
cancel() {
|
|
65
|
+
closed = true;
|
|
66
|
+
cancelled.abort();
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
return new Response(stream, {
|
|
70
|
+
headers: { 'Content-Type': 'application/x-ndjson', 'Cache-Control': 'no-store', 'X-Accel-Buffering': 'no' },
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
23
74
|
export function matchOperationPath(template: string, pathname: string): Record<string, string> | null {
|
|
24
75
|
const templateSegments = template.split('/');
|
|
25
76
|
const pathSegments = pathname.split('/');
|
|
@@ -83,6 +134,7 @@ export async function dispatchOperationRoute(req: Request, url: URL): Promise<Re
|
|
|
83
134
|
const params = matchOperationPath(route.path, url.pathname);
|
|
84
135
|
if (!params) continue;
|
|
85
136
|
try {
|
|
137
|
+
req.signal.throwIfAborted();
|
|
86
138
|
const input = route.readInput
|
|
87
139
|
? await route.readInput(req, params, url)
|
|
88
140
|
: await readOperationInput(req, params, url);
|
|
@@ -100,27 +152,23 @@ export async function dispatchOperationRoute(req: Request, url: URL): Promise<Re
|
|
|
100
152
|
// HTTP callers read as 'api'.
|
|
101
153
|
const sourceHeader = req.headers.get('x-pmx-source');
|
|
102
154
|
const source = sourceHeader && SOURCE_LABEL_RE.test(sourceHeader) ? sourceHeader : 'api';
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
155
|
+
const run = async (options: OperationExecutionOptions): Promise<Response> => {
|
|
156
|
+
const result = await executeOperation(op.name, input, {
|
|
157
|
+
...options,
|
|
158
|
+
// The legacy marker remains a UI-policy signal (ghost suppression).
|
|
159
|
+
// Only the unguessable boot token classifies authorship as human.
|
|
160
|
+
suppressAutoGhost: workbenchMarked,
|
|
161
|
+
fromWorkbench: workbenchMarked,
|
|
162
|
+
humanAuthor,
|
|
163
|
+
source,
|
|
164
|
+
proxiedRead: req.headers.get('x-pmx-proxied-read') === '1',
|
|
165
|
+
});
|
|
166
|
+
return responseJson(result, route.status ? route.status(result) : 200);
|
|
167
|
+
};
|
|
168
|
+
if (req.headers.get('accept') === 'application/x-ndjson') return progressResponse(req, op.name, run);
|
|
169
|
+
return await run({ signal: req.signal });
|
|
113
170
|
} catch (error) {
|
|
114
|
-
|
|
115
|
-
return responseJson({ ok: false, error: error.message, ...(error.details ?? {}) }, error.status);
|
|
116
|
-
}
|
|
117
|
-
// An unexpected (non-OperationError) throw from a handler MUST NOT escape the
|
|
118
|
-
// dispatcher: Bun.serve has no per-request boundary, so an escaped throw renders
|
|
119
|
-
// its dev error overlay (HTTP 500 text/html leaking the absolute server source
|
|
120
|
-
// path + stack). Return a clean JSON 500 with a generic message instead (the
|
|
121
|
-
// real error is logged server-side, never echoed to the client).
|
|
122
|
-
console.error(`[operation] unhandled error dispatching ${op.name}:`, error);
|
|
123
|
-
return responseJson({ ok: false, error: 'Internal error processing the request.' }, 500);
|
|
171
|
+
return operationFailure(op.name, error, req.signal);
|
|
124
172
|
}
|
|
125
173
|
}
|
|
126
174
|
return null;
|
|
@@ -77,7 +77,12 @@ export { runCanvasBatchOperation, type BatchEnvelope } from './ops/batch.js';
|
|
|
77
77
|
// OpenMcpAppCoreResult is the SDK's cast target for executeOperation('mcpapp.open').
|
|
78
78
|
export type { OpenMcpAppCoreResult } from './ops/app.js';
|
|
79
79
|
export { LocalOperationInvoker, HttpOperationInvoker, type OperationInvoker } from './invoker.js';
|
|
80
|
-
export {
|
|
80
|
+
export {
|
|
81
|
+
registerOperationTools,
|
|
82
|
+
registerCompositeTools,
|
|
83
|
+
registerOperationHelpTool,
|
|
84
|
+
type OperationToolHost,
|
|
85
|
+
} from './mcp.js';
|
|
81
86
|
export { compositeToolDefinitions, type CompositeToolDefinition } from './composites.js';
|
|
82
87
|
export {
|
|
83
88
|
OperationError,
|
|
@@ -85,5 +90,7 @@ export {
|
|
|
85
90
|
type Operation,
|
|
86
91
|
type OperationContext,
|
|
87
92
|
type OperationDefinition,
|
|
93
|
+
type OperationExecutionOptions,
|
|
88
94
|
type OperationErrorStatus,
|
|
95
|
+
type OperationProgress,
|
|
89
96
|
} from './types.js';
|
|
@@ -5,10 +5,11 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { SOURCE_LABEL_RE } from '../agent-presence.js';
|
|
7
7
|
import { executeOperation, getOperation } from './registry.js';
|
|
8
|
-
import { OperationError, type OperationErrorStatus } from './types.js';
|
|
8
|
+
import { OperationError, type OperationErrorStatus, type OperationExecutionOptions } from './types.js';
|
|
9
|
+
import type { OperationStreamFrame } from './http.js';
|
|
9
10
|
|
|
10
11
|
export interface OperationInvoker {
|
|
11
|
-
invoke(name: string, input: Record<string, unknown
|
|
12
|
+
invoke(name: string, input: Record<string, unknown>, options?: OperationExecutionOptions): Promise<unknown>;
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
/**
|
|
@@ -40,9 +41,14 @@ export class LocalOperationInvoker implements OperationInvoker {
|
|
|
40
41
|
this.proxiedRead = source === 'mcp';
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
async invoke(
|
|
44
|
+
async invoke(
|
|
45
|
+
name: string,
|
|
46
|
+
input: Record<string, unknown>,
|
|
47
|
+
options: OperationExecutionOptions = {},
|
|
48
|
+
): Promise<unknown> {
|
|
44
49
|
const args = this.allowExternalMcp ? input : { ...input, allowExternalMcp: false };
|
|
45
50
|
return await executeOperation(name, defaultPresenceSource(name, args, this.source), {
|
|
51
|
+
...options,
|
|
46
52
|
source: this.source,
|
|
47
53
|
proxiedRead: this.proxiedRead,
|
|
48
54
|
});
|
|
@@ -53,6 +59,37 @@ function toOperationErrorStatus(status: number): OperationErrorStatus {
|
|
|
53
59
|
return status === 404 ? 404 : status === 409 ? 409 : status === 403 ? 403 : 400;
|
|
54
60
|
}
|
|
55
61
|
|
|
62
|
+
async function readProgressResponse(response: Response, options: OperationExecutionOptions): Promise<Response> {
|
|
63
|
+
if (!response.body) throw new OperationError('Missing operation progress stream.');
|
|
64
|
+
const reader = response.body.getReader();
|
|
65
|
+
const decoder = new TextDecoder();
|
|
66
|
+
let buffer = '';
|
|
67
|
+
try {
|
|
68
|
+
while (true) {
|
|
69
|
+
const { done, value } = await reader.read();
|
|
70
|
+
buffer += decoder.decode(value, { stream: !done });
|
|
71
|
+
let end = buffer.indexOf('\n');
|
|
72
|
+
while (end !== -1) {
|
|
73
|
+
const frame = JSON.parse(buffer.slice(0, end)) as OperationStreamFrame;
|
|
74
|
+
buffer = buffer.slice(end + 1);
|
|
75
|
+
if (frame.type === 'result') return Response.json(frame.body, { status: frame.status });
|
|
76
|
+
if (frame.type !== 'progress') throw new OperationError('Invalid operation progress frame.');
|
|
77
|
+
try {
|
|
78
|
+
await options.onProgress?.(frame.progress);
|
|
79
|
+
} catch (error) {
|
|
80
|
+
if (!options.signal?.aborted) console.error('[operation] progress callback failed:', error);
|
|
81
|
+
}
|
|
82
|
+
end = buffer.indexOf('\n');
|
|
83
|
+
}
|
|
84
|
+
if (done) throw new OperationError('Operation progress stream ended without a result.');
|
|
85
|
+
}
|
|
86
|
+
} finally {
|
|
87
|
+
// A fetch abort already errors the reader; preserve that original failure.
|
|
88
|
+
await reader.cancel().catch(() => {});
|
|
89
|
+
reader.releaseLock();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
56
93
|
/** Builds the HTTP request from the op's route template (`:id` from input, GET flags to query). */
|
|
57
94
|
export class HttpOperationInvoker implements OperationInvoker {
|
|
58
95
|
private readonly baseUrl: string;
|
|
@@ -70,7 +107,12 @@ export class HttpOperationInvoker implements OperationInvoker {
|
|
|
70
107
|
this.proxiedRead = source === 'mcp';
|
|
71
108
|
}
|
|
72
109
|
|
|
73
|
-
async invoke(
|
|
110
|
+
async invoke(
|
|
111
|
+
name: string,
|
|
112
|
+
rawInput: Record<string, unknown>,
|
|
113
|
+
options: OperationExecutionOptions = {},
|
|
114
|
+
): Promise<unknown> {
|
|
115
|
+
options.signal?.throwIfAborted();
|
|
74
116
|
const args = this.allowExternalMcp ? rawInput : { ...rawInput, allowExternalMcp: false };
|
|
75
117
|
const input = defaultPresenceSource(name, args, this.source);
|
|
76
118
|
const op = getOperation(name);
|
|
@@ -96,8 +138,10 @@ export class HttpOperationInvoker implements OperationInvoker {
|
|
|
96
138
|
|
|
97
139
|
let url = `${this.baseUrl}${path}`;
|
|
98
140
|
const proxyHeaders: Record<string, string> = this.proxiedRead ? { 'x-pmx-proxied-read': '1' } : {};
|
|
141
|
+
if (options.onProgress) proxyHeaders.accept = 'application/x-ndjson';
|
|
99
142
|
const init: RequestInit = {
|
|
100
143
|
method: route.method,
|
|
144
|
+
signal: options.signal,
|
|
101
145
|
headers: { 'x-pmx-source': this.source, ...proxyHeaders },
|
|
102
146
|
};
|
|
103
147
|
if (route.method === 'GET' || route.method === 'DELETE') {
|
|
@@ -112,7 +156,10 @@ export class HttpOperationInvoker implements OperationInvoker {
|
|
|
112
156
|
init.body = JSON.stringify(rest);
|
|
113
157
|
}
|
|
114
158
|
|
|
115
|
-
|
|
159
|
+
let response = await fetch(url, init);
|
|
160
|
+
if (response.headers.get('content-type')?.startsWith('application/x-ndjson')) {
|
|
161
|
+
response = await readProgressResponse(response, options);
|
|
162
|
+
}
|
|
116
163
|
const text = await response.text();
|
|
117
164
|
let parsed: unknown = null;
|
|
118
165
|
let parsedAsJson = false;
|
|
@@ -125,6 +172,7 @@ export class HttpOperationInvoker implements OperationInvoker {
|
|
|
125
172
|
}
|
|
126
173
|
}
|
|
127
174
|
if (!response.ok) {
|
|
175
|
+
if (response.status === 499) throw new DOMException('Operation cancelled.', 'AbortError');
|
|
128
176
|
// errorBodyAsResult carries the route's STRUCTURED partial-failure
|
|
129
177
|
// envelope (e.g. canvas_batch { ok:false, failedIndex, ... }) through as
|
|
130
178
|
// a result. A non-JSON error body (an HTML 502 from a proxy, a foreign
|