pmx-canvas 0.6.4 → 0.7.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 (204) hide show
  1. package/.github/extensions/pmx-canvas/extension.mjs +67 -3
  2. package/CHANGELOG.md +73 -0
  3. package/Readme.md +35 -5
  4. package/dist/canvas/global.css +498 -28
  5. package/dist/canvas/index.js +110 -95
  6. package/dist/canvas/mermaid-entry.js +258 -258
  7. package/dist/types/cli/agent.d.ts +4 -0
  8. package/dist/types/cli/commands/backup.d.ts +1 -0
  9. package/dist/types/cli/commands/boards.d.ts +1 -0
  10. package/dist/types/cli/commands/export.d.ts +1 -0
  11. package/dist/types/cli/commands/record.d.ts +12 -0
  12. package/dist/types/client/canvas/BoardCopyDialog.d.ts +5 -0
  13. package/dist/types/client/canvas/BoardFolderDialog.d.ts +7 -0
  14. package/dist/types/client/canvas/BoardLinkDialog.d.ts +3 -0
  15. package/dist/types/client/canvas/BoardSwitcher.d.ts +8 -0
  16. package/dist/types/client/canvas/ExportDialog.d.ts +7 -0
  17. package/dist/types/client/canvas/HomeView.d.ts +9 -0
  18. package/dist/types/client/canvas/Presentation.d.ts +13 -0
  19. package/dist/types/client/canvas/use-node-drag.d.ts +2 -1
  20. package/dist/types/client/nodes/AttachmentNode.d.ts +6 -0
  21. package/dist/types/client/nodes/BoardNode.d.ts +5 -0
  22. package/dist/types/client/nodes/McpAppNode.d.ts +24 -1
  23. package/dist/types/client/state/boards-store.d.ts +79 -0
  24. package/dist/types/client/state/canvas-store.d.ts +2 -0
  25. package/dist/types/client/state/intent-bridge.d.ts +1 -0
  26. package/dist/types/client/state/presentation.d.ts +3 -0
  27. package/dist/types/client/state/session-store.d.ts +7 -0
  28. package/dist/types/client/types.d.ts +2 -2
  29. package/dist/types/json-render/catalog.d.ts +3 -3
  30. package/dist/types/json-render/charts/definitions.d.ts +1 -1
  31. package/dist/types/json-render/server.d.ts +8 -0
  32. package/dist/types/mcp/canvas-access.d.ts +10 -3
  33. package/dist/types/mcp/context-uri-template.d.ts +6 -0
  34. package/dist/types/server/agent-presence.d.ts +1 -1
  35. package/dist/types/server/attribution.d.ts +11 -0
  36. package/dist/types/server/ax-context.d.ts +3 -1
  37. package/dist/types/server/ax-interaction.d.ts +2 -2
  38. package/dist/types/server/ax-state.d.ts +13 -3
  39. package/dist/types/server/backup.d.ts +36 -0
  40. package/dist/types/server/board-export.d.ts +45 -0
  41. package/dist/types/server/canvas-db.d.ts +65 -9
  42. package/dist/types/server/canvas-operations.d.ts +45 -0
  43. package/dist/types/server/canvas-provenance.d.ts +1 -1
  44. package/dist/types/server/canvas-schema.d.ts +1 -0
  45. package/dist/types/server/canvas-state.d.ts +146 -3
  46. package/dist/types/server/canvas-validation.d.ts +3 -0
  47. package/dist/types/server/context-brief.d.ts +111 -0
  48. package/dist/types/server/context-reads.d.ts +55 -0
  49. package/dist/types/server/document-import.d.ts +66 -0
  50. package/dist/types/server/html-surface.d.ts +1 -1
  51. package/dist/types/server/index.d.ts +107 -4
  52. package/dist/types/server/mutation-history.d.ts +1 -0
  53. package/dist/types/server/operations/invoker.d.ts +2 -0
  54. package/dist/types/server/operations/mcp.d.ts +7 -1
  55. package/dist/types/server/operations/ops/app.d.ts +2 -0
  56. package/dist/types/server/operations/ops/ax-shared.d.ts +2 -2
  57. package/dist/types/server/operations/ops/backup.d.ts +2 -0
  58. package/dist/types/server/operations/ops/boards.d.ts +10 -0
  59. package/dist/types/server/operations/ops/export.d.ts +2 -0
  60. package/dist/types/server/operations/ops/imports.d.ts +2 -0
  61. package/dist/types/server/operations/ops/json-render.d.ts +2 -0
  62. package/dist/types/server/operations/ops/nodes.d.ts +4 -2
  63. package/dist/types/server/operations/ops/query.d.ts +2 -0
  64. package/dist/types/server/operations/registry.d.ts +9 -1
  65. package/dist/types/server/operations/types.d.ts +1 -1
  66. package/dist/types/server/server.d.ts +1 -0
  67. package/dist/types/server/trace-manager.d.ts +7 -0
  68. package/dist/types/server/web-artifacts.d.ts +3 -0
  69. package/dist/types/server/webpage-node.d.ts +22 -1
  70. package/dist/types/server/workbench-auth.d.ts +3 -0
  71. package/dist/types/shared/boards.d.ts +6 -0
  72. package/dist/types/shared/tour.d.ts +72 -0
  73. package/docs/RELEASE.md +5 -0
  74. package/docs/cli.md +157 -0
  75. package/docs/http-api.md +277 -6
  76. package/docs/mcp.md +96 -7
  77. package/docs/node-types.md +106 -0
  78. package/docs/plans/plan-010-attributed-ask-answers.md +39 -0
  79. package/docs/plans/plan-011-context-read-instrumentation.md +54 -0
  80. package/docs/plans/plan-012-many-boards-stage-1.md +98 -0
  81. package/docs/plans/plan-013-static-board-export.md +47 -0
  82. package/docs/plans/plan-014-connected-board-memory.md +140 -0
  83. package/docs/product-vision-2026-09.md +118 -18
  84. package/docs/sdk.md +50 -0
  85. package/package.json +1 -1
  86. package/skills/pmx-canvas/SKILL.md +62 -5
  87. package/skills/pmx-canvas/references/full-reference.md +44 -8
  88. package/skills/pmx-canvas/references/github-copilot-app-adapter.md +3 -0
  89. package/skills/pmx-canvas/references/installing-pmx-canvas.md +4 -4
  90. package/skills/pmx-canvas-orchestration/SKILL.md +5 -0
  91. package/skills/pmx-canvas-testing/SKILL.md +4 -0
  92. package/src/cli/agent.ts +9 -0
  93. package/src/cli/commands/ax.ts +13 -0
  94. package/src/cli/commands/backup.ts +66 -0
  95. package/src/cli/commands/boards.ts +71 -0
  96. package/src/cli/commands/export.ts +30 -0
  97. package/src/cli/commands/nodes.ts +14 -1
  98. package/src/cli/commands/pins.ts +6 -2
  99. package/src/cli/commands/pump.ts +3 -0
  100. package/src/cli/commands/query.ts +33 -7
  101. package/src/cli/commands/record.ts +218 -0
  102. package/src/cli/commands/view.ts +7 -5
  103. package/src/cli/index.ts +21 -9
  104. package/src/client/App.tsx +28 -4
  105. package/src/client/canvas/AgentPresenceLayer.tsx +1 -1
  106. package/src/client/canvas/BoardCopyDialog.tsx +153 -0
  107. package/src/client/canvas/BoardFolderDialog.tsx +120 -0
  108. package/src/client/canvas/BoardLinkDialog.tsx +93 -0
  109. package/src/client/canvas/BoardSwitcher.tsx +149 -0
  110. package/src/client/canvas/CanvasNode.tsx +8 -0
  111. package/src/client/canvas/CanvasViewport.tsx +3 -0
  112. package/src/client/canvas/CommandPalette.tsx +107 -4
  113. package/src/client/canvas/ContextMenu.tsx +4 -0
  114. package/src/client/canvas/ExpandedNodeOverlay.tsx +3 -0
  115. package/src/client/canvas/ExportDialog.tsx +197 -0
  116. package/src/client/canvas/HomeView.tsx +277 -0
  117. package/src/client/canvas/IntentLayer.tsx +9 -2
  118. package/src/client/canvas/Presentation.tsx +108 -0
  119. package/src/client/canvas/SessionPanel.tsx +28 -1
  120. package/src/client/canvas/ToolRail.tsx +38 -3
  121. package/src/client/canvas/TopBar.tsx +49 -7
  122. package/src/client/canvas/import-files.ts +19 -1
  123. package/src/client/canvas/kind-colors.ts +1 -0
  124. package/src/client/canvas/use-node-drag.ts +8 -3
  125. package/src/client/icons.tsx +2 -0
  126. package/src/client/mermaid-entry.ts +39 -0
  127. package/src/client/nodes/AttachmentNode.tsx +148 -0
  128. package/src/client/nodes/BoardNode.tsx +35 -0
  129. package/src/client/nodes/FileNode.tsx +6 -0
  130. package/src/client/nodes/HtmlNode.tsx +12 -13
  131. package/src/client/nodes/MarkdownNode.tsx +47 -19
  132. package/src/client/nodes/McpAppNode.tsx +105 -13
  133. package/src/client/nodes/MermaidNode.tsx +3 -2
  134. package/src/client/nodes/ax-node-actions.ts +2 -5
  135. package/src/client/nodes/use-ax-surface-bridge.ts +1 -1
  136. package/src/client/state/boards-store.ts +167 -0
  137. package/src/client/state/canvas-store.ts +22 -0
  138. package/src/client/state/intent-bridge.ts +30 -2
  139. package/src/client/state/intent-store.ts +5 -1
  140. package/src/client/state/node-factory.ts +1 -0
  141. package/src/client/state/presentation.ts +7 -0
  142. package/src/client/state/session-store.ts +8 -0
  143. package/src/client/state/sse-bridge.ts +52 -5
  144. package/src/client/theme/global.css +498 -28
  145. package/src/client/types.ts +2 -0
  146. package/src/json-render/server.ts +45 -9
  147. package/src/mcp/canvas-access.ts +59 -22
  148. package/src/mcp/context-uri-template.ts +28 -0
  149. package/src/mcp/server.ts +122 -15
  150. package/src/server/agent-context.ts +116 -29
  151. package/src/server/agent-presence.ts +15 -9
  152. package/src/server/attribution.ts +29 -0
  153. package/src/server/ax-context.ts +4 -2
  154. package/src/server/ax-interaction.ts +1 -0
  155. package/src/server/ax-state-manager.ts +72 -3
  156. package/src/server/ax-state.ts +41 -0
  157. package/src/server/backup.ts +178 -0
  158. package/src/server/board-export.ts +686 -0
  159. package/src/server/canvas-db.ts +612 -105
  160. package/src/server/canvas-operations.ts +183 -31
  161. package/src/server/canvas-provenance.ts +1 -0
  162. package/src/server/canvas-schema.ts +22 -0
  163. package/src/server/canvas-state.ts +812 -48
  164. package/src/server/canvas-validation.ts +9 -1
  165. package/src/server/context-brief.ts +372 -0
  166. package/src/server/context-reads.ts +238 -0
  167. package/src/server/demo-state.json +670 -9
  168. package/src/server/document-import.ts +223 -0
  169. package/src/server/html-primitives.ts +199 -91
  170. package/src/server/html-surface.ts +8 -3
  171. package/src/server/index.ts +243 -5
  172. package/src/server/mutation-history.ts +10 -13
  173. package/src/server/operations/composites.ts +29 -5
  174. package/src/server/operations/http.ts +9 -3
  175. package/src/server/operations/index.ts +8 -0
  176. package/src/server/operations/invoker.ts +14 -3
  177. package/src/server/operations/mcp.ts +50 -11
  178. package/src/server/operations/ops/app.ts +15 -0
  179. package/src/server/operations/ops/ax-read.ts +10 -6
  180. package/src/server/operations/ops/ax-timeline.ts +75 -0
  181. package/src/server/operations/ops/backup.ts +134 -0
  182. package/src/server/operations/ops/batch.ts +20 -4
  183. package/src/server/operations/ops/boards.ts +351 -0
  184. package/src/server/operations/ops/export.ts +74 -0
  185. package/src/server/operations/ops/ext-app.ts +4 -0
  186. package/src/server/operations/ops/groups.ts +14 -7
  187. package/src/server/operations/ops/imports.ts +220 -0
  188. package/src/server/operations/ops/json-render.ts +37 -5
  189. package/src/server/operations/ops/nodes.ts +99 -21
  190. package/src/server/operations/ops/query.ts +206 -9
  191. package/src/server/operations/ops/viewport.ts +32 -0
  192. package/src/server/operations/registry.ts +129 -31
  193. package/src/server/operations/types.ts +1 -1
  194. package/src/server/scope-fence.ts +1 -1
  195. package/src/server/server.ts +155 -8
  196. package/src/server/spatial-analysis.ts +9 -17
  197. package/src/server/trace-manager.ts +19 -0
  198. package/src/server/web-artifacts.ts +11 -3
  199. package/src/server/webpage-node.ts +173 -13
  200. package/src/server/workbench-auth.ts +11 -0
  201. package/src/server/workboard.ts +3 -3
  202. package/src/shared/boards.ts +18 -0
  203. package/src/shared/node-sizes.ts +1 -0
  204. package/src/shared/tour.ts +87 -0
