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
@@ -1,5 +1,8 @@
1
1
  import { findOpenCanvasPosition } from '../utils/placement.js';
2
- import { ownsCamera } from './presentation';
2
+ import { refreshKnowledge } from './knowledge-store';
3
+ import { applyWorkspaceSettings } from './workspace-settings';
4
+ import { cameraSchema, tourStopSchema } from '../../shared/tour.js';
5
+ import { ownsCamera, playStop, presentationError, presenting, presentStop, recordingCamera } from './presentation';
3
6
  import { isHostedWorkbench, workbenchFetch } from './workbench-transport';
4
7
  import { normalizeExtAppToolResult } from '../utils/ext-app-tool-result.js';
5
8
  import type { CanvasAnnotation, CanvasEdge, CanvasNodeState, NodeActor } from '../types';
@@ -11,8 +14,12 @@ import {
11
14
  axSurfaceState,
12
15
  bringToFront,
13
16
  cancelViewportAnimation,
17
+ commitViewport,
14
18
  canvasTheme,
15
19
  connectionStatus,
20
+ contextPinsRevision,
21
+ contextPinsPendingWrites,
22
+ refreshContextPins,
16
23
  replaceContextPinsFromServer,
17
24
  edges,
18
25
  focusNode,
@@ -30,8 +37,18 @@ import {
30
37
  reconnectDelay,
31
38
  resetCanvasInteractionState,
32
39
  } from './canvas-store';
33
- import { applyBoards, loadBoards, setBoardSwitchHandler } from './boards-store';
40
+ import {
41
+ activeBoardId,
42
+ applyBoards,
43
+ boardGeneration,
44
+ catalogEpoch,
45
+ connectBoardCatalog,
46
+ loadBoards,
47
+ memoryGraphRevision,
48
+ setBoardSwitchHandler,
49
+ } from './boards-store';
34
50
  import { applyContextStatus, refreshContextStatus } from './context-status-store';
51
+ import { refreshContextChip } from './context-chip-store';
35
52
  import {
36
53
  fetchAgentPresence,
37
54
  fetchAxSurfaceState,
@@ -41,9 +58,9 @@ import {
41
58
  tabAgentKey,
42
59
  } from './intent-bridge';
43
60
  import { agentActivity, applyPresenceSnapshot, sessionActive } from './presence-store';
44
- import { applyHumanSnapshot, startHumanPresence } from './human-store';
61
+ import { applyHumanSnapshot, humanClientId, startHumanPresence } from './human-store';
45
62
  import type { HumanPresenceSnapshot } from '../../shared/human-presence.js';
46
- import { applySessionReceipt, dismissSessionReceipt, refreshTimeline } from './session-store';
63
+ import { activityLens, applySessionReceipt, dismissSessionReceipt, refreshTimeline } from './session-store';
47
64
  import { initSessionThemeOverride, themeOverrideActive } from './theme-override';
48
65
  import { DEFAULT_POSITIONS, makeNodeState } from './node-factory';
49
66
  import { invalidateTokenCache } from '../theme/tokens';
@@ -64,14 +81,19 @@ let reconnectTimer: ReturnType<typeof setTimeout> | null = null;
64
81
  // "opens" (headers pass through) but no event ever arrives. Fallback: if the
65
82
  // stream produces no event at all within the watchdog window, switch to
66
83
  // polling GET /api/workbench/poll, which returns buffered events (or a full
67
- // connect snapshot) as a normal response every couple of seconds. The same
84
+ // connect snapshot) as a completed response as soon as an event arrives. The same
68
85
  // EVENT_HANDLERS map serves both transports. `?transport=poll` forces polling
69
86
  // from the start; `?transport=sse` disables the fallback.
70
87
  const SSE_FIRST_EVENT_WATCHDOG_MS = 3000;
71
88
  const POLL_INTERVAL_MS = 2000;
89
+ const POLL_WAIT_MS = 1000;
90
+ const POLL_LIVE_INTERVAL_MS = 80;
72
91
  const POLL_ERROR_INTERVAL_MS = 5000;
92
+ // Bound headers AND body, including host transports that ignore AbortSignal.
93
+ const POLL_REQUEST_TIMEOUT_MS = 10000;
73
94
  let pollingMode = false;
74
95
  let pollTimer: ReturnType<typeof setTimeout> | null = null;
96
+ let pollAbort: AbortController | null = null;
75
97
  let pollSeq: number | null = null;
76
98
  let pollGeneration = 0;
77
99
  let sseFirstEventWatchdog: ReturnType<typeof setTimeout> | null = null;
@@ -369,6 +391,7 @@ function parseCanvasNode(raw: Record<string, unknown>): CanvasNodeState | null {
369
391
  collapsed: raw.collapsed === true,
370
392
  pinned: raw.pinned === true,
371
393
  contentRevision: typeof raw.contentRevision === 'number' ? raw.contentRevision : 0,
394
+ linksRevision: typeof raw.linksRevision === 'number' ? raw.linksRevision : 0,
372
395
  ...(isActor(raw.createdBy) ? { createdBy: raw.createdBy } : {}),
373
396
  ...(isActor(raw.lastEditedBy) ? { lastEditedBy: raw.lastEditedBy } : {}),
374
397
  data,
@@ -391,10 +414,18 @@ function parseCanvasEdge(raw: Record<string, unknown>): CanvasEdge | null {
391
414
  id: raw.id,
392
415
  from: raw.from,
393
416
  to: raw.to,
417
+ ...(typeof raw.toBoardId === 'string' ? { toBoardId: raw.toBoardId } : {}),
394
418
  type: raw.type,
419
+ ...(typeof raw.kind === 'string' ? { kind: raw.kind } : {}),
420
+ ...(typeof raw.reason === 'string' ? { reason: raw.reason } : {}),
395
421
  ...(typeof raw.label === 'string' ? { label: raw.label } : {}),
396
422
  ...(raw.style === 'solid' || raw.style === 'dashed' || raw.style === 'dotted' ? { style: raw.style } : {}),
397
423
  ...(raw.animated === true ? { animated: true } : {}),
424
+ ...(typeof raw.revision === 'number' ? { revision: raw.revision } : {}),
425
+ ...(isActor(raw.changedBy) ? { changedBy: raw.changedBy } : {}),
426
+ ...(typeof raw.changedAt === 'string' ? { changedAt: raw.changedAt } : {}),
427
+ ...(isActor(raw.createdBy) ? { createdBy: raw.createdBy } : {}),
428
+ ...(typeof raw.createdAt === 'string' ? { createdAt: raw.createdAt } : {}),
398
429
  };
399
430
  }
400
431
 
@@ -474,7 +505,7 @@ function handleAgentSessionEnded(data: Record<string, unknown>): void {
474
505
  let stopHumanPresence: (() => void) | null = null;
475
506
  let boardResyncGeneration = 0;
476
507
  let layoutRevision = 0;
477
- let pinsRevision = 0;
508
+ let pendingBoardLayout: Record<string, unknown> | null = null;
478
509
 
479
510
  function handleHumanPresence(data: Record<string, unknown>): void {
480
511
  applyHumanSnapshot(data as Partial<HumanPresenceSnapshot>);
@@ -485,32 +516,61 @@ function handleHumanPresence(data: Record<string, unknown>): void {
485
516
  * a different board now, so take its layout like a fresh connection would —
486
517
  * camera included — and drop what belonged to the old board.
487
518
  */
488
- function resyncForBoardSwitch(): void {
519
+ function resyncForBoardSwitch(initial: boolean): void {
520
+ const pending = pendingBoardLayout;
521
+ pendingBoardLayout = null;
522
+ if (initial) {
523
+ // Untagged initial fetches may already have supplied the layout. Tagged
524
+ // SSE waits here for the first catalog to identify its owner.
525
+ if (pending?.boardId === activeBoardId.value) handleCanvasLayoutUpdate(pending);
526
+ return;
527
+ }
528
+ const boardId = activeBoardId.value;
489
529
  const generation = ++boardResyncGeneration;
490
530
  savedLayout = null;
491
531
  hasInitialServerLayout.value = false;
492
532
  resetIntents();
493
533
  resetCanvasInteractionState();
494
534
  resetAttentionBridge();
495
- dismissSessionReceipt();
535
+ applyServerCanvasLayout({ nodes: [], edges: [] });
536
+ if (pending?.boardId === activeBoardId.value) handleCanvasLayoutUpdate(pending);
537
+ // Keep the change available when visiting another board, so its Open board
538
+ // action can bring the person back to Undo. Home still clears board chrome.
539
+ if (activeBoardId.value === null) dismissSessionReceipt();
540
+ if (activityLens.value?.kind === 'receipt') activityLens.value = null;
496
541
  applyContextStatus({});
497
542
  axSurfaceState.value = null;
498
543
  void refreshAxSurface();
499
544
  const layoutAtRequest = layoutRevision;
500
- const pinsAtRequest = pinsRevision;
501
- void fetchCanvasState().then((layout) => {
545
+ const pinsAtRequest = contextPinsRevision;
546
+ void fetchCanvasState(boardId).then((layout) => {
502
547
  if (generation === boardResyncGeneration && layoutAtRequest === layoutRevision)
503
- handleCanvasLayoutUpdate({ layout });
548
+ handleCanvasLayoutUpdate({ layout, boardId });
504
549
  });
505
550
  void requestJson<{ nodeIds?: unknown }>('fetchPinnedContext', '/api/canvas/pinned-context', {}).then((data) => {
506
- if (generation === boardResyncGeneration && pinsAtRequest === pinsRevision)
551
+ if (generation === boardResyncGeneration && pinsAtRequest === contextPinsRevision)
507
552
  handleContextPinsChanged({ nodeIds: data.nodeIds });
508
553
  });
509
554
  }
510
555
  setBoardSwitchHandler(resyncForBoardSwitch);
511
556
 
512
557
  function handleConnected(data: Record<string, unknown>): void {
558
+ memoryGraphRevision.value++;
559
+ // A current connection authorizes catalogs from this server run. Old HTTP
560
+ // continuations and buffered layouts cannot cross the snapshot boundary.
561
+ boardResyncGeneration++;
562
+ pendingBoardLayout = null;
563
+ // The following snapshot is a fresh baseline, including lower revisions after
564
+ // a restart/restore. Keep same-board editors and their text selections mounted.
565
+ hasInitialServerLayout.value = false;
566
+ if (typeof data.catalogEpoch === 'string') connectBoardCatalog(data.catalogEpoch);
567
+ // A same-bundle daemon restart keeps drafts alive but rotates its human capability.
568
+ // Install it before connect-time requests or presence heartbeats can write.
569
+ if (!isHostedWorkbench() && typeof data.workbenchToken === 'string') {
570
+ (window as Window & { __PMX_WORKBENCH_TOKEN?: string }).__PMX_WORKBENCH_TOKEN = data.workbenchToken;
571
+ }
513
572
  sessionId.value = (data.sessionId as string) || '';
573
+ applyWorkspaceSettings(data.workspaceSettings);
514
574
  connectionStatus.value = 'connected';
515
575
  // Hosted writes capture the active board. Load its identity before reporting
516
576
  // the viewport, rather than sending a spurious Home precondition on boot.
@@ -522,6 +582,7 @@ function handleConnected(data: Record<string, unknown>): void {
522
582
  // keep it live from here on.
523
583
  void fetchAgentPresence().then(applyPresenceSnapshot);
524
584
  void refreshContextStatus();
585
+ refreshContextChip();
525
586
  // Human collaborators (phase 8): announce this tab and read who else is here.
526
587
  // An `?agent=<key>` tab is an AGENT's view — it must not heartbeat a human
527
588
  // guest cursor (its writes already book as the agent).
@@ -942,6 +1003,9 @@ function handleCanvasResponseComplete(data: Record<string, unknown>): void {
942
1003
  // ── Agent canvas tool events ──────────────────────────────────
943
1004
 
944
1005
  function handleCanvasLayoutUpdate(data: Record<string, unknown>): void {
1006
+ // Even an inactive board can affect library links. This invalidates a read;
1007
+ // the ownership guards below still control applying the canvas snapshot.
1008
+ memoryGraphRevision.value++;
945
1009
  const layout = data.layout as
946
1010
  | {
947
1011
  nodes?: Array<Record<string, unknown>>;
@@ -951,6 +1015,22 @@ function handleCanvasLayoutUpdate(data: Record<string, unknown>): void {
951
1015
  }
952
1016
  | undefined;
953
1017
  if (!layout?.nodes) return;
1018
+ // Board IDs repeat on A → B → A and after restores. Only the accepted
1019
+ // activation may establish its camera/content baseline.
1020
+ if (typeof data.boardGeneration === 'number') {
1021
+ if (data.catalogEpoch !== catalogEpoch || (boardGeneration !== null && data.boardGeneration < boardGeneration))
1022
+ return;
1023
+ if (boardGeneration === null || data.boardGeneration > boardGeneration) {
1024
+ pendingBoardLayout = data;
1025
+ return;
1026
+ }
1027
+ }
1028
+ // The server sends a switched board's layout before boards-changed. Do not
1029
+ // mount it under the previous owner, even when card IDs happen to match.
1030
+ if (data.boardId !== undefined && data.boardId !== activeBoardId.value) {
1031
+ pendingBoardLayout = data;
1032
+ return;
1033
+ }
954
1034
  layoutRevision += 1;
955
1035
  const shouldApplyViewport = !hasInitialServerLayout.value;
956
1036
  hasInitialServerLayout.value = true;
@@ -986,6 +1066,8 @@ function handleCanvasLayoutUpdate(data: Record<string, unknown>): void {
986
1066
  );
987
1067
 
988
1068
  syncAttentionFromSse({ event: 'canvas-layout-update', data });
1069
+ // Content changes move the brief's size and can clip it (debounced, so a burst fetches once).
1070
+ refreshContextChip(1_000);
989
1071
  }
990
1072
 
991
1073
  function reconnectDelayMs(attempt: number): number {
@@ -1007,7 +1089,8 @@ function handleCanvasFocusNode(data: Record<string, unknown>): void {
1007
1089
  }
1008
1090
 
1009
1091
  function handleCanvasViewportUpdate(data: Record<string, unknown>): void {
1010
- if (ownsCamera()) return;
1092
+ // A delayed echo of this tab's earlier commit must not cancel its newer gesture.
1093
+ if (ownsCamera() || data.clientId === humanClientId) return;
1011
1094
  const viewport = data.viewport as Record<string, unknown> | undefined;
1012
1095
  if (!viewport) return;
1013
1096
  const x = typeof viewport.x === 'number' ? viewport.x : 0;
@@ -1017,6 +1100,40 @@ function handleCanvasViewportUpdate(data: Record<string, unknown>): void {
1017
1100
  replaceViewport({ x, y, scale });
1018
1101
  }
1019
1102
 
1103
+ // Tour steps and camera moves animate locally; a deterministic capture owns
1104
+ // its camera outright and ignores both.
1105
+ function handleCanvasTourStep(data: Record<string, unknown>): void {
1106
+ if (recordingCamera.value) return;
1107
+ // A human stepping in one tab moves tabs already presenting; only a
1108
+ // `present` step (the agent's default) starts presenting elsewhere.
1109
+ if (!presenting.value && data.present !== true) return;
1110
+ const stop = tourStopSchema.safeParse(data.stop);
1111
+ if (stop.success) presentStop(stop.data);
1112
+ }
1113
+
1114
+ function handleCanvasTourExit(): void {
1115
+ if (recordingCamera.value) return;
1116
+ presenting.value = false;
1117
+ presentationError.value = '';
1118
+ }
1119
+
1120
+ function handleCanvasCameraMove(data: Record<string, unknown>): void {
1121
+ if (recordingCamera.value) return;
1122
+ const stop = tourStopSchema.safeParse(data.stop);
1123
+ if (!stop.success) return;
1124
+ try {
1125
+ // The presentation camera is local; otherwise the end camera becomes the
1126
+ // board viewport — unrecorded, so a camera move is never an undo step.
1127
+ playStop(stop.data, presenting.value ? undefined : (end) => commitViewport(end, { recordHistory: false }));
1128
+ } catch {
1129
+ // Target not in this tab's layout yet: take the server's resolved camera.
1130
+ const fallback = cameraSchema.safeParse(data.viewport);
1131
+ if (!fallback.success) return;
1132
+ cancelViewportAnimation();
1133
+ replaceViewport(fallback.data);
1134
+ }
1135
+ }
1136
+
1020
1137
  function handleContextUsage(data: Record<string, unknown>): void {
1021
1138
  const id = 'context-main';
1022
1139
  const existing = nodes.value.get(id);
@@ -1042,15 +1159,17 @@ function handleThemeChanged(data: Record<string, unknown>): void {
1042
1159
  }
1043
1160
 
1044
1161
  function handleContextPinsChanged(data: Record<string, unknown>): void {
1045
- pinsRevision += 1;
1162
+ memoryGraphRevision.value++;
1046
1163
  const nodeIds = Array.isArray(data.nodeIds) ? data.nodeIds.filter((id): id is string => typeof id === 'string') : [];
1047
- replaceContextPinsFromServer(nodeIds);
1164
+ if (contextPinsPendingWrites === 0) replaceContextPinsFromServer(nodeIds);
1048
1165
  syncAttentionFromSse({ event: 'context-pins-changed', data });
1049
1166
  void refreshContextStatus();
1167
+ refreshContextChip();
1050
1168
  }
1051
1169
 
1052
1170
  function handleContextStatusChanged(): void {
1053
1171
  void refreshContextStatus();
1172
+ refreshContextChip();
1054
1173
  }
1055
1174
 
1056
1175
  // AX state changes arrive as per-primitive deltas; rather than reduce them, treat
@@ -1128,13 +1247,22 @@ export const EVENT_HANDLERS: Record<string, (data: Record<string, unknown>) => v
1128
1247
  'ext-app-result': handleExtAppResult,
1129
1248
  'context-pins-changed': handleContextPinsChanged,
1130
1249
  'context-status-changed': handleContextStatusChanged,
1131
- 'boards-changed': applyBoards,
1250
+ 'knowledge-changed': () => void refreshKnowledge(),
1251
+ 'lifecycle-changed': () => void refreshKnowledge(),
1252
+ 'boards-changed': (data) => {
1253
+ applyBoards(data);
1254
+ refreshContextChip();
1255
+ },
1132
1256
  'canvas-layout-update': handleCanvasLayoutUpdate,
1133
1257
  'canvas-focus-node': handleCanvasFocusNode,
1134
1258
  'canvas-viewport-update': handleCanvasViewportUpdate,
1259
+ 'canvas-tour-step': handleCanvasTourStep,
1260
+ 'canvas-tour-exit': handleCanvasTourExit,
1261
+ 'canvas-camera-move': handleCanvasCameraMove,
1135
1262
  'context-usage': handleContextUsage,
1136
1263
  'trace-state': handleTraceState,
1137
1264
  'theme-changed': handleThemeChanged,
1265
+ 'settings-changed': (data) => applyWorkspaceSettings(data.workspaceSettings),
1138
1266
  'canvas-prompt-created': handleCanvasPromptCreated,
1139
1267
  'canvas-prompt-status': handleCanvasPromptStatus,
1140
1268
  'canvas-response-start': handleCanvasResponseStart,
@@ -1174,6 +1302,12 @@ function startPollingTransport(): () => void {
1174
1302
  pollingMode = true;
1175
1303
  pollGeneration += 1;
1176
1304
  const generation = pollGeneration;
1305
+ pollAbort?.abort();
1306
+ const controller = new AbortController();
1307
+ pollAbort = controller;
1308
+ // MCP-app polls are host tool calls, not direct HTTP requests. Keep their
1309
+ // existing cadence; HTTP clients can park one request until an event arrives.
1310
+ const longPolling = !isHostedWorkbench();
1177
1311
  connectionStatus.value = 'connecting';
1178
1312
  // Server-run identity from the last poll response. The event seq counter
1179
1313
  // restarts at 0 on every server boot, so a cursor from a previous run can
@@ -1183,22 +1317,39 @@ function startPollingTransport(): () => void {
1183
1317
 
1184
1318
  const pollOnce = async (): Promise<void> => {
1185
1319
  if (generation !== pollGeneration) return;
1320
+ const requestController = new AbortController();
1321
+ let deadline: ReturnType<typeof setTimeout> | undefined;
1186
1322
  try {
1187
1323
  const params = new URLSearchParams();
1188
- if (pollSeq !== null) params.set('since', String(pollSeq));
1324
+ if (pollSeq !== null) {
1325
+ params.set('since', String(pollSeq));
1326
+ if (longPolling) params.set('waitMs', String(POLL_WAIT_MS));
1327
+ }
1189
1328
  const sid = sessionId.value;
1190
1329
  if (sid) params.set('session', sid);
1191
1330
  const query = params.toString();
1192
- const response = await workbenchFetch(`/api/workbench/poll${query ? `?${query}` : ''}`);
1193
- if (generation !== pollGeneration) return;
1194
- if (!response.ok) throw new Error(`poll failed with HTTP ${response.status}`);
1195
- const body = (await response.json()) as {
1196
- ok: boolean;
1197
- seq: number;
1198
- snapshot: boolean;
1199
- sessionId?: string;
1200
- events: Array<{ event: string; payload: unknown }>;
1201
- };
1331
+ const pinsAtRequest = contextPinsRevision;
1332
+ const body = await Promise.race([
1333
+ (async () => {
1334
+ const response = await workbenchFetch(`/api/workbench/poll${query ? `?${query}` : ''}`, {
1335
+ signal: AbortSignal.any([controller.signal, requestController.signal]),
1336
+ });
1337
+ if (!response.ok) throw new Error(`poll failed with HTTP ${response.status}`);
1338
+ return (await response.json()) as {
1339
+ ok: boolean;
1340
+ seq: number;
1341
+ snapshot: boolean;
1342
+ sessionId?: string;
1343
+ events: Array<{ event: string; payload: unknown }>;
1344
+ };
1345
+ })(),
1346
+ new Promise<never>((_, reject) => {
1347
+ deadline = setTimeout(() => {
1348
+ requestController.abort();
1349
+ reject(new Error('poll request timed out'));
1350
+ }, POLL_REQUEST_TIMEOUT_MS);
1351
+ }),
1352
+ ]);
1202
1353
  if (generation !== pollGeneration) return;
1203
1354
  if (!body.ok) throw new Error('poll returned ok: false');
1204
1355
  const priorSession = lastPollSessionId;
@@ -1222,13 +1373,30 @@ function startPollingTransport(): () => void {
1222
1373
  resetIntents();
1223
1374
  responseToThreadMap.clear();
1224
1375
  }
1225
- for (const entry of body.events) dispatchWorkbenchEvent(entry.event, entry.payload);
1376
+ // A poll may have captured pins before a newer human click or write acknowledgement.
1377
+ // Skip only those pin frames, then read the canonical set so peer changes and server
1378
+ // normalization still arrive even though the event cursor advances past the old batch.
1379
+ const acceptPins = pinsAtRequest === contextPinsRevision && contextPinsPendingWrites === 0;
1380
+ let skippedPins = false;
1381
+ for (const entry of body.events) {
1382
+ if (entry.event === 'context-pins-changed' && !acceptPins) {
1383
+ skippedPins = true;
1384
+ continue;
1385
+ }
1386
+ dispatchWorkbenchEvent(entry.event, entry.payload);
1387
+ }
1388
+ if (skippedPins) {
1389
+ void refreshContextPins();
1390
+ void refreshContextStatus();
1391
+ refreshContextChip();
1392
+ }
1226
1393
  dispatchedOnce = true;
1227
1394
  pollSeq = body.seq;
1228
1395
  reconnectAttempt.value = 0;
1229
1396
  reconnectDelay.value = 0;
1230
1397
  connectionStatus.value = 'connected';
1231
- pollTimer = setTimeout(() => void pollOnce(), POLL_INTERVAL_MS);
1398
+ // Bound busy-stream request rate without imposing a two-second cursor gap.
1399
+ pollTimer = setTimeout(() => void pollOnce(), longPolling ? POLL_LIVE_INTERVAL_MS : POLL_INTERVAL_MS);
1232
1400
  } catch (err) {
1233
1401
  if (generation !== pollGeneration) return;
1234
1402
  console.warn('[sse-bridge] poll transport error:', err);
@@ -1236,6 +1404,8 @@ function startPollingTransport(): () => void {
1236
1404
  reconnectAttempt.value += 1;
1237
1405
  reconnectDelay.value = POLL_ERROR_INTERVAL_MS;
1238
1406
  pollTimer = setTimeout(() => void pollOnce(), POLL_ERROR_INTERVAL_MS);
1407
+ } finally {
1408
+ clearTimeout(deadline);
1239
1409
  }
1240
1410
  };
1241
1411
  void pollOnce();
@@ -1244,6 +1414,8 @@ function startPollingTransport(): () => void {
1244
1414
  pollGeneration += 1;
1245
1415
  pollingMode = false;
1246
1416
  pollSeq = null;
1417
+ controller.abort();
1418
+ if (pollAbort === controller) pollAbort = null;
1247
1419
  if (pollTimer) {
1248
1420
  clearTimeout(pollTimer);
1249
1421
  pollTimer = null;
@@ -1252,6 +1424,8 @@ function startPollingTransport(): () => void {
1252
1424
  }
1253
1425
 
1254
1426
  export function connectSSE(): () => void {
1427
+ boardResyncGeneration++;
1428
+ pendingBoardLayout = null;
1255
1429
  // Host-default theming: a ?theme= param applies before the first server
1256
1430
  // frame so an embedding host panel never flashes the server-global theme.
1257
1431
  initSessionThemeOverride(applyCanvasTheme);
@@ -1302,6 +1476,7 @@ export function connectSSE(): () => void {
1302
1476
 
1303
1477
  for (const [event] of Object.entries(EVENT_HANDLERS)) {
1304
1478
  source.addEventListener(event, (e) => {
1479
+ if (eventSource !== source) return;
1305
1480
  gotFirstEvent = true;
1306
1481
  try {
1307
1482
  dispatchWorkbenchEvent(event, JSON.parse((e as MessageEvent).data));
@@ -1348,6 +1523,8 @@ export function connectSSE(): () => void {
1348
1523
  pollGeneration += 1;
1349
1524
  pollingMode = false;
1350
1525
  pollSeq = null;
1526
+ pollAbort?.abort();
1527
+ pollAbort = null;
1351
1528
  if (pollTimer) {
1352
1529
  clearTimeout(pollTimer);
1353
1530
  pollTimer = null;
@@ -0,0 +1,11 @@
1
+ import { computed, signal } from '@preact/signals';
2
+ import { workspaceSettingsSchema, type WorkspaceSettings } from '../../shared/workspace-settings.js';
3
+
4
+ export const workspaceSettings = signal<WorkspaceSettings | null>(null);
5
+ export const agentSessionsEnabled = computed(() => workspaceSettings.value?.agentSessions ?? false);
6
+ export const knowledgeLifecycleEnabled = computed(() => workspaceSettings.value?.knowledgeLifecycle ?? false);
7
+
8
+ export function applyWorkspaceSettings(value: unknown): void {
9
+ const parsed = workspaceSettingsSchema.safeParse(value);
10
+ if (parsed.success) workspaceSettings.value = parsed.data;
11
+ }