pmx-canvas 0.8.0 → 0.9.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 (341) hide show
  1. package/.github/extensions/pmx-canvas/runtime.json +1 -1
  2. package/.github/extensions/pmx-canvas/tools.json +601 -22
  3. package/CHANGELOG.md +66 -0
  4. package/Readme.md +131 -532
  5. package/dist/canvas/global.css +2759 -510
  6. package/dist/canvas/index.js +168 -107
  7. package/dist/canvas/mcp-app.html +2936 -632
  8. package/dist/json-render/index.css +81 -1
  9. package/dist/json-render/index.js +20 -20
  10. package/dist/types/client/canvas/BarHint.d.ts +12 -2
  11. package/dist/types/client/canvas/BoardLinkDialog.d.ts +3 -1
  12. package/dist/types/client/canvas/BoardPinButton.d.ts +10 -0
  13. package/dist/types/client/canvas/BoardSwitcher.d.ts +1 -1
  14. package/dist/types/client/canvas/CanvasViewport.d.ts +1 -1
  15. package/dist/types/client/canvas/CommandBar.d.ts +3 -4
  16. package/dist/types/client/canvas/ConnectionBanner.d.ts +2 -1
  17. package/dist/types/client/canvas/ContextChip.d.ts +3 -0
  18. package/dist/types/client/canvas/CrossBoardReferences.d.ts +5 -0
  19. package/dist/types/client/canvas/EdgeLayer.d.ts +14 -3
  20. package/dist/types/client/canvas/EmptyState.d.ts +4 -4
  21. package/dist/types/client/canvas/HomeGraphView.d.ts +2 -0
  22. package/dist/types/client/canvas/HomeMapView.d.ts +1 -0
  23. package/dist/types/client/canvas/HomeReviewView.d.ts +6 -0
  24. package/dist/types/client/canvas/HomeView.d.ts +9 -7
  25. package/dist/types/client/canvas/KnowledgeDialog.d.ts +4 -0
  26. package/dist/types/client/canvas/LifecycleChrome.d.ts +5 -0
  27. package/dist/types/client/canvas/LifecycleFields.d.ts +7 -0
  28. package/dist/types/client/canvas/LifecyclePanel.d.ts +1 -0
  29. package/dist/types/client/canvas/MemoryInspector.d.ts +7 -0
  30. package/dist/types/client/canvas/NearPinLayer.d.ts +14 -0
  31. package/dist/types/client/canvas/NodeContextMark.d.ts +40 -7
  32. package/dist/types/client/canvas/RelationPresetsDialog.d.ts +4 -0
  33. package/dist/types/client/canvas/RelationsPanel.d.ts +4 -0
  34. package/dist/types/client/canvas/SessionPanel.d.ts +2 -0
  35. package/dist/types/client/canvas/canvas-area.d.ts +1 -1
  36. package/dist/types/client/canvas/graph-geometry.d.ts +27 -0
  37. package/dist/types/client/canvas/graph-model.d.ts +37 -0
  38. package/dist/types/client/canvas/memory-layout.d.ts +23 -0
  39. package/dist/types/client/canvas/use-chrome-height.d.ts +2 -0
  40. package/dist/types/client/canvas/use-focus-trap.d.ts +1 -0
  41. package/dist/types/client/canvas/use-memory-graph.d.ts +8 -0
  42. package/dist/types/client/canvas/use-pan-zoom.d.ts +4 -1
  43. package/dist/types/client/icons.d.ts +0 -10
  44. package/dist/types/client/nodes/AxStepControls.d.ts +1 -1
  45. package/dist/types/client/nodes/HtmlNode.d.ts +1 -0
  46. package/dist/types/client/nodes/InlineMarkdownEditor.d.ts +4 -4
  47. package/dist/types/client/nodes/MarkdownNode.d.ts +5 -1
  48. package/dist/types/client/nodes/McpAppNode.d.ts +4 -4
  49. package/dist/types/client/nodes/viewer-runtime-state.d.ts +1 -1
  50. package/dist/types/client/state/boards-store.d.ts +81 -9
  51. package/dist/types/client/state/canvas-store.d.ts +14 -1
  52. package/dist/types/client/state/context-chip-store.d.ts +22 -0
  53. package/dist/types/client/state/context-status-store.d.ts +35 -0
  54. package/dist/types/client/state/intent-bridge.d.ts +11 -8
  55. package/dist/types/client/state/knowledge-store.d.ts +22 -0
  56. package/dist/types/client/state/near-pin-store.d.ts +44 -1
  57. package/dist/types/client/state/presence-store.d.ts +4 -4
  58. package/dist/types/client/state/presentation.d.ts +28 -0
  59. package/dist/types/client/state/relation-store.d.ts +10 -0
  60. package/dist/types/client/state/session-store.d.ts +41 -2
  61. package/dist/types/client/state/workspace-settings.d.ts +7 -0
  62. package/dist/types/client/types.d.ts +13 -1
  63. package/dist/types/client/utils/keyboard.d.ts +2 -0
  64. package/dist/types/client/utils/time-ago.d.ts +4 -0
  65. package/dist/types/mcp/context-uri-template.d.ts +3 -1
  66. package/dist/types/server/agent-presence.d.ts +10 -3
  67. package/dist/types/server/ax-context.d.ts +1 -1
  68. package/dist/types/server/canvas-db.d.ts +30 -7
  69. package/dist/types/server/canvas-operations.d.ts +24 -2
  70. package/dist/types/server/canvas-provenance.d.ts +22 -2
  71. package/dist/types/server/canvas-serialization.d.ts +2 -0
  72. package/dist/types/server/canvas-state.d.ts +93 -15
  73. package/dist/types/server/canvas-validation.d.ts +1 -1
  74. package/dist/types/server/context-brief.d.ts +94 -6
  75. package/dist/types/server/context-reads.d.ts +33 -7
  76. package/dist/types/server/edit-summary.d.ts +20 -0
  77. package/dist/types/server/index.d.ts +71 -10
  78. package/dist/types/server/knowledge-lifecycle.d.ts +221 -0
  79. package/dist/types/server/lifecycle-status.d.ts +10 -0
  80. package/dist/types/server/memory-graph.d.ts +9 -0
  81. package/dist/types/server/mutation-history.d.ts +2 -0
  82. package/dist/types/server/operations/ops/ax-shared.d.ts +1 -1
  83. package/dist/types/server/operations/ops/boards.d.ts +2 -1
  84. package/dist/types/server/operations/ops/lifecycle.d.ts +2 -0
  85. package/dist/types/server/operations/ops/memory-graph.d.ts +2 -0
  86. package/dist/types/server/operations/ops/nodes.d.ts +5 -0
  87. package/dist/types/server/operations/ops/query.d.ts +23 -0
  88. package/dist/types/server/operations/registry.d.ts +1 -0
  89. package/dist/types/server/placement.d.ts +7 -0
  90. package/dist/types/server/scope-fence.d.ts +2 -1
  91. package/dist/types/server/spatial-analysis.d.ts +6 -3
  92. package/dist/types/server/tour-control.d.ts +32 -0
  93. package/dist/types/server/wiki-links.d.ts +14 -0
  94. package/dist/types/server/workbench-auth.d.ts +2 -0
  95. package/dist/types/shared/agent-context.d.ts +2 -0
  96. package/dist/types/shared/agent-presence.d.ts +15 -12
  97. package/dist/types/shared/auto-arrange.d.ts +1 -0
  98. package/dist/types/shared/boards.d.ts +4 -4
  99. package/dist/types/shared/knowledge-lifecycle.d.ts +41 -0
  100. package/dist/types/shared/knowledge-presets.d.ts +6 -0
  101. package/dist/types/shared/lifecycle-status.d.ts +24 -0
  102. package/dist/types/shared/memory-graph.d.ts +53 -0
  103. package/dist/types/shared/relations.d.ts +68 -0
  104. package/dist/types/shared/semantic-attention.d.ts +2 -0
  105. package/dist/types/shared/tour.d.ts +38 -0
  106. package/dist/types/shared/workspace-settings.d.ts +11 -0
  107. package/docs/RELEASE.md +21 -3
  108. package/docs/adr-001-bun-only-runtime.md +7 -1
  109. package/docs/adr-002-shared-product-separate-runtimes.md +93 -0
  110. package/docs/api-reference.md +109 -0
  111. package/docs/ax-host-adapter-contract.md +1 -1
  112. package/docs/ax-state-contract.md +5 -4
  113. package/docs/cli.md +153 -15
  114. package/docs/demos/pmx-canvas-demo.mp4 +0 -0
  115. package/docs/design/AgentContext.dc.html +3 -3
  116. package/docs/design/BoardPins.dc.html +37 -22
  117. package/docs/design/Categories.dc.html +208 -0
  118. package/docs/design/CategoryEditor.dc.html +33 -0
  119. package/docs/design/Chrome.dc.html +55 -9
  120. package/docs/design/ContextChip.dc.html +224 -0
  121. package/docs/design/ContextChip600.dc.html +136 -0
  122. package/docs/design/EmptyState.dc.html +45 -0
  123. package/docs/design/Flows.dc.html +23 -1
  124. package/docs/design/HeaderMarks.dc.html +183 -0
  125. package/docs/design/Home.dc.html +19 -1
  126. package/docs/design/HomeGraph.dc.html +86 -202
  127. package/docs/design/HomeMap.dc.html +23 -1
  128. package/docs/design/HomeTeam.dc.html +32 -14
  129. package/docs/design/Lifecycle.dc.html +22 -0
  130. package/docs/design/Lifecycle600.dc.html +18 -0
  131. package/docs/design/LifecycleVariants.dc.html +157 -0
  132. package/docs/design/LinkAuthorship.dc.html +139 -0
  133. package/docs/design/LinksChanged.dc.html +203 -0
  134. package/docs/design/LinksOptions.dc.html +137 -0
  135. package/docs/design/LinksOptions600.dc.html +134 -0
  136. package/docs/design/NearPin.dc.html +9 -10
  137. package/docs/design/Pane600.dc.html +4 -3
  138. package/docs/design/Relations.dc.html +3 -2
  139. package/docs/design/Restore.dc.html +152 -0
  140. package/docs/design/SeeChange.dc.html +267 -0
  141. package/docs/design/Uses.dc.html +12 -6
  142. package/docs/design/canvas.json +146 -19
  143. package/docs/design.md +454 -18
  144. package/docs/environment.md +0 -1
  145. package/docs/evals/curation-effect.md +128 -0
  146. package/docs/http-api.md +465 -38
  147. package/docs/mcp.md +150 -19
  148. package/docs/node-types.md +58 -3
  149. package/docs/plans/design-gaps-2026-10-08.md +53 -0
  150. package/docs/plans/plan-009-tech-debt-backlog.md +379 -0
  151. package/docs/plans/plan-016-context-made-visible-wave-1.md +150 -5
  152. package/docs/plans/plan-017-knowledge-categories-and-lifecycle.md +175 -0
  153. package/docs/plans/plan-018-connected-memory-wave-2.md +594 -0
  154. package/docs/plans/plan-019-post-0.9-roadmap.md +198 -0
  155. package/docs/product-context-vision-2026-09.md +17 -1
  156. package/docs/product-vision-2026-09.md +1082 -43
  157. package/docs/product-vision-review-2026-09.md +13 -1
  158. package/docs/screenshots/workbench-live-dark.png +0 -0
  159. package/docs/screenshots/workbench-live-light.png +0 -0
  160. package/docs/sdk.md +62 -2
  161. package/package.json +1 -1
  162. package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/runtime.json +1 -1
  163. package/plugins/pmx-canvas/plugin.json +1 -1
  164. package/plugins/pmx-canvas/runtime.json +1 -1
  165. package/plugins/pmx-canvas/tools.json +601 -22
  166. package/skills/pmx-canvas/SKILL.md +234 -43
  167. package/skills/pmx-canvas/evals/evals.json +62 -0
  168. package/skills/pmx-canvas/references/ax-html-control-surface.md +6 -0
  169. package/skills/pmx-canvas/references/full-reference.md +120 -21
  170. package/skills/pmx-canvas/references/github-copilot-app-adapter.md +2 -2
  171. package/skills/pmx-canvas/references/importing-context.md +132 -0
  172. package/skills/pmx-canvas/references/installing-pmx-canvas.md +6 -6
  173. package/skills/pmx-canvas/references/tours-and-recording.md +16 -1
  174. package/skills/pmx-canvas-testing/SKILL.md +20 -0
  175. package/src/cli/agent.ts +2 -0
  176. package/src/cli/commands/ax.ts +12 -0
  177. package/src/cli/commands/boards.ts +87 -11
  178. package/src/cli/commands/edges.ts +28 -0
  179. package/src/cli/commands/groups.ts +7 -0
  180. package/src/cli/commands/nodes.ts +4 -2
  181. package/src/cli/commands/query.ts +34 -2
  182. package/src/cli/commands/record.ts +199 -48
  183. package/src/cli/index.ts +7 -1
  184. package/src/client/App.tsx +177 -70
  185. package/src/client/canvas/AttentionHistory.tsx +0 -2
  186. package/src/client/canvas/BarHint.tsx +40 -2
  187. package/src/client/canvas/BoardCopyDialog.tsx +1 -1
  188. package/src/client/canvas/BoardFolderDialog.tsx +7 -7
  189. package/src/client/canvas/BoardLinkDialog.tsx +138 -33
  190. package/src/client/canvas/BoardPinButton.tsx +33 -0
  191. package/src/client/canvas/BoardSwitcher.tsx +26 -40
  192. package/src/client/canvas/CanvasNode.tsx +360 -237
  193. package/src/client/canvas/CanvasViewport.tsx +45 -7
  194. package/src/client/canvas/CommandBar.tsx +29 -23
  195. package/src/client/canvas/CommandPalette.tsx +5 -12
  196. package/src/client/canvas/ConnectionBanner.tsx +3 -3
  197. package/src/client/canvas/ContextChip.tsx +296 -0
  198. package/src/client/canvas/ContextMenu.tsx +63 -5
  199. package/src/client/canvas/CrossBoardReferences.tsx +60 -0
  200. package/src/client/canvas/EdgeLayer.tsx +339 -76
  201. package/src/client/canvas/EmptyState.tsx +33 -18
  202. package/src/client/canvas/ExpandedNodeOverlay.tsx +72 -40
  203. package/src/client/canvas/ExternalWriters.tsx +57 -42
  204. package/src/client/canvas/HomeGraphView.tsx +942 -0
  205. package/src/client/canvas/HomeMapView.tsx +641 -0
  206. package/src/client/canvas/HomeReviewView.tsx +167 -0
  207. package/src/client/canvas/HomeView.tsx +687 -160
  208. package/src/client/canvas/IntentLayer.tsx +19 -12
  209. package/src/client/canvas/KnowledgeDialog.tsx +488 -0
  210. package/src/client/canvas/LifecycleChrome.tsx +133 -0
  211. package/src/client/canvas/LifecycleFields.tsx +91 -0
  212. package/src/client/canvas/LifecyclePanel.tsx +533 -0
  213. package/src/client/canvas/MemoryInspector.tsx +104 -0
  214. package/src/client/canvas/Minimap.tsx +3 -3
  215. package/src/client/canvas/NearPinLayer.tsx +187 -0
  216. package/src/client/canvas/NodeContextMark.tsx +175 -97
  217. package/src/client/canvas/Presentation.tsx +23 -57
  218. package/src/client/canvas/RelationPresetsDialog.tsx +413 -0
  219. package/src/client/canvas/RelationsPanel.tsx +82 -0
  220. package/src/client/canvas/SelectionBar.tsx +3 -6
  221. package/src/client/canvas/SessionPanel.tsx +90 -50
  222. package/src/client/canvas/SessionReceipt.tsx +311 -39
  223. package/src/client/canvas/ShortcutOverlay.tsx +1 -1
  224. package/src/client/canvas/SnapshotPanel.tsx +27 -36
  225. package/src/client/canvas/ToolRail.tsx +367 -192
  226. package/src/client/canvas/TopBar.tsx +354 -124
  227. package/src/client/canvas/canvas-area.ts +10 -3
  228. package/src/client/canvas/graph-geometry.ts +195 -0
  229. package/src/client/canvas/graph-model.ts +254 -0
  230. package/src/client/canvas/memory-layout.ts +106 -0
  231. package/src/client/canvas/use-chrome-height.ts +20 -0
  232. package/src/client/canvas/use-focus-trap.ts +31 -5
  233. package/src/client/canvas/use-memory-graph.ts +58 -0
  234. package/src/client/canvas/use-node-drag.ts +2 -1
  235. package/src/client/canvas/use-pan-zoom.ts +64 -7
  236. package/src/client/icons.tsx +0 -63
  237. package/src/client/mcp-app/style.css +0 -6
  238. package/src/client/nodes/AxStepControls.tsx +74 -50
  239. package/src/client/nodes/BoardNode.tsx +5 -1
  240. package/src/client/nodes/ContextNode.tsx +18 -11
  241. package/src/client/nodes/ExtAppFrame.tsx +0 -1
  242. package/src/client/nodes/FileNode.tsx +46 -42
  243. package/src/client/nodes/HtmlNode.tsx +44 -2
  244. package/src/client/nodes/ImageNode.tsx +1 -1
  245. package/src/client/nodes/InlineFormatBar.tsx +6 -9
  246. package/src/client/nodes/InlineMarkdownEditor.tsx +56 -17
  247. package/src/client/nodes/MarkdownNode.tsx +377 -109
  248. package/src/client/nodes/McpAppNode.tsx +50 -20
  249. package/src/client/nodes/MdFormatBar.tsx +21 -18
  250. package/src/client/nodes/PromptNode.tsx +1 -1
  251. package/src/client/nodes/ResponseNode.tsx +1 -1
  252. package/src/client/nodes/viewer-runtime-state.ts +54 -22
  253. package/src/client/state/attention-bridge.ts +64 -15
  254. package/src/client/state/boards-store.ts +193 -39
  255. package/src/client/state/canvas-store.ts +99 -12
  256. package/src/client/state/context-chip-store.ts +70 -0
  257. package/src/client/state/context-status-store.ts +53 -0
  258. package/src/client/state/intent-bridge.ts +51 -30
  259. package/src/client/state/knowledge-store.ts +43 -0
  260. package/src/client/state/near-pin-store.ts +115 -6
  261. package/src/client/state/presence-store.ts +7 -10
  262. package/src/client/state/presentation.ts +91 -0
  263. package/src/client/state/relation-store.ts +81 -0
  264. package/src/client/state/session-store.ts +144 -18
  265. package/src/client/state/sse-bridge.ts +206 -29
  266. package/src/client/state/workspace-settings.ts +11 -0
  267. package/src/client/theme/global.css +2759 -510
  268. package/src/client/types.ts +12 -0
  269. package/src/client/utils/keyboard.ts +10 -0
  270. package/src/client/utils/time-ago.ts +14 -0
  271. package/src/json-render/renderer/index.css +6 -1
  272. package/src/json-render/renderer/index.tsx +31 -1
  273. package/src/mcp/canvas-app.ts +12 -2
  274. package/src/mcp/context-uri-template.ts +7 -4
  275. package/src/mcp/server.ts +63 -41
  276. package/src/mcp/workbench-app.ts +39 -6
  277. package/src/server/agent-presence.ts +128 -28
  278. package/src/server/ax-context.ts +27 -13
  279. package/src/server/board-export.ts +12 -1
  280. package/src/server/canvas-db.ts +331 -42
  281. package/src/server/canvas-operations.ts +196 -84
  282. package/src/server/canvas-provenance.ts +53 -22
  283. package/src/server/canvas-serialization.ts +2 -0
  284. package/src/server/canvas-state.ts +950 -122
  285. package/src/server/canvas-validation.ts +9 -2
  286. package/src/server/context-brief.ts +345 -38
  287. package/src/server/context-reads.ts +179 -30
  288. package/src/server/demo-state.json +355 -24
  289. package/src/server/edit-summary.ts +74 -0
  290. package/src/server/html-surface.ts +8 -1
  291. package/src/server/index.ts +203 -13
  292. package/src/server/knowledge-lifecycle.ts +304 -0
  293. package/src/server/lifecycle-status.ts +232 -0
  294. package/src/server/memory-graph.ts +166 -0
  295. package/src/server/mutation-history.ts +18 -6
  296. package/src/server/operations/composites.ts +32 -11
  297. package/src/server/operations/http.ts +7 -2
  298. package/src/server/operations/index.ts +4 -0
  299. package/src/server/operations/mcp.ts +32 -1
  300. package/src/server/operations/ops/ax-presence.ts +1 -1
  301. package/src/server/operations/ops/ax-read.ts +57 -6
  302. package/src/server/operations/ops/ax-timeline.ts +28 -2
  303. package/src/server/operations/ops/backup.ts +5 -3
  304. package/src/server/operations/ops/batch.ts +35 -4
  305. package/src/server/operations/ops/boards.ts +188 -47
  306. package/src/server/operations/ops/canvas-wire.ts +31 -1
  307. package/src/server/operations/ops/edges.ts +36 -2
  308. package/src/server/operations/ops/groups.ts +6 -1
  309. package/src/server/operations/ops/json-render.ts +5 -1
  310. package/src/server/operations/ops/lifecycle.ts +262 -0
  311. package/src/server/operations/ops/memory-graph.ts +45 -0
  312. package/src/server/operations/ops/nodes.ts +107 -24
  313. package/src/server/operations/ops/query.ts +281 -68
  314. package/src/server/operations/ops/snapshots.ts +73 -1
  315. package/src/server/operations/ops/validate.ts +5 -1
  316. package/src/server/operations/ops/viewport.ts +97 -3
  317. package/src/server/operations/registry.ts +77 -26
  318. package/src/server/placement.ts +29 -0
  319. package/src/server/scope-fence.ts +78 -6
  320. package/src/server/server.ts +115 -16
  321. package/src/server/spatial-analysis.ts +48 -23
  322. package/src/server/tour-control.ts +82 -0
  323. package/src/server/wiki-links.ts +137 -0
  324. package/src/server/workbench-auth.ts +11 -0
  325. package/src/shared/agent-context.ts +3 -0
  326. package/src/shared/agent-presence.ts +15 -15
  327. package/src/shared/auto-arrange.ts +2 -0
  328. package/src/shared/boards.ts +4 -4
  329. package/src/shared/knowledge-lifecycle.ts +22 -0
  330. package/src/shared/knowledge-presets.ts +80 -0
  331. package/src/shared/lifecycle-status.ts +26 -0
  332. package/src/shared/memory-graph.ts +74 -0
  333. package/src/shared/relations.ts +98 -0
  334. package/src/shared/semantic-attention.ts +15 -1
  335. package/src/shared/tour.ts +23 -5
  336. package/src/shared/workspace-settings.ts +13 -0
  337. package/dist/types/client/canvas/ContextPinBar.d.ts +0 -1
  338. package/dist/types/client/canvas/FocusFieldLayer.d.ts +0 -1
  339. package/dist/types/client/canvas/kind-colors.d.ts +0 -3
  340. package/dist/types/server/agent-context.d.ts +0 -22
  341. package/src/client/canvas/ContextPinBar.tsx +0 -25
