pmx-canvas 0.7.3 → 0.8.2

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 (244) hide show
  1. package/.github/extensions/pmx-canvas/THIRD_PARTY_LICENSES.txt +331 -0
  2. package/.github/extensions/pmx-canvas/extension.mjs +25 -172
  3. package/.github/extensions/pmx-canvas/mcp-bootstrap.ts +198 -0
  4. package/.github/extensions/pmx-canvas/runtime.d.mts +41 -0
  5. package/.github/extensions/pmx-canvas/runtime.json +3 -0
  6. package/.github/extensions/pmx-canvas/runtime.mjs +194 -0
  7. package/.github/extensions/pmx-canvas/start-mcp.mjs +51 -0
  8. package/.github/extensions/pmx-canvas/tools.json +2560 -0
  9. package/CHANGELOG.md +64 -0
  10. package/Readme.md +21 -7
  11. package/dist/canvas/fonts/OFL.txt +93 -0
  12. package/dist/canvas/fonts/ibm-plex-mono-latin-400-normal.woff2 +0 -0
  13. package/dist/canvas/fonts/ibm-plex-mono-latin-500-normal.woff2 +0 -0
  14. package/dist/canvas/fonts/ibm-plex-mono-latin-ext-400-normal.woff2 +0 -0
  15. package/dist/canvas/fonts/ibm-plex-mono-latin-ext-500-normal.woff2 +0 -0
  16. package/dist/canvas/fonts/ibm-plex-sans-latin-400-normal.woff2 +0 -0
  17. package/dist/canvas/fonts/ibm-plex-sans-latin-500-normal.woff2 +0 -0
  18. package/dist/canvas/fonts/ibm-plex-sans-latin-600-normal.woff2 +0 -0
  19. package/dist/canvas/fonts/ibm-plex-sans-latin-ext-400-normal.woff2 +0 -0
  20. package/dist/canvas/fonts/ibm-plex-sans-latin-ext-500-normal.woff2 +0 -0
  21. package/dist/canvas/fonts/ibm-plex-sans-latin-ext-600-normal.woff2 +0 -0
  22. package/dist/canvas/global.css +726 -674
  23. package/dist/canvas/index.js +90 -88
  24. package/dist/canvas/mcp-app.html +7879 -0
  25. package/dist/canvas/mermaid-entry.js +1 -1
  26. package/dist/canvas/surface-theme.css +151 -46
  27. package/dist/json-render/index.css +81 -1
  28. package/dist/json-render/index.js +1 -1
  29. package/dist/types/cli/commands/record.d.ts +1 -1
  30. package/dist/types/client/canvas/NodeContextMark.d.ts +24 -0
  31. package/dist/types/client/canvas/SessionReceipt.d.ts +3 -1
  32. package/dist/types/client/icons.d.ts +1 -2
  33. package/dist/types/client/mcp-app/index.d.ts +1 -0
  34. package/dist/types/client/nodes/HtmlNode.d.ts +1 -0
  35. package/dist/types/client/nodes/ax-node-actions.d.ts +2 -2
  36. package/dist/types/client/nodes/markdown-sanitize.d.ts +2 -0
  37. package/dist/types/client/nodes/use-workbench-asset.d.ts +2 -0
  38. package/dist/types/client/state/attention-store.d.ts +0 -5
  39. package/dist/types/client/state/context-status-store.d.ts +53 -0
  40. package/dist/types/client/state/intent-bridge.d.ts +3 -0
  41. package/dist/types/client/state/near-pin-store.d.ts +16 -0
  42. package/dist/types/client/state/presence-store.d.ts +5 -0
  43. package/dist/types/client/state/session-store.d.ts +33 -3
  44. package/dist/types/client/state/workbench-transport.d.ts +16 -0
  45. package/dist/types/client/theme/tokens.d.ts +1 -3
  46. package/dist/types/client/types.d.ts +9 -0
  47. package/dist/types/json-render/server.d.ts +1 -1
  48. package/dist/types/mcp/app-server.d.ts +3 -0
  49. package/dist/types/mcp/canvas-access.d.ts +1 -1
  50. package/dist/types/mcp/canvas-app.d.ts +4 -0
  51. package/dist/types/mcp/workbench-app.d.ts +4 -0
  52. package/dist/types/server/agent-presence.d.ts +1 -1
  53. package/dist/types/server/ax-state.d.ts +1 -1
  54. package/dist/types/server/canvas-db.d.ts +8 -0
  55. package/dist/types/server/canvas-geometry.d.ts +25 -0
  56. package/dist/types/server/canvas-operations.d.ts +1 -11
  57. package/dist/types/server/canvas-state.d.ts +25 -3
  58. package/dist/types/server/context-brief.d.ts +8 -1
  59. package/dist/types/server/context-reads.d.ts +30 -7
  60. package/dist/types/server/index.d.ts +6 -1
  61. package/dist/types/server/mcp-app-runtime.d.ts +1 -0
  62. package/dist/types/server/operations/http.d.ts +2 -0
  63. package/dist/types/server/operations/invoker.d.ts +4 -2
  64. package/dist/types/server/operations/ops/nodes.d.ts +0 -6
  65. package/dist/types/server/server.d.ts +1 -0
  66. package/dist/types/server/spatial-analysis.d.ts +2 -0
  67. package/dist/types/shared/agent-context.d.ts +22 -0
  68. package/dist/types/shared/agent-presence.d.ts +3 -10
  69. package/dist/types/shared/canvas-app.d.ts +54 -0
  70. package/dist/types/shared/themes.d.ts +1 -1
  71. package/docs/RELEASE.md +7 -3
  72. package/docs/agent-plugin.md +123 -0
  73. package/docs/ax-state-contract.md +40 -0
  74. package/docs/cli.md +7 -2
  75. package/docs/design/AgentContext.dc.html +186 -0
  76. package/docs/design/Before.dc.html +208 -0
  77. package/docs/design/BoardPins.dc.html +151 -0
  78. package/docs/design/Chrome.dc.html +211 -0
  79. package/docs/design/Context.dc.html +239 -0
  80. package/docs/design/Flows.dc.html +181 -0
  81. package/docs/design/Home.dc.html +209 -0
  82. package/docs/design/HomeGraph.dc.html +211 -0
  83. package/docs/design/HomeMap.dc.html +117 -0
  84. package/docs/design/HomeTeam.dc.html +176 -0
  85. package/docs/design/Light.dc.html +28 -0
  86. package/docs/design/Main.dc.html +280 -0
  87. package/docs/design/NearPin.dc.html +260 -0
  88. package/docs/design/Pane600.dc.html +120 -0
  89. package/docs/design/Relations.dc.html +297 -0
  90. package/docs/design/Themes.dc.html +121 -0
  91. package/docs/design/TokenSystem.dc.html +136 -0
  92. package/docs/design/Tokens.dc.html +163 -0
  93. package/docs/design/Types1.dc.html +177 -0
  94. package/docs/design/Types2.dc.html +226 -0
  95. package/docs/design/Types3.dc.html +195 -0
  96. package/docs/design/Uses.dc.html +152 -0
  97. package/docs/design/Vision.dc.html +116 -0
  98. package/docs/design/Zoom.dc.html +82 -0
  99. package/docs/design/canvas.json +274 -0
  100. package/docs/design.md +217 -0
  101. package/docs/environment.md +1 -1
  102. package/docs/http-api.md +86 -16
  103. package/docs/mcp.md +202 -4
  104. package/docs/node-types.md +34 -10
  105. package/docs/plans/plan-009-tech-debt-backlog.md +1 -1
  106. package/docs/plans/plan-015-design-foundation-wave-0.md +177 -0
  107. package/docs/plans/plan-016-context-made-visible-wave-1.md +148 -0
  108. package/docs/product-vision-2026-09.md +47 -7
  109. package/docs/screenshot.png +0 -0
  110. package/docs/sdk.md +1 -0
  111. package/package.json +10 -4
  112. package/plugins/pmx-canvas/LICENSE +21 -0
  113. package/plugins/pmx-canvas/THIRD_PARTY_LICENSES.txt +331 -0
  114. package/plugins/pmx-canvas/assets/preview.png +0 -0
  115. package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/extension.mjs +930 -0
  116. package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/runtime.json +3 -0
  117. package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/runtime.mjs +194 -0
  118. package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/steering-delivery.mjs +79 -0
  119. package/plugins/pmx-canvas/mcp.json +12 -0
  120. package/plugins/pmx-canvas/plugin.json +24 -0
  121. package/plugins/pmx-canvas/runtime.json +3 -0
  122. package/plugins/pmx-canvas/runtime.mjs +194 -0
  123. package/plugins/pmx-canvas/start-mcp.mjs +51 -0
  124. package/plugins/pmx-canvas/tools.json +2560 -0
  125. package/skills/pmx-canvas/SKILL.md +29 -9
  126. package/skills/pmx-canvas/evals/evals.json +62 -0
  127. package/skills/pmx-canvas/references/ax-html-control-surface.md +6 -0
  128. package/skills/pmx-canvas/references/full-reference.md +11 -7
  129. package/skills/pmx-canvas/references/github-copilot-app-adapter.md +20 -8
  130. package/skills/pmx-canvas/references/installing-pmx-canvas.md +6 -6
  131. package/skills/pmx-canvas-testing/SKILL.md +5 -4
  132. package/src/cli/commands/copilot.ts +5 -0
  133. package/src/cli/commands/nodes.ts +3 -2
  134. package/src/cli/commands/view.ts +1 -1
  135. package/src/cli/index.ts +10 -4
  136. package/src/client/App.tsx +6 -1
  137. package/src/client/canvas/AgentPresenceLayer.tsx +3 -3
  138. package/src/client/canvas/AnnotationLayer.tsx +1 -1
  139. package/src/client/canvas/CanvasNode.tsx +32 -49
  140. package/src/client/canvas/CanvasViewport.tsx +0 -2
  141. package/src/client/canvas/CommandPalette.tsx +3 -2
  142. package/src/client/canvas/ContextMenu.tsx +11 -0
  143. package/src/client/canvas/EdgeLayer.tsx +1 -1
  144. package/src/client/canvas/ExportDialog.tsx +33 -6
  145. package/src/client/canvas/IntentLayer.tsx +2 -2
  146. package/src/client/canvas/Minimap.tsx +12 -7
  147. package/src/client/canvas/NodeContextMark.tsx +156 -0
  148. package/src/client/canvas/SessionPanel.tsx +4 -7
  149. package/src/client/canvas/SessionReceipt.tsx +114 -17
  150. package/src/client/canvas/ShortcutOverlay.tsx +6 -1
  151. package/src/client/canvas/SnapshotPanel.tsx +11 -2
  152. package/src/client/canvas/ToolRail.tsx +12 -9
  153. package/src/client/canvas/TopBar.tsx +16 -11
  154. package/src/client/icons.tsx +2 -12
  155. package/src/client/mcp-app/index.tsx +137 -0
  156. package/src/client/mcp-app/style.css +10 -0
  157. package/src/client/nodes/AttachmentNode.tsx +8 -1
  158. package/src/client/nodes/AxStepControls.tsx +1 -1
  159. package/src/client/nodes/ContextNode.tsx +9 -9
  160. package/src/client/nodes/ExtAppFrame.tsx +13 -8
  161. package/src/client/nodes/FileNode.tsx +8 -5
  162. package/src/client/nodes/HtmlNode.tsx +12 -2
  163. package/src/client/nodes/ImageNode.tsx +4 -2
  164. package/src/client/nodes/InlineMarkdownEditor.tsx +2 -1
  165. package/src/client/nodes/LedgerNode.tsx +2 -2
  166. package/src/client/nodes/MarkdownNode.tsx +44 -9
  167. package/src/client/nodes/McpAppNode.tsx +6 -4
  168. package/src/client/nodes/PromptNode.tsx +5 -17
  169. package/src/client/nodes/ResponseNode.tsx +3 -15
  170. package/src/client/nodes/StatusNode.tsx +2 -2
  171. package/src/client/nodes/StatusSummary.tsx +3 -1
  172. package/src/client/nodes/TraceNode.tsx +6 -4
  173. package/src/client/nodes/WebpageNode.tsx +1 -1
  174. package/src/client/nodes/ax-node-actions.ts +2 -2
  175. package/src/client/nodes/iframe-document-url.ts +3 -2
  176. package/src/client/nodes/markdown-sanitize.ts +10 -0
  177. package/src/client/nodes/surface-url.ts +10 -0
  178. package/src/client/nodes/use-surface-frame.ts +3 -1
  179. package/src/client/nodes/use-workbench-asset.ts +26 -0
  180. package/src/client/state/attention-bridge.ts +0 -8
  181. package/src/client/state/attention-store.ts +0 -17
  182. package/src/client/state/canvas-store.ts +4 -2
  183. package/src/client/state/context-status-store.ts +66 -0
  184. package/src/client/state/human-store.ts +2 -0
  185. package/src/client/state/iframe-mode.ts +5 -0
  186. package/src/client/state/intent-bridge.ts +10 -4
  187. package/src/client/state/near-pin-store.ts +53 -0
  188. package/src/client/state/presence-store.ts +5 -10
  189. package/src/client/state/session-store.ts +106 -9
  190. package/src/client/state/sse-bridge.ts +26 -7
  191. package/src/client/state/theme-override.ts +1 -1
  192. package/src/client/state/workbench-transport.ts +36 -0
  193. package/src/client/theme/global.css +726 -674
  194. package/src/client/theme/surface-theme.css +151 -46
  195. package/src/client/theme/tokens.ts +7 -11
  196. package/src/client/types.ts +10 -0
  197. package/src/json-render/renderer/index.css +6 -1
  198. package/src/json-render/renderer/index.tsx +0 -1
  199. package/src/json-render/server.ts +71 -14
  200. package/src/mcp/app-server.ts +64 -0
  201. package/src/mcp/canvas-access.ts +14 -11
  202. package/src/mcp/canvas-app.ts +318 -0
  203. package/src/mcp/server.ts +1 -1
  204. package/src/mcp/workbench-app.ts +211 -0
  205. package/src/server/agent-presence.ts +45 -6
  206. package/src/server/ax-context.ts +1 -1
  207. package/src/server/ax-state.ts +1 -1
  208. package/src/server/board-export.ts +5 -2
  209. package/src/server/canvas-db.ts +46 -12
  210. package/src/server/canvas-geometry.ts +64 -0
  211. package/src/server/canvas-operations.ts +7 -25
  212. package/src/server/canvas-state.ts +105 -15
  213. package/src/server/canvas-validation.ts +19 -4
  214. package/src/server/context-brief.ts +39 -2
  215. package/src/server/context-reads.ts +87 -15
  216. package/src/server/demo-state.json +31 -31
  217. package/src/server/html-surface.ts +4 -5
  218. package/src/server/index.ts +41 -5
  219. package/src/server/mcp-app-runtime.ts +11 -0
  220. package/src/server/operations/composites.ts +4 -3
  221. package/src/server/operations/http.ts +4 -4
  222. package/src/server/operations/invoker.ts +13 -4
  223. package/src/server/operations/mcp.ts +6 -0
  224. package/src/server/operations/ops/ax-read.ts +1 -1
  225. package/src/server/operations/ops/ax-timeline.ts +37 -0
  226. package/src/server/operations/ops/boards.ts +1 -1
  227. package/src/server/operations/ops/canvas-wire.ts +3 -6
  228. package/src/server/operations/ops/intent.ts +31 -2
  229. package/src/server/operations/ops/json-render.ts +6 -2
  230. package/src/server/operations/ops/nodes.ts +8 -76
  231. package/src/server/operations/ops/query.ts +8 -2
  232. package/src/server/operations/ops/snapshots.ts +4 -1
  233. package/src/server/operations/ops/viewport.ts +29 -5
  234. package/src/server/operations/registry.ts +41 -5
  235. package/src/server/scope-fence.ts +55 -10
  236. package/src/server/server.ts +83 -5
  237. package/src/server/spatial-analysis.ts +2 -2
  238. package/src/server/workboard.ts +6 -1
  239. package/src/{server → shared}/agent-context.ts +2 -2
  240. package/src/shared/agent-presence.ts +3 -15
  241. package/src/shared/canvas-app.ts +62 -0
  242. package/src/shared/themes.ts +5 -5
  243. package/src/client/canvas/FocusFieldLayer.tsx +0 -116
  244. package/src/client/canvas/kind-colors.ts +0 -23
