pmx-canvas 0.11.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.
Files changed (298) hide show
  1. package/.github/extensions/pmx-canvas/extension.mjs +3 -1
  2. package/.github/extensions/pmx-canvas/runtime.json +1 -1
  3. package/.github/extensions/pmx-canvas/tools.json +440 -18
  4. package/CHANGELOG.md +37 -0
  5. package/dist/amp-plugin/pmx-canvas/LICENSE +21 -0
  6. package/dist/amp-plugin/pmx-canvas/THIRD_PARTY_LICENSES.txt +331 -0
  7. package/dist/amp-plugin/pmx-canvas/index.js +5445 -0
  8. package/dist/amp-plugin/pmx-canvas/runtime.json +3 -0
  9. package/dist/amp-plugin/pmx-canvas/shared-runtime.mjs +194 -0
  10. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/SKILL.md +251 -0
  11. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/evals/evals.json +1086 -0
  12. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/evals/fixtures/code-exploration/src/auth/jwt.ts +17 -0
  13. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/evals/fixtures/code-exploration/src/auth/login.ts +12 -0
  14. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/evals/fixtures/code-exploration/src/auth/middleware.ts +13 -0
  15. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/evals/fixtures/code-exploration/src/routes/auth.ts +13 -0
  16. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/evals/fixtures/investigation-board/src/handlers/users.ts +27 -0
  17. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/mcp.json +36 -0
  18. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/ax-html-control-surface.md +101 -0
  19. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/codex-app-adapter.md +151 -0
  20. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/excalidraw-diagram-authoring.md +150 -0
  21. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/full-reference.md +1785 -0
  22. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/github-copilot-app-adapter.md +223 -0
  23. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/html-primitives.md +139 -0
  24. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/importing-context.md +132 -0
  25. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/installing-pmx-canvas.md +251 -0
  26. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/tours-and-recording.md +111 -0
  27. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/workbench-workflows.md +900 -0
  28. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas-orchestration/SKILL.md +141 -0
  29. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas-testing/SKILL.md +266 -0
  30. package/dist/amp-plugin/pmx-canvas/start-mcp.mjs +14857 -0
  31. package/dist/amp-plugin/pmx-canvas/tools.json +10963 -0
  32. package/dist/canvas/chunks/{abnfDiagram-N423BO3Z-p8dtp6va.js → abnfDiagram-N423BO3Z-cwtsds5d.js} +1 -1
  33. package/dist/canvas/chunks/architecture-TIHT7OUA-x63cwejs.js +1 -0
  34. package/dist/canvas/chunks/{architectureDiagram-T3A2C74G-5jhmxrzf.js → architectureDiagram-T3A2C74G-b4xc9tpz.js} +3 -3
  35. package/dist/canvas/chunks/{blockDiagram-VBNYF7ZC-pw1fa9yb.js → blockDiagram-VBNYF7ZC-ahp3yxxs.js} +7 -7
  36. package/dist/canvas/chunks/{c4Diagram-5PPSVZJV-x07nq0tt.js → c4Diagram-5PPSVZJV-nappnay7.js} +4 -4
  37. package/dist/canvas/chunks/classDiagram-JCYQIIEL-k1j2xc1t.js +1 -0
  38. package/dist/canvas/chunks/classDiagram-v2-OCEON4UE-33x08xf2.js +1 -0
  39. package/dist/canvas/chunks/{cose-bilkent-JH36ORCC-nm39npq0.js → cose-bilkent-JH36ORCC-a428syda.js} +1 -1
  40. package/dist/canvas/chunks/cynefin-VYW2F7L2-8v6jxfz8.js +1 -0
  41. package/dist/canvas/chunks/{cynefinDiagram-MW4NZA55-vdxrwahz.js → cynefinDiagram-MW4NZA55-nspsqebj.js} +1 -1
  42. package/dist/canvas/chunks/{dagre-VZM6K2ZE-bqdm7wpd.js → dagre-VZM6K2ZE-8y6177fh.js} +2 -2
  43. package/dist/canvas/chunks/{diagram-7IWD3JNH-gs5gpr93.js → diagram-7IWD3JNH-vym028wn.js} +4 -4
  44. package/dist/canvas/chunks/{diagram-B4RE2ZJO-cgwjg47s.js → diagram-B4RE2ZJO-jddqb8sy.js} +1 -1
  45. package/dist/canvas/chunks/{diagram-LBJQPF4R-w3vahpav.js → diagram-LBJQPF4R-bjwbpkrn.js} +1 -1
  46. package/dist/canvas/chunks/{diagram-Q27KOJAE-42837z6h.js → diagram-Q27KOJAE-j6ph6hcx.js} +2 -2
  47. package/dist/canvas/chunks/{diagram-UB23O5K3-4fz83q59.js → diagram-UB23O5K3-p09w8jej.js} +1 -1
  48. package/dist/canvas/chunks/{ebnfDiagram-BXEA7PRR-zphtgegj.js → ebnfDiagram-BXEA7PRR-j975ycq5.js} +1 -1
  49. package/dist/canvas/chunks/{erDiagram-JOGREHBK-zvbbdxp4.js → erDiagram-JOGREHBK-sxp6hjn8.js} +5 -5
  50. package/dist/canvas/chunks/eventmodeling-45OFAUF4-xzfgn6xg.js +1 -0
  51. package/dist/canvas/chunks/flowDiagram-UKHOOZJN-2nsmkskb.js +1 -0
  52. package/dist/canvas/chunks/{ganttDiagram-PKOTCBZU-282jzycm.js → ganttDiagram-PKOTCBZU-4tv230y2.js} +2 -2
  53. package/dist/canvas/chunks/gitGraph-TEB2WS4Q-macbhand.js +1 -0
  54. package/dist/canvas/chunks/{gitGraphDiagram-DS77QQ5N-vqqy6p1b.js → gitGraphDiagram-DS77QQ5N-mqgmnw9g.js} +1 -1
  55. package/dist/canvas/chunks/{index-wgv7en21.js → index-037z5dz2.js} +2 -2
  56. package/dist/canvas/chunks/{index-w2mra17t.js → index-081n3har.js} +1 -1
  57. package/dist/canvas/chunks/{index-353m7b0p.js → index-5n03dmb9.js} +1 -1
  58. package/dist/canvas/chunks/{index-yw8h9vha.js → index-60pgzdqe.js} +1 -1
  59. package/dist/canvas/chunks/index-8g1vb57n.js +2 -0
  60. package/dist/canvas/chunks/{index-vv56h019.js → index-8rjes9r6.js} +1 -1
  61. package/dist/canvas/chunks/{index-nprm96xp.js → index-9022d1n9.js} +1 -1
  62. package/dist/canvas/chunks/{index-194m3hc3.js → index-9b02042t.js} +1 -1
  63. package/dist/canvas/chunks/{index-0rk4gp1g.js → index-ap4q4vez.js} +1 -1
  64. package/dist/canvas/chunks/{index-rh7nv68n.js → index-dyp2nhd3.js} +1 -1
  65. package/dist/canvas/chunks/index-frvta4tm.js +2 -0
  66. package/dist/canvas/chunks/{index-s4j678gj.js → index-h9ycj8n2.js} +1 -1
  67. package/dist/canvas/chunks/{index-fjtdggjn.js → index-mergmj97.js} +2 -2
  68. package/dist/canvas/chunks/index-nwsya6dj.js +301 -0
  69. package/dist/canvas/chunks/{index-3cy1bzwh.js → index-nz8qbd1p.js} +1 -1
  70. package/dist/canvas/chunks/{index-d3b3gqe9.js → index-qhfrazv3.js} +1 -1
  71. package/dist/canvas/chunks/{index-6qmsnxwy.js → index-v119nznk.js} +4 -4
  72. package/dist/canvas/chunks/{index-r2fyc9w1.js → index-vpt37m3d.js} +6 -6
  73. package/dist/canvas/chunks/{index-qj8fxwsf.js → index-y9ma6azy.js} +1 -1
  74. package/dist/canvas/chunks/{index-tb1wf525.js → index-zx0j3a2z.js} +26 -26
  75. package/dist/canvas/chunks/info-DKCQHKI2-a0ykzs9z.js +1 -0
  76. package/dist/canvas/chunks/{infoDiagram-6WML65LV-v1yvggqj.js → infoDiagram-6WML65LV-qv97qq6a.js} +2 -2
  77. package/dist/canvas/chunks/ishikawaDiagram-WSZJBQD7-p9306gh0.js +70 -0
  78. package/dist/canvas/chunks/{journeyDiagram-NVQOT4AX-zjcffveh.js → journeyDiagram-NVQOT4AX-sv67zats.js} +5 -5
  79. package/dist/canvas/chunks/{kanban-definition-27J2QSJJ-dakqtfp1.js → kanban-definition-27J2QSJJ-k2pfam6p.js} +1 -1
  80. package/dist/canvas/chunks/{mermaid.core-j2geswrd.js → mermaid.core-83r0fdce.js} +4 -4
  81. package/dist/canvas/chunks/{mindmap-definition-FAOFIHXS-142xhr3k.js → mindmap-definition-FAOFIHXS-vg2newm1.js} +1 -1
  82. package/dist/canvas/chunks/packet-7NZHBO7P-tqnabcdv.js +1 -0
  83. package/dist/canvas/chunks/{pegDiagram-VL7TDLO6-5wvk7vsd.js → pegDiagram-VL7TDLO6-k2h7zc5n.js} +1 -1
  84. package/dist/canvas/chunks/pie-RZYD4A2V-wke6gyhe.js +1 -0
  85. package/dist/canvas/chunks/{pieDiagram-7S7Q4E2Y-ztdhwycc.js → pieDiagram-7S7Q4E2Y-hg169p1v.js} +2 -2
  86. package/dist/canvas/chunks/{quadrantDiagram-CIZ2JOQS-c5p9gmg6.js → quadrantDiagram-CIZ2JOQS-bxtvsq0b.js} +5 -5
  87. package/dist/canvas/chunks/radar-I7S5WNFK-w7ka0drw.js +1 -0
  88. package/dist/canvas/chunks/railroad-3IZDKUUU-y3vg318x.js +1 -0
  89. package/dist/canvas/chunks/railroad-abnf-AHOZXSZD-6hqvcz3c.js +1 -0
  90. package/dist/canvas/chunks/railroad-ebnf-EBAXGLYW-vcae260m.js +1 -0
  91. package/dist/canvas/chunks/railroad-peg-LSFZ7HO6-0dbyme23.js +1 -0
  92. package/dist/canvas/chunks/{railroadDiagram-AXF67PYL-xkmqkjwf.js → railroadDiagram-AXF67PYL-njyhcp4p.js} +1 -1
  93. package/dist/canvas/chunks/{requirementDiagram-LRYGKXZP-9wm2dyaw.js → requirementDiagram-LRYGKXZP-2z6v09gm.js} +4 -4
  94. package/dist/canvas/chunks/{sankeyDiagram-W5VNT64P-2w19etgb.js → sankeyDiagram-W5VNT64P-15mzam0e.js} +1 -1
  95. package/dist/canvas/chunks/{sequenceDiagram-SI44F4Z6-0jn0vwmz.js → sequenceDiagram-SI44F4Z6-rbshyd2c.js} +12 -12
  96. package/dist/canvas/chunks/{sizeCapture-X5ZJPWSS-x75epwdq.js → sizeCapture-X5ZJPWSS-sg26xwts.js} +1 -1
  97. package/dist/canvas/chunks/{stateDiagram-OKZ733FA-schjpy6d.js → stateDiagram-OKZ733FA-6eahp98e.js} +1 -1
  98. package/dist/canvas/chunks/stateDiagram-v2-UEYNNEHI-tty0ggn5.js +1 -0
  99. package/dist/canvas/chunks/{swimlanes-SLNWSIFB-gkw4xce3.js → swimlanes-SLNWSIFB-ze5db10t.js} +1 -1
  100. package/dist/canvas/chunks/swimlanesDiagram-ULZ7WXOC-thze75gq.js +8 -0
  101. package/dist/canvas/chunks/{timeline-definition-Z64GVDOM-x7nkcy01.js → timeline-definition-Z64GVDOM-vnp7jyy8.js} +3 -3
  102. package/dist/canvas/chunks/treeView-QDETBFTQ-6ysffnm6.js +1 -0
  103. package/dist/canvas/chunks/treemap-6X3UGDF4-m3h9p066.js +1 -0
  104. package/dist/canvas/chunks/vennDiagram-T6HMQDX7-0qrnb7mj.js +34 -0
  105. package/dist/canvas/chunks/wardley-OPB4EBWU-dp37drwx.js +1 -0
  106. package/dist/canvas/chunks/{wardleyDiagram-T6FBY63Y-t1j52t9r.js → wardleyDiagram-T6FBY63Y-8nxg8wqt.js} +4 -4
  107. package/dist/canvas/chunks/{xychartDiagram-ELKLHX3M-38t16pme.js → xychartDiagram-ELKLHX3M-nj921e7s.js} +5 -5
  108. package/dist/canvas/global.css +94 -1
  109. package/dist/canvas/index.js +154 -145
  110. package/dist/canvas/mcp-app.html +600 -498
  111. package/dist/json-render/index.css +1 -1
  112. package/dist/types/client/canvas/BoardMoveDialog.d.ts +2 -0
  113. package/dist/types/client/canvas/EdgeEditor.d.ts +19 -0
  114. package/dist/types/client/canvas/GroupNavigation.d.ts +2 -0
  115. package/dist/types/client/canvas/TextPrompt.d.ts +4 -0
  116. package/dist/types/client/nodes/PromptNode.d.ts +1 -0
  117. package/dist/types/client/nodes/ViewerFrame.d.ts +1 -1
  118. package/dist/types/client/nodes/WorkCard.d.ts +7 -0
  119. package/dist/types/client/nodes/use-ax-surface-bridge.d.ts +1 -0
  120. package/dist/types/client/state/canvas-store.d.ts +15 -0
  121. package/dist/types/client/state/human-store.d.ts +2 -0
  122. package/dist/types/client/state/intent-bridge.d.ts +4 -2
  123. package/dist/types/client/state/relation-store.d.ts +11 -0
  124. package/dist/types/client/types.d.ts +1 -1
  125. package/dist/types/json-render/catalog.d.ts +2 -2
  126. package/dist/types/server/agent-presence.d.ts +1 -0
  127. package/dist/types/server/ax-interaction.d.ts +1 -0
  128. package/dist/types/server/ax-state-manager.d.ts +10 -2
  129. package/dist/types/server/board-local.d.ts +4 -0
  130. package/dist/types/server/board-move.d.ts +38 -0
  131. package/dist/types/server/canvas-db.d.ts +12 -2
  132. package/dist/types/server/canvas-operations.d.ts +5 -6
  133. package/dist/types/server/canvas-provenance.d.ts +1 -1
  134. package/dist/types/server/canvas-serialization.d.ts +1 -0
  135. package/dist/types/server/canvas-state.d.ts +84 -20
  136. package/dist/types/server/card-relocation.d.ts +6 -0
  137. package/dist/types/server/context-reads.d.ts +3 -1
  138. package/dist/types/server/human-presence.d.ts +1 -1
  139. package/dist/types/server/index.d.ts +16 -5
  140. package/dist/types/server/intent-registry.d.ts +8 -4
  141. package/dist/types/server/knowledge-lifecycle.d.ts +4 -2
  142. package/dist/types/server/mutation-history.d.ts +7 -5
  143. package/dist/types/server/operations/registry.d.ts +2 -0
  144. package/dist/types/server/trace-manager.d.ts +1 -0
  145. package/dist/types/shared/ax-kinds.d.ts +6 -0
  146. package/dist/types/shared/ax-surface-protocol.d.ts +8 -0
  147. package/dist/types/shared/human-presence.d.ts +1 -0
  148. package/dist/types/shared/lifecycle-status.d.ts +2 -0
  149. package/docs/RELEASE.md +21 -14
  150. package/docs/agent-plugin.md +16 -2
  151. package/docs/amp-plugin.md +271 -0
  152. package/docs/cli.md +42 -3
  153. package/docs/design/GuestName.dc.html +50 -0
  154. package/docs/design/PluginLogo.svg +7 -0
  155. package/docs/design/WorkCards.dc.html +39 -0
  156. package/docs/design/canvas.json +16 -0
  157. package/docs/design/plugin-logo.png +0 -0
  158. package/docs/design.md +111 -8
  159. package/docs/evals/agents-md-sections.md +38 -0
  160. package/docs/http-api.md +128 -10
  161. package/docs/mcp.md +61 -9
  162. package/docs/node-types.md +71 -1
  163. package/docs/plans/plan-009-tech-debt-backlog.md +1 -1
  164. package/docs/plans/plan-019-post-0.9-roadmap.md +44 -20
  165. package/docs/plans/plan-020-wave-5-work-planning.md +553 -0
  166. package/docs/product-vision-2026-09.md +33 -17
  167. package/docs/sdk.md +18 -0
  168. package/package.json +6 -3
  169. package/plugins/pmx-canvas/assets/preview.png +0 -0
  170. package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/extension.mjs +3 -1
  171. package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/runtime.json +1 -1
  172. package/plugins/pmx-canvas/plugin.json +2 -2
  173. package/plugins/pmx-canvas/runtime.json +1 -1
  174. package/plugins/pmx-canvas/tools.json +440 -18
  175. package/skills/data-analysis/SKILL.md +13 -26
  176. package/skills/frontend-design/LICENSE.txt +177 -0
  177. package/skills/frontend-design/SKILL.md +3 -0
  178. package/skills/pmx-canvas/SKILL.md +232 -842
  179. package/skills/pmx-canvas/evals/evals.json +26 -3
  180. package/skills/pmx-canvas/references/full-reference.md +80 -20
  181. package/skills/pmx-canvas/references/html-primitives.md +7 -0
  182. package/skills/pmx-canvas/references/installing-pmx-canvas.md +6 -6
  183. package/skills/pmx-canvas/references/tours-and-recording.md +29 -2
  184. package/skills/pmx-canvas/references/workbench-workflows.md +900 -0
  185. package/skills/pmx-canvas-testing/SKILL.md +7 -5
  186. package/skills/published-consumer-e2e/SKILL.md +25 -8
  187. package/skills/published-consumer-e2e/scripts/run-published-consumer-e2e.sh +51 -75
  188. package/skills/web-artifacts-builder/LICENSE.txt +202 -0
  189. package/skills/web-artifacts-builder/SKILL.md +6 -0
  190. package/skills/web-artifacts-builder/scripts/init-artifact.sh +2 -2
  191. package/src/cli/commands/apps.ts +1 -0
  192. package/src/cli/commands/boards.ts +30 -0
  193. package/src/client/App.tsx +20 -4
  194. package/src/client/canvas/AttentionHistory.tsx +10 -2
  195. package/src/client/canvas/BoardMoveDialog.tsx +225 -0
  196. package/src/client/canvas/CanvasNode.tsx +45 -2
  197. package/src/client/canvas/CanvasViewport.tsx +38 -9
  198. package/src/client/canvas/ContextMenu.tsx +34 -12
  199. package/src/client/canvas/EdgeEditor.tsx +149 -0
  200. package/src/client/canvas/EdgeLayer.tsx +87 -16
  201. package/src/client/canvas/ExpandedNodeOverlay.tsx +4 -0
  202. package/src/client/canvas/GroupNavigation.tsx +85 -0
  203. package/src/client/canvas/RelationsPanel.tsx +9 -0
  204. package/src/client/canvas/SelectionBar.tsx +21 -0
  205. package/src/client/canvas/SessionPanel.tsx +35 -1
  206. package/src/client/canvas/TextPrompt.tsx +18 -1
  207. package/src/client/canvas/TopBar.tsx +30 -0
  208. package/src/client/canvas/canvas-area.ts +1 -1
  209. package/src/client/nodes/ExtAppFrame.tsx +26 -10
  210. package/src/client/nodes/MarkdownNode.tsx +6 -7
  211. package/src/client/nodes/PromptNode.tsx +32 -3
  212. package/src/client/nodes/StatusNode.tsx +12 -0
  213. package/src/client/nodes/StatusSummary.tsx +11 -0
  214. package/src/client/nodes/ViewerFrame.tsx +21 -10
  215. package/src/client/nodes/WorkCard.tsx +175 -0
  216. package/src/client/nodes/use-ax-surface-bridge.ts +18 -14
  217. package/src/client/state/attention-bridge.ts +5 -0
  218. package/src/client/state/attention-store.ts +1 -0
  219. package/src/client/state/canvas-store.ts +185 -15
  220. package/src/client/state/human-store.ts +22 -2
  221. package/src/client/state/intent-bridge.ts +19 -5
  222. package/src/client/state/relation-store.ts +127 -1
  223. package/src/client/theme/global.css +94 -1
  224. package/src/client/types.ts +1 -0
  225. package/src/mcp/server.ts +13 -3
  226. package/src/server/agent-presence.ts +43 -23
  227. package/src/server/ax-flow-loop.ts +6 -0
  228. package/src/server/ax-gate-ttl.ts +8 -1
  229. package/src/server/ax-interaction.ts +16 -10
  230. package/src/server/ax-state-manager.ts +17 -4
  231. package/src/server/ax-wait.ts +20 -6
  232. package/src/server/board-export.ts +19 -6
  233. package/src/server/board-local.ts +20 -0
  234. package/src/server/board-move.ts +297 -0
  235. package/src/server/canvas-db.ts +249 -158
  236. package/src/server/canvas-operations.ts +139 -109
  237. package/src/server/canvas-schema.ts +21 -0
  238. package/src/server/canvas-serialization.ts +2 -0
  239. package/src/server/canvas-state.ts +792 -159
  240. package/src/server/card-relocation.ts +53 -0
  241. package/src/server/code-graph.ts +4 -3
  242. package/src/server/context-reads.ts +83 -55
  243. package/src/server/demo-state.json +35 -15
  244. package/src/server/file-watcher.ts +30 -10
  245. package/src/server/human-presence.ts +18 -5
  246. package/src/server/index.ts +35 -15
  247. package/src/server/intent-registry.ts +43 -5
  248. package/src/server/knowledge-lifecycle.ts +32 -9
  249. package/src/server/lifecycle-status.ts +29 -1
  250. package/src/server/mcp-app-runtime.ts +26 -7
  251. package/src/server/mutation-history.ts +37 -8
  252. package/src/server/operations/composites.ts +6 -3
  253. package/src/server/operations/mcp-contracts.ts +15 -0
  254. package/src/server/operations/mcp.ts +36 -9
  255. package/src/server/operations/ops/annotation.ts +22 -1
  256. package/src/server/operations/ops/ax-await.ts +1 -0
  257. package/src/server/operations/ops/ax-timeline.ts +28 -2
  258. package/src/server/operations/ops/batch.ts +25 -11
  259. package/src/server/operations/ops/boards.ts +44 -3
  260. package/src/server/operations/ops/nodes.ts +70 -0
  261. package/src/server/operations/ops/query.ts +32 -3
  262. package/src/server/operations/ops/snapshots.ts +1 -0
  263. package/src/server/operations/registry.ts +49 -4
  264. package/src/server/server.ts +62 -16
  265. package/src/server/tour-control.ts +37 -12
  266. package/src/server/trace-manager.ts +4 -0
  267. package/src/server/web-artifacts/scripts/init-artifact.sh +2 -2
  268. package/src/shared/agent-context.ts +7 -1
  269. package/src/shared/auto-arrange.ts +5 -4
  270. package/src/shared/ax-kinds.ts +13 -0
  271. package/src/shared/ax-surface-protocol.ts +22 -0
  272. package/src/shared/human-presence.ts +1 -0
  273. package/src/shared/lifecycle-status.ts +2 -0
  274. package/dist/canvas/chunks/architecture-TIHT7OUA-9tffvpsv.js +0 -1
  275. package/dist/canvas/chunks/classDiagram-JCYQIIEL-nc6g5x1r.js +0 -1
  276. package/dist/canvas/chunks/classDiagram-v2-OCEON4UE-0qnbpwvh.js +0 -1
  277. package/dist/canvas/chunks/cynefin-VYW2F7L2-ys8p46ts.js +0 -1
  278. package/dist/canvas/chunks/eventmodeling-45OFAUF4-v9q6dn4c.js +0 -1
  279. package/dist/canvas/chunks/flowDiagram-UKHOOZJN-yrr5z8tc.js +0 -1
  280. package/dist/canvas/chunks/gitGraph-TEB2WS4Q-kdwb8m0a.js +0 -1
  281. package/dist/canvas/chunks/index-1r72hdzb.js +0 -301
  282. package/dist/canvas/chunks/index-g3pwnqeb.js +0 -2
  283. package/dist/canvas/chunks/index-rft2hcz5.js +0 -2
  284. package/dist/canvas/chunks/info-DKCQHKI2-wpzs6sz3.js +0 -1
  285. package/dist/canvas/chunks/ishikawaDiagram-WSZJBQD7-5hnq1a9p.js +0 -70
  286. package/dist/canvas/chunks/packet-7NZHBO7P-xn439f0s.js +0 -1
  287. package/dist/canvas/chunks/pie-RZYD4A2V-0kf16rwh.js +0 -1
  288. package/dist/canvas/chunks/radar-I7S5WNFK-0g2pz36t.js +0 -1
  289. package/dist/canvas/chunks/railroad-3IZDKUUU-fcaran6j.js +0 -1
  290. package/dist/canvas/chunks/railroad-abnf-AHOZXSZD-wgdya41q.js +0 -1
  291. package/dist/canvas/chunks/railroad-ebnf-EBAXGLYW-gy07a4ka.js +0 -1
  292. package/dist/canvas/chunks/railroad-peg-LSFZ7HO6-ewk6wtkh.js +0 -1
  293. package/dist/canvas/chunks/stateDiagram-v2-UEYNNEHI-k8sfp2e7.js +0 -1
  294. package/dist/canvas/chunks/swimlanesDiagram-ULZ7WXOC-nqysw67j.js +0 -8
  295. package/dist/canvas/chunks/treeView-QDETBFTQ-cesw6gp0.js +0 -1
  296. package/dist/canvas/chunks/treemap-6X3UGDF4-zdv4knp9.js +0 -1
  297. package/dist/canvas/chunks/vennDiagram-T6HMQDX7-x8dqn9yf.js +0 -34
  298. package/dist/canvas/chunks/wardley-OPB4EBWU-44pdddez.js +0 -1
