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.
Files changed (237) hide show
  1. package/.github/extensions/pmx-canvas/runtime.json +1 -1
  2. package/.github/extensions/pmx-canvas/tools.json +9418 -2016
  3. package/CHANGELOG.md +54 -1
  4. package/dist/canvas/chunks/abnfDiagram-N423BO3Z-p8dtp6va.js +1 -0
  5. package/dist/canvas/chunks/architecture-TIHT7OUA-9tffvpsv.js +1 -0
  6. package/dist/canvas/chunks/architectureDiagram-T3A2C74G-5jhmxrzf.js +36 -0
  7. package/dist/canvas/chunks/blockDiagram-VBNYF7ZC-pw1fa9yb.js +132 -0
  8. package/dist/canvas/chunks/c4Diagram-5PPSVZJV-x07nq0tt.js +10 -0
  9. package/dist/canvas/chunks/classDiagram-JCYQIIEL-nc6g5x1r.js +1 -0
  10. package/dist/canvas/chunks/classDiagram-v2-OCEON4UE-0qnbpwvh.js +1 -0
  11. package/dist/canvas/chunks/cose-bilkent-JH36ORCC-nm39npq0.js +1 -0
  12. package/dist/canvas/chunks/cynefin-VYW2F7L2-ys8p46ts.js +1 -0
  13. package/dist/canvas/chunks/cynefinDiagram-MW4NZA55-vdxrwahz.js +62 -0
  14. package/dist/canvas/chunks/dagre-VZM6K2ZE-bqdm7wpd.js +4 -0
  15. package/dist/canvas/chunks/diagram-7IWD3JNH-gs5gpr93.js +30 -0
  16. package/dist/canvas/chunks/diagram-B4RE2ZJO-cgwjg47s.js +3 -0
  17. package/dist/canvas/chunks/diagram-LBJQPF4R-w3vahpav.js +24 -0
  18. package/dist/canvas/chunks/diagram-Q27KOJAE-42837z6h.js +24 -0
  19. package/dist/canvas/chunks/diagram-UB23O5K3-4fz83q59.js +41 -0
  20. package/dist/canvas/chunks/ebnfDiagram-BXEA7PRR-zphtgegj.js +1 -0
  21. package/dist/canvas/chunks/erDiagram-JOGREHBK-zvbbdxp4.js +85 -0
  22. package/dist/canvas/chunks/eventmodeling-45OFAUF4-v9q6dn4c.js +1 -0
  23. package/dist/canvas/chunks/flowDiagram-UKHOOZJN-yrr5z8tc.js +1 -0
  24. package/dist/canvas/chunks/ganttDiagram-PKOTCBZU-282jzycm.js +292 -0
  25. package/dist/canvas/chunks/gitGraph-TEB2WS4Q-kdwb8m0a.js +1 -0
  26. package/dist/canvas/chunks/gitGraphDiagram-DS77QQ5N-vqqy6p1b.js +106 -0
  27. package/dist/canvas/chunks/index-0rk4gp1g.js +2 -0
  28. package/dist/canvas/chunks/index-194m3hc3.js +2 -0
  29. package/dist/canvas/chunks/index-1r72hdzb.js +301 -0
  30. package/dist/canvas/chunks/index-353m7b0p.js +2 -0
  31. package/dist/canvas/chunks/index-3cy1bzwh.js +2 -0
  32. package/dist/canvas/chunks/index-5tv8v1hk.js +1 -0
  33. package/dist/canvas/chunks/index-6ceya556.js +332 -0
  34. package/dist/canvas/chunks/index-6qmsnxwy.js +4 -0
  35. package/dist/canvas/chunks/index-d3b3gqe9.js +2 -0
  36. package/dist/canvas/chunks/index-fjtdggjn.js +2 -0
  37. package/dist/canvas/chunks/index-g3pwnqeb.js +2 -0
  38. package/dist/canvas/chunks/index-gf4e0et4.js +2 -0
  39. package/dist/canvas/chunks/index-hj99nz1f.js +1 -0
  40. package/dist/canvas/chunks/index-nprm96xp.js +16 -0
  41. package/dist/canvas/chunks/index-qj8fxwsf.js +2 -0
  42. package/dist/canvas/chunks/index-r2fyc9w1.js +89 -0
  43. package/dist/canvas/chunks/index-rft2hcz5.js +2 -0
  44. package/dist/canvas/chunks/index-rh7nv68n.js +2 -0
  45. package/dist/canvas/chunks/index-s4j678gj.js +2 -0
  46. package/dist/canvas/chunks/index-tb1wf525.js +179 -0
  47. package/dist/canvas/chunks/index-vv56h019.js +2 -0
  48. package/dist/canvas/chunks/index-w2mra17t.js +207 -0
  49. package/dist/canvas/chunks/index-wgv7en21.js +232 -0
  50. package/dist/canvas/chunks/index-yw8h9vha.js +157 -0
  51. package/dist/canvas/chunks/info-DKCQHKI2-wpzs6sz3.js +1 -0
  52. package/dist/canvas/chunks/infoDiagram-6WML65LV-v1yvggqj.js +2 -0
  53. package/dist/canvas/chunks/ishikawaDiagram-WSZJBQD7-5hnq1a9p.js +70 -0
  54. package/dist/canvas/chunks/journeyDiagram-NVQOT4AX-zjcffveh.js +139 -0
  55. package/dist/canvas/chunks/kanban-definition-27J2QSJJ-dakqtfp1.js +89 -0
  56. package/dist/canvas/chunks/katex-dmg8k4zm.js +257 -0
  57. package/dist/canvas/chunks/mermaid.core-j2geswrd.js +12 -0
  58. package/dist/canvas/chunks/mindmap-definition-FAOFIHXS-142xhr3k.js +96 -0
  59. package/dist/canvas/chunks/packet-7NZHBO7P-xn439f0s.js +1 -0
  60. package/dist/canvas/chunks/pegDiagram-VL7TDLO6-5wvk7vsd.js +1 -0
  61. package/dist/canvas/chunks/pie-RZYD4A2V-0kf16rwh.js +1 -0
  62. package/dist/canvas/chunks/pieDiagram-7S7Q4E2Y-ztdhwycc.js +39 -0
  63. package/dist/canvas/chunks/quadrantDiagram-CIZ2JOQS-c5p9gmg6.js +7 -0
  64. package/dist/canvas/chunks/radar-I7S5WNFK-0g2pz36t.js +1 -0
  65. package/dist/canvas/chunks/railroad-3IZDKUUU-fcaran6j.js +1 -0
  66. package/dist/canvas/chunks/railroad-abnf-AHOZXSZD-wgdya41q.js +1 -0
  67. package/dist/canvas/chunks/railroad-ebnf-EBAXGLYW-gy07a4ka.js +1 -0
  68. package/dist/canvas/chunks/railroad-peg-LSFZ7HO6-ewk6wtkh.js +1 -0
  69. package/dist/canvas/chunks/railroadDiagram-AXF67PYL-xkmqkjwf.js +1 -0
  70. package/dist/canvas/chunks/requirementDiagram-LRYGKXZP-9wm2dyaw.js +84 -0
  71. package/dist/canvas/chunks/sankeyDiagram-W5VNT64P-2w19etgb.js +40 -0
  72. package/dist/canvas/chunks/sequenceDiagram-SI44F4Z6-0jn0vwmz.js +162 -0
  73. package/dist/canvas/chunks/sizeCapture-X5ZJPWSS-x75epwdq.js +1 -0
  74. package/dist/canvas/chunks/stateDiagram-OKZ733FA-schjpy6d.js +1 -0
  75. package/dist/canvas/chunks/stateDiagram-v2-UEYNNEHI-k8sfp2e7.js +1 -0
  76. package/dist/canvas/chunks/swimlanes-SLNWSIFB-gkw4xce3.js +1 -0
  77. package/dist/canvas/chunks/swimlanesDiagram-ULZ7WXOC-nqysw67j.js +8 -0
  78. package/dist/canvas/chunks/timeline-definition-Z64GVDOM-x7nkcy01.js +120 -0
  79. package/dist/canvas/chunks/treeView-QDETBFTQ-cesw6gp0.js +1 -0
  80. package/dist/canvas/chunks/treemap-6X3UGDF4-zdv4knp9.js +1 -0
  81. package/dist/canvas/chunks/vennDiagram-T6HMQDX7-x8dqn9yf.js +34 -0
  82. package/dist/canvas/chunks/wardley-OPB4EBWU-44pdddez.js +1 -0
  83. package/dist/canvas/chunks/wardleyDiagram-T6FBY63Y-t1j52t9r.js +78 -0
  84. package/dist/canvas/chunks/xychartDiagram-ELKLHX3M-38t16pme.js +7 -0
  85. package/dist/canvas/global.css +389 -229
  86. package/dist/canvas/index.js +141 -211
  87. package/dist/canvas/mcp-app.html +4205 -554
  88. package/dist/canvas/mermaid-entry.js +338 -338
  89. package/dist/canvas/surface-theme.css +6 -6
  90. package/dist/json-render/index.css +1 -1
  91. package/dist/json-render/index.js +49 -49
  92. package/dist/types/client/canvas/BarHint.d.ts +5 -3
  93. package/dist/types/client/canvas/CanvasNode.d.ts +0 -11
  94. package/dist/types/client/canvas/EdgeLayer.d.ts +1 -2
  95. package/dist/types/client/canvas/auto-fit.d.ts +4 -5
  96. package/dist/types/client/mermaid-entry.d.ts +1 -2
  97. package/dist/types/client/mermaid-render.d.ts +1 -0
  98. package/dist/types/client/nodes/BoardNode.d.ts +1 -1
  99. package/dist/types/client/nodes/ExtAppFrame.d.ts +7 -90
  100. package/dist/types/client/nodes/McpAppNode.d.ts +1 -25
  101. package/dist/types/client/nodes/MermaidNode.d.ts +1 -8
  102. package/dist/types/client/nodes/ViewerFrame.d.ts +29 -0
  103. package/dist/types/client/nodes/ViewerHost.d.ts +6 -0
  104. package/dist/types/client/nodes/iframe-document-url.d.ts +3 -9
  105. package/dist/types/client/nodes/primitive-copy.d.ts +5 -0
  106. package/dist/types/client/nodes/use-iframe-content-height.d.ts +1 -1
  107. package/dist/types/client/nodes/use-surface-frame.d.ts +4 -6
  108. package/dist/types/client/nodes/viewer-runtime-state.d.ts +2 -1
  109. package/dist/types/client/state/canvas-store.d.ts +3 -1
  110. package/dist/types/client/state/intent-bridge.d.ts +1 -0
  111. package/dist/types/client/state/presentation.d.ts +17 -6
  112. package/dist/types/json-render/server.d.ts +2 -1
  113. package/dist/types/mcp/canvas-access.d.ts +1 -12
  114. package/dist/types/mcp/resource-subscriptions.d.ts +7 -0
  115. package/dist/types/mcp/server.d.ts +2 -0
  116. package/dist/types/server/ax-wait.d.ts +1 -0
  117. package/dist/types/server/canvas-operations.d.ts +1 -0
  118. package/dist/types/server/canvas-state.d.ts +2 -0
  119. package/dist/types/server/index.d.ts +11 -6
  120. package/dist/types/server/operations/http.d.ts +10 -0
  121. package/dist/types/server/operations/index.d.ts +2 -2
  122. package/dist/types/server/operations/invoker.d.ts +4 -3
  123. package/dist/types/server/operations/mcp-contracts.d.ts +20 -0
  124. package/dist/types/server/operations/mcp.d.ts +2 -0
  125. package/dist/types/server/operations/ops/batch.d.ts +1 -0
  126. package/dist/types/server/operations/ops/boards.d.ts +0 -1
  127. package/dist/types/server/operations/registry.d.ts +2 -2
  128. package/dist/types/server/operations/types.d.ts +12 -2
  129. package/dist/types/server/server.d.ts +2 -0
  130. package/dist/types/server/tour-control.d.ts +13 -3
  131. package/dist/types/server/webpage-node.d.ts +1 -0
  132. package/dist/types/shared/ax-surface-protocol.d.ts +0 -9
  133. package/dist/types/shared/tour.d.ts +8 -0
  134. package/docs/api-reference.md +2 -2
  135. package/docs/cli.md +1 -1
  136. package/docs/design/Chrome.dc.html +32 -6
  137. package/docs/design/Types1.dc.html +3 -2
  138. package/docs/design/Types2.dc.html +1 -1
  139. package/docs/design/Types3.dc.html +1 -1
  140. package/docs/design/Zoom.dc.html +37 -60
  141. package/docs/design/canvas.json +1 -1
  142. package/docs/design.md +169 -4
  143. package/docs/http-api.md +82 -1
  144. package/docs/mcp.md +101 -21
  145. package/docs/node-types.md +38 -3
  146. package/docs/plans/plan-009-tech-debt-backlog.md +2 -2
  147. package/docs/plans/plan-019-post-0.9-roadmap.md +402 -13
  148. package/docs/product-vision-2026-09.md +9 -0
  149. package/docs/sdk.md +5 -2
  150. package/package.json +2 -2
  151. package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/runtime.json +1 -1
  152. package/plugins/pmx-canvas/plugin.json +1 -1
  153. package/plugins/pmx-canvas/runtime.json +1 -1
  154. package/plugins/pmx-canvas/tools.json +9418 -2016
  155. package/skills/pmx-canvas/SKILL.md +19 -21
  156. package/skills/pmx-canvas/references/full-reference.md +44 -6
  157. package/skills/pmx-canvas/references/installing-pmx-canvas.md +7 -7
  158. package/skills/published-consumer-e2e/scripts/run-published-consumer-e2e.sh +3 -5
  159. package/src/client/canvas/BarHint.tsx +6 -3
  160. package/src/client/canvas/CanvasNode.tsx +34 -47
  161. package/src/client/canvas/CanvasViewport.tsx +6 -7
  162. package/src/client/canvas/ContextMenu.tsx +63 -3
  163. package/src/client/canvas/EdgeLayer.tsx +365 -169
  164. package/src/client/canvas/ExpandedNodeOverlay.tsx +1 -2
  165. package/src/client/canvas/Presentation.tsx +53 -3
  166. package/src/client/canvas/ToolRail.tsx +2 -0
  167. package/src/client/canvas/TopBar.tsx +24 -9
  168. package/src/client/canvas/auto-fit.ts +9 -11
  169. package/src/client/canvas/canvas-area.ts +1 -1
  170. package/src/client/canvas/use-focus-trap.ts +6 -1
  171. package/src/client/mermaid-entry.ts +6 -20
  172. package/src/client/mermaid-render.ts +39 -0
  173. package/src/client/nodes/AxStepControls.tsx +45 -69
  174. package/src/client/nodes/BoardNode.tsx +65 -9
  175. package/src/client/nodes/ContextNode.tsx +88 -396
  176. package/src/client/nodes/ExtAppFrame.tsx +283 -1462
  177. package/src/client/nodes/FileNode.tsx +92 -81
  178. package/src/client/nodes/HtmlNode.tsx +8 -36
  179. package/src/client/nodes/LedgerNode.tsx +31 -17
  180. package/src/client/nodes/MarkdownNode.tsx +8 -17
  181. package/src/client/nodes/McpAppNode.tsx +48 -154
  182. package/src/client/nodes/MermaidNode.tsx +80 -69
  183. package/src/client/nodes/PromptNode.tsx +12 -14
  184. package/src/client/nodes/ResponseNode.tsx +8 -6
  185. package/src/client/nodes/StatusNode.tsx +22 -7
  186. package/src/client/nodes/StatusSummary.tsx +5 -3
  187. package/src/client/nodes/TraceNode.tsx +8 -8
  188. package/src/client/nodes/ViewerFrame.tsx +262 -0
  189. package/src/client/nodes/ViewerHost.tsx +41 -0
  190. package/src/client/nodes/WebpageNode.tsx +22 -24
  191. package/src/client/nodes/iframe-document-url.ts +20 -27
  192. package/src/client/nodes/primitive-copy.ts +97 -0
  193. package/src/client/nodes/use-iframe-content-height.ts +34 -10
  194. package/src/client/nodes/use-surface-frame.ts +22 -11
  195. package/src/client/nodes/viewer-runtime-state.ts +41 -35
  196. package/src/client/state/canvas-store.ts +18 -12
  197. package/src/client/state/intent-bridge.ts +1 -0
  198. package/src/client/state/presentation.ts +135 -15
  199. package/src/client/state/sse-bridge.ts +18 -19
  200. package/src/client/theme/global.css +389 -229
  201. package/src/client/theme/surface-theme.css +6 -6
  202. package/src/json-render/charts/tufte-components.tsx +44 -2
  203. package/src/json-render/renderer/index.css +62 -2
  204. package/src/json-render/renderer/index.tsx +31 -7
  205. package/src/json-render/server.ts +3 -2
  206. package/src/mcp/canvas-access.ts +6 -47
  207. package/src/mcp/resource-subscriptions.ts +181 -0
  208. package/src/mcp/server.ts +185 -219
  209. package/src/server/ax-wait.ts +21 -6
  210. package/src/server/board-export.ts +35 -24
  211. package/src/server/canvas-geometry.ts +1 -0
  212. package/src/server/canvas-operations.ts +6 -2
  213. package/src/server/canvas-state.ts +5 -0
  214. package/src/server/demo-state.json +106 -100
  215. package/src/server/html-primitives.ts +3 -9
  216. package/src/server/html-surface.ts +8 -0
  217. package/src/server/index.ts +30 -12
  218. package/src/server/operations/http.ts +69 -21
  219. package/src/server/operations/index.ts +8 -1
  220. package/src/server/operations/invoker.ts +53 -5
  221. package/src/server/operations/mcp-contracts.ts +354 -0
  222. package/src/server/operations/mcp.ts +153 -18
  223. package/src/server/operations/ops/ax-await.ts +18 -10
  224. package/src/server/operations/ops/ax-read.ts +2 -0
  225. package/src/server/operations/ops/ax-timeline.ts +2 -2
  226. package/src/server/operations/ops/batch.ts +27 -8
  227. package/src/server/operations/ops/boards.ts +1 -3
  228. package/src/server/operations/ops/nodes.ts +18 -3
  229. package/src/server/operations/ops/viewport.ts +11 -9
  230. package/src/server/operations/registry.ts +24 -4
  231. package/src/server/operations/types.ts +16 -2
  232. package/src/server/server.ts +17 -59
  233. package/src/server/tour-control.ts +66 -6
  234. package/src/server/webpage-node.ts +9 -4
  235. package/src/shared/ax-surface-protocol.ts +0 -11
  236. package/src/shared/content-height-reporter.ts +4 -3
  237. 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 { display: grid; grid-template-columns: 1fr auto; gap: 16px; align-items: start; margin-bottom: 20px; }
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; } header.hero, .two, .three { grid-template-columns: 1fr; } .actions { justify-content: flex-start; } .sticky { position: static; } }
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,8 +1,17 @@
1
1
  import { EventEmitter } from 'node:events';