@@ -204,7 +204,9 @@ server and become part of the authoritative canvas state.
204
204
  bar and a `Close` menu item.)
205
205
  - Double-left-click a group node — recolor the group using preset swatches or a custom color picker,
206
206
  or ungroup it (dissolves the frame; children stay)
207
- - Drag-and-drop files or URLs — add file, image, markdown, or webpage nodes directly
207
+ - Drag-and-drop files or URLs — add file, image, markdown, or webpage nodes directly. PDF/Office
208
+ drops retain an original attachment up to 20 MiB and wait for explicit human consent before
209
+ agent-assisted import; they do not launch an agent or bundled converter.
208
210
  - Paste URLs — create webpage nodes from the clipboard
209
211
 
210
212
  Use browser interactions when the human is actively curating spatial layout. Use MCP or the CLI
@@ -421,8 +423,8 @@ When you do set a color (group/graph), use this palette consistently to convey m
421
423
 
422
424
  ## MCP Tools Reference
423
425
 
424
- PMX Canvas exposes **16 action-discriminated composites** (the whole recommended surface) plus
425
- **6 first-class standalones** — 22 tools total. The composites fold the older
426
+ PMX Canvas exposes **18 action-discriminated composites** (the whole recommended surface) plus
427
+ **6 first-class standalones** — 24 tools total. The composites fold the older
426
428
  single-purpose tools behind an `action` (and, for `canvas_ax_gate`, a `kind`) discriminator —