@@ -289,7 +289,17 @@ const axTimelineGetOperation = defineOperation<z.infer<typeof axTimelineGetSchem
289
289
  const axDeliveryPendingShape = {
290
290
  consumer: z.unknown().optional().describe('Consumer/source label to exclude from results (e.g. copilot, mcp).'),
291
291
  limit: z.unknown().optional().describe('Max steering messages to return.'),
292
+ offset: z.coerce
293
+ .number()
294
+ .int()
295
+ .nonnegative()
296
+ .optional()
297
+ .describe('Skip this many matching pending messages. Restart at zero after an empty page.'),
292
298
  order: z.unknown().optional().describe('"oldest" (FIFO, default) or "newest" first.'),
299
+ addressedOnly: z
300
+ .boolean()
301
+ .optional()
302
+ .describe('Only messages addressed to consumer, excluding broadcasts before applying the limit.'),
293
303
  timeoutMs: z
294
304
  .unknown()
295
305
  .optional()
@@ -310,10 +320,13 @@ const axDeliveryPendingOperation = defineOperation<z.infer<typeof axDeliveryPend
310
320
  // consumer arrives or the timeout elapses. Custom readInput replaces the
311
321
  // default query merge, so re-read the standard params here too.
312
322
  readInput: (_req, params, url) => {
313
- const raw = Number(url.searchParams.get('waitMs') ?? '');
323
+ const raw = Number(url.searchParams.get('timeoutMs') ?? url.searchParams.get('waitMs') ?? '');
314
324
  return {
315
325
  ...Object.fromEntries(url.searchParams),
316
326
  ...params,
327
+ ...(url.searchParams.has('addressedOnly')
328
+ ? { addressedOnly: url.searchParams.get('addressedOnly') === 'true' }
329
+ : {}),
317
330
  timeoutMs: Number.isFinite(raw) && raw > 0 ? raw : 0,
318
331
  };
319
332
  },
@@ -353,7 +366,13 @@ const axDeliveryPendingOperation = defineOperation<z.infer<typeof axDeliveryPend
353
366
  // can't bury the human's current action behind stale undelivered rows. Both
354
367
  // queries apply the same loop-safe consumer filter before the limit.
355
368
  const newest = input.order === 'newest';
356
- const scope = { ...(consumer ? { consumer } : {}), ...(limit ? { limit } : {}) };
369
+ if (input.addressedOnly && !consumer) throw new OperationError('addressedOnly requires consumer.', 400);
370
+ const scope = {
371
+ ...(consumer ? { consumer } : {}),
372
+ ...(limit ? { limit } : {}),
373
+ addressedOnly: input.addressedOnly,
374
+ offset: input.offset,
375
+ };
357
376
  const readPending = () =>
358
377
  newest ? canvasState.getPendingSteeringForContext(scope) : canvasState.getPendingSteering(scope);
359
378
  let pending = readPending();
@@ -495,6 +514,12 @@ const axReadsListOperation = defineOperation<z.infer<typeof axReadsListSchema>,
495
514
  });