2
- import type { Camera, Tour, TourStop } from '../shared/tour.js';
3
- import { currentTour, exitTour, goToTourStop, moveCamera, type TourStep, type TourStepResult } from './tour-control.js';
4
- export type { Tour, TourStop } from '../shared/tour.js';
5
- export type { TourStep, TourStepResult } from './tour-control.js';
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(): Tour {
616
- return currentTour().tour;
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
- const result = goToTourStop(step);
628
- emitPrimaryWorkbenchEvent('canvas-tour-step', { ...result, present: options?.present !== false });
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 result = await executeOperation(op.name, input, {
104
- // The legacy marker remains a UI-policy signal (ghost suppression).
105
- // Only the unguessable boot token classifies authorship as human.
106
- suppressAutoGhost: workbenchMarked,
107
- fromWorkbench: workbenchMarked,
108
- humanAuthor,
109
- source,
110
- proxiedRead: req.headers.get('x-pmx-proxied-read') === '1',
111
- });
112
- return responseJson(result, route.status ? route.status(result) : 200);
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
- if (error instanceof OperationError) {
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 { registerOperationTools, registerCompositeTools, type OperationToolHost } from './mcp.js';
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>): Promise<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(name: string, input: Record<string, unknown>): Promise<unknown> {
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(name: string, rawInput: Record<string, unknown>): Promise<unknown> {
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
- const response = await fetch(url, init);
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