@@ -12,11 +12,11 @@
12
12
  * the one-shot boot migration into SQLite was retired.
13
13
  */
14
14
  import { type Tour } from '../shared/tour.js';
15
- import { type CanvasBoard, type PersistedCanvasState, type CanvasTheme, type AxTimelineQuery } from './canvas-db.js';
15
+ import { type CanvasBoard, type ContextPinMeta, type PersistedCanvasState, type CanvasTheme, type AxTimelineQuery } from './canvas-db.js';
16
16
  import { type DocumentImport, type DocumentImportStatus, type ImportSection, type Attachment } from './document-import.js';
17
17
  import { type PmxAxActivityKind, type PmxAxElicitation, type PmxAxModeRequest, type PmxAxMode, type PmxAxCommandDescriptor, type PmxAxPolicy, type PmxAxFocusState, type PmxAxSource, type PmxAxState, type PmxAxWorkItem, type PmxAxWorkItemStatus, type PmxAxApprovalGate, type PmxAxReviewAnnotation, type PmxAxReviewKind, type PmxAxReviewSeverity, type PmxAxReviewStatus, type PmxAxReviewAnchorType, type PmxAxReviewRegion, type PmxAxEvent, type PmxAxEventKind, type PmxAxEvidence, type PmxAxEvidenceKind, type PmxAxSteeringMessage, type PmxAxHostCapability, type PmxAxTimelineSummary } from './ax-state.js';
