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
@@ -849,6 +849,31 @@ body,
849
849
  color: var(--c-text-soft);
850
850
  }
851
851
 
852
+ /* In-card editing is view state, never a persisted resize. */
853
+ .canvas-node:has(.work-card-editor) { height: auto !important; }
854
+ .canvas-node:has(.work-card-editor) .node-body { flex: none; overflow: visible; }
855
+ .work-card { display: flex; flex-direction: column; gap: 12px; }
856
+ .work-card-state { color: var(--c-text); font-weight: 600; }
857
+ .work-card-blocked .work-card-state, .work-card-state.work-card-blocked, .work-card-error { color: var(--c-warn); }
858
+ .work-card-done .work-card-state, .work-card-state.work-card-done { color: var(--c-ok); }
859
+ .work-card-cancelled .work-card-state, .work-card-state.work-card-cancelled { color: var(--c-muted); }
860
+ .work-card-detail { white-space: pre-wrap; overflow-wrap: anywhere; }
861
+ .work-card-live { color: var(--c-muted); font-size: var(--fs-meta); overflow-wrap: anywhere; }
862
+ .work-card-progress { color: var(--c-muted); font-size: var(--fs-meta); }
863
+ .work-card footer, .work-card-editor footer { display: flex; justify-content: flex-end; gap: 8px; margin-top: 8px; }
864
+ .work-card button, .work-card-editor button, .work-card-editor input, .work-card-editor select, .work-card-editor textarea {
865
+ font: inherit; color: var(--c-text); background: var(--c-input-bg); border: 1px solid var(--c-line); border-radius: var(--r-control); padding: 8px;
866
+ }
867
+ .work-card button, .work-card-editor button { cursor: pointer; }
868
+ .work-card button:hover, .work-card-editor button:hover { border-color: var(--c-text-soft); }
869
+ .work-card-editor fieldset { border: 0; margin: 0; padding: 0; min-width: 0; }
870
+ .work-card-editor label { display: flex; flex-direction: column; gap: 4px; margin-bottom: 12px; }
871
+ .work-card-editor input, .work-card-editor select, .work-card-editor textarea { width: 100%; box-sizing: border-box; }
872
+ .work-card-editor textarea { min-height: 80px; resize: vertical; }
873
+ .work-card-editor .work-card-save { background: var(--c-accent); color: var(--c-contrast-fg); }
874
+ .work-card-editor :disabled { cursor: wait; opacity: 0.65; }
875
+ .work-card-editor :focus-visible, .work-card button:focus-visible { outline: 2px solid var(--c-accent); outline-offset: 2px; }
876
+
852
877
  /* Markdown rendered content inside nodes */
853
878
  .canvas-node .node-body h1,
854
879
  .canvas-node .node-body h2,
@@ -1297,6 +1322,18 @@ body,
1297
1322
  border-color: var(--c-line);
1298
1323
  }
1299
1324
 
1325
+ .human-name-control { order: 4; flex-shrink: 0; }
1326
+ .human-name-button {
1327
+ width: 28px;
1328
+ height: 28px;
1329
+ padding: 0;
1330
+ border-color: var(--c-line);
1331
+ border-radius: var(--r-pill);
1332
+ background: var(--c-panel-soft);
1333
+ color: var(--c-text-soft);
1334
+ font: 600 12px var(--font-ui);
1335
+ }
1336
+
1300
1337
  .top-bar-zoom-label {
1301
1338
  min-width: 38px;
1302
1339
  text-align: center;
@@ -1852,6 +1889,19 @@ body,
1852
1889
  flex: 1;
1853
1890
  min-width: 0;
1854
1891
  }
