pmx-canvas 0.8.2 → 0.9.1
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.
- package/.github/extensions/pmx-canvas/runtime.json +1 -1
- package/.github/extensions/pmx-canvas/tools.json +601 -22
- package/CHANGELOG.md +59 -0
- package/Readme.md +131 -532
- package/dist/canvas/global.css +2724 -476
- package/dist/canvas/index.js +168 -107
- package/dist/canvas/mcp-app.html +2899 -596
- package/dist/json-render/index.js +20 -20
- package/dist/types/client/canvas/BarHint.d.ts +12 -2
- package/dist/types/client/canvas/BoardLinkDialog.d.ts +3 -1
- package/dist/types/client/canvas/BoardPinButton.d.ts +10 -0
- package/dist/types/client/canvas/BoardSwitcher.d.ts +1 -1
- package/dist/types/client/canvas/CanvasViewport.d.ts +1 -1
- package/dist/types/client/canvas/CommandBar.d.ts +3 -4
- package/dist/types/client/canvas/ConnectionBanner.d.ts +2 -1
- package/dist/types/client/canvas/ContextChip.d.ts +3 -0
- package/dist/types/client/canvas/CrossBoardReferences.d.ts +5 -0
- package/dist/types/client/canvas/EdgeLayer.d.ts +14 -3
- package/dist/types/client/canvas/EmptyState.d.ts +4 -4
- package/dist/types/client/canvas/HomeGraphView.d.ts +2 -0
- package/dist/types/client/canvas/HomeMapView.d.ts +1 -0
- package/dist/types/client/canvas/HomeReviewView.d.ts +6 -0
- package/dist/types/client/canvas/HomeView.d.ts +9 -7
- package/dist/types/client/canvas/KnowledgeDialog.d.ts +4 -0
- package/dist/types/client/canvas/LifecycleChrome.d.ts +5 -0
- package/dist/types/client/canvas/LifecycleFields.d.ts +7 -0
- package/dist/types/client/canvas/LifecyclePanel.d.ts +1 -0
- package/dist/types/client/canvas/MemoryInspector.d.ts +7 -0
- package/dist/types/client/canvas/NearPinLayer.d.ts +14 -0
- package/dist/types/client/canvas/NodeContextMark.d.ts +40 -7
- package/dist/types/client/canvas/RelationPresetsDialog.d.ts +4 -0
- package/dist/types/client/canvas/RelationsPanel.d.ts +4 -0
- package/dist/types/client/canvas/SessionPanel.d.ts +2 -0
- package/dist/types/client/canvas/canvas-area.d.ts +1 -1
- package/dist/types/client/canvas/graph-geometry.d.ts +27 -0
- package/dist/types/client/canvas/graph-model.d.ts +37 -0
- package/dist/types/client/canvas/memory-layout.d.ts +23 -0
- package/dist/types/client/canvas/use-chrome-height.d.ts +2 -0
- package/dist/types/client/canvas/use-focus-trap.d.ts +1 -0
- package/dist/types/client/canvas/use-memory-graph.d.ts +8 -0
- package/dist/types/client/canvas/use-pan-zoom.d.ts +4 -1
- package/dist/types/client/icons.d.ts +0 -10
- package/dist/types/client/nodes/AxStepControls.d.ts +1 -1
- package/dist/types/client/nodes/InlineMarkdownEditor.d.ts +4 -4
- package/dist/types/client/nodes/MarkdownNode.d.ts +5 -1
- package/dist/types/client/nodes/McpAppNode.d.ts +4 -4
- package/dist/types/client/nodes/viewer-runtime-state.d.ts +1 -1
- package/dist/types/client/state/boards-store.d.ts +81 -9
- package/dist/types/client/state/canvas-store.d.ts +14 -1
- package/dist/types/client/state/context-chip-store.d.ts +22 -0
- package/dist/types/client/state/context-status-store.d.ts +35 -0
- package/dist/types/client/state/human-store.d.ts +2 -1
- package/dist/types/client/state/intent-bridge.d.ts +11 -8
- package/dist/types/client/state/knowledge-store.d.ts +22 -0
- package/dist/types/client/state/near-pin-store.d.ts +44 -1
- package/dist/types/client/state/presence-store.d.ts +4 -4
- package/dist/types/client/state/presentation.d.ts +28 -0
- package/dist/types/client/state/relation-store.d.ts +10 -0
- package/dist/types/client/state/session-store.d.ts +41 -2
- package/dist/types/client/state/workspace-settings.d.ts +7 -0
- package/dist/types/client/types.d.ts +13 -1
- package/dist/types/client/utils/keyboard.d.ts +2 -0
- package/dist/types/client/utils/time-ago.d.ts +4 -0
- package/dist/types/mcp/context-uri-template.d.ts +3 -1
- package/dist/types/server/agent-presence.d.ts +10 -3
- package/dist/types/server/ax-context.d.ts +1 -1
- package/dist/types/server/canvas-db.d.ts +30 -7
- package/dist/types/server/canvas-operations.d.ts +24 -2
- package/dist/types/server/canvas-provenance.d.ts +22 -2
- package/dist/types/server/canvas-serialization.d.ts +2 -0
- package/dist/types/server/canvas-state.d.ts +93 -15
- package/dist/types/server/canvas-validation.d.ts +1 -1
- package/dist/types/server/context-brief.d.ts +94 -6
- package/dist/types/server/context-reads.d.ts +33 -7
- package/dist/types/server/edit-summary.d.ts +20 -0
- package/dist/types/server/index.d.ts +71 -10
- package/dist/types/server/knowledge-lifecycle.d.ts +221 -0
- package/dist/types/server/lifecycle-status.d.ts +10 -0
- package/dist/types/server/memory-graph.d.ts +9 -0
- package/dist/types/server/mutation-history.d.ts +2 -0
- package/dist/types/server/operations/ops/ax-shared.d.ts +1 -1
- package/dist/types/server/operations/ops/boards.d.ts +2 -1
- package/dist/types/server/operations/ops/lifecycle.d.ts +2 -0
- package/dist/types/server/operations/ops/memory-graph.d.ts +2 -0
- package/dist/types/server/operations/ops/nodes.d.ts +5 -0
- package/dist/types/server/operations/ops/query.d.ts +23 -0
- package/dist/types/server/operations/registry.d.ts +1 -0
- package/dist/types/server/placement.d.ts +7 -0
- package/dist/types/server/scope-fence.d.ts +2 -1
- package/dist/types/server/spatial-analysis.d.ts +6 -3
- package/dist/types/server/tour-control.d.ts +32 -0
- package/dist/types/server/wiki-links.d.ts +14 -0
- package/dist/types/server/workbench-auth.d.ts +2 -0
- package/dist/types/shared/agent-context.d.ts +2 -0
- package/dist/types/shared/agent-presence.d.ts +15 -12
- package/dist/types/shared/auto-arrange.d.ts +1 -0
- package/dist/types/shared/boards.d.ts +4 -4
- package/dist/types/shared/knowledge-lifecycle.d.ts +41 -0
- package/dist/types/shared/knowledge-presets.d.ts +6 -0
- package/dist/types/shared/lifecycle-status.d.ts +24 -0
- package/dist/types/shared/memory-graph.d.ts +53 -0
- package/dist/types/shared/relations.d.ts +68 -0
- package/dist/types/shared/semantic-attention.d.ts +2 -0
- package/dist/types/shared/tour.d.ts +38 -0
- package/dist/types/shared/workspace-settings.d.ts +11 -0
- package/docs/RELEASE.md +21 -3
- package/docs/adr-001-bun-only-runtime.md +7 -1
- package/docs/adr-002-shared-product-separate-runtimes.md +93 -0
- package/docs/api-reference.md +109 -0
- package/docs/ax-host-adapter-contract.md +1 -1
- package/docs/ax-state-contract.md +5 -4
- package/docs/cli.md +153 -15
- package/docs/demos/pmx-canvas-demo.mp4 +0 -0
- package/docs/design/AgentContext.dc.html +3 -3
- package/docs/design/BoardPins.dc.html +37 -22
- package/docs/design/Categories.dc.html +208 -0
- package/docs/design/CategoryEditor.dc.html +33 -0
- package/docs/design/Chrome.dc.html +52 -9
- package/docs/design/ContextChip.dc.html +224 -0
- package/docs/design/ContextChip600.dc.html +136 -0
- package/docs/design/EmptyState.dc.html +45 -0
- package/docs/design/Flows.dc.html +23 -1
- package/docs/design/HeaderMarks.dc.html +183 -0
- package/docs/design/Home.dc.html +19 -1
- package/docs/design/HomeGraph.dc.html +86 -202
- package/docs/design/HomeMap.dc.html +23 -1
- package/docs/design/HomeTeam.dc.html +32 -14
- package/docs/design/Lifecycle.dc.html +22 -0
- package/docs/design/Lifecycle600.dc.html +18 -0
- package/docs/design/LifecycleVariants.dc.html +157 -0
- package/docs/design/LinkAuthorship.dc.html +139 -0
- package/docs/design/LinksChanged.dc.html +203 -0
- package/docs/design/LinksOptions.dc.html +137 -0
- package/docs/design/LinksOptions600.dc.html +134 -0
- package/docs/design/NearPin.dc.html +9 -10
- package/docs/design/Pane600.dc.html +4 -3
- package/docs/design/Relations.dc.html +3 -2
- package/docs/design/Restore.dc.html +152 -0
- package/docs/design/SeeChange.dc.html +267 -0
- package/docs/design/Uses.dc.html +12 -6
- package/docs/design/canvas.json +146 -19
- package/docs/design.md +449 -16
- package/docs/environment.md +0 -1
- package/docs/evals/curation-effect.md +128 -0
- package/docs/http-api.md +459 -37
- package/docs/mcp.md +146 -18
- package/docs/node-types.md +58 -3
- package/docs/plans/design-gaps-2026-10-08.md +53 -0
- package/docs/plans/plan-009-tech-debt-backlog.md +379 -0
- package/docs/plans/plan-016-context-made-visible-wave-1.md +150 -5
- package/docs/plans/plan-017-knowledge-categories-and-lifecycle.md +175 -0
- package/docs/plans/plan-018-connected-memory-wave-2.md +594 -0
- package/docs/plans/plan-019-post-0.9-roadmap.md +198 -0
- package/docs/product-context-vision-2026-09.md +17 -1
- package/docs/product-vision-2026-09.md +1082 -43
- package/docs/product-vision-review-2026-09.md +13 -1
- package/docs/screenshots/workbench-live-dark.png +0 -0
- package/docs/screenshots/workbench-live-light.png +0 -0
- package/docs/sdk.md +62 -2
- package/package.json +1 -1
- package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/runtime.json +1 -1
- package/plugins/pmx-canvas/plugin.json +1 -1
- package/plugins/pmx-canvas/runtime.json +1 -1
- package/plugins/pmx-canvas/tools.json +601 -22
- package/skills/pmx-canvas/SKILL.md +216 -42
- package/skills/pmx-canvas/evals/evals.json +224 -0
- package/skills/pmx-canvas/references/full-reference.md +120 -21
- package/skills/pmx-canvas/references/github-copilot-app-adapter.md +2 -2
- package/skills/pmx-canvas/references/importing-context.md +132 -0
- package/skills/pmx-canvas/references/installing-pmx-canvas.md +6 -6
- package/skills/pmx-canvas/references/tours-and-recording.md +16 -1
- package/skills/pmx-canvas-testing/SKILL.md +20 -0
- package/src/cli/agent.ts +2 -0
- package/src/cli/commands/ax.ts +12 -0
- package/src/cli/commands/boards.ts +88 -11
- package/src/cli/commands/edges.ts +28 -0
- package/src/cli/commands/groups.ts +7 -0
- package/src/cli/commands/nodes.ts +1 -0
- package/src/cli/commands/query.ts +34 -2
- package/src/cli/commands/record.ts +199 -48
- package/src/cli/index.ts +6 -1
- package/src/client/App.tsx +177 -70
- package/src/client/canvas/AttentionHistory.tsx +0 -2
- package/src/client/canvas/BarHint.tsx +40 -2
- package/src/client/canvas/BoardCopyDialog.tsx +1 -1
- package/src/client/canvas/BoardFolderDialog.tsx +7 -7
- package/src/client/canvas/BoardLinkDialog.tsx +138 -33
- package/src/client/canvas/BoardPinButton.tsx +33 -0
- package/src/client/canvas/BoardSwitcher.tsx +26 -40
- package/src/client/canvas/CanvasNode.tsx +360 -237
- package/src/client/canvas/CanvasViewport.tsx +45 -7
- package/src/client/canvas/CommandBar.tsx +29 -23
- package/src/client/canvas/CommandPalette.tsx +5 -12
- package/src/client/canvas/ConnectionBanner.tsx +3 -3
- package/src/client/canvas/ContextChip.tsx +296 -0
- package/src/client/canvas/ContextMenu.tsx +63 -5
- package/src/client/canvas/CrossBoardReferences.tsx +60 -0
- package/src/client/canvas/EdgeLayer.tsx +339 -76
- package/src/client/canvas/EmptyState.tsx +33 -18
- package/src/client/canvas/ExpandedNodeOverlay.tsx +72 -40
- package/src/client/canvas/ExternalWriters.tsx +57 -42
- package/src/client/canvas/HomeGraphView.tsx +942 -0
- package/src/client/canvas/HomeMapView.tsx +641 -0
- package/src/client/canvas/HomeReviewView.tsx +167 -0
- package/src/client/canvas/HomeView.tsx +687 -160
- package/src/client/canvas/IntentLayer.tsx +19 -12
- package/src/client/canvas/KnowledgeDialog.tsx +488 -0
- package/src/client/canvas/LifecycleChrome.tsx +133 -0
- package/src/client/canvas/LifecycleFields.tsx +91 -0
- package/src/client/canvas/LifecyclePanel.tsx +543 -0
- package/src/client/canvas/MemoryInspector.tsx +104 -0
- package/src/client/canvas/Minimap.tsx +3 -3
- package/src/client/canvas/NearPinLayer.tsx +187 -0
- package/src/client/canvas/NodeContextMark.tsx +175 -97
- package/src/client/canvas/Presentation.tsx +23 -57
- package/src/client/canvas/RelationPresetsDialog.tsx +413 -0
- package/src/client/canvas/RelationsPanel.tsx +82 -0
- package/src/client/canvas/SelectionBar.tsx +3 -6
- package/src/client/canvas/SessionPanel.tsx +90 -50
- package/src/client/canvas/SessionReceipt.tsx +311 -39
- package/src/client/canvas/ShortcutOverlay.tsx +1 -1
- package/src/client/canvas/SnapshotPanel.tsx +27 -36
- package/src/client/canvas/ToolRail.tsx +367 -192
- package/src/client/canvas/TopBar.tsx +354 -124
- package/src/client/canvas/canvas-area.ts +10 -3
- package/src/client/canvas/graph-geometry.ts +195 -0
- package/src/client/canvas/graph-model.ts +254 -0
- package/src/client/canvas/memory-layout.ts +106 -0
- package/src/client/canvas/use-chrome-height.ts +20 -0
- package/src/client/canvas/use-focus-trap.ts +31 -5
- package/src/client/canvas/use-memory-graph.ts +58 -0
- package/src/client/canvas/use-node-drag.ts +2 -1
- package/src/client/canvas/use-pan-zoom.ts +64 -7
- package/src/client/icons.tsx +0 -63
- package/src/client/mcp-app/style.css +0 -6
- package/src/client/nodes/AxStepControls.tsx +74 -50
- package/src/client/nodes/BoardNode.tsx +5 -1
- package/src/client/nodes/ContextNode.tsx +18 -11
- package/src/client/nodes/ExtAppFrame.tsx +6 -3
- package/src/client/nodes/FileNode.tsx +46 -42
- package/src/client/nodes/HtmlNode.tsx +32 -0
- package/src/client/nodes/ImageNode.tsx +1 -1
- package/src/client/nodes/InlineFormatBar.tsx +6 -9
- package/src/client/nodes/InlineMarkdownEditor.tsx +56 -17
- package/src/client/nodes/MarkdownNode.tsx +377 -109
- package/src/client/nodes/McpAppNode.tsx +50 -20
- package/src/client/nodes/MdFormatBar.tsx +21 -18
- package/src/client/nodes/PromptNode.tsx +1 -1
- package/src/client/nodes/ResponseNode.tsx +1 -1
- package/src/client/nodes/viewer-runtime-state.ts +54 -22
- package/src/client/state/attention-bridge.ts +64 -15
- package/src/client/state/boards-store.ts +193 -39
- package/src/client/state/canvas-store.ts +99 -12
- package/src/client/state/context-chip-store.ts +70 -0
- package/src/client/state/context-status-store.ts +53 -0
- package/src/client/state/human-store.ts +13 -7
- package/src/client/state/intent-bridge.ts +51 -30
- package/src/client/state/knowledge-store.ts +43 -0
- package/src/client/state/near-pin-store.ts +115 -6
- package/src/client/state/presence-store.ts +7 -10
- package/src/client/state/presentation.ts +91 -0
- package/src/client/state/relation-store.ts +81 -0
- package/src/client/state/session-store.ts +144 -18
- package/src/client/state/sse-bridge.ts +235 -32
- package/src/client/state/workspace-settings.ts +11 -0
- package/src/client/theme/global.css +2724 -476
- package/src/client/types.ts +12 -0
- package/src/client/utils/keyboard.ts +10 -0
- package/src/client/utils/time-ago.ts +14 -0
- package/src/json-render/renderer/index.tsx +31 -0
- package/src/mcp/canvas-app.ts +12 -2
- package/src/mcp/context-uri-template.ts +7 -4
- package/src/mcp/server.ts +63 -41
- package/src/mcp/workbench-app.ts +39 -6
- package/src/server/agent-presence.ts +128 -28
- package/src/server/ax-context.ts +27 -13
- package/src/server/board-export.ts +12 -1
- package/src/server/canvas-db.ts +331 -42
- package/src/server/canvas-operations.ts +195 -83
- package/src/server/canvas-provenance.ts +53 -22
- package/src/server/canvas-serialization.ts +2 -0
- package/src/server/canvas-state.ts +950 -122
- package/src/server/canvas-validation.ts +9 -2
- package/src/server/context-brief.ts +345 -38
- package/src/server/context-reads.ts +178 -30
- package/src/server/demo-state.json +355 -24
- package/src/server/edit-summary.ts +74 -0
- package/src/server/html-surface.ts +8 -1
- package/src/server/index.ts +181 -11
- package/src/server/knowledge-lifecycle.ts +304 -0
- package/src/server/lifecycle-status.ts +232 -0
- package/src/server/memory-graph.ts +166 -0
- package/src/server/mutation-history.ts +18 -6
- package/src/server/operations/composites.ts +32 -11
- package/src/server/operations/http.ts +7 -2
- package/src/server/operations/index.ts +4 -0
- package/src/server/operations/mcp.ts +26 -1
- package/src/server/operations/ops/ax-presence.ts +1 -1
- package/src/server/operations/ops/ax-read.ts +57 -6
- package/src/server/operations/ops/ax-timeline.ts +28 -2
- package/src/server/operations/ops/backup.ts +5 -3
- package/src/server/operations/ops/batch.ts +35 -4
- package/src/server/operations/ops/boards.ts +188 -47
- package/src/server/operations/ops/canvas-wire.ts +31 -1
- package/src/server/operations/ops/edges.ts +36 -2
- package/src/server/operations/ops/groups.ts +6 -1
- package/src/server/operations/ops/lifecycle.ts +262 -0
- package/src/server/operations/ops/memory-graph.ts +45 -0
- package/src/server/operations/ops/nodes.ts +107 -24
- package/src/server/operations/ops/query.ts +281 -68
- package/src/server/operations/ops/snapshots.ts +73 -1
- package/src/server/operations/ops/validate.ts +5 -1
- package/src/server/operations/ops/viewport.ts +97 -3
- package/src/server/operations/registry.ts +74 -25
- package/src/server/placement.ts +29 -0
- package/src/server/scope-fence.ts +78 -6
- package/src/server/server.ts +115 -16
- package/src/server/spatial-analysis.ts +48 -23
- package/src/server/tour-control.ts +82 -0
- package/src/server/wiki-links.ts +137 -0
- package/src/server/workbench-auth.ts +11 -0
- package/src/shared/agent-context.ts +3 -0
- package/src/shared/agent-presence.ts +15 -15
- package/src/shared/auto-arrange.ts +2 -0
- package/src/shared/boards.ts +4 -4
- package/src/shared/knowledge-lifecycle.ts +22 -0
- package/src/shared/knowledge-presets.ts +80 -0
- package/src/shared/lifecycle-status.ts +26 -0
- package/src/shared/memory-graph.ts +74 -0
- package/src/shared/relations.ts +98 -0
- package/src/shared/semantic-attention.ts +15 -1
- package/src/shared/tour.ts +23 -5
- package/src/shared/workspace-settings.ts +13 -0
- package/dist/types/client/canvas/ContextPinBar.d.ts +0 -1
- package/dist/types/client/canvas/FocusFieldLayer.d.ts +0 -1
- package/dist/types/client/canvas/kind-colors.d.ts +0 -3
- package/dist/types/server/agent-context.d.ts +0 -22
- package/src/client/canvas/ContextPinBar.tsx +0 -25
package/docs/http-api.md
CHANGED
|
@@ -3,6 +3,145 @@
|
|
|
3
3
|
REST endpoints for all canvas operations + an SSE event stream. Works from
|
|
4
4
|
any language. Default base URL: `http://localhost:4313`.
|
|
5
5
|
|
|
6
|
+
## Knowledge lifecycle data (plan 017 slice 1)
|
|
7
|
+
|
|
8
|
+
The slice-1 data contract now also drives the workbench, due engine and qualified agent context.
|
|
9
|
+
Categories remain explicit; nothing classifies cards automatically.
|
|
10
|
+
`Lifecycle` is `{ decayType: "reality"|"decision"|"dependency"|"relevance",
|
|
11
|
+
rate: "fast"|"medium"|"slow"|"durable", triggers: string[], revalidationMethod: string, cadenceDays?: number }`.
|
|
12
|
+
Strings in triggers and the method must be nonempty after trimming. `cadenceDays` is an integer,
|
|
13
|
+
1–36500, required for Medium and optional for other rates. Category, folder and card policy writes
|
|
14
|
+
refuse Medium without a cadence rather than saving a policy with no expiry. Fast checks at use in a
|
|
15
|
+
new session, slow on resumed work, durable has no passive expiry. The latest attached session
|
|
16
|
+
is the boundary, falling back to the active board's last-open time, then process start.
|
|
17
|
+
A category is the same fields plus stable `id`, editable `name` and `glyph` (nonempty strings).
|
|
18
|
+
|
|
19
|
+
Newly authored cards start fresh without a review record. Medium's first deadline is
|
|
20
|
+
creation plus `cadenceDays`; Fast/Slow first require review in a later use/resumed
|
|
21
|
+
session. Evidence validation stays null until an actual successful review. Concrete
|
|
22
|
+
source changes and contradictions can still require review immediately. Edits, reads,
|
|
23
|
+
undo/redo and snapshot restore never restart the initial freshness clock.
|
|
24
|
+
Creation accepts `data.contentOrigin: "authored"|"imported"` (immutable afterward).
|
|
25
|
+
Use `"imported"` for historical text pasted or extracted into a new Markdown card;
|
|
26
|
+
attachments and externally sourced provenance are also treated as imports. Imports
|
|
27
|
+
and pre-existing undated content do not acquire a synthetic freshness baseline.
|
|
28
|
+
Groups and board portals skip inherited factual decay unless given an explicit
|
|
29
|
+
card lifecycle override; explicit dependency/source warnings remain visible.
|
|
30
|
+
|
|
31
|
+
| Route | Input / result |
|
|
32
|
+
|---|---|
|
|
33
|
+
| `GET /api/canvas/knowledge` | `{ok, categories, folders, relationPresets, cards, reviewCards, resolvedFolders}`; `cards` uses JSON `[boardId,cardId]` keys |
|
|
34
|
+
| `POST /api/canvas/knowledge/category` | `{category}` creates or replaces by id; returns `{ok, category}` |
|
|
35
|
+
| `DELETE /api/canvas/knowledge/category/:id` | Deletes an unused category; returns `{ok:true, id}`. Unknown id: 404; blank id: invalid input (400); omitted path id: no matching route (404). Explicit folder assignments, including empty folders, refuse deletion (409) and name the folders to reassign or clear first. Never clears folders or inherited policies. |
|
|
36
|
+
| `POST /api/canvas/knowledge/folder` | `{path, categoryId: string|null, lifecycleOverride: Lifecycle|null}` replaces folder properties; returns `{ok, folder}` |
|
|
37
|
+
| `POST /api/canvas/knowledge/relation-preset` | `{preset: {id, name, vocabulary: [{kind, ink, directed}]}}` creates or replaces a shared template; returns `{ok, preset}`. Duplicate names: 409. |
|
|
38
|
+
| `DELETE /api/canvas/knowledge/relation-preset/:id` | Deletes the shared template; returns `{ok:true, id}`. Unknown id: 404. Existing board vocabularies and connections remain unchanged. |
|
|
39
|
+
| `GET /api/canvas/node/:id/lifecycle?board=<board-id>` | Effective category/lifecycle and history, without opening that board |
|
|
40
|
+
| `POST /api/canvas/node/:id/lifecycle` | `{lifecycleOverride: Lifecycle|null, expectedBoardId?}`; returns `{ok:true}` |
|
|
41
|
+
| `POST /api/canvas/node/:id/review` | `{outcome, evidence: string, applied: boolean, expectedBoardId?, expectedContentRevision?, expectedSources?}`; returns `{ok, record}` |
|
|
42
|
+
| `POST /api/canvas/node/:id/lifecycle/restore` | `{evidence: string, expectedBoardId?}`; returns `{ok, record}` |
|
|
43
|
+
|
|
44
|
+
**Additive local contract, 2026-10-14 (validation ongoing):** `knowledge.list.reviewCards`
|
|
45
|
+
is an array of `{boardId: string, cardId: string, boardTitle: string, folder: string|null,
|
|
46
|
+
title: string}`. Titles and folder placement come from the authoritative board/card observation,
|
|
47
|
+
not cached Home previews. Each nonretired card with `due === true` or nonempty `sources` appears
|
|
48
|
+
once, including a card with both. Join to `cards[JSON.stringify([boardId, cardId])]` for reasons,
|
|
49
|
+
source warnings and `lastValidatedAt`; missing validation is not a synthetic last-checked date.
|
|
50
|
+
Unknown policy alone does not qualify, but explicit content or source warnings still do.
|
|
51
|
+
The read covers the library without opening boards or writing validation. Consumers may filter
|
|
52
|
+
to a canonical folder and its descendants; null folder is unfiled and included in whole-library
|
|
53
|
+
scope. Opening a result is navigation, not a review record. The same additive result is returned
|
|
54
|
+
by SDK `knowledge({action:"list"})`.
|
|
55
|
+
|
|
56
|
+
Folder paths use the board-folder canonicalization (trimmed segments, at most eight levels).
|
|
57
|
+
Null category inherits; null override clears that local override. An explicit child category
|
|
58
|
+
starts its own defaults, discarding ancestor lifecycle overrides. A card's full override changes
|
|
59
|
+
only lifecycle, never category. Unfiled/unclassified stays unknown. No defaults are inferred.
|
|
60
|
+
The effective read returns `boardId`, `nodeId`, `contentRevision`, `category`, `categorySourceFolder`, `lifecycle`,
|
|
61
|
+
`lifecycleSourceFolder`, `lifecycleSource` (`card`/`folder`/`unknown`), `records`, `retired`,
|
|
62
|
+
`lastValidation` (record or null), and `qualification`. Source folders are canonical paths; null means no folder source.
|
|
63
|
+
Qualification carries `retired`, `unknown`, `due`, `reasons`, `changedSinceRead`, `lastReadAt`,
|
|
64
|
+
`contentChangedAt`, `lastValidatedAt`, `contentRevision`, `relevance`, source warnings in `sources`,
|
|
65
|
+
and inspected source revision tokens in `observations`. Direct node reads and search results also
|
|
66
|
+
carry qualification. Reads on an inactive board do not open it.
|
|
67
|
+
|
|
68
|
+
Outcomes are `retain`, `revise`, `externalize`, `retire`, `unresolved`. Only an **applied**
|
|
69
|
+
retain/revise/externalize with non-whitespace evidence renews validation. Reviews do not perform
|
|
70
|
+
content edits or externalization for you: `applied` records whether the disposition was actually
|
|
71
|
+
carried out. Validating outcomes require `expectedContentRevision` from the actual content checked;
|
|
72
|
+
missing is 400, a stale version is 409. Never copy a newer metadata revision onto evidence for an older
|
|
73
|
+
body. For cards with dependencies, also pass `expectedSources` from the checked lifecycle's
|
|
74
|
+
`qualification.observations`. Changed, added, removed or unavailable sources refuse validation
|
|
75
|
+
with 409; a later read is not permission to apply older evidence. The reviewed content is saved successfully before its record is appended; save failure adds
|
|
76
|
+
no review. Applied retire persists until explicit restore, even after later successful reviews.
|
|
77
|
+
Restore is audited with outcome `restore`, applied true, and never validates. Rows retain server
|
|
78
|
+
id, board+node identity, evidence, application, actor `who`, server timestamp `when`, and reviewed
|
|
79
|
+
`contentRevision` (null for legacy unversioned records, never backfilled as a current validation);
|
|
80
|
+
append order resolves retirement even when timestamps coincide. Reload and board snapshots
|
|
81
|
+
never erase this history. Restoring content is not restoring retirement.
|
|
82
|
+
|
|
83
|
+
Workspace writes work on Home without opening/switching boards; agent workspace writes are
|
|
84
|
+
refused under a scope fence. Moving or unfiling a board changes inherited lifecycle for all its
|
|
85
|
+
cards, so agent folder changes are also refused under a fence; other board metadata is unaffected.
|
|
86
|
+
This checks both the active board's applicable fence and the target board's stored global fence
|
|
87
|
+
or the writer's assigned territory, even when another board or Home is open. It never opens the target.
|
|
88
|
+
Category and folder-policy edits also check stored fences on every board whose effective
|
|
89
|
+
inheritance would change. Unrelated folders and policies hidden by a child category remain editable.
|
|
90
|
+
Card writes target the active board and use the existing expected
|
|
91
|
+
board, scope and human edit-lock guards. Writes emit live knowledge/lifecycle notifications;
|
|
92
|
+
metadata-only edits do not advance content revision or lastEditedBy. Read/delivery clocks,
|
|
93
|
+
content revisions, evidence validation and task relevance remain separate; reading, editing,
|
|
94
|
+
refreshing and restoration never validate. No synthetic content-change date is added to legacy cards.
|
|
95
|
+
Generic node PATCH/SDK `updateNode` rejects `data.lifecycleOverride`; use the dedicated lifecycle
|
|
96
|
+
setter (including to clear an override) so metadata cannot replay the body or resolve wiki links.
|
|
97
|
+
|
|
98
|
+
Shared relation presets live in Home alongside Categories, not in per-board settings.
|
|
99
|
+
Research, Dependencies, Decision and Flow are initial editable templates. Applying a saved
|
|
100
|
+
preset explicitly copies its vocabulary through the existing board update contract; editing
|
|
101
|
+
or deleting a template never silently rewrites boards. The canvas dropdown filters only the
|
|
102
|
+
relation kinds actually present on that board, plus All relations, without a Custom vocabulary state.
|
|
103
|
+
|
|
104
|
+
SDK (Bun): `canvas.knowledge({action:"list"|"category-set"|"category-remove"|"folder-set"|"relation-preset-set"|"relation-preset-remove", ...payload})`
|
|
105
|
+
(`category-remove` takes `id`)
|
|
106
|
+
and `canvas.lifecycle({action:"get"|"set"|"review"|"restore", id, ...payload})` use these
|
|
107
|
+
same inputs/results. `get` accepts `board`; writes accept `expectedBoardId`, and validating
|
|
108
|
+
reviews require `expectedContentRevision` and the matching `expectedSources` when dependencies exist.
|
|
109
|
+
|
|
110
|
+
### Relations, source observations and active context
|
|
111
|
+
|
|
112
|
+
Edge add/update accept open `kind` (1–80 characters) and `reason` (up to 4000), independently
|
|
113
|
+
of the existing `type`, label, style and animation. Update with null clears kind/reason.
|
|
114
|
+
Kinds/reasons survive undo, snapshots and restart and advance the links clock, not content.
|
|
115
|
+
Board update accepts `relationVocabulary: [{kind, ink, directed}]` (up to 64 unique kinds,
|
|
116
|
+
including an explicit empty list). `ink` is supports/contradicts/cites/derived/informs/related;
|
|
117
|
+
pin, agent and warning colours are not relation inks. Unknown kinds remain legal.
|
|
118
|
+
|
|
119
|
+
For lifecycle, `derived-from`, `contradicts` and `supersedes` point **source → dependent**;
|
|
120
|
+
`depends-on` points **dependent → prerequisite**. `cites`, `supports`, `related`, wiki links
|
|
121
|
+
and portals are navigational unless an explicit dependency or provenance source is also recorded.
|
|
122
|
+
The server captures `sourceRevision` when a dependency is authored or retyped. A reason or
|
|
123
|
+
style edit never resets that baseline. Source changes propagate through dependencies, including
|
|
124
|
+
cycles, but not unrelated references. A new contradiction or superseding decision needs review.
|
|
125
|
+
Folder defaults stop at links. Portal source warnings stay separate from the portal's own lifecycle.
|
|
126
|
+
|
|
127
|
+
`data.provenance` accepts `sources: [{boardId, cardId, contentRevision?, observedAt?,
|
|
128
|
+
availability?: "live"|"cached"|"unavailable"}]` and an inert `recipe: {tool, input?, prompt}`.
|
|
129
|
+
Explicit provenance requires `sourceKind`, nonempty `sourceUri` and `refreshStrategy`;
|
|
130
|
+
invalid or incomplete input returns 400 with field errors rather than being discarded.
|
|
131
|
+
Omit provenance to use the normal inference for file, image and other sourced nodes.
|
|
132
|
+
Keep `observedAt`/source revision distinct from retrieval `syncedAt`/`fetchedAt`; retrieval-only
|
|
133
|
+
updates do not advance content or validation. Cached/unavailable warnings never claim a fresh check.
|
|
134
|
+
The human's **Ask agent to refresh this board** sends steering; Canvas executes no recipe tool.
|
|
135
|
+
|
|
136
|
+
Retired cards remain in raw layout, direct reads and search, marked retired. They leave briefs,
|
|
137
|
+
pinned context, nearby active context and README summaries. Restore is audited, never validation.
|
|
138
|
+
Brief entries carry qualification and board overviews carry `readmeQualification` separately.
|
|
139
|
+
Every brief also carries a lifecycle snapshot independent of its content cursor. With
|
|
140
|
+
`lifecycle.complete: true`, its retired/unknown/review lists replace cached qualification; keys
|
|
141
|
+
are JSON `[boardId,cardId]`. If the snapshot exceeds a quarter of the budget, `complete: false`
|
|
142
|
+
explicitly requires rechecking cached cards via lifecycle-get before reuse. Empty lists in an
|
|
143
|
+
incomplete snapshot do not mean healthy or active. Delivered entries still carry qualification.
|
|
144
|
+
|
|
6
145
|
`GET /health` includes `contracts: ["pmx-embedded-workbench-v4"]` when the daemon
|
|
7
146
|
enforces `expectedBoardId` and `expectedContentRevision` preconditions, bulk-write
|
|
8
147
|
fences/locks using validated geometry and affected automatic parent frames only,
|
|
@@ -39,11 +178,25 @@ content auto-height or indirect parent-group reflow after editing a child.
|
|
|
39
178
|
|
|
40
179
|
## Board tours
|
|
41
180
|
|
|
42
|
-
- `GET /api/canvas/tour` → `{ tour: { stops: [...] }, derived: boolean }
|
|
181
|
+
- `GET /api/canvas/tour` → `{ tour: { stops: [...] }, derived: boolean, position: number | null }`
|
|
182
|
+
(`position` is the stop being presented, or `null`).
|
|
43
183
|
- `POST /api/canvas/tour` with `{ "tour": { "stops": [...] } }` persists the tour;
|
|
44
184
|
`{ "tour": null }` resets to derived group reading order. Returns `{ "ok": true }`.
|
|
45
185
|
- `GET /api/canvas/state` includes optional `tour`. Tour writes broadcast a layout
|
|
46
186
|
update and are included in snapshots/undo. They are board-wide writes under a scope fence.
|
|
187
|
+
- `POST /api/canvas/tour/go` with `{ "stop": 2 }` or `{ "step": "next" | "previous" }`
|
|
188
|
+
(plus optional `"present": false`) moves the shared tour cursor →
|
|
189
|
+
`{ ok, index, total, stop }`; 400 when the stop does not exist or the board has no stops.
|
|
190
|
+
Broadcasts `canvas-tour-step`; idle workbenches start presenting unless `present` is false.
|
|
191
|
+
- `POST /api/canvas/tour/exit` leaves presentation in every viewer (`canvas-tour-exit`).
|
|
192
|
+
- `POST /api/canvas/camera/move` with exactly one of `nodeId`, `rect: { x, y, width, height }`
|
|
193
|
+
or `viewport: { x, y, scale }`, plus optional `duration`, `easing`, `padding`, `pullback`
|
|
194
|
+
→ `{ ok, stop, viewport }`. Broadcasts `canvas-camera-move`; browsers ease to the target in
|
|
195
|
+
their real canvas area. `viewport` is the server's estimate from the last reported window
|
|
196
|
+
size and is stored as the board viewport. 404 for an unknown node.
|
|
197
|
+
|
|
198
|
+
Tour steps and camera moves are navigation: no undo entry, no agent-presence write, no
|
|
199
|
+
scope-fence check.
|
|
47
200
|
|
|
48
201
|
The [tour model and recording walkthrough](cli.md#tour-and-presentation) describes
|
|
49
202
|
targets, duration, easing, padding, and pullback. Open `/workbench?present=1` to hide
|
|
@@ -62,16 +215,71 @@ trusted workbench deletes a board — deletion requires both `x-pmx-workbench: 1
|
|
|
62
215
|
per-boot `x-pmx-workbench-token` embedded in the served `/workbench` HTML;
|
|
63
216
|
the marker alone is not authority. Public HTTP integrations must not delete
|
|
64
217
|
boards. In-process SDK embedding hosts retain deletion authority.
|
|
65
|
-
Every change emits a
|
|
66
|
-
`
|
|
67
|
-
|
|
68
|
-
`
|
|
218
|
+
Every change emits a `boards-changed` SSE frame with
|
|
219
|
+
`{ activeBoardId, boards, catalogEpoch, catalogRevision, reloadRevision, boardGeneration }`.
|
|
220
|
+
Board-list and board-mutation responses carry the same catalog fields. Within a server
|
|
221
|
+
process, `catalogRevision` increases for every catalog snapshot; reject older or duplicate
|
|
222
|
+
snapshots before changing board identity or camera. Only a current `connected` handshake
|
|
223
|
+
authorizes a replacement `catalogEpoch`. `reloadRevision` is a sticky library-restore marker,
|
|
224
|
+
retained by subsequent GETs so an overtaking response cannot hide a restore.
|
|
225
|
+
`boardGeneration` identifies the loaded board incarnation, including revisits and restores.
|
|
226
|
+
|
|
227
|
+
`folder` is a folder path, for example `Engineering/Canvas/Decisions`.
|
|
69
228
|
Home renders nested, collapsible folders and searches board names and paths.
|
|
70
229
|
Paths support up to eight levels, each with 1–60 characters; surrounding spaces
|
|
71
230
|
are trimmed. Empty segments and `.`/`..` are rejected. `null` or `""` unfiles a
|
|
72
231
|
board. Folders are derived from boards, so empty folders do not persist. Moving
|
|
73
232
|
a board changes its path, not its ID or content.
|
|
74
233
|
|
|
234
|
+
Home offers Folders, Map, and Graph. Folders are neutral organization, independent
|
|
235
|
+
of links. `PATCH /api/canvas/boards/<board-id>` accepts
|
|
236
|
+
`mapPosition: { x, y }` (finite numbers) to persist a dragged Map position in
|
|
237
|
+
SQLite, or `mapPosition: null` to return to the generated folder layout.
|
|
238
|
+
Creating a copy records `previousBoardId` as its source board ID; it does not
|
|
239
|
+
open either board.
|
|
240
|
+
Board-list `links` expose qualified `boardId`/`cardId` targets; native card
|
|
241
|
+
connections include `cardTitle` (null if missing), so footers need not display raw IDs.
|
|
242
|
+
|
|
243
|
+
### Markdown previews and saved links
|
|
244
|
+
|
|
245
|
+
`POST /api/render` accepts `{markdown, nodeId?, expectedBoardId?}` and returns `{html}`.
|
|
246
|
+
For a card-aware preview, send its captured board ID with `nodeId`; a different active
|
|
247
|
+
board returns 409 without HTML, so a delayed request cannot borrow another card's bindings.
|
|
248
|
+
Workbench previews discard responses that no longer belong to the displayed board or draft.
|
|
249
|
+
Plain Markdown without `nodeId` renders independently of a card.
|
|
250
|
+
|
|
251
|
+
Saved text replays its durable wiki bindings, including unresolved targets. Changed drafts
|
|
252
|
+
retain existing bindings and may resolve newly authored references by title; previews do not
|
|
253
|
+
persist those bindings. An explicit save is required to bind a saved unresolved reference.
|
|
254
|
+
|
|
255
|
+
### Connected memory projection
|
|
256
|
+
|
|
257
|
+
`GET /api/canvas/memory-graph?focus=<focus>&depth=2&projection=full` returns the
|
|
258
|
+
connected-memory projection used by Home and discovery in the context brief. Omit
|
|
259
|
+
`focus` for the library; otherwise use a board ID, a JSON qualified node key
|
|
260
|
+
`["board-id","card-id"]` (or `["board-id",null]`), or `pins`. URL-encode focus.
|
|
261
|
+
`depth` is an integer from 0–4, default 2: undirected hops including board/card
|
|
262
|
+
membership. Explicit 0 is valid; empty depth values are rejected. An unknown focus returns 404.
|
|
263
|
+
`projection` is `full` (default) or `boards` (compact board-only summaries).
|
|
264
|
+
Home Map uses `boards` and loads depth-2 details when you select a board.
|
|
265
|
+
Map/Graph coalesce live catalog, layout, pin and reconnect updates. Failed reads preserve
|
|
266
|
+
the last graph with **Retry**. Neither a refresh nor an agent board switch resets the
|
|
267
|
+
human's library camera or selection; explicit Open board/card leaves the library.
|
|
268
|
+
|
|
269
|
+
The response has `nodes`, `edges`, `unresolvedLinks: [{ source, ref }]`, `focus`,
|
|
270
|
+
and `depth`. Nodes have board-qualified `key`, `boardId`, optional `cardId`,
|
|
271
|
+
`kind` (`board` or `card`), `title`, `summary` (at most 280 characters), `folder`,
|
|
272
|
+
`inContext`, and `summaryOnly: true`. Edges have qualified `from`/`to`,
|
|
273
|
+
`source`/`target`, `missing`, and kind `member`, `portal`, `wiki`, `card`, or
|
|
274
|
+
`previous`. These projection kinds are separate from a card edge's rich `relationKind` and `reason`.
|
|
275
|
+
Graph reads mark delivered links seen on each owning board without switching
|
|
276
|
+
boards; they do not count as full content reads. Card nodes carry `linksRevision`
|
|
277
|
+
captured with the projection, so a later write is never acknowledged by an earlier read. In focused views,
|
|
278
|
+
`linksComplete: false` means some incident relations fall outside the requested
|
|
279
|
+
depth; that card's links are not acknowledged as seen. SDK: `getMemoryGraph({ focus,
|
|
280
|
+
depth, projection })`; MCP: `canvas_query { action: "graph", focus, depth, projection }`.
|
|
281
|
+
Cross-board briefs give incoming and outgoing connections readable board/card titles.
|
|
282
|
+
|
|
75
283
|
```bash
|
|
76
284
|
# List boards (most recently opened first) and which is open (null = Home)
|
|
77
285
|
curl http://localhost:4313/api/canvas/boards
|
|
@@ -79,23 +287,31 @@ curl http://localhost:4313/api/canvas/boards
|
|
|
79
287
|
# Read one board
|
|
80
288
|
curl http://localhost:4313/api/canvas/boards/<board-id>
|
|
81
289
|
|
|
82
|
-
# Create a board (optionally in a
|
|
290
|
+
# Create a board (optionally in a folder) — it is NOT opened
|
|
83
291
|
curl -X POST http://localhost:4313/api/canvas/boards \
|
|
84
|
-
-H "Content-Type: application/json" -d '{"name":"Q4 planning","
|
|
292
|
+
-H "Content-Type: application/json" -d '{"name":"Q4 planning","folder":"Planning"}'
|
|
85
293
|
|
|
86
294
|
# Preview/create an inactive copy from reusable cards (preview false creates)
|
|
87
295
|
curl -X POST http://localhost:4313/api/canvas/boards/from \
|
|
88
296
|
-H "Content-Type: application/json" \
|
|
89
297
|
-d '{"sourceBoardId":"board-source","name":"Follow-up","nodeIds":["node-1"],"includeReadme":true,"includeStructure":true,"preview":true}'
|
|
90
298
|
|
|
91
|
-
# Rename and/or file under a
|
|
299
|
+
# Rename and/or file under a folder on Home ("folder": "" removes it)
|
|
92
300
|
curl -X PATCH http://localhost:4313/api/canvas/boards/<board-id> \
|
|
93
|
-
-H "Content-Type: application/json" -d '{"name":"Q4 planning (final)","
|
|
301
|
+
-H "Content-Type: application/json" -d '{"name":"Q4 planning (final)","folder":"Planning"}'
|
|
94
302
|
|
|
95
303
|
# Designate a Markdown card on this board as its README (null clears it)
|
|
96
304
|
curl -X PATCH http://localhost:4313/api/canvas/boards/<board-id> \
|
|
97
305
|
-H "Content-Type: application/json" -d '{"readmeNodeId":"<markdown-node-id>"}'
|
|
98
306
|
|
|
307
|
+
# Pin a board into the agent's working set (its README and pinned cards reach every
|
|
308
|
+
# brief, whichever board is open; read-only context, never a write target), or unpin it.
|
|
309
|
+
# A repeat pin keeps its first attribution. Every board carries `pin` ({pinnedBy,
|
|
310
|
+
# pinnedAt, reason} or null).
|
|
311
|
+
curl -X POST http://localhost:4313/api/canvas/boards/<board-id>/pin \
|
|
312
|
+
-H "Content-Type: application/json" -d '{"reason":"Q4 rests on it"}'
|
|
313
|
+
curl -X POST http://localhost:4313/api/canvas/boards/<board-id>/unpin
|
|
314
|
+
|
|
99
315
|
# Open a board, or Home with {"id":null} — also available to agents
|
|
100
316
|
curl -X POST http://localhost:4313/api/canvas/boards/open \
|
|
101
317
|
-H "Content-Type: application/json" -d '{"id":"<board-id>"}'
|
|
@@ -222,6 +438,9 @@ curl http://localhost:4313/api/canvas/state
|
|
|
222
438
|
# Search the library (omit scope for the active board)
|
|
223
439
|
curl "http://localhost:4313/api/canvas/search?q=auth&limit=10&scope=library"
|
|
224
440
|
|
|
441
|
+
# Restrict to a board inside a folder subtree without opening it
|
|
442
|
+
curl "http://localhost:4313/api/canvas/search?q=auth&boardId=<id>&folder=Research"
|
|
443
|
+
|
|
225
444
|
# Validate the current layout. Reports collisions, groupFrameOverlaps (a group
|
|
226
445
|
# frame covering a node that is not one of its members, nested members
|
|
227
446
|
# included — e.g. after a group auto-fits around enlarged children),
|
|
@@ -254,6 +473,52 @@ curl -X POST http://localhost:4313/api/canvas/schema/validate \
|
|
|
254
473
|
|
|
255
474
|
## Nodes
|
|
256
475
|
|
|
476
|
+
Search accepts intersecting `boardId` (stable ID) and `folder` filters. A folder
|
|
477
|
+
matches itself and slash-boundary descendants; `folder=` selects unfiled boards.
|
|
478
|
+
Filters default an omitted scope to `library`. Explicit `scope=active` stays
|
|
479
|
+
constrained to the active board, even when another board is selected. Unknown or
|
|
480
|
+
nonmatching selections return no results. Filtering precedes ranking and limits;
|
|
481
|
+
term-OR discovery and whole-term ranking remain unchanged. Search never navigates.
|
|
482
|
+
|
|
483
|
+
`POST /api/canvas/node` accepts `destinationBoardId` to create on a stored board
|
|
484
|
+
without opening it or changing the human's active board. `expectedBoardId` remains
|
|
485
|
+
an **active-board** precondition, not a destination selector; stale values return 409.
|
|
486
|
+
Unknown destinations return 404. This is creation-only, not background CRUD.
|
|
487
|
+
|
|
488
|
+
Optional `requestId` identifies a logical creation within the workspace database.
|
|
489
|
+
Use the same id, destination and creation input when retrying; a new intentional
|
|
490
|
+
creation needs a new id even for identical content. Node and receipt commit in one
|
|
491
|
+
transaction. Receipts survive restart, deletion, undo and snapshot restore; replay
|
|
492
|
+
returns the original creation payload without recreating or updating anything.
|
|
493
|
+
Reusing an id with different input/destination returns 409. Object key order and
|
|
494
|
+
transport-only `full`, `verbose`, `agentId`, `expectedBoardId` do not affect identity.
|
|
495
|
+
Receipts survive individual node removal but are deleted with their owning board.
|
|
496
|
+
|
|
497
|
+
Either field opts into static creation only: `markdown`, `status`, `context`,
|
|
498
|
+
`ledger`, `trace`, `diff`, `mermaid`, and plain `html`. Runtime-backed types, generated
|
|
499
|
+
HTML primitives, ghost intents and membership mutations are explicitly refused (400),
|
|
500
|
+
never redirected to the active board. Persistent workspace storage is required.
|
|
501
|
+
New creations are conservatively refused (403) if either the active or destination
|
|
502
|
+
board has a scope fence or per-agent territory. Inactive creation creates no ghost or
|
|
503
|
+
undo entry on the active board and starts no runtime watcher. Only genuine changes
|
|
504
|
+
to active cards' incoming link clocks can emit an active layout; the destination's
|
|
505
|
+
layout is never substituted. Library metadata refreshes through `boards-changed`.
|
|
506
|
+
Auto-placement uses the destination's nodes. Batch entries accept the same fields;
|
|
507
|
+
batches still prefix-commit and assigned references work on receipts as on new nodes.
|
|
508
|
+
|
|
509
|
+
`POST /api/canvas/group` and `POST /api/canvas/edge` also accept
|
|
510
|
+
`destinationBoardId`, `requestId` and `expectedBoardId` under the same commit,
|
|
511
|
+
retry, scope-fence and no-navigation contract. Groups resolve all `childIds` on
|
|
512
|
+
the destination, preserve their positions by default, and support explicit frames
|
|
513
|
+
and `childLayout`. Membership changes respect human edit locks, including moved
|
|
514
|
+
descendants and affected parent frames. Edges resolve `from`/`fromSearch` and
|
|
515
|
+
`to`/`toSearch` on the destination; `toBoardId` independently selects a foreign
|
|
516
|
+
target board, not the owner of the edge. Both endpoints' relation clocks update
|
|
517
|
+
without changing their content/freshness. A retry returns its original group or
|
|
518
|
+
edge receipt, preserving later edits and not resurrecting removals. A request ID
|
|
519
|
+
cannot be reused for a different creation operation. Inactive group membership
|
|
520
|
+
updates/removals and other background CRUD remain unsupported.
|
|
521
|
+
|
|
257
522
|
```bash
|
|
258
523
|
# Add a node
|
|
259
524
|
curl -X POST http://localhost:4313/api/canvas/node \
|
|
@@ -294,6 +559,26 @@ against the node's top-level `contentRevision`; stale content returns `409`
|
|
|
294
559
|
without changing the node. Conditional edits cannot request webpage refresh.
|
|
295
560
|
The private MCP App uses these guards to reject stale board actions and note drafts.
|
|
296
561
|
|
|
562
|
+
Native Markdown Save captures the currently observed revision for each attempt. Newer content
|
|
563
|
+
arriving during rendering or network delivery refuses that attempt and keeps the draft; a later
|
|
564
|
+
deliberate human Save may replace already-observed work. Queued saves advance their precondition
|
|
565
|
+
only through successful acknowledgements of their own preceding saves, never through an external
|
|
566
|
+
revision or an automatic retry after conflict. Acknowledgements from before a restart, restore,
|
|
567
|
+
or board replacement cannot update canonical content, clear an unsaved draft or advance queued
|
|
568
|
+
preconditions, even if the replacement reused the same card ID with a lower revision.
|
|
569
|
+
Board switches detach the old cards until the
|
|
570
|
+
replacement layout arrives, keeping unsaved drafts under their original board/card. The replacement
|
|
571
|
+
`GET /api/canvas/state` supplies the captured `expectedBoardId` query (`null` for Home), so another
|
|
572
|
+
tab switching boards before the read returns causes `409`, not a layout attributed to the wrong board.
|
|
573
|
+
|
|
574
|
+
For file-backed Markdown, `PATCH /api/canvas/node/:id` accepts `persistFile: true` with top-level
|
|
575
|
+
`content`, `expectedBoardId`, and `expectedContentRevision`. It checks both preconditions and
|
|
576
|
+
validates the patch before synchronously writing the existing workspace `.md` file and card,
|
|
577
|
+
normalizing line endings to LF. In this mode, `data` accepts only the `rendered` preview;
|
|
578
|
+
change other metadata separately, never alongside the disk save.
|
|
579
|
+
A stale/refused save changes neither disk nor card. Ordinary PATCH remains data-only, and the
|
|
580
|
+
hosted MCP App still refuses file-linked edits. This is not a crash-recovery disk/SQLite transaction.
|
|
581
|
+
|
|
297
582
|
Replacing a JSON-render node's `spec` preserves its current title; include `title`
|
|
298
583
|
to rename it explicitly. A new authored spec still resets the viewer's local drafts.
|
|
299
584
|
|
|
@@ -336,6 +621,25 @@ workspace root returns `403`, and an unknown node id returns `404`.
|
|
|
336
621
|
|
|
337
622
|
## Edges
|
|
338
623
|
|
|
624
|
+
Edge creation accepts optional `toBoardId` to target a card on another board;
|
|
625
|
+
`from` stays on the open source board. For example:
|
|
626
|
+
`{ "from": "source-card", "to": "remote-card", "toBoardId": "target-board", "type": "references" }`.
|
|
627
|
+
The source footer and human More-menu card picker expose remote connections.
|
|
628
|
+
Auto-arrange stays local; static export omits remote lines but preserves explicit
|
|
629
|
+
external connection evidence.
|
|
630
|
+
|
|
631
|
+
Markdown saves bind `[[Board]]`, `[[Board#Card]]`, and `|alias` variants to durable
|
|
632
|
+
IDs in server-owned `data.wikiLinks`. IDs take precedence; names/titles must be
|
|
633
|
+
unique. Existing bindings survive renames, while ambiguous or missing targets
|
|
634
|
+
stay explicit and never guess or silently retarget. Rendering uses saved bindings:
|
|
635
|
+
an unresolved link stays unresolved until an explicit save; Undo and restore preserve bindings.
|
|
636
|
+
Rich-text editing preserves wiki references and aliases; editing a visible label changes
|
|
637
|
+
the alias, not the bound target. Code spans/fences are ignored.
|
|
638
|
+
The human Create dialog for an unresolved board-only link creates exactly its
|
|
639
|
+
authored name and re-saves the source using its captured board/revision; a stale
|
|
640
|
+
source is refused. Rich relationship kinds/reasons and source-driven lifecycle qualification
|
|
641
|
+
use the [shared contract above](#relations-source-observations-and-active-context).
|
|
642
|
+
|
|
339
643
|
```bash
|
|
340
644
|
# Add an edge
|
|
341
645
|
curl -X POST http://localhost:4313/api/canvas/edge \
|
|
@@ -379,6 +683,11 @@ inspection or screenshots when the drawn shape matters.
|
|
|
379
683
|
|
|
380
684
|
## Viewport
|
|
381
685
|
|
|
686
|
+
Workbench camera writes include an optional `clientId` (1–128 characters), echoed
|
|
687
|
+
in `canvas-viewport-update`. The originating tab ignores that echo so an older
|
|
688
|
+
Fit or zoom cannot cancel a newer local gesture. Other tabs and agent camera
|
|
689
|
+
commands still update the view. The identifier is echo metadata, not authorization.
|
|
690
|
+
|
|
382
691
|
```bash
|
|
383
692
|
# Set the camera transform (screen = world * scale + {x,y})
|
|
384
693
|
curl -X POST http://localhost:4313/api/canvas/viewport \
|
|
@@ -437,16 +746,30 @@ curl http://localhost:4313/api/canvas/pinned-context
|
|
|
437
746
|
curl 'http://localhost:4313/api/canvas/context?consumer=codex&budget=16000'
|
|
438
747
|
```
|
|
439
748
|
|
|
749
|
+
At most 20 cards can be pinned at once. `remove` processes every supplied ID, even when
|
|
750
|
+
a merged receipt has recorded more than 20 IDs across sessions; unrelated pins stay.
|
|
751
|
+
|
|
440
752
|
`consumer` is optional. Supply a stable unique value only for a durable,
|
|
441
753
|
board-scoped cursor; without `consumer` or `since`, each request starts from the
|
|
442
|
-
beginning. An explicit `since` never advances a cursor. The brief
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
changes and asks, then
|
|
447
|
-
same-folder board context.
|
|
448
|
-
|
|
449
|
-
|
|
754
|
+
beginning. An explicit `since` never advances a cursor. The brief is a map,
|
|
755
|
+
not a dump: an `overview` entry for the open board first, then pins, then each
|
|
756
|
+
pin's neighbours (reason `near`: up to five unpinned cards within a 600 canvas-pixel
|
|
757
|
+
edge-to-edge gap, nearest first, with `near: { pinNodeId, pinTitle }`), then relevant
|
|
758
|
+
current-board changes and asks, then pinned boards (reason `pinned-board`: the board and its pinned
|
|
759
|
+
cards), then compact linked or same-folder board context. Cards arrive as title,
|
|
760
|
+
a short summary (`summaryOnly: true`) and their relations; a pin puts a card
|
|
761
|
+
first, it does not send its full text. On a later read a changed card lists what changed in
|
|
762
|
+
`changes` (`text`, `links` or both) and marks relations drawn or changed since the cursor,
|
|
763
|
+
e.g. `(new, by a person)`. Every item carries source board/card IDs:
|
|
764
|
+
use them with the board/node read route to pull a card in full when you need it,
|
|
765
|
+
rather than guessing from the summary. Reading inactive boards does not open them.
|
|
766
|
+
Entries with capped or omitted relations declare `linksComplete: false` and do
|
|
767
|
+
not acknowledge that card's complete links revision as seen. Near-pin, human,
|
|
768
|
+
linked README and pinned-board card summaries carry no card relations. Complete
|
|
769
|
+
relation entries carry their captured `linksRevision`; delivery is qualified by
|
|
770
|
+
`sourceBoardId`, never by a bare card ID across boards. Single-card and layout
|
|
771
|
+
reads record delivered content but do not acknowledge incident relations. Read
|
|
772
|
+
a complete graph or brief relation entry for relations; pull cards separately for full content.
|
|
450
773
|
|
|
451
774
|
## AX context and focus
|
|
452
775
|
|
|
@@ -454,6 +777,10 @@ AX context is the host-agnostic agent-experience layer. It combines existing
|
|
|
454
777
|
context pins with a persisted focus node set that adapters can inject into
|
|
455
778
|
their native prompt/context hooks.
|
|
456
779
|
|
|
780
|
+
AX node excerpts declare `linksComplete: false`. AX reads, including annotation
|
|
781
|
+
anchors, never acknowledge incident relations as seen; use a complete graph or
|
|
782
|
+
brief relation entry for that acknowledgement.
|
|
783
|
+
|
|
457
784
|
The context response includes `boardId` (`null` on Home), captured with its content
|
|
458
785
|
and pins. Adapters should send that ID when recording a proxied context read, rather
|
|
459
786
|
than looking up whichever board is active after delivery.
|
|
@@ -516,7 +843,15 @@ curl "http://localhost:4313/api/canvas/ax/context-reads?limit=50"
|
|
|
516
843
|
# read that delivered its content: when, who, and the content revision it had.
|
|
517
844
|
# A node whose current contentRevision is newer was changed since it was read.
|
|
518
845
|
# pins maps each pinned node with a known pinner to { pinnedBy, pinnedAt, reason }.
|
|
846
|
+
# board is the latest agent read on that board of any kind ({ lastReadAt, lastReadBy } or
|
|
847
|
+
# null): a pinned board whose map reached an agent reads as read even before any card is pulled.
|
|
848
|
+
# A brief's card summaries mark nothing read; only a full pull of a card does.
|
|
849
|
+
# links maps each node to the newest linksRevision any read carried (summaries count, since a
|
|
850
|
+
# brief summary carries the card's relations). An edge whose revision is newer than both
|
|
851
|
+
# ends' links value has not been seen by an agent; the workbench marks it on the link's pill.
|
|
519
852
|
curl "http://localhost:4313/api/canvas/ax/context-status?board=<board-id>"
|
|
853
|
+
# boards=<id>,<id> also returns boards: { <id>: { lastReadAt, lastReadBy } | null } for each
|
|
854
|
+
# listed board, so the context chip reads every pinned board's status in one request.
|
|
520
855
|
|
|
521
856
|
# Context reads — a proxy (an MCP server attached to this daemon, a host adapter)
|
|
522
857
|
# records the read its agent actually made. Its own fetches send
|
|
@@ -525,7 +860,11 @@ curl "http://localhost:4313/api/canvas/ax/context-status?board=<board-id>"
|
|
|
525
860
|
# currently open board. MCP bytes measure the final formatted text response.
|
|
526
861
|
# readNodeIds lists every serialized node in what the agent received (pinned or
|
|
527
862
|
# not); the server keeps those on the board and stamps their revisions. Omitted,
|
|
528
|
-
# it defaults to deliveredNodeIds.
|
|
863
|
+
# it defaults to deliveredNodeIds. seenNodeIds adds the nodes that arrived only as
|
|
864
|
+
# summaries, which marks their links seen; omitted, it defaults to readNodeIds.
|
|
865
|
+
# For a captured graph/brief, also pass seenLinkRevisions: {"node-1": 12} from
|
|
866
|
+
# the delivered linksRevision values, not revisions fetched after delivery.
|
|
867
|
+
# Only seenNodeIds with captured entries are acknowledged when this map is supplied.
|
|
529
868
|
curl -X POST http://localhost:4313/api/canvas/ax/context-reads \
|
|
530
869
|
-H "Content-Type: application/json" \
|
|
531
870
|
-d '{"channel":"adapter","resource":"copilot:prompt-context","consumer":"copilot","pinnedNodeIds":["node-1"],"deliveredNodeIds":["node-1"],"bytes":2048}'
|
|
@@ -592,7 +931,7 @@ without a `session-end`.
|
|
|
592
931
|
curl http://localhost:4313/api/canvas/ax/presence
|
|
593
932
|
# → { ok, presences: [{ sessionId, source, agentId, label, phase, detail,
|
|
594
933
|
# focusNodeId, cursor, attached, opCount, contextUsage, lastSeenAt }],
|
|
595
|
-
#
|
|
934
|
+
# sessionActive,
|
|
596
935
|
# activity: [{ id, at, sessionId, label, op, summary, nodeId }] } # newest first, last 50
|
|
597
936
|
|
|
598
937
|
# Attach a session and report a phase (idle | thinking | tooling | waiting-approval)
|
|
@@ -619,13 +958,13 @@ a transport writer folds into it), bounded to the last 50 and kept after a
|
|
|
619
958
|
writer fades. Detaching (`attached: false`, `session-end`) removes the
|
|
620
959
|
presence outright — an ended session never lingers as an external writer.
|
|
621
960
|
|
|
622
|
-
**The context
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
961
|
+
**The context summary shows a cost, not a budget.** The top bar's context
|
|
962
|
+
summary shows what the brief costs, as ≈ tokens (about four characters per
|
|
963
|
+
token). There is no share, meter or clipped state: a brief longer than one page
|
|
964
|
+
continues on the next read. A host that knows how many tokens its agent holds
|
|
965
|
+
reports it on the presence (`contextUsage: { used, total }` on
|
|
966
|
+
`POST /api/canvas/ax/presence` / `set-presence`); the context panel then also
|
|
967
|
+
names that count ("Copilot’s whole context holds 42.8k tokens"). The
|
|
629
968
|
bundled Copilot extension reports it from the SDK's `session.usage_info`
|
|
630
969
|
event (root agent, coalesced to one report per 500 ms); the legacy
|
|
631
970
|
`context-usage` workbench event feeds the single attached session the same
|
|
@@ -663,21 +1002,27 @@ When that session ends (`attached: false`, `session-end`, or the idle expiry)
|
|
|
663
1002
|
the stream carries one `agent-session-ended` frame:
|
|
664
1003
|
|
|
665
1004
|
```json
|
|
666
|
-
{ "label": "Copilot", "parentAgentId": null, "endedAt": "…", "endedBy": "agent",
|
|
1005
|
+
{ "label": "Copilot", "writer": "copilot", "parentAgentId": null, "startedAt": "…", "endedAt": "…", "endedBy": "agent",
|
|
667
1006
|
"unchanged": false, "counts": { "items": 4, "done": 3, "cancelled": 1, "rejected": 0, "held": 0 },
|
|
668
|
-
"snapshot": { "id": "…", "name": "Before session · Copilot · 14:00" },
|
|
669
|
-
"context": { "read": [{ "id": "…", "title": "Brief" }], "pinned": [{ "id": "…", "title": "Chart", "reason": "…" }],
|
|
670
|
-
"created": [], "edited": [
|
|
1007
|
+
"snapshot": { "id": "…", "name": "Before session · Copilot · 14:00", "boardId": "…" },
|
|
1008
|
+
"context": { "read": [{ "id": "…", "boardId": "…", "title": "Brief" }], "pinned": [{ "id": "…", "boardId": "…", "title": "Chart", "reason": "…" }],
|
|
1009
|
+
"created": [], "edited": [{ "id": "…", "boardId": "…", "title": "SMB", "change": "rewrote the second paragraph", "before": "…", "after": "…" }],
|
|
1010
|
+
"changedSinceRead": [{ "id": "…", "boardId": "…", "title": "Brief" }] } }
|
|
671
1011
|
```
|
|
672
1012
|
|
|
673
1013
|
`context` is what the session did with the board's context: nodes whose content an
|
|
674
1014
|
agent read since the session attached, pins the agent made (with their reason), nodes
|
|
675
|
-
it created and edited (against the pre-session snapshot
|
|
1015
|
+
it created and edited (against the pre-session snapshot; each edit says what it did in
|
|
1016
|
+
`change` and keeps bounded `before`/`after` excerpts around the first difference), and pinned nodes changed
|
|
676
1017
|
after their latest read. A session that only read or pinned is not `unchanged`.
|
|
1018
|
+
Every touch carries its `boardId` (null with no active board); receipt identities are
|
|
1019
|
+
board + node ID, so equal IDs on different boards are not merged.
|
|
677
1020
|
|
|
678
1021
|
`items`/`done` count the work items on the board; cancelled items, rejected
|
|
679
1022
|
gates, and held gates have separate counts. `snapshot` is null when the board was
|
|
680
1023
|
empty at attach or unchanged at end (the redundant snapshot is removed).
|
|
1024
|
+
Its `boardId` identifies the owning board: a retained receipt offers to reopen it before
|
|
1025
|
+
comparing, including sessions that only added or removed cards. Snapshot access remains board-scoped.
|
|
681
1026
|
The browser shows a receipt only for a changed top-level session: `unchanged: true`
|
|
682
1027
|
and worker endings (`parentAgentId` set) never open, replace, or extend a popup —
|
|
683
1028
|
the orchestrator's receipt covers the board since it attached. An empty
|
|
@@ -687,8 +1032,26 @@ There is at most one receipt. Qualifying endings that arrive while it is up merg
|
|
|
687
1032
|
into it ("3 sessions ended", their labels, the union of their context); a merged
|
|
688
1033
|
receipt has no *View diff* and points to History, which keeps each session's
|
|
689
1034
|
snapshot. The receipt is kept until dismissed so restore/diff actions do not
|
|
690
|
-
disappear on a timer.
|
|
691
|
-
|
|
1035
|
+
disappear on a timer. It also stays across direct board switches, with a button to
|
|
1036
|
+
reopen an edited card's board; Home dismisses it. Undo and Unpin are unavailable
|
|
1037
|
+
on another board, and the lens and edit marks only apply to the active board's touches.
|
|
1038
|
+
The browser shows one stacked Before/After at a time, marking removed and added words;
|
|
1039
|
+
its Undo restores the whole card, not just the excerpt, and leaves cards whose latest edit is human alone.
|
|
1040
|
+
The caption distinguishes clipped excerpts ("undoes the whole card"), an already restored card
|
|
1041
|
+
("the card shows Before again"), and a protected human edit ("undo would lose your edit").
|
|
1042
|
+
A single session's *View diff* is `GET /api/canvas/snapshots/<id>/diff` against that
|
|
1043
|
+
snapshot, and restoring the snapshot undoes the session. *Undo* on one edited card is
|
|
1044
|
+
`POST /api/canvas/snapshots/<id>/restore-node` with `{ "nodeId": "…", "preserveHumanEdits": true }`:
|
|
1045
|
+
the server checks the latest editor at the write boundary, so an unseen human edit is safe even
|
|
1046
|
+
before polling delivers it. A skipped restore returns `{ "ok": true, "id": "…", "restored": false,
|
|
1047
|
+
"skipped": "human-edit" }`; a successful restore returns `restored: true`. Without
|
|
1048
|
+
`preserveHumanEdits`, explicit restore can replace human content. The same option is available on
|
|
1049
|
+
`canvas_snapshot { action: "restore-node", id, nodeId, preserveHumanEdits: true }` and
|
|
1050
|
+
`canvas.restoreSnapshotNode(id, nodeId, { preserveHumanEdits: true })`. That card's content goes
|
|
1051
|
+
back to the snapshot's, as one undoable edit; position, group, links and pins stay (file cards
|
|
1052
|
+
refuse, they follow their file; a card whose type changed since answers 409).
|
|
1053
|
+
The receipt's Unpin removes only its recorded pins and is safe to repeat; pins added later stay.
|
|
1054
|
+
Adapters should end their session
|
|
692
1055
|
explicitly so the human gets the receipt promptly rather than after the idle
|
|
693
1056
|
expiry. The browser's *Start agent session* button is this same endpoint
|
|
694
1057
|
(`source: "browser"`, `attached: true`) — subsequent agent-less writes are
|
|
@@ -898,6 +1261,25 @@ curl -N http://localhost:4313/api/workbench/events
|
|
|
898
1261
|
The browser, the CLI `watch` command, and the MCP resource notifications
|
|
899
1262
|
all consume this stream. Auto-reconnect with exponential backoff.
|
|
900
1263
|
|
|
1264
|
+
The direct workbench's `connected` snapshot renews its per-process
|
|
1265
|
+
`workbenchToken` (also embedded in boot HTML) over both SSE and polling.
|
|
1266
|
+
A same-bundle daemon restart does not reload the page or discard an open draft.
|
|
1267
|
+
Registered writes and file saves marked as workbench calls with a supplied stale capability
|
|
1268
|
+
return 409 until reconnect renews it; they are never silently recorded as agent
|
|
1269
|
+
edits. A refused Markdown save retains its draft and enabled Save control for
|
|
1270
|
+
retry after reconnect. Drafts and complete save queues are qualified by board + card in the
|
|
1271
|
+
current tab, surviving close/reopen and board switches (not a page reload); superseded drafts
|
|
1272
|
+
cannot resume an older render and overwrite newer text. Do not log or forward this human capability: hosted MCP
|
|
1273
|
+
workbench polling strips it from tool results, including hydration metadata.
|
|
1274
|
+
|
|
1275
|
+
`canvas-layout-update` carries its owning `boardId` (null for Home), `catalogEpoch`
|
|
1276
|
+
and `boardGeneration`; accept its baseline only for the activation in the accepted catalog.
|
|
1277
|
+
`context-pins-changed` carries the owning `boardId`. A board transition is a new baseline,
|
|
1278
|
+
not a deletion or unpin. Same-board reconnects install a fresh baseline without closing
|
|
1279
|
+
the editor or discarding its draft and selection, whichever snapshot arrives first.
|
|
1280
|
+
Remote-edge endpoint changes also publish `boards-changed` so card titles and
|
|
1281
|
+
missing-target states update; ordinary geometry changes do not refresh the catalog.
|
|
1282
|
+
|
|
901
1283
|
`agent-presence` frames carry the full presence snapshot (the same body as
|
|
902
1284
|
`GET /api/canvas/ax/presence`) on every change, including TTL expiry — a
|
|
903
1285
|
client never needs its own expiry timer. `agent-session-ended` carries the
|
|
@@ -922,12 +1304,24 @@ through any proxy:
|
|
|
922
1304
|
# current cursor. With `since=<seq>`: only events after that cursor.
|
|
923
1305
|
curl "http://localhost:4313/api/workbench/poll"
|
|
924
1306
|
curl "http://localhost:4313/api/workbench/poll?since=42"
|
|
1307
|
+
# Optional long poll: return when an event arrives, or after at most 1000 ms.
|
|
1308
|
+
curl "http://localhost:4313/api/workbench/poll?since=42&waitMs=1000"
|
|
925
1309
|
```
|
|
926
1310
|
|
|
927
|
-
Responses are `{ ok, transport: "poll", snapshot, seq, events }
|
|
928
|
-
with `since=<seq
|
|
929
|
-
|
|
930
|
-
|
|
1311
|
+
Responses are `{ ok, transport: "poll", snapshot, seq, sessionId, events }`.
|
|
1312
|
+
Poll again with `since=<seq>`. Without `waitMs`, the response is immediate;
|
|
1313
|
+
with it, a caught-up cursor waits for the next event. The wait is clamped to
|
|
1314
|
+
0–1000 ms and ends on request cancellation or server shutdown. A cursor with
|
|
1315
|
+
pending events or needing a full snapshot never waits. The direct HTTP browser
|
|
1316
|
+
client requests a 1000 ms wait and pauses 80 ms between completed requests:
|
|
1317
|
+
cursor movement arrives promptly without fast empty polling while idle.
|
|
1318
|
+
Hosted MCP-app tool polling retains its two-second cadence; SSE is unchanged.
|
|
1319
|
+
|
|
1320
|
+
A cursor that fell off the bounded event ring recovers with a fresh snapshot.
|
|
1321
|
+
After a server restart, an out-of-range cursor receives a snapshot; if the old
|
|
1322
|
+
cursor happens to fit the new ring, the browser detects the changed `sessionId`
|
|
1323
|
+
and requests a new snapshot without `since`. The browser client uses polling
|
|
1324
|
+
automatically: if the SSE stream
|
|
931
1325
|
produces no event within 3 seconds of connecting, it switches to polling (and
|
|
932
1326
|
tells the boot screen to wait for the fallback instead of alarming). Pages
|
|
933
1327
|
served by an Amp orb (`AMP_ORB` set — the same stamp that drives srcdoc mode)
|
|
@@ -935,6 +1329,34 @@ skip SSE entirely and default straight to polling. Force a transport with
|
|
|
935
1329
|
`/workbench?transport=poll` (or `transport=sse` to disable the fallback /
|
|
936
1330
|
override the orb default).
|
|
937
1331
|
|
|
1332
|
+
## Workspace UI beta settings
|
|
1333
|
+
|
|
1334
|
+
`GET /api/canvas/settings` returns `{ ok: true, workspaceSettings: {
|
|
1335
|
+
agentSessions: false, knowledgeLifecycle: false } }`. Both switches default to
|
|
1336
|
+
false, including existing libraries with no saved settings.
|
|
1337
|
+
|
|
1338
|
+
`POST /api/canvas/settings` accepts a partial object such as
|
|
1339
|
+
`{ "agentSessions": true }`. Values must be booleans (not strings, numbers,
|
|
1340
|
+
or null); omitted switches are preserved. Success returns the full canonical
|
|
1341
|
+
`workspaceSettings` only after SQLite persistence; writes without a workspace
|
|
1342
|
+
database fail. Settings are workspace-wide SQLite metadata, independent of
|
|
1343
|
+
boards and snapshots, and included in whole-library backups/restores.
|
|
1344
|
+
|
|
1345
|
+
The HTTP-only operations `settings.get` / `settings.set` are UI preferences,
|
|
1346
|
+
not agent MCP tools or restrictions on session/knowledge APIs. Writes never
|
|
1347
|
+
create a board or attribute agent activity. POST requires the workbench's
|
|
1348
|
+
`X-PMX-Workbench: 1` UI marker; unmarked API writes receive 403. The hosted
|
|
1349
|
+
app-only bridge supplies that marker for this preferences operation alone,
|
|
1350
|
+
without forwarding the daemon's human-author token or exempting subsequent
|
|
1351
|
+
canvas edits from agent protections. The marker is a UI-policy convention,
|
|
1352
|
+
not authentication against a caller deliberately forging HTTP headers; hosted
|
|
1353
|
+
UI isolation relies on the host honoring app-only tool visibility.
|
|
1354
|
+
|
|
1355
|
+
`settings-changed` carries `{ workspaceSettings: { agentSessions,
|
|
1356
|
+
knowledgeLifecycle } }`, including after a full-library restore. The `connected`
|
|
1357
|
+
snapshot includes `workspaceSettings` for SSE reconnect and polling recovery.
|
|
1358
|
+
Clients use these ordered events/snapshots as canonical, not late POST replies.
|
|
1359
|
+
|
|
938
1360
|
## Session theme override (host-default theming)
|
|
939
1361
|
|
|
940
1362
|
`/workbench?theme=<name>` gives that browser session its own theme without
|