18
18
  import { type ActorAttribution } from './attribution.js';
19
- import { type ContextRead, type ContextReadConsumerSummary, type ContextReadInput } from './context-reads.js';
19
+ import { type ContextRead, type ContextReadConsumerSummary, type ContextReadInput, type NodeReadStatus } from './context-reads.js';
20
20
  export declare const PMX_CANVAS_DIR = ".pmx-canvas";
21
21
  export interface PersistedBlobRef {
22
22
  __pmxCanvasBlob: 'v1';
@@ -171,6 +171,8 @@ declare class CanvasStateManager {
171
171
  private _tour;
172
172
  private _theme;
173
173
  private _contextPinnedNodeIds;
174
+ /** Who pinned each pin, when and why; kept only for ids in `_contextPinnedNodeIds`. */
175
+ private _contextPinMeta;
174
176
  private _workspaceRoot;
175
177
  private _contentRevision;
176
178
  private _revisionFloor;
@@ -185,12 +187,15 @@ declare class CanvasStateManager {
185
187
  */
186
188
  onChange(cb: (type: CanvasChangeType) => void): () => void;
187
189
  private _workItemsChangedListener;
190
+ /** Single slot (architecture rule 8): server.ts tells the workbench a read landed. Never MCP. */
191
+ private _contextReadListener;
188
192
  /**
189
193
  * Register THE work-item change listener (single slot, last-write-wins).
190
194
  * Fired after addWorkItem/updateWorkItem completes (including the node
191
195
  * status mirror), so live views like the workboard can rebuild from the
192
196
  * fresh work-item list.
193
197
  */
198
+ setContextReadListener(listener: ((boardId: string | null) => void) | null): void;
194
199
  setWorkItemsChangedListener(listener: (() => void) | null): void;
195
200
  private notifyWorkItemsChanged;
196
201
  private notifyChange;
@@ -609,6 +614,12 @@ declare class CanvasStateManager {
609
614
  getAxTimelineSummary(): PmxAxTimelineSummary;
610
615
  /** Records one agent context read (diagnostics; never notifies, so a read cannot trigger reads). */
611
616
  recordContextRead(input: ContextReadInput, boardId?: string | null): ContextRead | null;
617
+ /** The delivered ids that are nodes on the read board, each with its content revision now. */
618
+ private readRevisions;
619
+ /** Per node on a board, the latest agent read that delivered its content. */
620
+ getNodeReadStatus(boardId?: string | null): NodeReadStatus[];
621
+ /** Nodes on a board whose content an agent read at or after `since` (ISO). */
622
+ getReadNodeIdsSince(boardId: string | null, since: string): string[];
612
623
  getContextReads(limit?: number): {
613
624
  reads: ContextRead[];
614
625
  summary: ContextReadConsumerSummary[];
@@ -619,7 +630,18 @@ declare class CanvasStateManager {
619
630
  steering: PmxAxSteeringMessage[];
620
631
  summary: PmxAxTimelineSummary;
621
632
  };
622
- setContextPins(nodeIds: string[]): void;
633
+ /**
634
+ * Replace the pin set. Newly pinned nodes are attributed to the current
635
+ * actor (with `reason` when given); pins that stay keep their attribution.
636
+ * `restore` re-applies saved attribution, so undo does not re-attribute.
637
+ */
638
+ setContextPins(nodeIds: string[], options?: {
639
+ reason?: string;
640
+ restore?: Map<string, ContextPinMeta>;
641
+ }): void;
642
+ /** Who pinned each current pin, when and why (pins with an unknown pinner are absent). */
643
+ getContextPinMeta(): Record<string, ContextPinMeta>;
644
+ private pinMetaRecord;
623
645
  clearContextPins(): void;
624
646
  /** Move child nodes into a group. Sets data.parentGroup on children and data.children on the group. */
625
647
  groupNodes(groupId: string, childIds: string[], options?: GroupNodesOptions): boolean;
@@ -1,5 +1,7 @@
1
1
  import type { CanvasNodeState, NodeDeletionTombstone } from './canvas-state.js';
2
- export type ContextBriefReason = 'pinned' | 'changed' | 'human' | 'ask' | 'steer' | 'linked' | 'category';
2
+ export type ContextBriefReason = 'pinned' | 'near' | 'changed' | 'human' | 'ask' | 'steer' | 'linked' | 'category';
3
+ /** A near entry carries a short summary, never full content: pin the node to send that. */
4
+ export declare const NEAR_SUMMARY_LENGTH = 280;
3
5
  export interface ContextBriefSourceEntry {
4
6
  sourceBoardId: string;
5
7
  nodeId: string;
@@ -57,6 +59,11 @@ export interface CompiledContextEntry {
57
59
  text: string;
58
60
  /** A linked board's pin title is discovery metadata, not delivery of that pinned card. */
59
61
  titleOnly?: true;
62
+ /** reason "near": the pin this node sits next to on the board (docs/design/NearPin.dc.html). */
63
+ near?: {
64
+ pinNodeId: string;
65
+ pinTitle: string;
66
+ };
60
67
  /** The body was shortened to fit; the same revision remains eligible on the next pull. */
61
68
  truncated?: true;
62
69
  provenance?: {
@@ -22,14 +22,29 @@ export interface ContextRead {
22
22
  agentId: string | null;
23
23
  pinnedNodeIds: string[];
24
24
  deliveredNodeIds: string[];
25
+ /** Every node on the read board whose content was delivered, with its content revision at read time. */
26
+ readNodes: Record<string, number>;
25
27
  bytes: number;
26
28
  /** The board open when the read happened (null on Home). */
27
29
  boardId: string | null;
28
30
  }
29
- export type ContextReadInput = Omit<ContextRead, 'seq' | 'id' | 'at' | 'boardId'> & {
31
+ export type ContextReadInput = Omit<ContextRead, 'seq' | 'id' | 'at' | 'boardId' | 'readNodes'> & {
30
32
  /** Explicit read target, captured before asynchronous formatting/proxy work. */
31
33
  boardId?: string | null;
34
+ /** Ids of every serialized node in what the reader received; the server keeps those on the board. */
35
+ readNodeIds?: string[];
32
36
  };
37
+ /** One node's read state on a board: the latest agent read that delivered its content. */
38
+ export interface NodeReadStatus {
39
+ nodeId: string;
40
+ lastReadAt: string;
41
+ /** Who read it: consumer, else agent id, else transport label. */
42
+ lastReadBy: string;
43
+ /** The node's content revision in that read; newer current revision means "changed since read". */
44
+ readRevision: number;
45
+ /** How many retained agent reads delivered its content. */
46
+ readCount: number;
47
+ }
33
48
  export interface ContextReadConsumerSummary {
34
49
  consumer: string;
35
50
  reads: number;
@@ -39,14 +54,22 @@ export interface ContextReadConsumerSummary {
39
54
  resources: Record<string, number>;
40
55
  }
41
56
  /**
42
- * Pinned nodes whose serialized node (an object carrying `"id": "<id>"`) is in
43
- * what the reader received. A bare id list, a title, or a clipped-off node does
44
- * not count — the agent got the pin's name, not its content.
57
+ * Ids of every serialized node (an object carrying `"id": "<id>"`, or a
58
+ * `nodeId` with its text) in what the reader received. A bare id list, a
59
+ * title, or a clipped-off node does not count — the agent got the node's
60
+ * name, not its content. Non-node ids (edges, intents) are dropped by the
61
+ * caller against the board's nodes.
45
62
  */
63
+ export declare function deliveredIds(payloadText: string): Set<string>;
64
+ /** Pinned nodes whose content is in what the reader received (see `deliveredIds`). */
46
65
  export declare function deliveredPinnedIds(pinnedNodeIds: string[], payloadText: string): string[];
47
- export declare function contextReadFromPayload(base: Omit<ContextReadInput, 'deliveredNodeIds' | 'bytes'>, payload: unknown): ContextReadInput;
48
- export declare const CONTEXT_READS_SCHEMA_SQL = "\n CREATE TABLE IF NOT EXISTS context_reads (\n seq INTEGER PRIMARY KEY AUTOINCREMENT,\n id TEXT NOT NULL UNIQUE,\n at TEXT NOT NULL,\n channel TEXT NOT NULL,\n resource TEXT NOT NULL,\n source TEXT NOT NULL,\n consumer TEXT,\n agent_id TEXT,\n pinned_node_ids TEXT NOT NULL DEFAULT '[]',\n delivered_node_ids TEXT NOT NULL DEFAULT '[]',\n bytes INTEGER NOT NULL DEFAULT 0,\n board_id TEXT\n );\n";
49
- export declare function appendContextReadToDB(db: Database, input: ContextReadInput, boardId: string | null): ContextRead;
66
+ export declare function contextReadFromPayload(base: Omit<ContextReadInput, 'deliveredNodeIds' | 'bytes' | 'readNodeIds'>, payload: unknown): ContextReadInput;
67
+ export declare const CONTEXT_READS_SCHEMA_SQL = "\n CREATE TABLE IF NOT EXISTS context_reads (\n seq INTEGER PRIMARY KEY AUTOINCREMENT,\n id TEXT NOT NULL UNIQUE,\n at TEXT NOT NULL,\n channel TEXT NOT NULL,\n resource TEXT NOT NULL,\n source TEXT NOT NULL,\n consumer TEXT,\n agent_id TEXT,\n pinned_node_ids TEXT NOT NULL DEFAULT '[]',\n delivered_node_ids TEXT NOT NULL DEFAULT '[]',\n bytes INTEGER NOT NULL DEFAULT 0,\n board_id TEXT,\n read_nodes TEXT NOT NULL DEFAULT '{}'\n );\n";
68
+ export declare function appendContextReadToDB(db: Database, input: ContextReadInput, boardId: string | null, readNodes?: Record<string, number>): ContextRead;
69
+ /** Per node on a board, the latest agent read that delivered its content (newest row wins). */
70
+ export declare function loadNodeReadStatusFromDB(db: Database, boardId: string): NodeReadStatus[];
71
+ /** Every node on a board whose content an agent read at or after `since` (ISO). */
72
+ export declare function loadReadNodeIdsSince(db: Database, boardId: string, since: string): string[];
50
73
  /** Newest first. The summary covers every retained row, not just the returned page. */
51
74
  export declare function loadContextReadsFromDB(db: Database, limit?: number): {
52
75
  reads: ContextRead[];
@@ -42,6 +42,8 @@ export declare class PmxCanvas extends EventEmitter {
42
42
  start(options?: {
43
43
  open?: boolean;
44
44
  automationWebView?: boolean | CanvasAutomationWebViewOptions;
45
+ /** Embedded startup must not launch external MCP backends from saved nodes. */
46
+ allowExternalMcp?: boolean;
45
47
  /**
46
48
  * Bind a nearby free port when the preferred one is taken instead of
47
49
  * failing. Default false (an explicit SDK port is honored exactly); the
@@ -293,6 +295,8 @@ export declare class PmxCanvas extends EventEmitter {
293
295
  restoreBackup(file: string): Promise<void>;
294
296
  /** The context read log: which canvas context each agent read and which pinned nodes reached it. */
295
297
  getContextReads(limit?: number): ReturnType<typeof canvasState.getContextReads>;
298
+ /** Per node on a board: when an agent last read its content, who, and the content revision it read. */
299
+ getNodeReadStatus(board?: string): ReturnType<typeof canvasState.getNodeReadStatus>;
296
300
  listWorkItems(): PmxAxWorkItem[];
297
301
  addWorkItem(input: {
298
302
  title: string;
@@ -519,7 +523,8 @@ export declare class PmxCanvas extends EventEmitter {
519
523
  applied: number;
520
524
  skipped: number;
521
525
  };
522
- setContextPins(nodeIds: string[], mode?: 'set' | 'add' | 'remove'): {
526
+ /** `reason` is kept with newly pinned nodes and shown to the human on hover. */
527
+ setContextPins(nodeIds: string[], mode?: 'set' | 'add' | 'remove', reason?: string): {
523
528
  count: number;
524
529
  nodeIds: string[];
525
530
  };
@@ -1,5 +1,6 @@
1
1
  import type { CallToolResult, ListPromptsResult, ListResourcesResult, ListResourceTemplatesResult, ListToolsResult, ReadResourceResult, Tool } from '@modelcontextprotocol/sdk/types.js';
2
2
  import type { McpUiResourceMeta } from '@modelcontextprotocol/ext-apps';
3
+ export declare function withoutExternalMcp<T>(run: () => T): T;
3
4
  export interface ExternalMcpHttpTransportConfig {
4
5
  type: 'http';
5
6
  url: string;
@@ -1,3 +1,4 @@
1
+ export declare function matchOperationPath(template: string, pathname: string): Record<string, string> | null;
1
2
  /**
2
3
  * Shared body reader: preserves the parsed JSON value as-is (object, array,
3
4
  * or primitive) — per-op `readInput` decides what to do with non-object
@@ -5,4 +6,5 @@
5
6
  * parse is a 400 (OperationError), never a silent empty input.
6
7
  */
7
8
  export declare function readJsonValue(req: Request): Promise<unknown>;
9
+ export declare function readOperationInput(req: Request, params: Record<string, string>, url: URL): Promise<Record<string, unknown>>;
8
10
  export declare function dispatchOperationRoute(req: Request, url: URL): Promise<Response | null>;
@@ -9,18 +9,20 @@ export interface OperationInvoker {
9
9
  export declare function agentSourceLabel(fallback: string): string;
10
10
  /** Runs operations in-process against the shared canvasState singleton. */
11
11
  export declare class LocalOperationInvoker implements OperationInvoker {
12
+ private readonly allowExternalMcp;
12
13
  private readonly source;
13
14
  private readonly proxiedRead;
14
15
  /** `source` labels this caller's agent presence ('mcp', 'sdk', …). */
15
- constructor(source?: string);
16
+ constructor(source?: string, allowExternalMcp?: boolean);
16
17
  invoke(name: string, input: Record<string, unknown>): Promise<unknown>;
17
18
  }
18
19
  /** Builds the HTTP request from the op's route template (`:id` from input, GET flags to query). */
19
20
  export declare class HttpOperationInvoker implements OperationInvoker {
21
+ private readonly allowExternalMcp;
20
22
  private readonly baseUrl;
21
23
  private readonly source;
22
24
  private readonly proxiedRead;
23
25
  /** `source` labels this caller's agent presence on the server ('cli', 'mcp', …). */
24
- constructor(baseUrl: string, source?: string);
26
+ constructor(baseUrl: string, source?: string, allowExternalMcp?: boolean);
25
27
  invoke(name: string, rawInput: Record<string, unknown>): Promise<unknown>;
26
28
  }
@@ -11,12 +11,6 @@ export declare function isRecord(value: unknown): value is Record<string, unknow
11
11
  export declare function pickFiniteNumber(record: Record<string, unknown>, key: string): number | undefined;
12
12
  export declare function getRecord(value: unknown): Record<string, unknown> | undefined;
13
13
  export declare function pickPositiveNumber(record: Record<string, unknown>, key: string): number | undefined;
14
- export declare function resolveCreateGeometry(body: Record<string, unknown>): {
15
- x?: number;
16
- y?: number;
17
- width?: number;
18
- height?: number;
19
- };
20
14
  export declare function setGroupChildrenFromApi(groupId: string, childIds: string[]): boolean;
21
15
  export declare function nodeAppSessionId(node: CanvasNodeState | undefined): string | null;
22
16
  export declare function closeNodeAppSession(node: CanvasNodeState | undefined): void;
@@ -108,6 +108,7 @@ export interface CanvasServerOptions {
108
108
  workspaceRoot?: string;
109
109
  autoOpenBrowser?: boolean;
110
110
  allowPortFallback?: boolean;
111
+ allowExternalMcp?: boolean;
111
112
  }
112
113
  export declare function startCanvasServer(options?: CanvasServerOptions): string | null;
113
114
  export declare function stopCanvasServer(): void;
@@ -68,6 +68,8 @@ export interface SpatialContext {
68
68
  }[];
69
69
  annotations: SpatialAnnotationContext[];
70
70
  }
71
+ /** Euclidean distance between two node centers */
72
+ export declare function centerDistance(a: CanvasNodeState, b: CanvasNodeState): number;
71
73
  /**
72
74
  * Detect proximity clusters using single-linkage clustering.
73
75
  * Two nodes are "close" if their edge-to-edge gap is within the threshold.
@@ -0,0 +1,22 @@
1
+ import type { CanvasNodeState } from '../server/canvas-state.js';
2
+ export interface AgentContextNode {
3
+ id: string;
4
+ type: CanvasNodeState['type'];
5
+ kind: string;
6
+ title: string | null;
7
+ content: string | null;
8
+ metadata?: Record<string, unknown>;
9
+ position?: {
10
+ x: number;
11
+ y: number;
12
+ };
13
+ }
14
+ interface AgentContextOptions {
15
+ defaultTextLength?: number;
16
+ webpageTextLength?: number;
17
+ includePosition?: boolean;
18
+ }
19
+ export declare function summarizeNodeForAgentContext(node: CanvasNodeState, options?: AgentContextOptions): string;
20
+ export declare function serializeNodeForAgentContext(node: CanvasNodeState, options?: AgentContextOptions): AgentContextNode;
21
+ export declare function buildAgentContextPreamble(nodes: CanvasNodeState[], options?: AgentContextOptions): string;
22
+ export {};
@@ -1,7 +1,7 @@
1
1
  /**
2
- * Agent presence — the contract behind every agent surface in the rail chrome
3
- * (rail-chrome-v2 phase 2). Shared by server and client so the two cannot
4
- * drift. See design/rail-chrome-v2/PLAN.md, "Phase 2 contract".
2
+ * Agent presence — the contract behind every agent surface in the rail chrome.
3
+ * Shared by server and client so the two cannot drift. See
4
+ * docs/ax-state-contract.md, "Agent presence (contract)".
5
5
  *
6
6
  * Presence is DERIVED from feeds that already exist (AX activity ingest,
7
7
  * agent-originated mutations) plus an explicit set for adapters with richer
@@ -121,10 +121,3 @@ export declare function externalWriters(presences: readonly AgentPresence[]): Ag
121
121
  export declare function estimateTokens(text: string): number;
122
122
  /** Chip label per phase — shared by the top-bar chip and the on-canvas cursor chip. */
123
123
  export declare function agentPhaseLabel(presence: Pick<AgentPresence, 'phase' | 'detail'>): string;
124
- /**
125
- * Stable per-agent identity hue (0-359) from the writer key. Phase colors
126
- * kept telling the human WHAT an agent is doing while erasing WHO — two
127
- * thinking agents rendered identically. Identity rides the glyph/border;
128
- * phase stays on dots and labels.
129
- */
130
- export declare function agentIdentityHue(key: string): number;
@@ -0,0 +1,54 @@
1
+ import { z } from 'zod';
2
+ export declare const CANVAS_APP_URI = "ui://pmx/canvas";
3
+ export declare const CANVAS_APP_WRITE_CONTRACT = "pmx-embedded-workbench-v4";
4
+ export declare const canvasAppSnapshotSchema: z.ZodObject<{
5
+ boardId: z.ZodNullable<z.ZodString>;
6
+ boardName: z.ZodString;
7
+ boards: z.ZodArray<z.ZodObject<{
8
+ id: z.ZodString;
9
+ name: z.ZodString;
10
+ }, z.core.$strip>>;
11
+ revision: z.ZodString;
12
+ nodes: z.ZodArray<z.ZodObject<{
13
+ id: z.ZodString;
14
+ type: z.ZodString;
15
+ title: z.ZodString;
16
+ text: z.ZodString;
17
+ contentRevision: z.ZodNumber;
18
+ editableContent: z.ZodOptional<z.ZodString>;
19
+ position: z.ZodObject<{
20
+ x: z.ZodNumber;
21
+ y: z.ZodNumber;
22
+ }, z.core.$strip>;
23
+ size: z.ZodObject<{
24
+ width: z.ZodNumber;
25
+ height: z.ZodNumber;
26
+ }, z.core.$strip>;
27
+ image: z.ZodOptional<z.ZodString>;
28
+ }, z.core.$strip>>;
29
+ edges: z.ZodArray<z.ZodObject<{
30
+ id: z.ZodString;
31
+ from: z.ZodString;
32
+ to: z.ZodString;
33
+ label: z.ZodString;
34
+ }, z.core.$strip>>;
35
+ pinnedNodeIds: z.ZodArray<z.ZodString>;
36
+ }, z.core.$strip>;
37
+ export type CanvasAppSnapshot = z.infer<typeof canvasAppSnapshotSchema>;
38
+ /** Share only curated text, never image bytes or the whole board's contents. */
39
+ export declare function canvasAppContext(snapshot: Pick<CanvasAppSnapshot, 'boardId' | 'boardName' | 'pinnedNodeIds'> & {
40
+ nodes: Pick<CanvasAppSnapshot['nodes'][number], 'id' | 'type' | 'title' | 'text' | 'contentRevision'>[];
41
+ }, selectedIds: string[], focusedIds?: string[]): {
42
+ boardId: string | null;
43
+ boardName: string;
44
+ selectedNodeIds: string[];
45
+ focusedNodeIds: string[];
46
+ pinnedNodeIds: string[];
47
+ nodes: {
48
+ id: string;
49
+ type: string;
50
+ title: string;
51
+ contentRevision: number;
52
+ text: string;
53
+ }[];
54
+ };
@@ -9,7 +9,7 @@
9
9
  * theme picker, server validation, and surface documents pick it up from this
10
10
  * list.
11
11
  */
12
- export declare const CANVAS_THEMES: readonly ["dark", "light", "high-contrast", "midnight", "sepia", "arctic", "ember", "forest", "volt"];
12
+ export declare const CANVAS_THEMES: readonly ["harbor", "daylight", "high-contrast", "midnight", "sepia", "arctic", "ember", "forest", "volt"];
13
13
  export type CanvasThemeName = (typeof CANVAS_THEMES)[number];
14
14
  export declare function isCanvasTheme(value: unknown): value is CanvasThemeName;
15
15
  export declare function normalizeCanvasThemeName(value: unknown, fallback?: CanvasThemeName): CanvasThemeName;
package/docs/RELEASE.md CHANGED
@@ -11,6 +11,9 @@ single source of truth for the release dance — see also
11
11
 
12
12
  1. Land all the changes you want in the release on `main` with green CI.
13
13
  2. Bump `package.json` `version`.
14
+ Run `bun run build:plugin` to update the Agent Plugins manifest, canonical adapter runtime
15
+ pin, and generated plugin copies. Publish that exact npm runtime version before advertising
16
+ a GitHub plugin release pinned to it.
14
17
  3. Add a `## [X.Y.Z]` block to `CHANGELOG.md`.
15
18
  4. Commit, push, wait for `test.yml` green.
16
19
  5. `git tag -a vX.Y.Z -m "..." && git push origin vX.Y.Z` →
@@ -40,9 +43,10 @@ bun run pack:dry-run # confirms the tarball shape
40
43
  do not call `bun x playwright test` directly; it fails before test
41
44
  discovery with a `.esm.preflight` loader error (ERR-20260508-001).
42
45
 
43
- The Playwright run includes the `pane-600` project
44
- (`tests/e2e/reference-pane.pw.ts`): the reference surface, Chromium at
45
- 600 px. Every node type must paint there or the release does not ship.
46
+ The reference cases (`tests/e2e/reference-pane.pw.ts`) run in three
47
+ Playwright projects, `narrow-600`, `middle-1024` and `wide-1920`: the
48
+ workbench lives anywhere from a narrow side pane to a full window. Every
49
+ node type must paint at all three or the release does not ship.
46
50
  Embedded WebKit panes (the Copilot app) stay best-effort.
47
51
 
48
52
  `bun run test:e2e-cli` starts a local server in a fresh temp workspace
@@ -0,0 +1,123 @@
1
+ # PMX Canvas agent plugin
2
+
3
+ `plugins/pmx-canvas/` is a self-contained [Agent Plugins 1.0](https://agent-plugins.org/)
4
+ package. It contains only PMX Canvas: the portable MCP server configuration and a native
5
+ canvas extension for the GitHub Copilot app. It does not include frame integrations,
6
+ general-purpose skills, or custom agents.
7
+
8
+ ## Client support
9
+
10
+ | Client | Experience |
11
+ | --- | --- |
12
+ | GitHub Copilot app | PMX MCP tools and the native PMX Canvas side panel |
13
+ | Other Agent Plugins-compatible clients | PMX MCP tools; open the workbench in a browser or the client's browser surface |
14
+ | Clients without Agent Plugins support | Configure the MCP launcher manually using the client's MCP settings |
15
+
16
+ The native Copilot canvas is **not** a portable capability. Other clients ignore the
17
+ `com.github.copilot/` namespace. Supporting the package format also does not guarantee a
18
+ client has an embedded browser, canvas panel, or host-specific steering adapter.
19
+
20
+ ## Install
21
+
22
+ The local package can be loaded without changing the user's installed plugins:
23
+
24
+ ```bash
25
+ copilot --plugin-dir /absolute/path/to/pmx-canvas/plugins/pmx-canvas
26
+ ```
27
+
28
+ Once the plugin directory is published in the GitHub repository, install it with:
29
+
30
+ ```bash
31
+ copilot plugin install pskoett/pmx-canvas:plugins/pmx-canvas
32
+ ```
33
+
34
+ In the Copilot app, enable the plugin and start a new project session. Open the **PMX Canvas**
35
+ canvas. Ask the agent to connect PMX to the active project; it calls `canvas_connect_workspace`
36
+ with the project's absolute directory before using board tools. Do not separately install its
37
+ project adapter or configure a second PMX MCP server.
38
+ An existing project adapter can coexist with the plugin canvas. If both declare `pmx-canvas`,
39
+ select the provider explicitly when opening: `plugin:pmx-canvas:pmx-canvas` for this plugin,
40
+ or `project:pmx-canvas` for the project extension. An unqualified open can be ambiguous.
41
+
42
+ Other clients should install the same plugin directory using their Agent Plugins support.
43
+ For a manual MCP setup, invoke `bun /absolute/path/to/plugin/start-mcp.mjs` from the intended
44
+ project directory. The launcher returns MCP JSON-RPC on stdout; diagnostics go to stderr.
45
+
46
+ ## Connect the workspace
47
+
48
+ Agent Plugins launches MCP processes from the **plugin installation directory**, not the active
49
+ project. Installing or initializing this plugin therefore does not create a board or start a
50
+ daemon. Before any board operation, the agent calls:
51
+
52
+ ```json
53
+ {
54
+ "name": "canvas_connect_workspace",
55
+ "arguments": { "workspaceRoot": "/absolute/path/to/project" }
56
+ }
57
+ ```
58
+
59
+ The result contains `serverUrl` and `workbenchUrl`. Other harnesses can open `workbenchUrl`
60
+ in a browser. Copilot's native panel resolves the same workspace server.
61
+
62
+ Board tools are advertised from a generated catalog so clients with a fixed initial tool list
63
+ can discover them, but refuse execution until connected. Relative roots and the plugin
64
+ installation directory are rejected. A connected MCP session cannot silently switch projects;
65
+ start a new session for a different workspace.
66
+
67
+ Clients advertising exactly one standard MCP file root can connect automatically. An explicit
68
+ absolute `PMX_CANVAS_WORKSPACE_ROOT` in the MCP process environment also permits auto-connection.
69
+ Neither `process.cwd()` nor an inherited `PWD` is used to guess the project.
70
+
71
+ ## Runtime and workspace ownership
72
+
73
+ [Bun](https://bun.sh/) must be installed and available on the host's `PATH`. No globally
74
+ installed `pmx-canvas` executable is required. The first workspace connection downloads the exact package
75
+ version in `runtime.json` using `bun x --bun pmx-canvas@<version>`; it therefore needs npm
76
+ access unless that version is already cached.
77
+
78
+ Both the native canvas and the MCP launcher use the same resolver. It discovers the
79
+ workspace's daemon port from `.pmx-canvas/daemon-<port>.pid` filenames, checks `/health`
80
+ ownership, and serializes concurrent startup with `.pmx-canvas/plugin-start.lock`.
81
+ The MCP subprocess receives the selected `PMX_CANVAS_URL` and an absolute workspace root.
82
+ A foreign server on the preferred port is never treated as this project's server. Each forwarded
83
+ MCP request rechecks ownership; a server replaced by another workspace is refused.
84
+
85
+ The adapter uses the session's **working directory**, not Copilot's session-state
86
+ `workspacePath` or the plugin installation directory. Boards remain in the project's
87
+ `.pmx-canvas/canvas.db`.
88
+
89
+ The daemon belongs to the workspace and stays running when one client closes, so closing
90
+ the native panel cannot break another MCP client. Stop it explicitly from the project:
91
+
92
+ ```bash
93
+ bun x --bun pmx-canvas@<version> serve stop --port=<port>
94
+ ```
95
+
96
+ Use the exact version from `runtime.json` and the port from the panel's `status` action.
97
+ `PMX_CANVAS_PORT` selects a preferred port; `PMX_CANVAS_URL` targets an existing server.
98
+ An explicit URL that fails ownership checks is reported rather than silently replaced.
99
+
100
+ ## Package maintenance
101
+
102
+ The plugin's adapter copies are generated from `.github/extensions/pmx-canvas/`:
103
+
104
+ ```bash
105
+ bun run build:plugin
106
+ bun test tests/unit/agent-plugin.test.js tests/unit/agent-plugin-mcp.test.ts tests/unit/copilot-runtime.test.js
107
+ ```
108
+
109
+ The build bundles the MCP bootstrap with the existing SDK, generates its initial tool catalog
110
+ through the real MCP `tools/list` API, and copies the canonical adapter and shared resolver into
111
+ the plugin boundary. It pins their runtime to `package.json`'s version. No plugin code
112
+ imports files outside the installed package. `bun run build` also regenerates the plugin,
113
+ and drift tests byte-compare the distributed files against their canonical sources.
114
+
115
+ The portable root contains `plugin.json`, `mcp.json`, `start-mcp.mjs`, `tools.json`, `runtime.mjs`,
116
+ `runtime.json`, and license notices for the bundled dependencies. Copilot auto-discovers the native adapter at
117
+ `com.github.copilot/extensions/pmx-canvas/extension.mjs`.
118
+
119
+ The marketplace preview is copied from `docs/screenshot.png` to `assets/preview.png` on each
120
+ plugin build. The manifest's `extensions.com.github.copilot.logo` points to that packaged PNG.
121
+
122
+ Publishing a repository package does not register it in a curated marketplace or make it
123
+ Featured in the Copilot app. Those are separate submission and review steps.
@@ -71,3 +71,43 @@ record; redo replays `removeNode` inside suppressed recording
71
71
  **not** append a second note. Consumers should read `reanchoredIds` /
72
72
  `removedReviewIds` against the *current* canvas-bound state, not assume the
73
73
  referenced items are still re-anchored.
74
+
75
+ ## Agent presence (contract)
76
+
77
+ Presence is the shape behind every agent surface in the rail chrome: the agent
78
+ cursor and phase chip, the session panel, the command bar and the external
79
+ steering indicator. It is **derived, in-memory and never persisted** — not a
80
+ fourth partition, and not a second source of truth for work items, gates or
81
+ steering, which stay in the AX state above. The shared shape lives in
82
+ `src/shared/agent-presence.ts` (server and client import the same types and
83
+ constants, so they cannot drift); the registry is `src/server/agent-presence.ts`.
84
+
85
+ - **Sources.** Agent-originated mutations through `executeOperation` (no
86
+ workbench marker), `ax.activity.ingest` kinds (`session-start` /
87
+ `session-end` attach and detach; `tool-start` / `tool-result` / `failure` /
88
+ `error` drive the phase), and an explicit `ax.presence.set` for adapters with
89
+ richer hooks (`thinking`, cursor, focus).
90
+ - **Modes.** `sessionActive` = any presence with `attached: true`. No presences →
91
+ quiet board; live but unattached writers → external steering (passive
92
+ indicator only); an attached session → the session panel and command bar.
93
+ Presence cursors render for every live writer regardless.
94
+ - **Phase.** An agent mutation or `tool-start` → `tooling` (detail = op or tool
95
+ name), settling to `idle` after `PRESENCE_TOOLING_SETTLE_MS`; `thinking` only
96
+ via the explicit set, settling after `PRESENCE_THINKING_SETTLE_MS`. A cursor
97
+ update that moves the cursor restarts the settle window of a live explicit
98
+ `tooling`; a stationary cursor or focus heartbeat does not, and neither
99
+ extends `thinking`. An idle writer that reported a cursor keeps painting it.
100
+ An attached session with a pending approval gate reads as `waiting-approval`.
101
+ - **Lifetime.** Unattached writers fade `PRESENCE_ACTIVITY_TTL_MS` after their
102
+ last write; attached sessions expire after `PRESENCE_ATTACHED_IDLE_TTL_MS`
103
+ without activity; `session-end` or an explicit `attached: false` removes the
104
+ presence immediately; at most `MAX_PRESENCES`, oldest evicted. Expiry emits,
105
+ so clients never run their own ticker.
106
+ - **Transport.** One SSE frame, `agent-presence`, carries the full snapshot on
107
+ every change, including the last `MAX_ACTIVITY_ENTRIES` agent writes with a
108
+ one-line summary. `GET /api/canvas/ax/presence` for the connect-time read;
109
+ `POST /api/canvas/ax/presence` (`canvas_ax_state { action: "set-presence" }`)
110
+ for explicit updates.
111
+ - **Budget.** `used` = estimated tokens of the `pinned-context.get` payload;
112
+ `total` = `PMX_CANVAS_CONTEXT_BUDGET_TOKENS` (default
113
+ `CONTEXT_BUDGET_DEFAULT_TOKENS`).
package/docs/cli.md CHANGED
@@ -159,12 +159,17 @@ pmx-canvas # Start canvas, open browser
159
159
  pmx-canvas --demo # Start with the showcase demo board
160
160
  pmx-canvas --port=8080 # Custom port
161
161
  pmx-canvas --no-open # Headless (for agents/CI)
162
- pmx-canvas --theme=light # dark | light | high-contrast | midnight | sepia | arctic | ember | forest | volt
162
+ pmx-canvas --theme=daylight # harbor | daylight | high-contrast | midnight | sepia | arctic | ember | forest | volt
163
163
  pmx-canvas --mcp # Run as MCP server (stdio)
164
+ pmx-canvas --mcp-app # Private MCP App preview (stdio; shared active board)
164
165
  pmx-canvas --webview-automation # Start headless Bun.WebView session
165
166
  pmx-canvas open # Open the current workbench in a browser
166
167
  ```
167
168
 
169
+ The experimental `--mcp-app` profile has ten assistant tools plus an app-only
170
+ transport for the real PMX workbench, separate from the standard MCP tool set. See
171
+ [private MCP App setup and limitations](mcp.md#private-mcp-app-preview).
172
+
168
173
  ### Daemon mode
169
174
 
170
175
  Run detached with pid/log tracking instead of holding a terminal:
@@ -223,7 +228,7 @@ content.
223
228
 
224
229
  ```bash
225
230
  pmx-canvas node schema --type json-render --component Table --summary
226
- pmx-canvas validate # Layout validation
231
+ pmx-canvas validate # Layout validation (collisions, groupFrameOverlaps, sizeWarnings)
227
232
  pmx-canvas validate spec --type json-render --spec-file ./dashboard.json --summary
228
233
  pmx-canvas validate spec --type html-primitive --kind choice-grid --data-json '{"items":[{"title":"A"}]}' --summary
229
234
  ```