1892
+ .session-show-work {
1893
+ margin-top: 8px;
1894
+ padding: 4px 8px;
1895
+ font: inherit;
1896
+ color: var(--c-text);
1897
+ background: var(--c-input-bg);
1898
+ border: 1px solid var(--c-line);
1899
+ border-radius: var(--r-control);
1900
+ cursor: pointer;
1901
+ }
1902
+ .session-show-work:hover { border-color: var(--c-text-soft); }
1903
+ .session-show-work:focus-visible { outline: 2px solid var(--c-accent); outline-offset: 2px; }
1904
+ .session-show-work:disabled { cursor: wait; opacity: 0.65; }
1855
1905
  .session-item-title {
1856
1906
  display: block;
1857
1907
  width: 100%;
@@ -4772,7 +4822,7 @@ html.is-node-resizing .ext-app-preview-catcher {
4772
4822
  background: var(--c-input-bg);
4773
4823
  color: var(--c-text);
4774
4824
  }
4775
- .relation-legend { position: absolute; top: 8px; left: 12px; right: 12px; display: flex; flex-wrap: wrap; align-content: flex-start; gap: 6px; max-height: min(180px, 25%); overflow-x: hidden; overflow-y: auto; }
4825
+ .relation-legend { position: absolute; top: 8px; left: 12px; right: 12px; display: flex; flex-wrap: wrap; align-content: flex-start; gap: 6px; max-height: min(180px, 25%); overflow-x: hidden; overflow-y: auto; pointer-events: none; }
4776
4826
  .lifecycle-filters ~ .relation-legend { top: 48px; }
4777
4827
  .relation-legend > * { pointer-events: auto; flex-shrink: 0; max-width: 100%; height: 24px; box-sizing: border-box; border: 1px solid var(--c-line); border-radius: var(--r-control); background: var(--c-panel); color: var(--c-text); font: 500 11px var(--font-ui); padding: 0 9px; cursor: pointer; }
4778
4828
  .relation-legend select { background: var(--c-panel-soft); font-weight: 600; padding: 0 10px; }
@@ -4780,6 +4830,31 @@ html.is-node-resizing .ext-app-preview-catcher {
4780
4830
  .relation-legend button[aria-pressed="true"] { border-color: var(--relation-ink); }
4781
4831
  .relation-swatch { width: 14px; height: 2.5px; border-radius: 2px; background: var(--relation-ink); }
4782
4832
  .relation-count { color: var(--c-muted); font-variant-numeric: tabular-nums; }
4833
+ .relation-legend > span { display: flex; align-items: center; cursor: default; }
4834
+ .relation-legend .relation-warning { color: var(--c-warn); }
4835
+ .edge-editor { position: fixed; z-index: 1000; box-sizing: border-box; padding: 3px; border: 1px solid var(--c-line); border-radius: 8px; background: var(--c-panel); color: var(--c-text); box-shadow: var(--e-1); font: 500 11px var(--font-ui); }
4836
+ .edge-editor fieldset { border: 0; padding: 0; margin: 0; min-width: 0; }
4837
+ .edge-editor-tools { display: flex; gap: 3px; }
4838
+ .edge-editor-tools select { flex: 1; min-width: 0; }
4839
+ .edge-editor button, .edge-editor select, .relation-navigation button, .group-breadcrumbs button { min-height: 24px; border: 1px solid var(--c-line); border-radius: var(--r-control); padding: 3px 7px; background: var(--c-panel-soft); color: var(--c-text); font: inherit; cursor: pointer; }
4840
+ .edge-editor button:hover, .group-breadcrumbs button:hover, .relation-navigation button:hover { background: var(--c-hover); }
4841
+ .edge-editor-field { display: grid; gap: 8px; padding: 8px; }
4842
+ .edge-editor-field label { display: grid; gap: 4px; }
4843
+ .edge-editor input { box-sizing: border-box; width: 100%; min-width: 0; padding: 6px; border: 1px solid var(--c-line); border-radius: var(--r-control); color: var(--c-text); background: var(--c-input-bg); font: inherit; }
4844
+ .edge-editor p { margin: 6px; color: var(--c-warn); }
4845
+ .relation-navigation { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 12px; }
4846
+ .group-breadcrumbs { position: absolute; top: 8px; left: 12px; right: 12px; display: flex; flex-wrap: wrap; align-items: center; gap: 4px; max-height: 64px; overflow: auto; pointer-events: auto; color: var(--c-muted); font: 500 11px var(--font-ui); }
4847
+ .group-breadcrumbs button[aria-current] { color: var(--c-text); cursor: default; }
4848
+ .group-breadcrumbs > button:last-child { margin-left: auto; }
4849
+ .canvas-region:has(.group-breadcrumbs) .relation-legend { top: 80px; }
4850
+ .canvas-region:has(.group-breadcrumbs) .lifecycle-filters { top: 80px; }
4851
+ .canvas-region:has(.group-breadcrumbs) .lifecycle-filters ~ .relation-legend { top: 120px; }
4852
+ .group-inside-frame { position: absolute; border: 1px dashed var(--c-line); border-radius: 12px; pointer-events: none; box-sizing: border-box; z-index: -6; }
4853
+ .group-inside-frame > span { position: absolute; top: -9px; left: 12px; padding: 0 6px; background: var(--c-bg); color: var(--c-muted); font: 500 12px var(--font-ui); }
4854
+ .group-outside-endpoint { position: absolute; width: 170px; height: 60px; z-index: 2; }
4855
+ .group-outside-label { position: absolute; top: -24px; color: var(--c-muted); font: 500 12px var(--font-ui); }
4856
+ .group-outside-stub { width: 100%; height: 100%; box-sizing: border-box; padding: 8px 12px; border: 1px dashed var(--c-line); border-radius: 10px; text-align: left; background: var(--c-panel); color: var(--c-text); font: 600 12px var(--font-ui); cursor: pointer; overflow: hidden; }
4857
+ .group-outside-stub span { display: block; margin-top: 4px; color: var(--c-muted); font: 11px var(--font-ui); }
4783
4858
  .expanded-content-row { display: flex; flex: 1; min-height: 0; }
4784
4859
  .expanded-content-row > .expanded-body { flex: 1; min-width: 0; }
4785
4860
  .relations-panel {
@@ -5294,6 +5369,11 @@ html.is-node-resizing .ext-app-preview-catcher {
5294
5369
  font-weight: 600;
5295
5370
  color: var(--c-text);
5296
5371
  }
5372
+ .text-prompt-description {
5373
+ font-size: 12px;
5374
+ line-height: 1.4;
5375
+ color: var(--c-muted);
5376
+ }
5297
5377
  .text-prompt-input {
5298
5378
  width: 100%;
5299
5379
  padding: 8px 10px;
@@ -8830,6 +8910,19 @@ html.is-node-dragging .intent-line-layer {
8830
8910
  cursor: pointer;
8831
8911
  }
8832
8912
  }
8913
+ .board-move-backdrop { padding: 20px 0; align-items: center; }
8914
+ .board-move-dialog {
8915
+ width: min(540px, calc(100% - 32px));
8916
+ max-height: calc(100% - 40px);
8917
+ overflow-y: auto;
8918
+ box-sizing: border-box;
8919
+ }
8920
+ .board-move-dialog form { display: flex; flex-direction: column; gap: 8px; }
8921
+ .board-move-dialog h2 { margin: 0 0 12px; font-size: 17px; }
8922
+ .board-move-dialog p { margin: 0; font-size: 12px; line-height: 1.5; color: var(--c-text-soft); }
8923
+ .board-move-dialog .board-move-warning { color: var(--c-warn); font-size: 12px; line-height: 1.5; overflow-wrap: anywhere; }
8924
+ .board-move-cards { margin: 0; max-height: 100px; overflow-y: auto; font-size: 12px; }
8925
+ .board-move-dialog .text-prompt-actions { margin-top: 8px; }
8833
8926
  .board-copy-dialog,
8834
8927
  .board-link-dialog {
8835
8928
  width: min(540px, calc(100vw - 32px));
@@ -115,6 +115,7 @@ export const TYPE_LABELS: Record<CanvasNodeState['type'], string> = {
115
115
  /** Node types that support the full-viewport expand/focus overlay. */
116
116
  export const EXPANDABLE_TYPES = new Set<CanvasNodeState['type']>([
117
117
  'markdown',
118
+ 'status',
118
119
  'mcp-app',
119
120
  'webpage',
120
121
  'json-render',
package/src/mcp/server.ts CHANGED
@@ -200,6 +200,8 @@ export function createMcpServer(): McpServer {
200
200
  annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
201
201
  outputSchema: interactionResult,
202
202
  inputSchema: {
203
+ targetBoardId: z.string().optional(),
204
+ agentId: z.string().optional(),
203
205
  type: z
204
206
  .enum(AX_INTERACTION_TYPES)
205
207
  .describe('Interaction type, e.g. ax.work.create, ax.evidence.add, ax.focus.set.'),
@@ -213,13 +215,15 @@ export function createMcpServer(): McpServer {
213
215
  source: z.enum(AX_SOURCES).optional().describe('Optional host/source label. Defaults to mcp.'),
214
216
  },
215
217
  },
216
- async ({ type, sourceNodeId, payload, sourceSurface, correlationId, source }, extra) => {
218
+ async ({ type, sourceNodeId, payload, sourceSurface, correlationId, source, targetBoardId, agentId }, extra) => {
217
219
  try {
218
220
  const c = await ensureCanvas();
219
221
  extra.signal.throwIfAborted();
220
222
  const result = await c.invoker().invoke(
221
223
  'ax.interaction.submit',
222
224
  {
225
+ targetBoardId,
226
+ agentId,
223
227
  type,
224
228
  sourceNodeId,
225
229
  ...(payload ? { payload } : {}),
@@ -269,6 +273,8 @@ export function createMcpServer(): McpServer {
269
273
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
270
274
  outputSchema: activityResult,
271
275
  inputSchema: {
276
+ targetBoardId: z.string().optional(),
277
+ agentId: z.string().optional(),
272
278
  kind: z.enum([
273
279
  'tool-start',
274
280
  'tool-result',
@@ -327,13 +333,15 @@ export function createMcpServer(): McpServer {
327
333
  source: z.enum(AX_SOURCES).optional(),
328
334
  },
329
335
  },
330
- async ({ kind, title, summary, outcome, ref, nodeIds, data, reactions, source }, extra) => {
336
+ async ({ kind, title, summary, outcome, ref, nodeIds, data, reactions, source, targetBoardId, agentId }, extra) => {
331
337
  try {
332
338
  const c = await ensureCanvas();
333
339
  extra.signal.throwIfAborted();
334
340
  const result = await c.invoker().invoke(
335
341
  'ax.activity.ingest',
336
342
  {
343
+ targetBoardId,
344
+ agentId,
337
345
  kind,
338
346
  title,
339
347
  ...(summary !== undefined ? { summary } : {}),
@@ -389,6 +397,7 @@ export function createMcpServer(): McpServer {
389
397
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
390
398
  outputSchema: screenshotResult,
391
399
  inputSchema: {
400
+ targetBoardId: z.string().optional().describe('Not supported: screenshots inspect the shared WebView.'),
392
401
  format: z
393
402
  .enum(['png', 'jpeg', 'webp'])
394
403
  .optional()
@@ -396,8 +405,9 @@ export function createMcpServer(): McpServer {
396
405
  quality: z.number().optional().describe('Optional quality for lossy formats'),
397
406
  },
398
407
  },
399
- async ({ format, quality }, extra) => {
408
+ async ({ format, quality, targetBoardId }, extra) => {
400
409
  try {
410
+ if (targetBoardId !== undefined) throw new Error('Screenshots inspect the shared WebView, not a scoped board.');
401
411
  const c = await ensureCanvas();
402
412
  extra.signal.throwIfAborted();
403
413
  const bytes = await c.screenshotAutomationWebView(
@@ -203,6 +203,8 @@ export const PRESENCE_SET_SHAPE = {
203
203
  const presenceSetSchema = z.object(PRESENCE_SET_SHAPE);
204
204
 
205
205
  interface StoredPresence extends AgentPresence {
206
+ spatialBoardId: string | null;
207
+ sessionBoardId: string | null;
206
208
  lastSeenMs: number;
207
209
  /** Last explicit phase report; cursor/focus heartbeats must not keep a stale phase alive. */
208
210
  phaseSetMs: number;
@@ -296,7 +298,7 @@ type SessionEndListener = (
296
298
  export class AgentPresenceRegistry {
297
299
  private readonly presences = new Map<string, StoredPresence>();
298
300
  /** Newest first; bounded. Survives a writer fading — the feed is history, the writer list is presence. */
299
- private activity: AgentActivityEntry[] = [];
301
+ private activity: Array<AgentActivityEntry & { boardId: string | null }> = [];
300
302
  private activitySeq = 0;
301
303
  private emit: PresenceEmitter = () => {};
302
304
  /** Single slots (like the emitter): server.ts owns the pre-session snapshot + receipt. */
@@ -435,6 +437,8 @@ export class AgentPresenceRegistry {
435
437
  }
436
438
  const existing = this.presences.get(key);
437
439
  const stored: StoredPresence = existing ?? {
440
+ spatialBoardId: canvasState.activeBoardId,
441
+ sessionBoardId: canvasState.activeBoardId,
438
442
  sessionId: key,
439
443
  source: input.source,
440
444
  agentId: input.agentId?.trim() || null,
@@ -458,6 +462,9 @@ export class AgentPresenceRegistry {
458
462
  touchesCache: null,
459
463
  };
460
464
  const wasAttached = stored.attached;
465
+ if (input.op || input.cursor !== undefined || input.focusNodeId !== undefined)
466
+ stored.spatialBoardId = canvasState.activeBoardId;
467
+ if (stored.attached && stored.sessionBoardId === null) stored.sessionBoardId = canvasState.activeBoardId;
461
468
  stored.lastSeenMs = now;
462
469
  stored.lastSeenAt = new Date(now).toISOString();
463
470
  if (input.label) stored.label = input.label;
@@ -466,6 +473,7 @@ export class AgentPresenceRegistry {
466
473
  if (input.activity?.summary) {
467
474
  this.activitySeq += 1;
468
475
  this.activity.unshift({
476
+ boardId: canvasState.activeBoardId,
469
477
  id: `act-${this.activitySeq}`,
470
478
  at: stored.lastSeenAt,
471
479
  sessionId: key,
@@ -480,6 +488,7 @@ export class AgentPresenceRegistry {
480
488
  }
481
489
  if (input.attached !== undefined) stored.attached = input.attached;
482
490
  if (!wasAttached && input.attached === true) {
491
+ stored.sessionBoardId = canvasState.activeBoardId;
483
492
  // *Start agent session* with one agent-less writer already on the board
484
493
  // adopts that writer — the human is claiming the agent they can see.
485
494
  if (stored.source === 'browser') {
@@ -539,13 +548,7 @@ export class AgentPresenceRegistry {
539
548
  captureBaseline(stored.startBoards, stored.startedAt);
540
549
  }
541
550
  if (input.attached === false) {
542
- if (wasAttached)
543
- this.onSessionEnd(
544
- this.publicView(stored, now),
545
- stored.startSnapshotId,
546
- input.endedBy ?? 'agent',
547
- stored.startedAt,
548
- );
551
+ if (wasAttached) this.endSession(stored, input.endedBy ?? 'agent', now);
549
552
  // An explicit detach always removes the presence. In particular, a
550
553
  // worker that reports attached:false must not be refreshed into another
551
554
  // full activity TTL as an external writer.
@@ -690,13 +693,21 @@ export class AgentPresenceRegistry {
690
693
  }
691
694
 
692
695
  /** Remove a writer (session-end). */
696
+ private endSession(stored: StoredPresence, endedBy: 'human' | 'agent' | 'idle-timeout', now: number): void {
697
+ const id = stored.sessionBoardId;
698
+ if (id && !canvasState.listBoards().some((board) => board.id === id)) return;
699
+ const finish = () =>
700
+ this.onSessionEnd(this.publicView(stored, now), stored.startSnapshotId, endedBy, stored.startedAt);
701
+ if (id) canvasState.withBoard(id, finish);
702
+ else canvasState.withVisibleBoard(finish);
703
+ }
704
+
693
705
  detach(sessionId: string): boolean {
694
706
  const stored = this.presences.get(sessionId);
695
707
  const removed = this.presences.delete(sessionId);
696
708
  if (removed) {
697
709
  this.dropAliasesTo(sessionId);
698
- if (stored?.attached)
699
- this.onSessionEnd(this.publicView(stored, Date.now()), stored.startSnapshotId, 'agent', stored.startedAt);
710
+ if (stored?.attached) this.endSession(stored, 'agent', Date.now());
700
711
  this.scheduleEmit();
701
712
  }
702
713
  this.maybeStopSweeper();
@@ -709,7 +720,10 @@ export class AgentPresenceRegistry {
709
720
  return {
710
721
  presences,
711
722
  sessionActive: isSessionActive(presences),
712
- activity: this.activity.map((entry) => ({ ...entry })),
723
+ activity: this.activity.map(({ boardId, ...entry }) => ({
724
+ ...entry,
725
+ nodeId: boardId === canvasState.activeBoardId ? entry.nodeId : null,
726
+ })),
713
727
  };
714
728
  }
715
729
 
@@ -744,6 +758,8 @@ export class AgentPresenceRegistry {
744
758
  startBoards: _startBoards,
745
759
  touchesCache: _touchesCache,
746
760
  lastActivityNodeId,
761
+ spatialBoardId,
762
+ sessionBoardId: _sessionBoardId,
747
763
  ...presence
748
764
  } = stored;
749
765
  if (withTouches) {
@@ -764,11 +780,15 @@ export class AgentPresenceRegistry {
764
780
  // Park-at fallback (joint-gaps #3): an attached-but-focusless writer is
765
781
  // invisible everywhere — fall back to the last node its activity touched.
766
782
  if (!presence.focusNodeId && lastActivityNodeId) presence.focusNodeId = lastActivityNodeId;
783
+ if (spatialBoardId !== canvasState.activeBoardId) {
784
+ presence.focusNodeId = null;
785
+ presence.cursor = null;
786
+ }
767
787
  // An attached session blocked on a human decision reads as waiting — the
768
788
  // gate itself lives in the AX state, this is only the presence view of it.
769
789
  // It overrides tooling too: the gate REQUEST is itself a write, and the
770
790
  // human's decision is the state that matters while it is pending.
771
- if (presence.attached && this.hasPendingGate()) {
791
+ if (presence.attached && spatialBoardId === canvasState.activeBoardId && this.hasPendingGate()) {
772
792
  phase = 'waiting-approval';
773
793
  detail = null;
774
794
  }
@@ -824,13 +844,7 @@ export class AgentPresenceRegistry {
824
844
  if (presence.lastSeenMs + ttl <= now) {
825
845
  this.presences.delete(key);
826
846
  this.dropAliasesTo(key);
827
- if (presence.attached)
828
- this.onSessionEnd(
829
- this.publicView(presence, now),
830
- presence.startSnapshotId,
831
- 'idle-timeout',
832
- presence.startedAt,
833
- );
847
+ if (presence.attached) this.endSession(presence, 'idle-timeout', now);
834
848
  changed = true;
835
849
  continue;
836
850
  }
@@ -866,14 +880,16 @@ export class AgentPresenceRegistry {
866
880
  if (this.emitTimer) return;
867
881
  this.emitTimer = setTimeout(() => {
868
882
  this.emitTimer = null;
869
- this.emit('agent-presence', this.snapshot() as unknown as Record<string, unknown>);
883
+ canvasState.withVisibleBoard(() =>
884
+ this.emit('agent-presence', this.snapshot() as unknown as Record<string, unknown>),
885
+ );
870
886
  }, 50);
871
887
  (this.emitTimer as { unref?: () => void }).unref?.();
872
888
  }
873
889
 
874
890
  private ensureSweeper(): void {
875
891
  if (this.sweepTimer || this.presences.size === 0) return;
876
- this.sweepTimer = setInterval(() => this.sweep(), 1000);
892
+ this.sweepTimer = setInterval(() => canvasState.withVisibleBoard(() => this.sweep()), 1000);
877
893
  (this.sweepTimer as { unref?: () => void }).unref?.();
878
894
  }
879
895
 
@@ -894,10 +910,14 @@ export const agentPresence = new AgentPresenceRegistry();
894
910
  // the operation registry are covered too.
895
911
  // The live lens also follows card content (a person's edit takes a card out of
896
912
  // the agent's session) and the open board — but not moves, so a drag stays quiet.
897
- let lastBoardId = canvasState.activeBoardId;
913
+ let lastBoardId = canvasState.visibleBoardId;
898
914
  let lastRevision = canvasState.getContentRevision().revision;
899
915
  canvasState.onChange((type) => {
900
- const boardId = canvasState.activeBoardId;
916
+ if (canvasState.isBackgroundBoard) {
917
+ agentPresence.refresh();
918
+ return;
919
+ }
920
+ const boardId = canvasState.visibleBoardId;
901
921
  const revision = canvasState.getContentRevision().revision;
902
922
  if (boardId !== lastBoardId) agentPresence.boardOpened();
903
923
  else if (type === 'ax' || type === 'pins' || revision !== lastRevision) agentPresence.refresh();
@@ -111,4 +111,10 @@ export function advanceAxFlowLoops(): void {
111
111
  canvasState.setWorkItemsChangedListener(() => {
112
112
  refreshWorkboardNodes();
113
113
  advanceAxFlowLoops();
114
+ // Task title/detail changes also repaint explicitly presented live cards.
115
+ if (
116
+ canvasState.getLayout().nodes.some((node) => node.type === 'status' && typeof node.data.workItemId === 'string')
117
+ ) {
118
+ emitCanvasLayoutUpdate();
119
+ }
114
120
  });
@@ -11,6 +11,7 @@
11
11
  */
12
12
  import { formatCountdown } from '../shared/approval-gates.js';
13
13
  import { canvasState } from './canvas-state.js';
14
+ import { withCurrentActor } from './attribution.js';
14
15
 
15
16
  type GateEmitter = (event: string, payload: Record<string, unknown>) => void;
16
17
 
@@ -50,7 +51,13 @@ export function sweepExpiredGates(now = Date.now()): string[] {
50
51
  export function startGateTtlSweeper(emitter: GateEmitter): void {
51
52
  emit = emitter;
52
53
  if (sweepTimer) return;
53
- sweepTimer = setInterval(() => sweepExpiredGates(), 1000);
54
+ sweepTimer = setInterval(
55
+ () =>
56
+ withCurrentActor({ actor: 'system', source: 'system' }, () => {
57
+ canvasState.forEachLiveBoard(() => sweepExpiredGates());
58
+ }),
59
+ 1000,
60
+ );
54
61
  (sweepTimer as { unref?: () => void }).unref?.();
55
62
  }
56
63
 
@@ -379,6 +379,7 @@ const PAYLOAD_SCHEMAS: Record<string, z.ZodType> = {
379
379
  * Injected so this module stays free of a runtime canvas-state import.
380
380
  */
381
381
  export interface AxInteractionManager {
382
+ readonly boardIdentity: object;
382
383
  getNode(id: string): CanvasNodeState | undefined;
383
384
  /** Canvas read for `ax.flow.materialize` (placement obstacles + stale flow sweep). */
384
385
  getLayout(): CanvasLayout;
@@ -722,22 +723,27 @@ function materializeFlow(
722
723
  */
723
724
  // ── ax.work.create idempotency window ─────────────
724
725
  const WORK_CREATE_DEDUPE_MS = 2_500;
725
- const recentWorkCreates = new Map<string, { item: PmxAxWorkItem; at: number }>();
726
+ const recentWorkCreates = new WeakMap<object, Map<string, { item: PmxAxWorkItem; at: number }>>();
726
727
 
727
- function recentWorkCreate(sourceNodeId: string, title: string): PmxAxWorkItem | null {
728
+ function recentWorkCreate(owner: object, sourceNodeId: string, title: string): PmxAxWorkItem | null {
728
729
  const key = `${sourceNodeId}\u0000${title}`;
729
- const hit = recentWorkCreates.get(key);
730
+ const hit = recentWorkCreates.get(owner)?.get(key);
730
731
  if (hit && Date.now() - hit.at <= WORK_CREATE_DEDUPE_MS) return hit.item;
731
732
  return null;
732
733
  }
733
734
 
734
- function rememberWorkCreate(sourceNodeId: string, title: string, item: PmxAxWorkItem): void {
735
+ function rememberWorkCreate(owner: object, sourceNodeId: string, title: string, item: PmxAxWorkItem): void {
735
736
  const key = `${sourceNodeId}\u0000${title}`;
736
- recentWorkCreates.set(key, { item, at: Date.now() });
737
- if (recentWorkCreates.size > 64) {
737
+ let entries = recentWorkCreates.get(owner);
738
+ if (!entries) {
739
+ entries = new Map();
740
+ recentWorkCreates.set(owner, entries);
741
+ }
742
+ entries.set(key, { item, at: Date.now() });
743
+ if (entries.size > 64) {
738
744
  const now = Date.now();
739
- for (const [k, v] of recentWorkCreates) {
740
- if (now - v.at > WORK_CREATE_DEDUPE_MS) recentWorkCreates.delete(k);
745
+ for (const [k, v] of entries) {
746
+ if (now - v.at > WORK_CREATE_DEDUPE_MS) entries.delete(k);
741
747
  }
742
748
  }
743
749
  }
@@ -851,7 +857,7 @@ export function applyAxInteraction(
851
857
  // booked two identical work items ~1s apart): the SAME node creating a
852
858
  // work item with the SAME title inside the window gets the first item
853
859
  // back instead of a duplicate.
854
- const dupe = recentWorkCreate(sourceNodeId, p.title);
860
+ const dupe = recentWorkCreate(manager.boardIdentity, sourceNodeId, p.title);
855
861
  if (dupe) return accept(type, sourceNodeId, dupe, 'ax-state-changed', { workItem: dupe });
856
862
  const workItem = manager.addWorkItem(
857
863
  {
@@ -862,7 +868,7 @@ export function applyAxInteraction(
862
868
  },
863
869
  opts,
864
870
  );
865
- rememberWorkCreate(sourceNodeId, p.title, workItem);
871
+ rememberWorkCreate(manager.boardIdentity, sourceNodeId, p.title, workItem);
866
872
  return withLayoutBroadcast(accept(type, sourceNodeId, workItem, 'ax-state-changed', { workItem }), manager, [
867
873
  workItem,
868
874
  ]);
@@ -137,9 +137,18 @@ function logAxStateWarning(action: string, error: unknown, details?: Record<stri
137
137
 
138
138
  export class AxStateManager {
139
139
  private _axState: PmxAxState = createEmptyAxState();
140
- private _axHostCapability: PmxAxHostCapability | null = null;
141
140
 
142
- constructor(private readonly deps: AxStateManagerDeps) {}
141
+ constructor(
142
+ private readonly deps: AxStateManagerDeps,
143
+ private readonly host: { capability: PmxAxHostCapability | null } = { capability: null },
144
+ ) {}
145
+
146
+ private get _axHostCapability() {
147
+ return this.host.capability;
148
+ }
149
+ private set _axHostCapability(value: PmxAxHostCapability | null) {
150
+ this.host.capability = value;
151
+ }
143
152
 
144
153
  // ── Normalization (against the owner's current node set) ──────────
145
154
  private normalizeForCurrentNodes(state: unknown): PmxAxState {
@@ -1088,7 +1097,9 @@ export class AxStateManager {
1088
1097
  * whose source equals the consumer to prevent delivery loops (e.g. Copilot
1089
1098
  * should not be handed back steering it originated).
1090
1099
  */
1091
- getPendingSteering(options: { consumer?: string; limit?: number } = {}): PmxAxSteeringMessage[] {
1100
+ getPendingSteering(
1101
+ options: { consumer?: string; limit?: number; addressedOnly?: boolean; offset?: number } = {},
1102
+ ): PmxAxSteeringMessage[] {
1092
1103
  const db = this.deps.getDb();
1093
1104
  return db ? loadPendingAxSteeringFromDB(db, options) : [];
1094
1105
  }
@@ -1098,7 +1109,9 @@ export class AxStateManager {
1098
1109
  * #57) — so a fresh steer is visible even behind a long backlog. Loop-safe like
1099
1110
  * getPendingSteering, but ordered DESC instead of the FIFO ASC delivery queue.
1100
1111
  */
1101
- getPendingSteeringForContext(options: { consumer?: string; limit?: number } = {}): PmxAxSteeringMessage[] {
1112
+ getPendingSteeringForContext(
1113
+ options: { consumer?: string; limit?: number; addressedOnly?: boolean; offset?: number } = {},
1114
+ ): PmxAxSteeringMessage[] {
1102
1115
  const db = this.deps.getDb();
1103
1116
  return db ? loadNewestPendingAxSteeringFromDB(db, options) : [];
1104
1117
  }
@@ -24,7 +24,8 @@ export async function waitForAxResolution<T extends { status: string }>(opts: {
24
24
  signal?: AbortSignal;
25
25
  }): Promise<AxWaitResult<T>> {
26
26
  opts.signal?.throwIfAborted();
27
- const { read, isResolved } = opts;
27
+ const read = canvasState.bindBoard(opts.read);
28
+ const { isResolved } = opts;
28
29
  const timeoutMs = Math.max(0, Math.min(opts.timeoutMs, AX_WAIT_MAX_MS));
29
30
  const pendingOf = (v: T | null): boolean => (v ? !isResolved(v) : false);
30
31
 
@@ -40,13 +41,18 @@ export async function waitForAxResolution<T extends { status: string }>(opts: {
40
41
  done = true;
41
42
  clearTimeout(timer);
42
43
  dispose();
44
+ disposeOwner();
43
45
  opts.signal?.removeEventListener('abort', onAbort);
44
46
  if (opts.signal?.aborted) {
45
47
  reject(opts.signal.reason);
46
48
  return;
47
49
  }
48
- const v = read();
49
- resolve({ value: v, pending: pendingOf(v) });
50
+ try {
51
+ const v = read();
52
+ resolve({ value: v, pending: pendingOf(v) });
53
+ } catch (error) {
54
+ reject(error);
55
+ }
50
56
  };
51
57
  const onAbort = finish;
52
58
  const check = (type: string): void => {
@@ -56,6 +62,9 @@ export async function waitForAxResolution<T extends { status: string }>(opts: {
56
62
  };
57
63
  const timer = setTimeout(finish, timeoutMs);
58
64
  const dispose = canvasState.onChange(check);
65
+ const disposeOwner = canvasState.onBoardDispose(() => {
66
+ finish();
67
+ });
59
68
  opts.signal?.addEventListener('abort', onAbort, { once: true });
60
69
  if (opts.signal?.aborted) finish();
61
70
  });
@@ -77,7 +86,8 @@ export async function waitForAxCondition(opts: {
77
86
  }): Promise<void> {
78
87
  opts.signal?.throwIfAborted();
79
88
  const timeoutMs = Math.max(0, Math.min(opts.timeoutMs, AX_WAIT_MAX_MS));
80
- if (timeoutMs === 0 || opts.ready()) return;
89
+ const ready = canvasState.bindBoard(opts.ready);
90
+ if (timeoutMs === 0 || ready()) return;
81
91
  const channel = opts.channel ?? 'ax-timeline';
82
92
  await new Promise<void>((resolve, reject) => {
83
93
  let done = false;
@@ -86,6 +96,7 @@ export async function waitForAxCondition(opts: {
86
96
  done = true;
87
97
  clearTimeout(timer);
88
98
  dispose();
99
+ disposeOwner();
89
100
  opts.signal?.removeEventListener('abort', finish);
90
101
  if (opts.signal?.aborted) {
91
102
  reject(opts.signal.reason);
@@ -94,10 +105,13 @@ export async function waitForAxCondition(opts: {
94
105
  resolve();
95
106
  };
96
107
  const check = (type: string): void => {
97
- if (type === channel && opts.ready()) finish();
108
+ if (type === channel && ready()) finish();
98
109
  };
99
110
  const timer = setTimeout(finish, timeoutMs);
100
- const dispose = canvasState.onChange(check);
111
+ // Steering is workspace-wide and can be sent from another board. Keep
112
+ // ready() bound to its owner but listen outside the board-specific filter.
113
+ const dispose = canvasState.withVisibleBoard(() => canvasState.onChange(check));
114
+ const disposeOwner = canvasState.onBoardDispose(finish);
101
115
  opts.signal?.addEventListener('abort', finish, { once: true });
102
116
  if (opts.signal?.aborted) finish();
103
117
  });
@@ -12,6 +12,7 @@
12
12
  import { existsSync, mkdirSync, readFileSync, realpathSync, statSync, writeFileSync } from 'node:fs';
13
13
  import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
14
14
  import { Marked, Renderer, type RendererThis, type Tokens } from 'marked';
15
+ import { isWorkCardState, WORK_CARD_STATES } from '../shared/ax-kinds.js';
15
16
  import { canvasThemeScheme, normalizeCanvasThemeName, type CanvasThemeName } from '../shared/themes.js';
16
17
  import { delimiterForPath, parseDelimitedText } from '../shared/delimited-text.js';
17
18
  import { buildJsonRenderViewerHtml, escapeInlineScriptSource, readJsonRenderBundle } from '../json-render/server.js';
@@ -281,12 +282,24 @@ async function collect(
281
282
  card.html = `<div class="md">${await markdown.parse(text(data.content))}</div>`;
282
283
  break;
283
284
  case 'status':
284
- card.html = rows([
285
- ['Phase', text(data.phase).trim() || text(data.content).trim() || text(data.status).trim() || 'idle'],
286
- ['Message', data.message],
287
- ['Detail', data.detail],
288
- ['Active tool', data.activeTool],
289
- ]);
285
+ if (typeof data.workItemId === 'string' && data.liveWorkAvailable === false) {
286
+ placeholder('Task unavailable. The linked task is no longer on this board.');
287
+ break;
288
+ }
289
+ card.html = isWorkCardState(data.workState)
290
+ ? rows([
291
+ ['Task view', typeof data.workItemId === 'string' ? 'Read-only snapshot of linked task' : undefined],
292
+ ['Task source', typeof data.workItemId === 'string' ? data.liveWorkSource : undefined],
293
+ ['Work state', WORK_CARD_STATES[data.workState].label],
294
+ ['Detail', data.detail],
295
+ ['Progress', typeof data.progress === 'number' ? `${data.progress}%` : undefined],
296
+ ])
297
+ : rows([
298
+ ['Phase', text(data.phase).trim() || text(data.content).trim() || text(data.status).trim() || 'idle'],
299
+ ['Message', data.message],
300
+ ['Detail', data.detail],
301
+ ['Active tool', data.activeTool],
302
+ ]);
290
303
  break;
291
304
  case 'context': {
292
305
  const items = Array.isArray(data.cards) ? (data.cards as Array<Record<string, unknown>>) : [];