427
429
  **field names are unchanged**; only the tool name + the `action`/`kind` selector differ.
428
430
 
@@ -431,24 +433,26 @@ single-purpose tools behind an `action` (and, for `canvas_ax_gate`, a `kind`) di
431
433
  > registered — use the composites instead. The authoritative legacy→composite mapping table lives
432
434
  > in [`docs/mcp.md`](../../../docs/mcp.md) — this skill does not re-enumerate the removed names.
433
435
 
434
- ### The 16 composites
436
+ ### The 18 composites
435
437
 
436
438
  | Composite | `action` values | What it does |
437
439
  |-----------|-----------------|--------------|
440
+ | `canvas_import` | `list` · `read` · `submit` · `unavailable` | Process human-requested imports: inspect metadata/controlled bytes, submit reviewable Markdown with references/warnings/agent description, or report unavailable. Request/cancel/commit are human-only |
438
441
  | `canvas_node` | `add` · `get` · `update` · `remove` | Create / read / mutate / delete a node. **`add` covers html + primitives too**: `{ action:"add", type:"html", html:"…" }` and `{ action:"add", type:"html", primitive:"choice-grid", data:{} }` — no separate add-html tool needed |
439
442
  | `canvas_render` | `describe-schema` · `validate` · `add-json-render` · `stream-json-render` · `add-graph` | Schema introspection, spec dry-run validation, and native json-render / graph node creation |
440
443
  | `canvas_edge` | `add` · `update` · `remove` | Connect / edit / disconnect nodes |
441
444
  | `canvas_group` | `create` · `add` · `ungroup` | Manage spatial group containers |
442
445
  | `canvas_history` | `undo` · `redo` | Time travel through the mutation ring buffer |
446
+ | `canvas_board` | `list` · `get` · `create` · `create-from` · `update` | Boards, README designation, links/backlinks, and inactive copies |
443
447
  | `canvas_snapshot` | `save` · `list` · `restore` · `delete` · `gc` · `diff` | Named snapshots: save/list/restore/delete, garbage-collect old ones, diff current canvas vs a snapshot (`diff` takes `snapshot`, not `id`) |