@@ -19,9 +19,6 @@ import {
19
19
  type AgentPhase,
20
20
  type AgentPresence,
21
21
  type AgentPresenceSnapshot,
22
- CONTEXT_BUDGET_DEFAULT_TOKENS,
23
- type ContextBudget,
24
- estimateTokens,
25
22
  HUMAN_STARTED_SESSION_LABEL,
26
23
  isSessionActive,
27
24
  MAX_ACTIVITY_ENTRIES,
@@ -30,11 +27,11 @@ import {
30
27
  PRESENCE_ATTACHED_IDLE_TTL_MS,
31
28
  PRESENCE_THINKING_SETTLE_MS,
32
29
  PRESENCE_TOOLING_SETTLE_MS,
30
+ type SessionTouches,
33
31
  TRANSPORT_SOURCES,
34
32
  } from '../shared/agent-presence.js';
35
- import { serializeNodeForAgentContext } from '../shared/agent-context.js';
36
33
  import type { PmxAxActivityKind } from './ax-state.js';
37
- import { type CanvasNodeState, canvasState } from './canvas-state.js';
34
+ import { canvasState } from './canvas-state.js';
38
35
  import { OperationError } from './operations/types.js';
39
36
 
40
37
  type PresenceEmitter = (event: string, payload: Record<string, unknown>) => void;
@@ -65,8 +62,12 @@ export interface PresenceTouch {
65
62
  }
66
63
 
67
64
  /** Title a write's summary can name, or a type-based fallback. */
68
- function nodeTitle(nodeId: string | null | undefined): string | null {
69
- const node = nodeId ? canvasState.getNode(nodeId) : undefined;
65
+ function nodeTitle(nodeId: string | null | undefined, boardId?: string): string | null {
66
+ const node = nodeId
67
+ ? boardId && boardId !== canvasState.activeBoardId
68
+ ? canvasState.readBoard(boardId, false)?.state.nodes.find((entry) => entry.id === nodeId)
69
+ : canvasState.getNode(nodeId)
70
+ : undefined;
70
71
  if (!node) return null;
71
72
  const title = typeof node.data.title === 'string' ? node.data.title.trim() : '';
72
73
  return title || `${node.type} node`;
@@ -109,11 +110,16 @@ export function describeWrite(
109
110
  return { summary: `Updated ${quote(title, 'a node')}`, nodeId };
110
111
  case 'node.remove':
111
112
  return { summary: 'Removed a node', nodeId };
112
- case 'edge.add':
113
+ case 'edge.add': {
114
+ const from = str(res.from) || str(input.from);
115
+ const to = str(res.to) || str(input.to);
116
+ const toBoardId = str(res.toBoardId) || str(input.toBoardId);
117
+ const foreignTarget = toBoardId && toBoardId !== canvasState.activeBoardId;
113
118
  return {
114
- summary: `Connected ${quote(nodeTitle(str(input.from)), 'a node')} → ${quote(nodeTitle(str(input.to)), 'a node')}`,
115
- nodeId: str(input.to) || null,
119
+ summary: `Connected ${quote(nodeTitle(from), 'a node')} → ${quote(nodeTitle(to, toBoardId), foreignTarget ? `a node on board ${toBoardId}` : 'a node')}`,
120
+ nodeId: foreignTarget ? (canvasState.getNode(from) ? from : null) : to || null,
116
121
  };
122
+ }
117
123
  case 'edge.remove':
118
124
  return { summary: 'Removed an edge', nodeId: null };
119
125
  case 'group.create':
@@ -207,26 +213,74 @@ interface StoredPresence extends AgentPresence {
207
213
  startSnapshotId: string | null;
208
214
  /** When this session attached (ISO); the receipt counts reads and pins from here. */
209
215
  startedAt: string | null;
216
+ /**
217
+ * Each board as the session found it — its cards and content revision —
218
+ * captured at attach and when a board opens mid-session (a session started
219
+ * on Home gets one when its first write opens a board). The live lens
220
+ * counts from here.
221
+ */
222
+ startBoards: Map<string, { nodeIds: Set<string>; revision: number }>;
223
+ /** The last computed touches and what they were computed from. */
224
+ touchesCache: { key: string; value: SessionTouches | undefined } | null;
210
225
  }
211
226
 
212
- function presenceKey(source: string, agentId: string | null | undefined): string {
213
- return agentId && agentId.trim() ? agentId.trim() : source;
227
+ type Baselines = StoredPresence['startBoards'];
228
+
229
+ /**
230
+ * Record the open board as this session finds it, unless it already has. A
231
+ * board created after the session started began empty: a write on Home opens
232
+ * a new board while saving, after its first cards exist, so those count as
233
+ * the session's.
234
+ */
235
+ function captureBaseline(baselines: Baselines, since: string | null): void {
236
+ const boardId = canvasState.activeBoardId;
237
+ if (!boardId || baselines.has(boardId)) return;
238
+ const createdAt = canvasState.listBoards().find((board) => board.id === boardId)?.createdAt;
239
+ const bornInSession = since !== null && createdAt !== undefined && createdAt >= since;
240
+ baselines.set(
241
+ boardId,
242
+ bornInSession
243
+ ? { nodeIds: new Set(), revision: 0 }
244
+ : {
245
+ nodeIds: new Set(canvasState.nodeStamps().map((node) => node.id)),
246
+ revision: canvasState.getContentRevision().revision,
247
+ },
248
+ );
214
249
  }
215
250
 
216
- function contextBudgetTotal(): number {
217
- const raw = Number(process.env.PMX_CANVAS_CONTEXT_BUDGET_TOKENS ?? '');
218
- return Number.isFinite(raw) && raw > 0 ? Math.floor(raw) : CONTEXT_BUDGET_DEFAULT_TOKENS;
251
+ /** The cards an attached session read, created, edited and pinned on the open board since it found it. */
252
+ function sessionTouches(stored: StoredPresence, readVersion: number): SessionTouches | undefined {
253
+ const boardId = canvasState.activeBoardId;
254
+ if (!stored.attached || !stored.startedAt || !boardId) return undefined;
255
+ // A board can open without a change event (a write on Home): record it now.
256
+ captureBaseline(stored.startBoards, stored.startedAt);
257
+ const start = stored.startBoards.get(boardId);
258
+ if (!start) return undefined;
259
+ const since = stored.startedAt;
260
+ const pins = Object.entries(canvasState.getContextPinMeta());
261
+ const key = `${boardId}|${canvasState.getContentRevision().revision}|${readVersion}|${pins.map(([id, meta]) => `${id}@${meta.pinnedAt}`).join(',')}`;
262
+ if (stored.touchesCache?.key === key) return stored.touchesCache.value;
263
+ const stamps = canvasState.nodeStamps();
264
+ const value: SessionTouches = {
265
+ startedAt: since,
266
+ read: canvasState.getReadNodeIdsSince(boardId, since),
267
+ created: stamps.filter((node) => !start.nodeIds.has(node.id) && node.createdBy?.actor === 'agent').map((n) => n.id),
268
+ edited: stamps
269
+ .filter(
270
+ (node) =>
271
+ start.nodeIds.has(node.id) &&
272
+ node.lastEditedBy?.actor === 'agent' &&
273
+ (node.contentRevision ?? 0) > start.revision,
274
+ )
275
+ .map((node) => node.id),
276
+ pinned: pins.filter(([, meta]) => meta.pinnedBy.actor === 'agent' && meta.pinnedAt >= since).map(([id]) => id),
277
+ };
278
+ stored.touchesCache = { key, value };
279
+ return value;
219
280
  }
220
281
 
221
- /** Token estimate of the pinned-context payload — the same serialization the MCP resource ships. */
222
- export function estimateContextBudget(): ContextBudget {
223
- const nodes = Array.from(canvasState.contextPinnedNodeIds)
224
- .map((id) => canvasState.getNode(id))
225
- .filter((node): node is CanvasNodeState => node !== undefined);
226
- const payload = nodes.map((node) =>
227
- serializeNodeForAgentContext(node, { defaultTextLength: 700, webpageTextLength: 1600, includePosition: true }),
228
- );
229
- return { used: nodes.length === 0 ? 0 : estimateTokens(JSON.stringify(payload)), total: contextBudgetTotal() };
282
+ function presenceKey(source: string, agentId: string | null | undefined): string {
283
+ return agentId && agentId.trim() ? agentId.trim() : source;
230
284
  }
231
285
 
232
286
  /** Returns the id of the pre-session snapshot the server took, if any. */
@@ -400,6 +454,8 @@ export class AgentPresenceRegistry {
400
454
  toolingUntilMs: null,
401
455
  startSnapshotId: null,
402
456
  startedAt: null,
457
+ startBoards: new Map(),
458
+ touchesCache: null,
403
459
  };
404
460
  const wasAttached = stored.attached;
405
461
  stored.lastSeenMs = now;
@@ -457,6 +513,7 @@ export class AgentPresenceRegistry {
457
513
  // placeholder that already adopted work is a real session and stays.
458
514
  let inheritedSnapshotId: string | null = null;
459
515
  let inheritedStartedAt: string | null = null;
516
+ let inheritedStartBoards: Baselines | null = null;
460
517
  if (stored.source !== 'browser') {
461
518
  const placeholder = [...this.presences.values()].find(
462
519
  (presence) =>
@@ -473,10 +530,13 @@ export class AgentPresenceRegistry {
473
530
  }
474
531
  inheritedSnapshotId = placeholder.startSnapshotId;
475
532
  inheritedStartedAt = placeholder.startedAt;
533
+ inheritedStartBoards = placeholder.startBoards;
476
534
  }
477
535
  }
478
536
  stored.startSnapshotId = inheritedSnapshotId ?? this.onSessionStart(this.publicView(stored, now));
479
537
  stored.startedAt = inheritedStartedAt ?? new Date(now).toISOString();
538
+ stored.startBoards = inheritedStartBoards ?? new Map();
539
+ captureBaseline(stored.startBoards, stored.startedAt);
480
540
  }
481
541
  if (input.attached === false) {
482
542
  if (wasAttached)
@@ -612,6 +672,23 @@ export class AgentPresenceRegistry {
612
672
  if (this.presences.size > 0) this.scheduleEmit();
613
673
  }
614
674
 
675
+ /** Bumped when an agent read is recorded: the live lens's read list changed. */
676
+ private readVersion = 0;
677
+
678
+ /** A read landed (server.ts's read listener): re-count and re-emit. */
679
+ noteRead(): void {
680
+ this.readVersion += 1;
681
+ this.refresh();
682
+ }
683
+
684
+ /** A board opened: attached sessions record it as they find it, before any write lands. */
685
+ boardOpened(): void {
686
+ for (const stored of this.presences.values()) {
687
+ if (stored.attached) captureBaseline(stored.startBoards, stored.startedAt);
688
+ }
689
+ this.refresh();
690
+ }
691
+
615
692
  /** Remove a writer (session-end). */
616
693
  detach(sessionId: string): boolean {
617
694
  const stored = this.presences.get(sessionId);
@@ -628,15 +705,22 @@ export class AgentPresenceRegistry {
628
705
 
629
706
  snapshot(now = Date.now()): AgentPresenceSnapshot {
630
707
  this.sweep(now, { emit: false });
631
- const presences = [...this.presences.values()].map((stored) => this.publicView(stored, now));
708
+ const presences = [...this.presences.values()].map((stored) => this.publicView(stored, now, true));
632
709
  return {
633
710
  presences,
634
- budget: estimateContextBudget(),
635
711
  sessionActive: isSessionActive(presences),
636
712
  activity: this.activity.map((entry) => ({ ...entry })),
637
713
  };
638
714
  }
639
715
 
716
+ /** Latest attached session: a newly attached reviewer must check fast context again. */
717
+ reviewSessionStartedAt(): number | null {
718
+ const starts = [...this.presences.values()]
719
+ .filter((presence) => presence.attached && presence.startedAt)
720
+ .map((presence) => Date.parse(presence.startedAt!));
721
+ return starts.length ? Math.max(...starts) : null;
722
+ }
723
+
640
724
  /** Test / shutdown hook. */
641
725
  reset(): void {
642
726
  this.steeringConsumers.clear();
@@ -650,15 +734,22 @@ export class AgentPresenceRegistry {
650
734
  this.maybeStopSweeper();
651
735
  }
652
736
 
653
- private publicView(stored: StoredPresence, now: number): AgentPresence {
737
+ /** `withTouches`: only emitted snapshots carry the session's touches (they read the DB). */
738
+ private publicView(stored: StoredPresence, now: number, withTouches = false): AgentPresence {
654
739
  const {
655
740
  lastSeenMs: _lastSeenMs,
656
741
  phaseSetMs: _phaseSetMs,
657
742
  toolingUntilMs,
658
743
  startSnapshotId: _startSnapshotId,
744
+ startBoards: _startBoards,
745
+ touchesCache: _touchesCache,
659
746
  lastActivityNodeId,
660
747
  ...presence
661
748
  } = stored;
749
+ if (withTouches) {
750
+ const session = sessionTouches(stored, this.readVersion);
751
+ if (session) presence.session = session;
752
+ }
662
753
  let phase = presence.phase;
663
754
  let detail = presence.detail;
664
755
  if (toolingUntilMs !== null && toolingUntilMs > now) {
@@ -801,6 +892,15 @@ export const agentPresence = new AgentPresenceRegistry();
801
892
  // and context pins. Re-emit on every AX / pin change — whatever transport made
802
893
  // it — so no caller has to remember to refresh, and the SDK paths that bypass
803
894
  // the operation registry are covered too.
895
+ // The live lens also follows card content (a person's edit takes a card out of
896
+ // the agent's session) and the open board — but not moves, so a drag stays quiet.
897
+ let lastBoardId = canvasState.activeBoardId;
898
+ let lastRevision = canvasState.getContentRevision().revision;
804
899
  canvasState.onChange((type) => {
805
- if (type === 'ax' || type === 'pins') agentPresence.refresh();
900
+ const boardId = canvasState.activeBoardId;
901
+ const revision = canvasState.getContentRevision().revision;
902
+ if (boardId !== lastBoardId) agentPresence.boardOpened();
903
+ else if (type === 'ax' || type === 'pins' || revision !== lastRevision) agentPresence.refresh();
904
+ lastBoardId = boardId;
905
+ lastRevision = revision;
806
906
  });
@@ -15,6 +15,8 @@ import {
15
15
  } from './ax-state.js';
16
16
  import type { CanvasLayout } from './canvas-state.js';
17
17
  import { canvasState, type CanvasNodeState } from './canvas-state.js';
18
+ import { lifecycleQualifications } from './lifecycle-status.js';
19
+ import { memoryKey } from '../shared/memory-graph.js';
18
20
 
19
21
  /**
20
22
  * Compact, surface-safe view of the canvas-bound AX state, injected into (and
@@ -54,18 +56,29 @@ export function buildCanvasAxSurfaceSnapshot(): PmxAxSurfaceSnapshot {
54
56
  };
55
57
  }
56
58
 
57
- function serializeNodes(nodes: CanvasNodeState[]) {
58
- return nodes.map((node) =>
59
- serializeNodeForAgentContext(node, {
60
- defaultTextLength: 700,
61
- webpageTextLength: 1600,
62
- includePosition: true,
63
- }),
64
- );
59
+ function serializeNodes(nodes: CanvasNodeState[], qualifications: ReturnType<typeof lifecycleQualifications>) {
60
+ const qualification = (node: CanvasNodeState) =>
61
+ canvasState.activeBoardId
62
+ ? qualifications[memoryKey({ boardId: canvasState.activeBoardId, cardId: node.id })]
63
+ : undefined;
64
+ return nodes
65
+ .filter((node) => !qualification(node)?.retired)
66
+ .map((node) => ({
67
+ ...serializeNodeForAgentContext(node, {
68
+ defaultTextLength: 700,
69
+ webpageTextLength: 1600,
70
+ includePosition: true,
71
+ }),
72
+ qualification: qualification(node),
73
+ }));
65
74
  }
66
75
 
67
- export function buildCanvasAxPinnedContext(): PmxAxPinnedContext {
68
- const nodeIds = Array.from(canvasState.contextPinnedNodeIds);
76
+ export function buildCanvasAxPinnedContext(qualifications = lifecycleQualifications()): PmxAxPinnedContext {
77
+ const nodeIds = Array.from(canvasState.contextPinnedNodeIds).filter(
78
+ (id) =>
79
+ !canvasState.activeBoardId ||
80
+ !qualifications[memoryKey({ boardId: canvasState.activeBoardId, cardId: id })]?.retired,
81
+ );
69
82
  const nodes = nodeIds
70
83
  .map((id) => canvasState.getNode(id))
71
84
  .filter((node): node is CanvasNodeState => node !== undefined);
@@ -73,7 +86,7 @@ export function buildCanvasAxPinnedContext(): PmxAxPinnedContext {
73
86
  preamble: nodes.length > 0 ? buildAgentContextPreamble(nodes) : '',
74
87
  nodeIds,
75
88
  count: nodeIds.length,
76
- nodes: serializeNodes(nodes),
89
+ nodes: serializeNodes(nodes, qualifications),
77
90
  };
78
91
  }
79
92
 
@@ -122,6 +135,7 @@ function buildHumanAnnotations(layout: CanvasLayout): PmxAxHumanAnnotation[] {
122
135
  export function buildCanvasAxContext(consumer?: string): PmxAxContext & { boardId: string | null } {
123
136
  const layout = canvasState.getLayout();
124
137
  const ax = canvasState.getAxState();
138
+ const qualifications = lifecycleQualifications();
125
139
  const focusNodes = ax.focus.nodeIds
126
140
  .map((id) => canvasState.getNode(id))
127
141
  .filter((node): node is CanvasNodeState => node !== undefined);
@@ -138,9 +152,9 @@ export function buildCanvasAxContext(consumer?: string): PmxAxContext & { boardI
138
152
  omittedPending: Math.max(0, totalPending - pendingSteering.length),
139
153
  pendingActivity: buildPendingAxActivity(ax, consumer),
140
154
  },
141
- pinned: buildCanvasAxPinnedContext(),
155
+ pinned: buildCanvasAxPinnedContext(qualifications),
142
156
  focus: ax.focus,
143
- focusNodes: serializeNodes(focusNodes),
157
+ focusNodes: serializeNodes(focusNodes, qualifications),
144
158
  workItems: ax.workItems,
145
159
  approvalGates: ax.approvalGates,
146
160
  reviewAnnotations: ax.reviewAnnotations,
@@ -489,6 +489,7 @@ export async function buildBoardExport(
489
489
  const edges = snapshotEdges.map((edge: CanvasEdge) => ({
490
490
  from: edge.from,
491
491
  to: edge.to,
492
+ ...(edge.toBoardId !== undefined ? { toBoardId: edge.toBoardId } : {}),
492
493
  label: edge.label ?? '',
493
494
  style: edge.style ?? 'solid',
494
495
  }));
@@ -537,7 +538,10 @@ function exportPage(input: {
537
538
  title: string;
538
539
  scheme: 'dark' | 'light';
539
540
  exportedAt: string;
540
- board: { cards: ExportCard[]; edges: Array<{ from: string; to: string; label: string; style: string }> };
541
+ board: {
542
+ cards: ExportCard[];
543
+ edges: Array<{ from: string; to: string; toBoardId?: string; label: string; style: string }>;
544
+ };
541
545
  assets: Record<string, string>;
542
546
  }): string {
543
547
  const date = new Date(input.exportedAt).toLocaleString('en-GB', { dateStyle: 'medium', timeStyle: 'short' });
@@ -670,10 +674,17 @@ for (const card of ordered) {
670
674
  el.appendChild(body(card));
671
675
  title.addEventListener('click', () => expand(card));
672
676
  }
677
+ for (const edge of board.edges.filter((edge) => edge.from === card.id && edge.toBoardId !== undefined)) {
678
+ const connection = document.createElement('div');
679
+ connection.className = 'card-title';
680
+ connection.textContent = 'External connection → ' + edge.toBoardId + '/' + edge.to + (edge.label ? ' · ' + edge.label : '');
681
+ el.appendChild(connection);
682
+ }
673
683
  world.appendChild(el);
674
684
  }
675
685
  const NS = 'http://www.w3.org/2000/svg';
676
686
  for (const edge of board.edges) {
687
+ if (edge.toBoardId !== undefined) continue;
677
688
  const a = byId.get(edge.from), b = byId.get(edge.to);
678
689
  if (!a || !b) continue;
679
690
  const x1 = a.x + a.w / 2, y1 = a.y + a.h / 2, x2 = b.x + b.w / 2, y2 = b.y + b.h / 2;