496
515
 
497
516
  const axReadsRecordShape = {
517
+ deliveryId: z
518
+ .string()
519
+ .min(1)
520
+ .max(200)
521
+ .optional()
522
+ .describe('Idempotent adapter receipt ID, scoped to source, consumer and board.'),
498
523
  channel: z.enum(CONTEXT_READ_CHANNELS).describe('How the read reached the agent.'),
499
524
  resource: z.string().min(1).describe('Resource URI or operation name that was read.'),
500
525
  consumer: z.string().nullable().optional().describe('Reader identity (MCP client name, adapter host).'),
@@ -532,6 +557,7 @@ const axReadsRecordOperation = defineOperation<z.infer<typeof axReadsRecordSchem
532
557
  handler: (input) => {
533
558
  const read = canvasState.recordContextRead(
534
559
  {
560
+ deliveryId: input.deliveryId,
535
561
  channel: input.channel,
536
562
  resource: input.resource,
537
563
  source: input.source ?? 'api',
@@ -26,7 +26,8 @@ import { z } from 'zod';
26
26
  import { canvasState } from '../../canvas-state.js';
27
27
  import { serializeCanvasNode, serializeCanvasNodeCompact } from '../../canvas-serialization.js';
28
28
  import { addCanvasNode } from '../../canvas-operations.js';
29
- import { executeOperation, runWithSuppressedEmits } from '../registry.js';
29
+ import { currentActor } from '../../attribution.js';
30
+ import { executeOperation, runWithSuppressedEmits, validateOperationTarget } from '../registry.js';
30
31
  import { defineOperation, type Operation, type OperationContext } from '../types.js';
31
32
  import { readJsonValue } from '../http.js';
32
33
  import { isRecord } from './nodes.js';
@@ -57,6 +58,8 @@ const SUPPORTED_BATCH_OPS = new Set([
57
58
  'group.create',
58
59
  'group.add',
59
60
  'group.remove',
61
+ 'annotation.add',
62
+ 'annotation.remove',
60
63
  'pin.set',
61
64
  'pin.add',
62
65
  'pin.remove',
@@ -224,6 +227,7 @@ function shapeBatchEntry(op: string, result: unknown, creationReceipt = false):
224
227
  async function runBatch(operations: BatchEntry[], ctx: OperationContext): Promise<BatchEnvelope> {
225
228
  const refs: Record<string, unknown> = {};
226
229
  const results: Array<Record<string, unknown>> = [];
230
+ const actor = currentActor('api');
227
231
 
228
232
  for (let index = 0; index < operations.length; index++) {
229
233
  const operation = operations[index]!;
@@ -233,6 +237,7 @@ async function runBatch(operations: BatchEntry[], ctx: OperationContext): Promis
233
237
  }
234
238
  try {
235
239
  ctx.signal?.throwIfAborted();
240
+ validateOperationTarget(operation.op, args);
236
241
  if (ctx.onProgress) {
237
242
  const boardGeneration = canvasState.boardGeneration;
238
243
  await ctx.onProgress({ progress: index, total: operations.length, message: operation.op });
@@ -268,14 +273,19 @@ async function runBatch(operations: BatchEntry[], ctx: OperationContext): Promis
268
273
  // Batch churn is exempt from auto-ghosts (matches the skill's explicit
269
274
  // ghost-cursor exemption for batch operations). No `fromWorkbench`: the
270
275
  // browser never issues batches, so inner writes are agent writes and the
271
- // scope fence applies to each of them.
272
- const pending = executeOperation(dispatch.name, dispatch.args, {
273
- suppressAutoGhost: true,
274
- signal: ctx.signal,
275
- onProgress: ctx.onProgress
276
- ? (progress) => ctx.onProgress!({ progress: index, total: operations.length, message: progress.message })
277
- : undefined,
278
- });
276
+ // scope fence applies to each of them using the original writer identity.
277
+ const pending = executeOperation(
278
+ dispatch.name,
279
+ { agentId: actor.agentId, ...dispatch.args },
280
+ {
281
+ source: actor.source,
282
+ suppressAutoGhost: true,
283
+ signal: ctx.signal,
284
+ onProgress: ctx.onProgress
285
+ ? (progress) => ctx.onProgress!({ progress: index, total: operations.length, message: progress.message })
286
+ : undefined,
287
+ },
288
+ );
279
289
  // The first write may synchronously open a board from Home. Capture after
280
290
  // dispatch, then refuse to continue a partially completed batch elsewhere.
281
291
  const boardGeneration = canvasState.boardGeneration;
@@ -319,6 +329,7 @@ const batchShape = {
319
329
  }),
320
330
  )
321
331
  .describe('Ordered array of batch operations'),
332
+ agentId: z.string().optional().describe('Default writer identity for entries without their own agentId'),
322
333
  full: z
323
334
  .boolean()
324
335
  .optional()
@@ -404,13 +415,16 @@ const batchOperation = defineOperation<z.infer<typeof batchSchema>, BatchEnvelop
404
415
  : isRecord(body) && Array.isArray(body.operations)
405
416
  ? body.operations
406
417
  : [];
407
- return { operations };
418
+ return {
419
+ operations,
420
+ ...(isRecord(body) ? { agentId: body.agentId, targetBoardId: body.targetBoardId } : {}),
421
+ };
408
422
  },
409
423
  },
410
424
  mcp: {
411
425
  toolName: 'canvas_batch',
412
426
  description:
413
- 'Run a non-atomic batch of canvas operations with optional assigned references. Use assign to name a result, then reference it later as "$name" for the created node id or "$name.id" for a specific result field. On failure, earlier successful operations remain applied and the response includes ok:false, failedIndex, error, results, and refs. Supports node.add, node.update, node.remove, graph.add, edge.add, edge.remove, group.create, group.add, group.remove, pin.set/add/remove, snapshot.save, and arrange.',
427
+ 'Run a non-atomic batch of canvas operations with optional assigned references. Use assign to name a result, then reference it later as "$name" for the created node id or "$name.id" for a specific result field. On failure, earlier successful operations remain applied and the response includes ok:false, failedIndex, error, results, and refs. Supports node.add, node.update, node.remove, graph.add, edge.add, edge.remove, group.create, group.add, group.remove, annotation.add, annotation.remove, pin.set/add/remove, snapshot.save, and arrange.',
414
428
  formatResult: (result, input) => {
415
429
  const envelope = result as BatchEnvelope;
416
430
  const payload = wantsFullBatch(input) ? envelope : compactBatchResult(envelope);
@@ -17,6 +17,7 @@ import { normalizeBoardFolder } from '../../../shared/boards.js';
17
17
  import { relationVocabularySchema } from '../../../shared/relations.js';
18
18
  import { boardCatalogEpoch, canvasState, type CanvasLayout } from '../../canvas-state.js';
19
19
  import { openCanvasBoard } from '../../canvas-operations.js';
20
+ import { rewatchAllFileNodes } from '../../file-watcher.js';
20
21
  import { summarizeNodeForAgentContext } from '../../../shared/agent-context.js';
21
22
  import { BRIEF_SUMMARY_LENGTH } from '../../context-brief.js';
22
23
  import { memoryGraph, boardConnections } from '../../memory-graph.js';
@@ -66,8 +67,8 @@ export function boardsPayload(reloaded = false): Record<string, unknown> {
66
67
  catalogEpoch: boardCatalogEpoch,
67
68
  catalogRevision: revision,
68
69
  reloadRevision,
69
- boardGeneration: canvasState.boardGeneration,
70
- activeBoardId: canvasState.activeBoardId,
70
+ boardGeneration: canvasState.visibleBoardGeneration,
71
+ activeBoardId: canvasState.visibleBoardId,
71
72
  boards: boards.map((board) => {
72
73
  const read = canvasState.readBoard(board.id, true);
73
74
  const readme = read?.layout.nodes.find((node) => node.id === board.readmeNodeId && node.type === 'markdown');
@@ -216,7 +217,8 @@ const boardGetOperation = defineOperation<z.infer<typeof getSchema>, Record<stri
216
217
  const id = typeof input.id === 'string' && input.id.trim() ? input.id.trim() : canvasState.activeBoardId;
217
218
  const board = id ? canvasState.listBoards().find((entry) => entry.id === id) : undefined;
218
219
  if (!board) throw new OperationError(id ? `Board "${id}" not found.` : 'No board is open.', 404);
219
- return { ok: true, board, open: board.id === canvasState.activeBoardId };
220
+ const contentRevision = canvasState.readBoard(board.id, false)!.state.revisionState?.revision ?? 0;
221
+ return { ok: true, board, contentRevision, open: board.id === canvasState.activeBoardId };
220
222
  },
221
223
  });
222
224
 
@@ -404,6 +406,44 @@ const boardCreateFromOperation = defineOperation<z.infer<typeof fromSchema>, Rec
404
406
  },
405
407
  });
406
408
 
409
+ const moveShape = {
410
+ sourceBoardId: z.string().min(1),
411
+ nodeIds: z.array(z.string().min(1)).min(1),
412
+ name: z.string().trim().min(1).max(120),
413
+ expectedRevision: z.number().int().nonnegative(),
414
+ requestId: z.string().trim().min(1).max(200),
415
+ preview: z.boolean().optional(),
416
+ };
417
+ const moveSchema = z.object(moveShape);
418
+ const boardMoveOperation = defineOperation<z.infer<typeof moveSchema>, Record<string, unknown>>({
419
+ name: 'board.move-selection',
420
+ // Own validation precedes activation, and preview must never emit mutation/intent events.
421
+ mutates: false,
422
+ input: moveSchema,
423
+ inputShape: moveShape,
424
+ http: { method: 'POST', path: '/api/canvas/boards/move-selection' },
425
+ mcp: {
426
+ toolName: 'canvas_move_selection_to_board',
427
+ description:
428
+ 'Preview or atomically move selected cards/group descendants to a new board, preserving identities and leaving a portal. Requires explicit sourceBoardId, expectedRevision and a durable requestId. Retry with identical input; never opens the destination. Preview lists unsafe bindings and the Undo/snapshot boundary.',
429
+ formatResult: jsonResult,
430
+ },
431
+ handler: (input, ctx) => {
432
+ const outcome = canvasState.moveSelectionToNewBoard(input);
433
+ if ('preview' in outcome) return { ok: true, ...outcome };
434
+ if (!outcome.replayed) {
435
+ rewatchAllFileNodes();
436
+ emitBoardsChanged(ctx);
437
+ ctx.emit('context-pins-changed', {
438
+ count: canvasState.contextPinnedNodeIds.size,
439
+ nodeIds: [...canvasState.contextPinnedNodeIds],
440
+ });
441
+ ctx.emit('ax-state-changed', {});
442
+ }
443
+ return { ok: true, ...outcome.result, replayed: outcome.replayed };
444
+ },
445
+ });
446
+
407
447
  // ── board.pin / board.unpin ───────────────────────────────────
408
448
  // A pinned board joins the agent's working set: its README and pinned cards
409
449
  // travel in the brief whichever board is open. Pinning is context, never a
@@ -490,6 +530,7 @@ export const boardOperations: Operation[] = [
490
530
  boardGetOperation,
491
531
  boardCreateOperation,
492
532
  boardCreateFromOperation,
533
+ boardMoveOperation,
493
534
  boardUpdateOperation,
494
535
  boardPinOperation,
495
536
  boardUnpinOperation,
@@ -32,6 +32,7 @@ import {
32
32
  } from '../../canvas-operations.js';
33
33
  import { resolveCreateGeometry, resolvePatchGeometry, validateCanvasNodePatch } from '../../canvas-geometry.js';
34
34
  import { normalizeNodeAxCapabilities } from '../../ax-interaction.js';
35
+ import { isWorkCardState } from '../../../shared/ax-kinds.js';
35
36
  import { applyFileContentToNodeData, readFileNodeContent } from '../../file-content.js';
36
37
  import { unwatchFileForNode, watchFileForNode } from '../../file-watcher.js';
37
38
  import {
@@ -327,6 +328,21 @@ export function createdNodePayloadFromNode(
327
328
 
328
329
  // ── Operation cores (the SDK wraps these directly) ────────────
329
330
 
331
+ function validateWorkCardData(data: Record<string, unknown> | undefined): void {
332
+ if (!data || data.workState === undefined) return;
333
+ if (!isWorkCardState(data.workState))
334
+ throw new OperationError('workState must be todo, in-progress, blocked, done or cancelled.');
335
+ if (data.detail !== undefined && typeof data.detail !== 'string')
336
+ throw new OperationError('Work card detail must be a string.');
337
+ if (
338
+ data.progress !== undefined &&
339
+ data.progress !== null &&
340
+ (typeof data.progress !== 'number' || !Number.isFinite(data.progress) || data.progress < 0 || data.progress > 100)
341
+ ) {
342
+ throw new OperationError('Work card progress must be a number from 0 to 100, or null to clear it.');
343
+ }
344
+ }
345
+
330
346
  /**
331
347
  * Create a basic (non-webpage / non-group / non-primitive) node. Union of the
332
348
  * legacy handleCanvasAddNode generic branch; the SDK passes fileMode 'path',
@@ -338,6 +354,34 @@ export function createBasicCanvasNode(
338
354
  ): { node: CanvasNodeState; needsCodeGraphRecompute: boolean; sizeAdjustment?: CanvasSizeAdjustment } {
339
355
  const type = typeof body.type === 'string' ? body.type : '';
340
356
  const extraData = isRecord(body.data) ? body.data : undefined;
357
+ if (type === 'status' && extraData?.workItemId !== undefined) {
358
+ if (body.boardId !== undefined || options.prepareOnlyNodes)
359
+ throw new OperationError('Live work cards must be created on the active board.');
360
+ const owner = extraData.workItemBoardId ?? canvasState.activeBoardId;
361
+ if (
362
+ typeof extraData.workItemId !== 'string' ||
363
+ (owner !== null && typeof owner !== 'string') ||
364
+ !(owner === null ? canvasState.getWorkItems() : canvasState.getWorkItemsOnBoard(owner)).some(
365
+ (item) => item.id === extraData.workItemId,
366
+ )
367
+ )
368
+ throw new OperationError('Live work card requires an existing work item on its owning board.');
369
+ if (
370
+ ['title', 'content'].some((key) => body[key] !== undefined) ||
371
+ [
372
+ 'title',
373
+ 'content',
374
+ 'workState',
375
+ 'detail',
376
+ 'progress',
377
+ 'axWorkStatus',
378
+ 'liveWorkAvailable',
379
+ 'liveWorkSource',
380
+ ].some((key) => key in extraData)
381
+ )
382
+ throw new OperationError('Live work card fields are read-only; update the work item instead.');
383
+ }
384
+ if (type === 'status') validateWorkCardData(extraData);
341
385
  if (
342
386
  extraData &&
343
387
  ('attachmentId' in extraData || (isRecord(extraData.source) && 'attachmentId' in extraData.source))
@@ -415,6 +459,9 @@ export function createBasicCanvasNode(
415
459
  : {}),
416
460
  ...(typeof content === 'string' ? { content } : {}),
417
461
  ...(htmlMergedData && Object.keys(htmlMergedData).length > 0 ? { data: htmlMergedData } : {}),
462
+ ...(type === 'status' && typeof extraData?.workItemId === 'string'
463
+ ? { data: { ...extraData, workItemBoardId: extraData.workItemBoardId ?? canvasState.activeBoardId } }
464
+ : {}),
418
465
  ...(type === 'trace' && typeof body.toolName === 'string' ? { toolName: body.toolName } : {}),
419
466
  ...(type === 'trace' && typeof body.category === 'string' ? { category: body.category } : {}),
420
467
  ...(type === 'trace' && typeof body.status === 'string' ? { status: body.status } : {}),
@@ -446,6 +493,28 @@ export function buildNodePatch(
446
493
  body: Record<string, unknown>,
447
494
  ): { patch: Partial<CanvasNodeState>; groupChildIds?: string[] } {
448
495
  if (body.type !== undefined && body.type !== existing.type) throw new OperationError('Node type is immutable.');
496
+ if (existing.type === 'status') {
497
+ const data = isRecord(body.data) ? body.data : {};
498
+ if (
499
+ 'workItemId' in data ||
500
+ 'workItemBoardId' in data ||
501
+ (typeof existing.data.workItemId === 'string' &&
502
+ (body.title !== undefined ||
503
+ body.content !== undefined ||
504
+ [
505
+ 'title',
506
+ 'content',
507
+ 'workState',
508
+ 'detail',
509
+ 'progress',
510
+ 'axWorkStatus',
511
+ 'liveWorkAvailable',
512
+ 'liveWorkSource',
513
+ ].some((key) => key in data)))
514
+ ) {
515
+ throw new OperationError('Live work cards are read-only; create a presentation or update the work item instead.');
516
+ }
517
+ }
449
518
  if (
450
519
  existing.type === 'file' &&
451
520
  typeof existing.data.attachmentId === 'string' &&
@@ -561,6 +630,7 @@ export function buildNodePatch(
561
630
  }
562
631
  patch.data = existing.type === 'trace' ? mergeTraceNodeDataFields(data, body) : data;
563
632
  }
633
+ if (existing.type === 'status' && isRecord(patch.data)) validateWorkCardData(patch.data);
564
634
  const error = validateCanvasNodePatch({
565
635
  ...(patch.position ? { position: patch.position as { x: number; y: number } } : {}),
566
636
  ...(patch.size ? { size: patch.size as { width: number; height: number } } : {}),
@@ -284,6 +284,11 @@ const contextShape = {
284
284
  consumer: z.unknown().optional().describe('Stable consumer id used for the durable board-scoped cursor'),
285
285
  since: z.unknown().optional().describe('Explicit content revision; bypasses the durable consumer cursor'),
286
286
  budget: z.unknown().optional().describe('Maximum response size in UTF-16 code units'),
287
+ contextDelivery: z
288
+ .boolean()
289
+ .optional()
290
+ .describe('False prepares a brief without recording delivery or advancing a cursor.'),
291
+ includeSteering: z.boolean().optional().describe('False omits steering for hosts that deliver it separately.'),
287
292
  };
288
293
  const contextSchema = z.looseObject(contextShape);
289
294
 
@@ -292,8 +297,20 @@ const contextOperation = defineOperation<z.infer<typeof contextSchema>, Record<s
292
297
  mutates: false,
293
298
  input: contextSchema,
294
299
  inputShape: contextShape,
295
- http: { method: 'GET', path: '/api/canvas/context' },
300
+ http: {
301
+ method: 'GET',
302
+ path: '/api/canvas/context',
303
+ readInput: (_req, params, url) => {
304
+ const input: Record<string, unknown> = { ...Object.fromEntries(url.searchParams), ...params };
305
+ for (const key of ['contextDelivery', 'includeSteering']) {
306
+ if (input[key] === 'false') input[key] = false;
307
+ else if (input[key] === 'true') input[key] = true;
308
+ }
309
+ return input;
310
+ },
311
+ },
296
312
  handler: (input) => {
313
+ canvasState.refreshContextWorkCards();
297
314
  const active = canvasState.getActiveBoard();
298
315
  if (!active) throw new OperationError('Open a board before requesting its context.', 409);
299
316
  const consumer =
@@ -507,7 +524,10 @@ const contextOperation = defineOperation<z.infer<typeof contextSchema>, Record<s
507
524
  })),
508
525
  );
509
526
  entries.push(
510
- ...canvasState.getPendingSteering({ consumer: consumer ?? undefined, limit: 50 }).map((item) => ({
527
+ ...(input.includeSteering === false
528
+ ? []
529
+ : canvasState.getPendingSteering({ consumer: consumer ?? undefined, limit: 50 })
530
+ ).map((item) => ({
511
531
  sourceBoardId: active.id,
512
532
  nodeId: `steering:${item.id}`,
513
533
  reason: 'steer' as const,
@@ -553,7 +573,14 @@ const contextOperation = defineOperation<z.infer<typeof contextSchema>, Record<s
553
573
  entries,
554
574
  budget,
555
575
  });
556
- if (consumer && !explicitSince && result.document && result.nextCursor !== null && !result.invalidCursor) {
576
+ if (
577
+ input.contextDelivery !== false &&
578
+ consumer &&
579
+ !explicitSince &&
580
+ result.document &&
581
+ result.nextCursor !== null &&
582
+ !result.invalidCursor
583
+ ) {
557
584
  canvasState.advanceContextBriefCursor(active.id, consumer, result.nextCursor);
558
585
  }
559
586
  return (result.document ?? {
@@ -634,6 +661,7 @@ const undoOperation = defineOperation<z.infer<typeof undoRedoSchema>, Record<str
634
661
  count: canvasState.contextPinnedNodeIds.size,
635
662
  nodeIds: Array.from(canvasState.contextPinnedNodeIds),
636
663
  });
664
+ ctx.emit('ax-state-changed', {});
637
665
  return { ok: true, description: `Undid: ${entry.description}` };
638
666
  },
639
667
  });
@@ -662,6 +690,7 @@ const redoOperation = defineOperation<z.infer<typeof undoRedoSchema>, Record<str
662
690
  count: canvasState.contextPinnedNodeIds.size,
663
691
  nodeIds: Array.from(canvasState.contextPinnedNodeIds),
664
692
  });
693
+ ctx.emit('ax-state-changed', {});
665
694
  return { ok: true, description: `Redid: ${entry.description}` };
666
695
  },
667
696
  });
@@ -364,6 +364,7 @@ const snapshotRestoreNodeOperation = defineOperation<
364
364
  handler: ({ id, nodeId, preserveHumanEdits }) => {
365
365
  const snapshot = canvasState.getSnapshotData(id ?? '');
366
366
  if (!snapshot) throw new OperationError('Snapshot not found', 404);
367
+ canvasState.assertSnapshotRestorable(id ?? '');
367
368
  const current = canvasState.getNode(nodeId ?? '');
368
369
  const earlier = snapshot.nodes.find((node) => node.id === nodeId);
369
370
  if (!current || !earlier)
@@ -359,6 +359,7 @@ function notePresence(
359
359
  ): void {
360
360
  if (!isPresenceWrite(op) || meta.suppressAutoGhost) return;
361
361
  const input = asRecord(rawInput);
362
+ if (name === 'board.move-selection' && (input.preview === true || asRecord(result).replayed === true)) return;
362
363
  // Delivery plumbing (claim/mark) must not book an anonymous transport
363
364
  // writer nor spam the timeline: attribute it to the consumer that marked,
364
365
  // and record no activity row — the Steer row flipping to delivered IS the
@@ -388,11 +389,52 @@ function notePresence(
388
389
  }
389
390
  }
390
391
 
392
+ const operationTarget = new AsyncLocalStorage<{ boardId: string; agentId?: string }>();
393
+
394
+ /** Also used by batch's internal node path, which does not dispatch an operation. */
395
+ export function validateOperationTarget(name: string, input: Record<string, unknown>): string | undefined {
396
+ const inherited = operationTarget.getStore();
397
+ const target = input.targetBoardId === undefined ? inherited?.boardId : input.targetBoardId;
398
+ if (target === undefined) return undefined;
399
+ if (typeof target !== 'string' || !target.trim())
400
+ throw new OperationError('targetBoardId must be an existing board ID.');
401
+ if (
402
+ inherited &&
403
+ (target !== inherited.boardId || (input.agentId !== undefined && input.agentId !== inherited.agentId))
404
+ )
405
+ throw new OperationError('Nested operations must retain their board and agent identity.', 409);
406
+ if (name === 'board.update' && input.id !== target)
407
+ throw new OperationError('Board id conflicts with targetBoardId. Rebind before editing another board.', 409);
408
+ for (const key of [
409
+ 'board',
410
+ 'expectedBoardId',
411
+ 'destinationBoardId',
412
+ ...(name === 'board.move-selection' ? ['sourceBoardId'] : []),
413
+ ]) {
414
+ if (input[key] !== undefined && input[key] !== target)
415
+ throw new OperationError(`${key} conflicts with targetBoardId. Rebind before targeting another board.`, 409);
416
+ }
417
+ if (['board.open', 'board.delete', 'backup.restore'].includes(name) || name.startsWith('webview.'))
418
+ throw new OperationError('This operation controls the shared view or workspace, not a scoped board.', 409);
419
+ return target;
420
+ }
421
+
391
422
  export async function executeOperation(
392
423
  name: string,
393
424
  rawInput: unknown,
394
425
  meta: ExecuteOperationMeta = {},
395
426
  ): Promise<unknown> {
427
+ const input = asRecord(rawInput);
428
+ const target = validateOperationTarget(name, input);
429
+ if (target === undefined) return executeOnCurrentBoard(name, rawInput, meta);
430
+ const agentId =
431
+ operationTarget.getStore()?.agentId ?? (typeof input.agentId === 'string' ? input.agentId : undefined);
432
+ return operationTarget.run({ boardId: target, agentId }, () =>
433
+ canvasState.withBoard(target, () => executeOnCurrentBoard(name, { ...input, agentId }, meta)),
434
+ );
435
+ }
436
+
437
+ async function executeOnCurrentBoard(name: string, rawInput: unknown, meta: ExecuteOperationMeta): Promise<unknown> {
396
438
  // Check before attribution, board auto-open, ghosts, or any handler writes.
397
439
  meta.signal?.throwIfAborted();
398
440
  // Attribution follows this call chain across awaits and nested operations.
@@ -406,7 +448,8 @@ export async function executeOperation(
406
448
  async () => {
407
449
  const input = asRecord(rawInput);
408
450
  const deliversContext =
409
- name !== 'node.get' || (input.contextDelivery !== false && input.contextDelivery !== 'false');
451
+ !['node.get', 'context.get'].includes(name) ||
452
+ (input.contextDelivery !== false && input.contextDelivery !== 'false');
410
453
  const recordRead = CONTEXT_READ_OPS.has(name) && deliversContext && !meta.fromWorkbench && !meta.proxiedRead;
411
454
  const requestedBoard =
412
455
  recordRead && 'board' in getOperation(name).inputShape && isString(input.board) && input.board.trim()
@@ -510,7 +553,8 @@ async function executeOperationInner(name: string, rawInput: unknown, meta: Exec
510
553
  const rawExpectedBoard = asRecord(rawInput).expectedBoardId;
511
554
  // HTTP DELETE query values are strings, including Home's JSON null.
512
555
  const expectedBoard = rawExpectedBoard === 'null' ? null : rawExpectedBoard;
513
- if (expectedBoard !== undefined && expectedBoard !== canvasState.activeBoardId) {
556
+ // Relocations check their durable receipt first, then their explicit source board.
557
+ if (name !== 'board.move-selection' && expectedBoard !== undefined && expectedBoard !== canvasState.activeBoardId) {
514
558
  throw new OperationError('The active board changed. Refresh before retrying this action.', 409);
515
559
  }
516
560
  // File drops carry their captured board through the HTTP round trip. Check
@@ -638,8 +682,9 @@ async function executeOperationInner(name: string, rawInput: unknown, meta: Exec
638
682
  }
639
683
 
640
684
  const beforeBoard = canvasState.activeBoardId;
641
- const beforeRevision =
642
- name === 'board.create-from' || name === 'board.delete' ? canvasState.getContentRevision().revision : null;
685
+ const beforeRevision = ['board.create-from', 'board.delete', 'board.move-selection'].includes(name)
686
+ ? canvasState.getContentRevision().revision
687
+ : null;
643
688
  meta.signal?.throwIfAborted();
644
689
  const result = await op.execute(rawInput, operationContext);
645
690
  // Library writes can invalidate relations on the open board without being