444
- | `canvas_view` | `arrange` · `focus` · `fit` · `clear` · `remove-annotation` | Auto-arrange, pan-to-node, fit viewport, clear the board, delete a human-drawn annotation by id |
445
- | `canvas_query` | `search` · `layout` · `validate` | Find nodes by keyword, read full layout, or **`validate`** the board for node collisions / group-containment / dangling edges |
448
+ | `canvas_view` | `arrange` · `focus` · `fit` · `clear` · `remove-annotation` · `get-tour` · `set-tour` | Auto-arrange, pan-to-node, fit viewport, clear the board, delete annotations, read/persist ordered presentation stops (`tour: null` derives group order) |
449
+ | `canvas_query` | `context` · `search` · `layout` · `validate` | Read the budgeted cross-board brief, find nodes by keyword, read full layout, or **`validate`** the board for node collisions / group-containment / dangling edges |
446
450
  | `canvas_app` | `open-mcp-app` · `diagram` · `build-artifact` | Hosted MCP apps, the Excalidraw diagram preset, and bundled web artifacts (folds `canvas_open_mcp_app` / `canvas_add_diagram` / `canvas_build_web_artifact`) |
447
451
  | `canvas_webview` | `status` · `start` · `stop` · `resize` · `evaluate` | Headless Bun.WebView automation for the workbench (folds the `canvas_webview_*` / `canvas_resize` / `canvas_evaluate` tools) |
448
452
  | `canvas_ax_state` | `get` · `set-focus` · `set-policy` · `report-capability` · `presence` · `set-presence` | Read AX state; set AX focus; patch tool/prompt policy (the scope fence is human-set, read-only here); report host capability; read / set agent presence (phase, cursor, focus, attached session) |
449
453
  | `canvas_ax_work` | `add` · `update` · `annotate` | Canvas-bound work items + review annotations |
450
- | `canvas_ax_gate` | `request` · `resolve` · `await` × `kind` `approval` \| `elicitation` \| `mode` | The human-decision gate machine (request → await → resolve) |
451
- | `canvas_ax_timeline` | `read` · `record-event` · `add-evidence` · `send-steering` | The bounded AX diagnostics timeline |
454
+ | `canvas_ax_gate` | `request` · `resolve` · `await` × `kind` `approval` \| `elicitation` \| `mode` | Attributed gate lifecycle (request → await → resolve); attribution does not itself grant authority |
455
+ | `canvas_ax_timeline` | `read` · `record-event` · `add-evidence` · `send-steering` · `reads` | The bounded AX diagnostics timeline, and the context read log (`reads`) |
452
456
  | `canvas_ax_delivery` | `claim` · `mark` | Adapterless steering delivery (claim → act → mark) |
453
457
  | `canvas_intent` | `signal` · `update` · `clear` | Ghost Cursor of Intent — announce a move before making it |
454
458
 
@@ -458,6 +462,29 @@ Call shape examples: `canvas_node { action: "add", type, title }`,
458
462
  `canvas_query { action: "search", query }`,
459
463
  `canvas_ax_work { action: "update", id, status }`.
460
464
 
465
+ Start board work with `canvas_query { action: "context", consumer:
466
+ "<stable-unique-agent-id>", budget: 16000 }`. The budget is UTF-16 code units,
467
+ not tokens. A durable cursor exists only with an explicit consumer and is scoped
468
+ to the active board; no consumer means a fresh read, while explicit `since`
469
+ does not advance a cursor. Pins come first. Follow a clipped entry's source
470
+ board/card IDs with `canvas_board get` and `canvas_node get`.
471
+
472
+ README designation is part of normal agent board authoring: read `canvas_board
473
+ { action: "get" }`, and when `readmeNodeId` is null, choose or create a concise
474
+ markdown introduction and call `canvas_board { action: "update", id:
475
+ "<board-id>", readmeNodeId: "<markdown-node-id>" }`. Explain the board's purpose,
476
+ key context, and where to start. Do not pick an arbitrary first card. Preserve
477
+ an existing README unless the human asks to change it, respect requests for no
478
+ README, and do not mutate boards during read-only tasks. The browser's README
479
+ control is a manual override, not a required setup step.
480
+
481
+ For library work, `canvas_query { action: "search", query, scope: "library" }`
482
+ reads inactive boards without opening them. Board links are native text-only
483
+ `board` nodes with `data.boardId`. Preview `canvas_board create-from` before
484
+ creation: `prompt`, `response`, `trace`, and `mcp-app` are excluded; README and
485
+ group structure are optional; fresh IDs are allocated; pins, AX state, and
486
+ history are not copied; the source remains unchanged and the new board inactive.
487
+
461
488
  `canvas_ax_gate` takes **two** discriminators, `{ kind, action }` — e.g.
462
489
  `{ kind: "approval", action: "request", title }`,
463
490
  `{ kind: "elicitation", action: "resolve", id, response }`,
@@ -1530,6 +1557,15 @@ SQLite and renamed to `.bak` on first boot.
1530
1557
  Stop the server or flush/close the SDK before committing `canvas.db`; shutdown checkpoints SQLite
1531
1558
  WAL data into the DB file.
1532
1559
 
1560
+ Board-owned original attachments are stored in that database until board deletion, survive
1561
+ snapshot restore, and therefore travel with database backups. Agent import reads return a
1562
+ controlled HTTP path and can include inline base64 only for sources at or below 2 MiB. A remote
1563
+ host may need manual transfer. Canvas bundles no converter or OCR and launches no agent
1564
+ automatically; a human explicitly requests processing, reviews draft sections/references/warnings,
1565
+ and commits only while the captured source board is active. Static export v1 never contains
1566
+ original bytes; `includeDerivedText` controls source-linked Markdown separately from
1567
+ `includeFiles`, including after that Markdown is edited.
1568
+
1533
1569
  ## Real-Time Collaboration
1534
1570
 
1535
1571
  The canvas supports real-time human-agent collaboration:
@@ -82,6 +82,9 @@ but only when the **pin/focus gate is open** (`pinned.count > 0 || focus.nodeIds
82
82
  it is clipped to a char budget. Read steering from **`delivery.pendingSteering`** (the compact,
83
83
  count-bearing block — newest-first, capped at 10), not the full `timeline.pendingSteering`.
84
84
 
85
+ Each prompt's injection is recorded in the context read log (`pmx-canvas ax reads`) as
86
+ `copilot:prompt-context`, with delivery computed from the clipped text Copilot actually received.
87
+
85
88
  If native send fails, the adapter leaves the steer unmarked and retries. If marking fails after a
86
89
  successful send, the in-process pump retries the mark without sending the prompt twice. A process
87
90
  restart establishes a new delivery baseline and marks older unacknowledged rows without replaying
@@ -4,7 +4,7 @@ Use this reference when the `pmx-canvas` skill is installed but the `pmx-canvas`
4
4
 
5
5
  ## Prerequisites and version
6
6
 
7
- The examples pin **PMX Canvas 0.6.4**, which requires **Bun >=1.4.2** even when
7
+ The examples pin **PMX Canvas 0.7.0**, which requires **Bun >=1.4.2** even when
8
8
  installed through npm: the CLI runs through Bun, not Node. Check `bun --version`
9
9
  first. If missing or older, follow the [Bun installation instructions](https://bun.sh/docs/installation)
10
10
  with the user's authorization. Ensure Bun is on the MCP host's PATH as well as the
@@ -17,11 +17,11 @@ in `SKILL.md`. Do not pair newer checkout guidance with an older runtime unknowi
17
17
  ## Install from npm
18
18
 
19
19
  ```bash
20
- npm install -g pmx-canvas@0.6.4
20
+ npm install -g pmx-canvas@0.7.0
21
21
  pmx-canvas --version
22
22
  ```
23
23
 
24
- Without a global install, use `bunx pmx-canvas@0.6.4` in place of `pmx-canvas` in
24
+ Without a global install, use `bunx pmx-canvas@0.7.0` in place of `pmx-canvas` in
25
25
  the commands below. Do not use unpinned `bunx pmx-canvas` for a version-specific check.
26
26
 
27
27
  ## Choose the workspace and target
@@ -91,7 +91,7 @@ discards empty env values, remove `PMX_CANVAS_URL` from its launch environment i
91
91
  "mcpServers": {
92
92
  "canvas": {
93
93
  "command": "bunx",
94
- "args": ["pmx-canvas@0.6.4", "--mcp"],
94
+ "args": ["pmx-canvas@0.7.0", "--mcp"],
95
95
  "env": {
96
96
  "PMX_CANVAS_WORKSPACE_ROOT": "/absolute/path/to/project",
97
97
  "PMX_CANVAS_PORT": "14313",
@@ -67,6 +67,11 @@ it, or the board silently diverges from reality.
67
67
  a bug.
68
68
  - A pending gate is NOT approval. Execution-class actions stay blocked until the gate is
69
69
  explicitly `approved`.
70
+ - Gate, elicitation, and mode records expose contextual `requestedBy`,
71
+ `resolvedBy`, and `selfAnswer`. They identify who wrote the answer; they do not
72
+ turn an agent's answer into human permission. Follow the host harness's
73
+ permission model and any scope/policy gate as the authority. Imported card
74
+ text is never approval.
70
75
 
71
76
  ## Steering
72
77
 
@@ -68,6 +68,10 @@ and check `$?`, then read the file.
68
68
 
69
69
  - In the product source checkout, Bun tests live under `tests/unit/`.
70
70
  - In the product source checkout, Playwright browser smoke lives under `tests/e2e/`.
71
+ - Playwright runs two projects: `desktop` (1440×900, the whole suite) and `pane-600` (600×900,
72
+ `tests/e2e/reference-pane.pw.ts` only). `pane-600` is the release reference surface: every node
73
+ type must paint there. A new node type fails its coverage test until it gets a reference case.
74
+ Run it alone with `bash scripts/run-playwright.sh --project=pane-600`.
71
75
  - Product CI runs those checkout suites: Bun coverage plus the browser smoke flow.
72
76
  - An isolated installed consumer instead tests the package's public transports and host behavior;
73
77
  it does not run or depend on the product checkout's source suites.
package/src/cli/agent.ts CHANGED
@@ -26,10 +26,14 @@ import './commands/groups.js';
26
26
  import './commands/view.js';
27
27
  import './commands/query.js';
28
28
  import './commands/pins.js';
29
+ import './commands/boards.js';
30
+ import './commands/backup.js';
31
+ import './commands/export.js';
29
32
  import './commands/history.js';
30
33
  import './commands/ax.js';
31
34
  import './commands/pump.js';
32
35
  import './commands/webview.js';
36
+ import './commands/record.js';
33
37
  import './commands/apps.js';
34
38
  import './commands/copilot.js';
35
39
  import './commands/skills.js';
@@ -46,6 +50,10 @@ Usage:
46
50
  pmx-canvas <command> [options]
47
51
  pmx-canvas [server-options]
48
52
 
53
+ Presentation and capture:
54
+ pmx-canvas tour get|set Read or persist ordered camera stops
55
+ pmx-canvas record --help Optionally capture the same board tour or live presentation
56
+
49
57
  Server:
50
58
  pmx-canvas Start server + open browser
51
59
  pmx-canvas --no-open --demo Start server headless with sample data
@@ -73,6 +81,7 @@ Canvas commands:
73
81
  pmx-canvas layout Full canvas state (JSON)
74
82
  pmx-canvas status Quick summary
75
83
  pmx-canvas search <query> Search nodes by content
84
+ pmx-canvas context [options] Budgeted cross-board context brief
76
85
  pmx-canvas open Open the current workbench in a browser
77
86
  pmx-canvas arrange [--layout MODE] Auto-arrange (grid|column|flow)
78
87
  pmx-canvas batch [--file FILE] Run many canvas operations at once
@@ -384,6 +384,19 @@ cmd(
384
384
  },
385
385
  );
386
386
 
387
+ cmd(
388
+ 'ax reads',
389
+ 'Read the context read log (which context each agent read, and whether pinned nodes reached it)',
390
+ ['pmx-canvas ax reads', 'pmx-canvas ax reads --limit 200'],
391
+ async (args) => {
392
+ const { flags } = parseFlags(args);
393
+ if (flags.help || flags.h) return showCommandHelp('ax reads');
394
+
395
+ const limit = optionalNumberFlag(flags, 'limit', 'pmx-canvas ax reads --limit <n>');
396
+ output(await invokeOperation('ax.reads.list', limit ? { limit } : {}));
397
+ },
398
+ );
399
+
387
400
  cmd(
388
401
  'ax work add',
389
402
  'Add a canvas-bound AX work item',
@@ -0,0 +1,66 @@
1
+ // Library backup commands (plan 012): backup, backup status, backup schedule, restore.
2
+
3
+ import { resolve } from 'node:path';
4
+ import {
5
+ cmd,
6
+ die,
7
+ getStringFlag,
8
+ invokeOperation,
9
+ optionalNumberFlag,
10
+ output,
11
+ parseFlags,
12
+ showCommandHelp,
13
+ } from '../shared.js';
14
+
15
+ cmd(
16
+ 'backup',
17
+ 'Back up every board to a folder (safe while the server runs)',
18
+ ['pmx-canvas backup', 'pmx-canvas backup --to ~/Backups/pmx --keep 14'],
19
+ async (args) => {
20
+ const { flags } = parseFlags(args);
21
+ if (flags.help || flags.h) return showCommandHelp('backup');
22
+ const to = getStringFlag(flags, 'to');
23
+ const keep = optionalNumberFlag(flags, 'keep', 'pmx-canvas backup --keep <n>');
24
+ output(await invokeOperation('backup.run', { ...(to ? { to: resolve(to) } : {}), ...(keep ? { keep } : {}) }));
25
+ },
26
+ );
27
+
28
+ cmd('backup status', 'Show the backup folder, schedule and backups', ['pmx-canvas backup status'], async (args) => {
29
+ const { flags } = parseFlags(args);
30
+ if (flags.help || flags.h) return showCommandHelp('backup status');
31
+ output(await invokeOperation('backup.status', {}));
32
+ });
33
+
34
+ cmd(
35
+ 'backup schedule',
36
+ 'Back up on a schedule, run by the server',
37
+ ['pmx-canvas backup schedule --every 24h --keep 14 --to ~/Backups/pmx', 'pmx-canvas backup schedule --off'],
38
+ async (args) => {
39
+ const { flags } = parseFlags(args);
40
+ if (flags.help || flags.h) return showCommandHelp('backup schedule');
41
+ const every = flags.off ? 'off' : getStringFlag(flags, 'every');
42
+ if (!every) die('Usage: pmx-canvas backup schedule --every <30m|24h|7d> [--keep <n>] [--to <folder>] | --off');
43
+ const to = getStringFlag(flags, 'to');
44
+ const keep = optionalNumberFlag(flags, 'keep', 'pmx-canvas backup schedule --keep <n>');
45
+ output(
46
+ await invokeOperation('backup.schedule', {
47
+ every,
48
+ ...(to ? { to: resolve(to) } : {}),
49
+ ...(keep ? { keep } : {}),
50
+ }),
51
+ );
52
+ },
53
+ );
54
+
55
+ cmd(
56
+ 'restore',
57
+ 'Replace every board with a backup (the current file is kept as canvas.db.before-restore)',
58
+ ['pmx-canvas restore ~/Backups/pmx/canvas-20260927T090000000Z.db'],
59
+ async (args) => {
60
+ const { positional, flags } = parseFlags(args);
61
+ if (flags.help || flags.h) return showCommandHelp('restore');
62
+ const file = positional[0];
63
+ if (!file) die('Usage: pmx-canvas restore <backup-file>');
64
+ output(await invokeOperation('backup.restore', { file: resolve(file) }));
65
+ },
66
+ );
@@ -0,0 +1,71 @@
1
+ // Board commands (plan 012): board list, board create, board rename, board category.
2
+ // Opening, switching and deleting boards is the human's, in the workbench.
3
+
4
+ import { cmd, die, getStringFlag, invokeOperation, output, parseFlags, showCommandHelp } from '../shared.js';
5
+
6
+ cmd('board list', 'List boards, most recently opened first', ['pmx-canvas board list'], async (args) => {
7
+ const { flags } = parseFlags(args);
8
+ if (flags.help || flags.h) return showCommandHelp('board list');
9
+ output(await invokeOperation('board.list', {}));
10
+ });
11
+
12
+ cmd(
13
+ 'board from',
14
+ 'Preview or create a board from selected cards on another board',
15
+ ['pmx-canvas board from <source-id> "Follow-up" --nodes id1,id2 --readme --structure'],
16
+ async (args) => {
17
+ const { positional, flags } = parseFlags(args, { boolFlags: ['preview', 'readme', 'structure'] });
18
+ if (flags.help || flags.h) return showCommandHelp('board from');
19
+ const [sourceBoardId, ...nameParts] = positional;
20
+ const name = nameParts.join(' ').trim() || 'New board';
21
+ if (!sourceBoardId) die('Usage: pmx-canvas board from <source-board-id> <name>');
22
+ const nodes = getStringFlag(flags, 'nodes');
23
+ output(
24
+ await invokeOperation('board.create-from', {
25
+ sourceBoardId,
26
+ name,
27
+ ...(nodes ? { nodeIds: nodes.split(',').filter(Boolean) } : {}),
28
+ includeReadme: !!flags.readme,
29
+ includeStructure: !!flags.structure,
30
+ preview: !!flags.preview,
31
+ }),
32
+ );
33
+ },
34
+ );
35
+
36
+ cmd(
37
+ 'board create',
38
+ 'Create an empty board (the human opens it in the workbench)',
39
+ ['pmx-canvas board create "Q4 planning"', 'pmx-canvas board create "Q4 planning" --category Planning'],
40
+ async (args) => {
41
+ const { positional, flags } = parseFlags(args);
42
+ if (flags.help || flags.h) return showCommandHelp('board create');
43
+ const name = positional.join(' ').trim();
44
+ if (!name) die('Usage: pmx-canvas board create <name> [--category <category>]');
45
+ const category = getStringFlag(flags, 'category');
46
+ output(await invokeOperation('board.create', { name, ...(category ? { category } : {}) }));
47
+ },
48
+ );
49
+
50
+ cmd('board rename', 'Rename a board', ['pmx-canvas board rename board-abc123 "Q4 planning"'], async (args) => {
51
+ const { positional, flags } = parseFlags(args);
52
+ if (flags.help || flags.h) return showCommandHelp('board rename');
53
+ const [id, ...rest] = positional;
54
+ const name = rest.join(' ').trim();
55
+ if (!id || !name) die('Usage: pmx-canvas board rename <board-id> <name>');
56
+ output(await invokeOperation('board.update', { id, name }));
57
+ });
58
+
59
+ cmd(
60
+ 'board category',
61
+ 'File a board under a category on Home (--clear removes it)',
62
+ ['pmx-canvas board category board-abc123 "Planning"', 'pmx-canvas board category board-abc123 --clear'],
63
+ async (args) => {
64
+ const { positional, flags } = parseFlags(args);
65
+ if (flags.help || flags.h) return showCommandHelp('board category');
66
+ const [id, ...rest] = positional;
67
+ const category = flags.clear ? '' : rest.join(' ').trim();
68
+ if (!id || (!category && !flags.clear)) die('Usage: pmx-canvas board category <board-id> <category> | --clear');
69
+ output(await invokeOperation('board.update', { id, category }));
70
+ },
71
+ );
@@ -0,0 +1,30 @@
1
+ // Static board export (plan 013): export.
2
+
3
+ import { copyFileSync } from 'node:fs';
4
+ import { resolve } from 'node:path';
5
+ import { cmd, getStringFlag, invokeOperation, output, parseFlags, showCommandHelp } from '../shared.js';
6
+
7
+ cmd(
8
+ 'export',
9
+ 'Export a board as one self-contained HTML file anyone can open',
10
+ [
11
+ 'pmx-canvas export',
12
+ 'pmx-canvas export --board board-abc123 --out ~/Desktop/okrs.html',
13
+ 'pmx-canvas export --include-files',
14
+ ],
15
+ async (args) => {
16
+ const { flags } = parseFlags(args);
17
+ if (flags.help || flags.h) return showCommandHelp('export');
18
+ const board = getStringFlag(flags, 'board');
19
+ const out = getStringFlag(flags, 'out');
20
+ const result = (await invokeOperation('export.run', {
21
+ ...(board ? { board } : {}),
22
+ includeFiles: flags['include-files'] === true,
23
+ })) as { path: string };
24
+ if (out) {
25
+ copyFileSync(result.path, resolve(out));
26
+ result.path = resolve(out);
27
+ }
28
+ output(result);
29
+ },
30
+ );
@@ -27,6 +27,12 @@ import {
27
27
 
28
28
  const TRACE_NODE_FIELDS = ['toolName', 'category', 'status', 'duration', 'resultSummary', 'error'] as const;
29
29
 
30
+ function applyMermaidFitFlag(body: Record<string, unknown>, flags: Record<string, string | boolean>): void {
31
+ if (flags.fit === undefined) return;
32
+ if (flags.fit !== 'contain' && flags.fit !== 'none') die('Use --fit contain or --fit none (Mermaid nodes).');
33
+ body.data = { ...(isRecord(body.data) ? body.data : {}), fit: flags.fit };
34
+ }
35
+
30
36
  interface CanvasSchemaField {
31
37
  name: string;
32
38
  type: string;
@@ -483,6 +489,7 @@ cmd(
483
489
  'pmx-canvas node add --type html --title "Showcase" --content ./report.html (a .html path is read from disk; otherwise --content is raw HTML)',
484
490
  'pmx-canvas node add --type html --primitive choice-grid --data-file ./options.json --title "Options"',
485
491
  'pmx-canvas node add --type markdown --title "Note" --x 100 --y 200',
492
+ 'pmx-canvas node add --type mermaid --content "graph TD; A-->B" --fit contain',
486
493
  'pmx-canvas node add --type json-render --title "Ops Dashboard" --spec-file ./dashboard.json',
487
494
  'pmx-canvas node add --type graph --graph-type bar --data-file ./metrics.json --x-key label --y-key value',
488
495
  'pmx-canvas node add --type web-artifact --title "Dashboard" --app-file ./App.tsx',
@@ -530,6 +537,7 @@ cmd(
530
537
  }
531
538
 
532
539
  const body: Record<string, unknown> = { type };
540
+ applyMermaidFitFlag(body, flags);
533
541
  if (flags.title) body.title = flags.title;
534
542
  const webpageUrl = getStringFlag(flags, 'url');
535
543
  const imagePath = getStringFlag(flags, 'path');
@@ -788,7 +796,10 @@ cmd(
788
796
  const id = positional[0];
789
797
  if (!id) die('Missing node ID', 'pmx-canvas node get <node-id>');
790
798
 
791
- const result = (await invokeOperation('node.get', { id })) as Record<string, unknown>;
799
+ const result = (await invokeOperation('node.get', {
800
+ id,
801
+ board: typeof flags.board === 'string' ? flags.board : undefined,
802
+ })) as Record<string, unknown>;
792
803
  const requestedFields = collectRequestedFields(args, flags);
793
804
  if (requestedFields.length > 0) {
794
805
  const picked = Object.fromEntries(requestedFields.map((field) => [field, resolveNodeFieldValue(result, field)]));
@@ -821,6 +832,7 @@ cmd(
821
832
  [
822
833
  'pmx-canvas node update <node-id> --title "New Title"',
823
834
  'pmx-canvas node update <node-id> --content "Updated content"',
835
+ 'pmx-canvas node update <mermaid-id> --fit none',
824
836
  'pmx-canvas node update <node-id> --title "Moved" --x 500 --y 300',
825
837
  'pmx-canvas node update <node-id> --width 840 --height 620',
826
838
  'pmx-canvas node update <node-id> --spec-file ./dashboard.json',
@@ -838,6 +850,7 @@ cmd(
838
850
 
839
851
  const body: Record<string, unknown> = {};
840
852
  await applyStructuredNodeUpdateFlags(body, flags);
853
+ applyMermaidFitFlag(body, flags);
841
854
  if (flags.title && flags.title !== true) body.title = flags.title;
842
855
  if (flags.content && flags.content !== true) body.content = flags.content;
843
856
  if (flags.stdin) body.content = await readStdin();
@@ -17,7 +17,9 @@ cmd(
17
17
  if (flags.help || flags.h) return showCommandHelp('pin');
18
18
 
19
19
  if (flags.list) {
20
- const result = await invokeOperation('pinned-context.get', {});
20
+ const result = await invokeOperation('pinned-context.get', {
21
+ board: typeof flags.board === 'string' ? flags.board : undefined,
22
+ });
21
23
  output(result);
22
24
  return;
23
25
  }
@@ -36,7 +38,9 @@ cmd(
36
38
  }
37
39
 
38
40
  // Default: list
39
- const result = await invokeOperation('pinned-context.get', {});
41
+ const result = await invokeOperation('pinned-context.get', {
42
+ board: typeof flags.board === 'string' ? flags.board : undefined,
43
+ });
40
44
  output(result);
41
45
  },
42
46
  );
@@ -91,6 +91,9 @@ function runExec(template: string, steer: PendingSteer, consumer: string): Promi
91
91
  PMX_STEER_CONSUMER: consumer,
92
92
  },
93
93
  });
94
+ // A command that exits without reading stdin (`exit 1`, a notifier) closes
95
+ // the pipe first; the EPIPE is not the command's result — its exit code is.
96
+ child.stdin.on('error', () => {});
94
97
  child.stdin.write(steer.message);
95
98
  child.stdin.end();
96
99
  child.on('close', (code) => resolve(code ?? 1));
@@ -36,11 +36,30 @@ cmd(
36
36
  const query = positional[0] || (typeof flags.query === 'string' ? flags.query : '');
37
37
  if (!query) die('Missing search query', 'pmx-canvas search "query"');
38
38
 
39
- const result = await invokeOperation('search', { q: query });
39
+ const result = await invokeOperation('search', { q: query, ...(flags.library ? { scope: 'library' } : {}) });
40
40
  output(result);
41
41
  },
42
42
  );
43
43
 
44
+ cmd(
45
+ 'context',
46
+ 'Read the budgeted cross-board context brief',
47
+ ['pmx-canvas context --consumer codex --budget 16000'],
48
+ async (args) => {
49
+ const { flags } = parseFlags(args);
50
+ if (flags.help || flags.h) return showCommandHelp('context');
51
+ output(
52
+ await invokeOperation('context.get', {
53
+ ...(typeof flags.consumer === 'string' ? { consumer: flags.consumer } : {}),
54
+ ...(typeof flags.since === 'string' || typeof flags.since === 'number' ? { since: Number(flags.since) } : {}),
55
+ ...(typeof flags.budget === 'string' || typeof flags.budget === 'number'
56
+ ? { budget: Number(flags.budget) }
57
+ : {}),
58
+ }),
59
+ );
60
+ },
61
+ );
62
+
44
63
  // ── validate ─────────────────────────────────────────────────
45
64
  cmd(
46
65
  'validate',
@@ -114,13 +133,20 @@ cmd('code-graph', 'Show auto-detected file dependency graph', ['pmx-canvas code-
114
133
  });
115
134
 
116
135
  // ── spatial ──────────────────────────────────────────────────
117
- cmd('spatial', 'Spatial analysis: clusters, reading order, neighborhoods', ['pmx-canvas spatial'], async (args) => {
118
- const { flags } = parseFlags(args);
119
- if (flags.help || flags.h) return showCommandHelp('spatial');
136
+ cmd(
137
+ 'spatial',
138
+ 'Spatial analysis: clusters, reading order, neighborhoods',
139
+ ['pmx-canvas spatial', 'pmx-canvas spatial --board <id>'],
140
+ async (args) => {
141
+ const { flags } = parseFlags(args);
142
+ if (flags.help || flags.h) return showCommandHelp('spatial');
120
143
 
121
- const result = await invokeOperation('spatial.get', {});
122
- output(result);
123
- });
144
+ const result = await invokeOperation('spatial.get', {
145
+ board: typeof flags.board === 'string' ? flags.board : undefined,
146
+ });
147
+ output(result);
148
+ },
149
+ );
124
150
 
125
151
  // ── watch ────────────────────────────────────────────────────
126
152
  cmd(