pmx-canvas 0.4.7 → 0.5.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.
- package/.github/extensions/pmx-canvas/extension.mjs +154 -5
- package/.github/extensions/pmx-canvas/steering-delivery.mjs +79 -0
- package/CHANGELOG.md +156 -0
- package/Readme.md +72 -23
- package/dist/canvas/global.css +3069 -796
- package/dist/canvas/index.js +104 -104
- package/dist/json-render/index.css +1 -1
- package/dist/types/cli/agent.d.ts +1 -0
- package/dist/types/cli/commands/pump.d.ts +9 -0
- package/dist/types/client/App.d.ts +1 -1
- package/dist/types/client/canvas/AgentPresenceLayer.d.ts +11 -0
- package/dist/types/client/canvas/BarHint.d.ts +22 -0
- package/dist/types/client/canvas/CanvasNode.d.ts +5 -0
- package/dist/types/client/canvas/CanvasViewport.d.ts +4 -4
- package/dist/types/client/canvas/CommandBar.d.ts +10 -0
- package/dist/types/client/canvas/ConnectionBanner.d.ts +13 -0
- package/dist/types/client/canvas/ContextMenu.d.ts +6 -0
- package/dist/types/client/canvas/EdgeLayer.d.ts +2 -1
- package/dist/types/client/canvas/EmptyState.d.ts +11 -0
- package/dist/types/client/canvas/ExternalWriters.d.ts +5 -0
- package/dist/types/client/canvas/HumanPresenceLayer.d.ts +8 -0
- package/dist/types/client/canvas/Minimap.d.ts +12 -1
- package/dist/types/client/canvas/ScopeFenceLayer.d.ts +8 -0
- package/dist/types/client/canvas/SelectionBar.d.ts +6 -0
- package/dist/types/client/canvas/SessionPanel.d.ts +1 -0
- package/dist/types/client/canvas/SessionReceipt.d.ts +17 -0
- package/dist/types/client/canvas/TextPrompt.d.ts +25 -0
- package/dist/types/client/canvas/ToolRail.d.ts +21 -0
- package/dist/types/client/canvas/TopBar.d.ts +7 -0
- package/dist/types/client/canvas/canvas-area.d.ts +25 -0
- package/dist/types/client/canvas/create-in-view.d.ts +18 -0
- package/dist/types/client/canvas/import-files.d.ts +8 -0
- package/dist/types/client/canvas/kind-colors.d.ts +3 -0
- package/dist/types/client/canvas/use-focus-trap.d.ts +14 -0
- package/dist/types/client/canvas/use-now.d.ts +6 -0
- package/dist/types/client/icons.d.ts +11 -4
- package/dist/types/client/nodes/ExtAppFrame.d.ts +32 -0
- package/dist/types/client/nodes/GroupNode.d.ts +6 -1
- package/dist/types/client/nodes/MarkdownNode.d.ts +2 -0
- package/dist/types/client/nodes/inline-editor-commands.d.ts +3 -4
- package/dist/types/client/state/canvas-store.d.ts +49 -5
- package/dist/types/client/state/human-store.d.ts +26 -0
- package/dist/types/client/state/iframe-mode.d.ts +1 -0
- package/dist/types/client/state/intent-bridge.d.ts +37 -5
- package/dist/types/client/state/node-factory.d.ts +0 -1
- package/dist/types/client/state/presence-store.d.ts +78 -0
- package/dist/types/client/state/session-store.d.ts +176 -0
- package/dist/types/client/types.d.ts +2 -1
- package/dist/types/client/utils/platform.d.ts +2 -0
- package/dist/types/server/agent-presence.d.ts +192 -0
- package/dist/types/server/ax-gate-ttl.d.ts +6 -0
- package/dist/types/server/ax-interaction.d.ts +4 -10
- package/dist/types/server/ax-state-manager.d.ts +21 -2
- package/dist/types/server/ax-state.d.ts +49 -3
- package/dist/types/server/ax-wait.d.ts +13 -0
- package/dist/types/server/canvas-db.d.ts +23 -1
- package/dist/types/server/canvas-operations.d.ts +1 -0
- package/dist/types/server/canvas-state.d.ts +27 -13
- package/dist/types/server/canvas-validation.d.ts +0 -14
- package/dist/types/server/html-surface.d.ts +1 -1
- package/dist/types/server/human-presence.d.ts +40 -0
- package/dist/types/server/index.d.ts +29 -3
- package/dist/types/server/intent-registry.d.ts +7 -0
- package/dist/types/server/mutation-history.d.ts +15 -2
- package/dist/types/server/operations/invoker.d.ts +13 -2
- package/dist/types/server/operations/ops/ax-presence.d.ts +2 -0
- package/dist/types/server/operations/ops/human-presence.d.ts +3 -0
- package/dist/types/server/operations/registry.d.ts +11 -0
- package/dist/types/server/operations/types.d.ts +1 -1
- package/dist/types/server/scope-fence.d.ts +62 -0
- package/dist/types/server/server.d.ts +0 -12
- package/dist/types/shared/agent-presence.d.ts +130 -0
- package/dist/types/shared/approval-gates.d.ts +19 -0
- package/dist/types/shared/auto-arrange.d.ts +0 -1
- package/dist/types/shared/ax-kinds.d.ts +11 -0
- package/dist/types/shared/human-presence.d.ts +31 -0
- package/dist/types/shared/iframe-probe.d.ts +8 -0
- package/dist/types/shared/scope-fence.d.ts +21 -0
- package/docs/RELEASE.md +54 -0
- package/docs/ax-host-adapter-contract.md +30 -14
- package/docs/cli.md +37 -1
- package/docs/environment.md +3 -0
- package/docs/http-api.md +222 -5
- package/docs/mcp.md +6 -5
- package/docs/node-types.md +3 -1
- package/docs/plans/plan-009-tech-debt-backlog.md +13 -0
- package/docs/screenshot.png +0 -0
- package/docs/screenshots/multi-agent-live.png +0 -0
- package/docs/screenshots/workbench-live-dark.png +0 -0
- package/docs/screenshots/workbench-live-light.png +0 -0
- package/docs/sdk.md +1 -1
- package/package.json +1 -1
- package/skills/pmx-canvas/SKILL.md +109 -18
- package/skills/pmx-canvas/references/codex-app-adapter.md +28 -0
- package/skills/pmx-canvas/references/full-reference.md +55 -16
- package/skills/pmx-canvas/references/github-copilot-app-adapter.md +41 -24
- package/skills/pmx-canvas-orchestration/SKILL.md +56 -2
- package/skills/pmx-canvas-testing/SKILL.md +57 -0
- package/src/cli/agent.ts +1 -0
- package/src/cli/commands/copilot.ts +15 -8
- package/src/cli/commands/nodes.ts +1 -19
- package/src/cli/commands/pump.ts +252 -0
- package/src/cli/index.ts +1 -5
- package/src/cli/shared.ts +1 -1
- package/src/client/App.tsx +209 -585
- package/src/client/canvas/AgentPresenceLayer.tsx +74 -0
- package/src/client/canvas/AttentionHistory.tsx +0 -10
- package/src/client/canvas/BarHint.tsx +49 -0
- package/src/client/canvas/CanvasNode.tsx +488 -136
- package/src/client/canvas/CanvasViewport.tsx +190 -94
- package/src/client/canvas/CommandBar.tsx +140 -0
- package/src/client/canvas/CommandPalette.tsx +133 -58
- package/src/client/canvas/ConnectionBanner.tsx +65 -0
- package/src/client/canvas/ContextMenu.tsx +88 -27
- package/src/client/canvas/ContextPinBar.tsx +2 -2
- package/src/client/canvas/EdgeLayer.tsx +121 -38
- package/src/client/canvas/EmptyState.tsx +93 -0
- package/src/client/canvas/ExpandedNodeOverlay.tsx +64 -118
- package/src/client/canvas/ExternalWriters.tsx +308 -0
- package/src/client/canvas/FocusFieldLayer.tsx +12 -1
- package/src/client/canvas/HumanPresenceLayer.tsx +45 -0
- package/src/client/canvas/IntentLayer.tsx +1 -1
- package/src/client/canvas/Minimap.tsx +117 -190
- package/src/client/canvas/ScopeFenceLayer.tsx +38 -0
- package/src/client/canvas/SelectionBar.tsx +175 -23
- package/src/client/canvas/SessionPanel.tsx +481 -0
- package/src/client/canvas/SessionReceipt.tsx +141 -0
- package/src/client/canvas/ShortcutOverlay.tsx +21 -6
- package/src/client/canvas/SnapshotPanel.tsx +166 -80
- package/src/client/canvas/TextPrompt.tsx +128 -0
- package/src/client/canvas/ToolRail.tsx +447 -0
- package/src/client/canvas/TopBar.tsx +308 -0
- package/src/client/canvas/auto-fit.ts +1 -5
- package/src/client/canvas/canvas-area.ts +41 -0
- package/src/client/canvas/create-in-view.ts +48 -0
- package/src/client/canvas/import-files.ts +64 -0
- package/src/client/canvas/kind-colors.ts +22 -0
- package/src/client/canvas/snap-guides.ts +1 -1
- package/src/client/canvas/use-focus-trap.ts +54 -0
- package/src/client/canvas/use-now.ts +17 -0
- package/src/client/canvas/use-pan-zoom.ts +13 -2
- package/src/client/icons.tsx +57 -18
- package/src/client/nodes/ExtAppFrame.tsx +79 -2
- package/src/client/nodes/GroupNode.tsx +9 -28
- package/src/client/nodes/InlineFormatBar.tsx +4 -3
- package/src/client/nodes/InlineMarkdownEditor.tsx +27 -0
- package/src/client/nodes/MarkdownNode.tsx +59 -24
- package/src/client/nodes/MdFormatBar.tsx +2 -4
- package/src/client/nodes/inline-editor-commands.ts +17 -6
- package/src/client/nodes/use-surface-frame.ts +3 -1
- package/src/client/state/attention-bridge.ts +3 -0
- package/src/client/state/canvas-store.ts +273 -84
- package/src/client/state/human-store.ts +175 -0
- package/src/client/state/iframe-mode.ts +33 -9
- package/src/client/state/intent-bridge.ts +75 -13
- package/src/client/state/node-factory.ts +2 -8
- package/src/client/state/presence-store.ts +168 -0
- package/src/client/state/session-store.ts +419 -0
- package/src/client/state/sse-bridge.ts +93 -30
- package/src/client/theme/global.css +3069 -796
- package/src/client/types.ts +3 -1
- package/src/client/utils/platform.ts +6 -1
- package/src/mcp/canvas-access.ts +2 -2
- package/src/server/agent-presence.ts +755 -0
- package/src/server/ax-context.ts +45 -0
- package/src/server/ax-gate-ttl.ts +61 -0
- package/src/server/ax-interaction.ts +29 -1
- package/src/server/ax-state-manager.ts +78 -6
- package/src/server/ax-state.ts +82 -15
- package/src/server/ax-wait.ts +33 -0
- package/src/server/canvas-db.ts +237 -61
- package/src/server/canvas-operations.ts +17 -8
- package/src/server/canvas-state.ts +131 -58
- package/src/server/canvas-validation.ts +3 -53
- package/src/server/demo-state.json +2 -70
- package/src/server/html-surface.ts +11 -2
- package/src/server/human-presence.ts +160 -0
- package/src/server/index.ts +107 -4
- package/src/server/intent-registry.ts +13 -0
- package/src/server/mutation-history.ts +47 -2
- package/src/server/operations/composites.ts +6 -3
- package/src/server/operations/http.ts +10 -1
- package/src/server/operations/index.ts +4 -0
- package/src/server/operations/invoker.ts +33 -6
- package/src/server/operations/ops/ax-presence.ts +62 -0
- package/src/server/operations/ops/ax-read.ts +8 -0
- package/src/server/operations/ops/ax-state.ts +106 -1
- package/src/server/operations/ops/ax-timeline.ts +54 -5
- package/src/server/operations/ops/ax-work.ts +46 -0
- package/src/server/operations/ops/batch.ts +3 -1
- package/src/server/operations/ops/edges.ts +88 -2
- package/src/server/operations/ops/groups.ts +4 -3
- package/src/server/operations/ops/human-presence.ts +35 -0
- package/src/server/operations/ops/json-render.ts +11 -1
- package/src/server/operations/ops/nodes.ts +65 -16
- package/src/server/operations/ops/query.ts +1 -0
- package/src/server/operations/ops/snapshots.ts +16 -0
- package/src/server/operations/registry.ts +196 -1
- package/src/server/operations/types.ts +1 -1
- package/src/server/scope-fence.ts +207 -0
- package/src/server/server.ts +177 -79
- package/src/server/trace-manager.ts +0 -2
- package/src/server/web-artifacts.ts +0 -1
- package/src/shared/agent-presence.ts +162 -0
- package/src/shared/approval-gates.ts +35 -0
- package/src/shared/auto-arrange.ts +121 -13
- package/src/shared/ax-kinds.ts +23 -0
- package/src/shared/human-presence.ts +30 -0
- package/src/shared/iframe-probe.ts +8 -0
- package/src/shared/scope-fence.ts +31 -0
- package/dist/types/client/canvas/DockedNode.d.ts +0 -4
- package/src/client/canvas/DockedNode.tsx +0 -165
package/docs/node-types.md
CHANGED
|
@@ -328,7 +328,9 @@ Groups are spatial containers that visually contain other nodes. They render
|
|
|
328
328
|
as dashed-border frames with a title bar and optional accent color.
|
|
329
329
|
|
|
330
330
|
- Select 2+ nodes and click "Group" in the selection bar
|
|
331
|
-
-
|
|
331
|
+
- Ungroup from the frame's ⋯ menu, the right-click menu, or Shift+G — the frame dissolves and
|
|
332
|
+
its children stay as independent nodes (one undo step); `canvas_group { action: "ungroup" }`
|
|
333
|
+
does exactly the same
|
|
332
334
|
- Collapsing a group hides children and shows a summary
|
|
333
335
|
- By default, group creation preserves the children's current positions and
|
|
334
336
|
expands the frame around them
|
|
@@ -79,3 +79,16 @@ The remaining items were re-analyzed with pros/cons and tiered; the maintainer a
|
|
|
79
79
|
| Finding U — sticky `-32001` session-error banner over rendering content | **Presentation fix field-confirmed (0.4.3 reports, both hosts); root cause open:** forced error state renders the soft saved-content notice. The underlying stressed-reconnect MCP timeout is still not clean-reproducible — investigate session re-establishment retry/backoff if a future cycle reproduces it. |
|
|
80
80
|
| Skill-mirror drift automation | **Done (2026-08-05):** `pmx-canvas skills sync` (+ `--check`, exit 1 on drift) discovers the skill copies already installed in the workspace — no assumed host layout, per maintainer direction — and whole-tree-replaces them from the package. Closes the recurring every-cycle hand-sync complaint in the audit addenda. Remaining audit ask: a true model-vs-baseline benchmark of the 17 eval prompts (a test-harness task, not a repo artifact). |
|
|
81
81
|
| Registry-migration dead-code sweep | **Done (2026-08-03):** removed the orphaned `describeOp`/`describeGateOp` (composites.ts, dead since the v0.3.0 registration filter) and the `compactNodePayload`/`compactLayoutPayload`/`wantsFullPayload` duplicates in `mcp/server.ts` (live copies are in `ops/nodes.ts`). |
|
|
82
|
+
|
|
83
|
+
## Added during rail-chrome-v2 (2026-08-23, phases 1–5)
|
|
84
|
+
|
|
85
|
+
Recorded while the redesign's phases land so nothing rides in silently. Plan: `design/rail-chrome-v2/PLAN.md`.
|
|
86
|
+
|
|
87
|
+
| Item | What's open |
|
|
88
|
+
|---|---|
|
|
89
|
+
| `window.prompt`-based rail creates | The rail's palette (`ToolRail.tsx` `promptedCreate`) collects a title/URL via `window.prompt`, which headless hosts and some embedded WebViews suppress (the create silently no-ops). Replace with an inline popover form once the phase-7 command-palette restyle lands; until then it is the same behavior the old toolbar had. |
|
|
90
|
+
| `dockPosition` retirement (phase 5c) | **Done (2026-08-23, rail-chrome-v2 phase 5c):** the HUD pill layer, `DockedNode`, first-run seeding, `undockNode`/`hasOpenDockedContextPanel`, the validator's `hiddenEdgeEndpoints` rule, the `--dock-position` CLI flag, the `dock_position` SQLite columns (dead column left in existing DBs, no migration), and the HUD/docked/context-dock CSS are all removed; the demo fixture was regenerated. Note for future removals: `tsc` never deletes stale emits under the committed `dist/types/` — delete the orphaned `.d.ts` by hand. |
|
|
91
|
+
| `annotationMode` / `annotationTool` redundancy | `CanvasViewport` takes both a boolean `annotationMode` prop and the `annotationTool` enum, and `App.tsx` derives the former from the latter (`annotationTool !== null`). Collapse to the enum alone in the phase-7 quality wave — `CanvasViewport.tsx` only. |
|
|
92
|
+
| Agents can resolve their own gates | Pre-existing design question surfaced by the session panel: `ax.approval.resolve` accepts any caller, so an agent could approve its own gate over HTTP/MCP. The panel and the `held` TTL path assume the human answers. Either gate `resolve` on the workbench marker (like the scope fence) or document it as a cooperating-agent contract; decide before the next adapter ships. |
|
|
93
|
+
| Batch never carries `fromWorkbench` | `canvas.batch` inner dispatches are always agent writes (the browser never uses batch), so the scope fence and presence treat them as such. Holds by construction today; if the client ever adopts batch, the marker must thread through `ops/batch.ts`. Comment at the dispatch site. |
|
|
94
|
+
| Presence registry is a second single-slot listener family | `setSessionStartListener` / `setSessionEndListener` / `setEmitter` on `agentPresence` follow architecture rule 8 (owned by `server.ts`). If a third consumer ever needs session lifecycle (e.g. an audit log), fan out inside the existing registration — do not add a competing one. |
|
package/docs/screenshot.png
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/docs/sdk.md
CHANGED
|
@@ -115,7 +115,7 @@ canvas.submitAxInteraction({ type: 'ax.work.create', sourceNodeId: n1, payload:
|
|
|
115
115
|
|
|
116
116
|
// Delivery — claim pending steering for a consumer (loop-safe), then acknowledge
|
|
117
117
|
const pending = canvas.getPendingSteering({ consumer: 'copilot', limit: 20 });
|
|
118
|
-
if (pending[0]) canvas.markSteeringDelivered(pending[0].id,
|
|
118
|
+
if (pending[0]) canvas.markSteeringDelivered(pending[0].id, 'copilot');
|
|
119
119
|
|
|
120
120
|
// Elicitation + mode requests (canvas-bound, snapshotted)
|
|
121
121
|
const elic = canvas.requestElicitation({ prompt: 'Who owns this?', fields: ['owner'] }, { source: 'sdk' });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pmx-canvas",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Spatial canvas workbench for coding agents — infinite 2D canvas with agent-native CLI, MCP integration, nodes, edges, file watching, and snapshots",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/server/index.ts",
|
|
@@ -49,7 +49,12 @@ Humans curate agent context by pinning nodes; agents read that curation through
|
|
|
49
49
|
floor, not a replacement: only an explicit signal gives the human a real pre-mutation veto
|
|
50
50
|
window, your reasoning (`reason`), and staged multi-step previews. Batch and browser-human
|
|
51
51
|
actions never auto-ghost.
|
|
52
|
-
6. **
|
|
52
|
+
6. **Attach a session so the human can see you (0.4.8+).** Start board work with
|
|
53
|
+
`canvas_ax_state { action: "set-presence", attached: true, label: "<who you are>" }` and end
|
|
54
|
+
it with `{ attached: false }` — detaching hands the human a receipt. Everything the session
|
|
55
|
+
gives you and asks of you (cursor + phase chip, the session panel, steering, the scope fence's
|
|
56
|
+
403s, the human edit lock's 409s, unattended approvals) is in **Sessions & the human** below.
|
|
57
|
+
7. **Mutate through current composites.** Prefer the 16 composite MCP tools below.
|
|
53
58
|
7. **Arrange and validate.** After batch changes, use `canvas_view { action: "arrange" }` when
|
|
54
59
|
appropriate and always finish with `canvas_query { action: "validate" }`.
|
|
55
60
|
7b. **Show the human.** After creating user-facing output, bring the camera to it: a single node
|
|
@@ -135,7 +140,7 @@ this table is kept in sync with the server's composite registry; `tools/list` on
|
|
|
135
140
|
|-----------|---------|
|
|
136
141
|
| `canvas_node` | `add`, `get`, `update`, `remove` |
|
|
137
142
|
| `canvas_render` | `describe-schema`, `validate`, `add-json-render`, `stream-json-render`, `add-graph` |
|
|
138
|
-
| `canvas_edge` | `add`, `remove` |
|
|
143
|
+
| `canvas_edge` | `add`, `update`, `remove` |
|
|
139
144
|
| `canvas_group` | `create`, `add`, `ungroup` |
|
|
140
145
|
| `canvas_history` | `undo`, `redo` |
|
|
141
146
|
| `canvas_view` | `arrange`, `focus`, `fit`, `clear`, `remove-annotation` |
|
|
@@ -199,10 +204,9 @@ distant nodes, an auto-placed node lands off-camera.
|
|
|
199
204
|
`fit` over the new ids. Manual pixel math is what produces long-line, unbalanced boards.
|
|
200
205
|
6. `fit` sizes itself to the connected browser window (0.4.6+) — you do not need to guess
|
|
201
206
|
`width`/`height`. Pass them only to fit for a window other than the human's.
|
|
202
|
-
7. **
|
|
203
|
-
|
|
204
|
-
`
|
|
205
|
-
trails off into empty canvas.
|
|
207
|
+
7. **Every node is a canvas card.** Nodes render on the canvas where you put them — there is
|
|
208
|
+
no docked/HUD placement and no `dockPosition` (removed in the rail-chrome redesign); `status`
|
|
209
|
+
and `context` nodes are ordinary cards like the rest.
|
|
206
210
|
|
|
207
211
|
**Size for content.** Omitting `width`/`height` gives readable per-type defaults — prefer them:
|
|
208
212
|
markdown 640×420, status 360×200, file 520×360, diff 640×420, mermaid 640×460, html 720×640,
|
|
@@ -233,24 +237,107 @@ Do not confuse context pinning with **Lock position**, which only excludes a nod
|
|
|
233
237
|
Every node type, including `status`, can be removed through `canvas_node { action: "remove" }`, the
|
|
234
238
|
title-bar × control, or the **Close** context-menu action.
|
|
235
239
|
|
|
240
|
+
## Sessions & the human
|
|
241
|
+
|
|
242
|
+
The board has three modes, all gated on one fact — whether a session is attached:
|
|
243
|
+
|
|
244
|
+
- **Quiet board** (nobody attached, nobody writing): a plain canvas. Your writes still show as
|
|
245
|
+
ghosts, nothing else changes.
|
|
246
|
+
- **External steering** (you write with no session attached): the top bar shows a passive
|
|
247
|
+
writers indicator with an activity feed listing each write under your label, plus a
|
|
248
|
+
connected-writers sheet. Identify yourself (`PMX_CANVAS_AGENT_SOURCE`, or `x-pmx-source` on
|
|
249
|
+
HTTP) so the feed names you, not the transport. Pending explicit intents carry an inline Veto
|
|
250
|
+
there.
|
|
251
|
+
- **Focus session** (attached): your cursor sits on the node you last touched with a phase chip
|
|
252
|
+
(`idle` / `thinking` / `tooling` / `waiting-approval`); every MCP/HTTP write is attributed to
|
|
253
|
+
your session automatically (pass `agentId` only to keep a sub-agent separate); the human gets
|
|
254
|
+
the session panel (work items, approval gates, timeline of your tool runs, board writes,
|
|
255
|
+
evidence and steering), a command bar that posts steering you read on your next turn (with a target
|
|
256
|
+
picker when several agents are connected), a timeline filterable by kind (Updates / Steer /
|
|
257
|
+
Events / Evidence), and a context meter. That meter is the pinned-context payload estimated against a configured budget
|
|
258
|
+
("Pins") unless your host reports your real token usage on the presence
|
|
259
|
+
(`set-presence { contextUsage: { used, total } }`) — then it shows your actual window
|
|
260
|
+
("Context"). The Copilot extension reports it automatically; other hosts report it themselves. Report `phase: "thinking"` before a long reasoning stretch if your host
|
|
261
|
+
gives you a hook; `tooling` is derived from your writes. Hosts with adapters (the Copilot
|
|
262
|
+
extension) attach for you; the human can also start a session from the board's *Start agent
|
|
263
|
+
session* button — your writes (transport or `PMX_CANVAS_AGENT_SOURCE` label alike) are
|
|
264
|
+
attributed to it and it takes your name.
|
|
265
|
+
|
|
266
|
+
What the session asks of you:
|
|
267
|
+
|
|
268
|
+
- **Detach explicitly** (`attached: false` or a `session-end` activity). Attaching over a
|
|
269
|
+
non-empty board saved a `Before session · …` snapshot; detaching emits the receipt (items
|
|
270
|
+
done / vetoed, a diff against that snapshot, one-click restore) — an idle timeout delays it.
|
|
271
|
+
- **403 = outside the scope fence.** The human may fence you to a region (`policy.scope`): writes
|
|
272
|
+
outside it are refused with a reason naming the node or position (a fenced group frame
|
|
273
|
+
grants its members too). Read `policy.scope` in `canvas://ax-context`, ask the human to widen
|
|
274
|
+
it, never retry blindly. The fence is the human's: `set-policy` does not take `scope`.
|
|
275
|
+
- **409 = a human is holding that node** (dragging or editing it right now). Requeue the change
|
|
276
|
+
and retry in a moment. If you had signalled an intent on it, it was vetoed — a `yield` timeline
|
|
277
|
+
event says who took over.
|
|
278
|
+
- **A gate you do not answer auto-holds** (`held`, default TTL 5 min). `held` is a non-approval:
|
|
279
|
+
do not proceed; the human can reopen it from the panel. A rejection reaches you as steering.
|
|
280
|
+
- **Your latest edit can be undone from the panel.** One shared undo stack: when the human undoes
|
|
281
|
+
it you get steering ("Undid your edit: …"); treat the board as the truth, not your last write.
|
|
282
|
+
- **Steering arrives as `steering-message` rows** in `canvas://ax-timeline` (or claim them via
|
|
283
|
+
`canvas_ax_delivery`). The command bar, gate rejections, undo and take-overs all speak through
|
|
284
|
+
it. Steering may be ADDRESSED: with several agents connected the human picks a recipient in
|
|
285
|
+
the composer, and a claim only returns broadcasts plus messages targeted at your `consumer`
|
|
286
|
+
label — so always claim as yourself (your `PMX_CANVAS_AGENT_SOURCE` label), or addressed
|
|
287
|
+
steering never reaches you.
|
|
288
|
+
- **Steering is also the agent-to-agent mailbox.** To coordinate with another agent on the
|
|
289
|
+
board, send `canvas_ax_timeline { action: "send-steering", message, target: "<their
|
|
290
|
+
consumer label>" }` — they claim it once on their next turn, you never receive your own, and
|
|
291
|
+
the human sees the exchange in the panel as "you → them · message". Read
|
|
292
|
+
`canvas_ax_state { action: "presence" }` for who is connected. Park shared state in work
|
|
293
|
+
items and evidence; split territory with group frames rather than editing the same node.
|
|
294
|
+
|
|
236
295
|
## Browser Workflows
|
|
237
296
|
|
|
238
297
|
Use the visible workbench when the human is actively curating layout:
|
|
239
298
|
|
|
240
299
|
- Drag nodes to move them.
|
|
241
|
-
-
|
|
242
|
-
|
|
300
|
+
- Drag empty space to lasso-select (Select tool, the default); hold Space or pick the rail's Pan
|
|
301
|
+
tool to pan instead — the Pan tool pans even when the drag starts on a node.
|
|
302
|
+
- The selection bar (floating bottom-center) offers count, align left/top, distribute,
|
|
303
|
+
auto-arrange, Group (G), Connect, Pin as context, delete and clear; selected nodes show an
|
|
304
|
+
accent outline with corner handles.
|
|
243
305
|
- Right-click a node for context pinning, position locking, focus, collapse, connect, refresh,
|
|
244
306
|
open, close, and type-specific actions.
|
|
245
|
-
- Drop files or URLs to create matching nodes
|
|
307
|
+
- Drop files or URLs to create matching nodes; an empty board shows starter actions (new note,
|
|
308
|
+
pick files, paste a link, start an agent session).
|
|
246
309
|
- Double-click markdown to edit inline.
|
|
247
|
-
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
310
|
+
- **Groups** are frames with the name pill and an action cluster (auto-arrange children, collapse,
|
|
311
|
+
⋯ rename / ungroup / pin all) on the top edge. Membership changes only on release while the
|
|
312
|
+
"release to add to <group>" pill shows (Esc keeps it out); dragging a child fully out offers
|
|
313
|
+
"release to remove". A collapsed group is a chip that hides its children (edges to them draw
|
|
314
|
+
to the chip). G groups the selection, Shift+G ungroups. Ungroup *dissolves* the frame (children
|
|
315
|
+
stay, nested children move up one level, one undo step) — and your
|
|
316
|
+
`canvas_group { action: "ungroup" }` is the same operation with the same result.
|
|
317
|
+
- **Edges**: drag from a node port, or pick the rail's Connect tool (C) and drag from anywhere on
|
|
318
|
+
a node; the target lights up, Esc cancels, L asks for a label on release.
|
|
319
|
+
- The **History** drawer (rail camera button, or the receipt's Full log) lists snapshots and
|
|
320
|
+
agent sessions in one timeline; save a snapshot there before experiments and restore only after
|
|
321
|
+
confirmation. Ctrl/Cmd+Z and Shift+Z work the shared undo stack.
|
|
322
|
+
- The **minimap** (bottom-right of the canvas) is a true-scale map; hover magnifies it, click
|
|
323
|
+
jumps, drag pans. A banner under the top bar reports a dropped stream (reconnecting) or a
|
|
324
|
+
post-reconnect resync; edits still save over HTTP meanwhile.
|
|
325
|
+
- Other open tabs appear as green cursors with a name tag (`/workbench?name=mia` sets yours);
|
|
326
|
+
a node you drag is locked for agents until you release it.
|
|
327
|
+
- The chrome is a persistent 52px left tool rail plus a slim 44px top bar (0.4.8+). The rail
|
|
328
|
+
carries the tools (Select V, Pan Space, Connect C), node creation (markdown M, image I, file
|
|
329
|
+
Shift+F, webpage W, HTML surface H, group G, annotate A — a popover with draw / text / eraser),
|
|
330
|
+
and utilities: search & commands (Cmd+K — actions with shortcuts, then jump-to-node), arrange,
|
|
331
|
+
trace, minimap, history, the theme picker (nine themes: dark, light, high-contrast, midnight,
|
|
332
|
+
sepia, arctic, ember, forest, volt), and shortcuts (?). The top bar holds the connection dot,
|
|
333
|
+
workspace title, the session chip / gate badge / context meter while a session is attached
|
|
334
|
+
(or the external-writers indicator and *Start agent session* otherwise), and the zoom cluster
|
|
335
|
+
(zoom out, % label = reset, zoom in, fit F). Hovering a rail button shows a tooltip with its
|
|
336
|
+
shortcut.
|
|
337
|
+
On viewports ≤1180px the top bar drops its meta text and the session panel becomes a drawer;
|
|
338
|
+
every control stays in the rail at any width — there is no separate mobile menu.
|
|
339
|
+
- Keyboard: nodes are focusable — arrow keys move to the nearest node, Enter opens it in focus
|
|
340
|
+
mode (a scrim + inset view with Open in tab), Esc closes the top-most overlay.
|
|
254
341
|
- Embedding hosts can open `/workbench?theme=<name>` (or `?theme=auto` to follow the host's
|
|
255
342
|
light/dark appearance) for a session-local default theme that never changes the server-global
|
|
256
343
|
theme other clients see; an explicit pick from the theme menu ends the override. The bundled
|
|
@@ -356,8 +443,12 @@ Prefer `canvas_query { action: "search" }` over parsing the full layout.
|
|
|
356
443
|
affordance. The connecting overlay stays up until paint is confirmed, so a black layer is
|
|
357
444
|
never presented as ready. The recovery trail
|
|
358
445
|
(`GET /api/canvas/debug/ext-app-recovery` / `window.__PMX_EXTAPP_LOG`) now records
|
|
359
|
-
`mount-slot`, `paint-ok`, `paint-fail`, `soft-expand-cycle`,
|
|
360
|
-
diagnosing, trust `paint-ok`/`paint-fail`, and never assert
|
|
446
|
+
`mount-slot`, `paint-ok`, `paint-fail`, `soft-expand-cycle`, `recovery-exhausted`, and
|
|
447
|
+
`assume-visible-rearm` — when diagnosing, trust `paint-ok`/`paint-fail`, and never assert
|
|
448
|
+
health from `settled` alone. Exception: in a host that reports the document hidden (the
|
|
449
|
+
GitHub Copilot panel does so continuously), `paint-ok` is recorded as
|
|
450
|
+
`paint-ok (unverified: host hidden)` — the app answered the paint probe, but nothing is
|
|
451
|
+
proven composited, so do not treat it as a verified paint. If a
|
|
361
452
|
tile still shows the Retry affordance, click Retry (fresh recovery budget) or expand-then-close;
|
|
362
453
|
attach the recovery trail when reporting.
|
|
363
454
|
- Ext-app frame documents live in server memory. Through 0.4.0, killing/restarting the daemon
|
|
@@ -60,6 +60,34 @@ The MCP server auto-starts the HTTP workbench on first tool call. Open the retur
|
|
|
60
60
|
in the Codex in-app Browser, usually `http://127.0.0.1:4313/workbench` or
|
|
61
61
|
`http://localhost:4313/workbench`.
|
|
62
62
|
|
|
63
|
+
### Agent presence (cursor + phase chip)
|
|
64
|
+
|
|
65
|
+
Name the agent on the MCP server so its writes, its session, and its cursor share one identity:
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"mcpServers": {
|
|
70
|
+
"canvas": {
|
|
71
|
+
"command": "bunx",
|
|
72
|
+
"args": ["pmx-canvas", "--mcp"],
|
|
73
|
+
"env": { "PMX_CANVAS_AGENT_SOURCE": "codex" }
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Then attach at the start of a task and detach at the end:
|
|
80
|
+
|
|
81
|
+
```json
|
|
82
|
+
{ "tool": "canvas_ax_state", "action": "set-presence", "attached": true, "label": "Codex", "phase": "idle" }
|
|
83
|
+
{ "tool": "canvas_ax_state", "action": "set-presence", "attached": false }
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
While attached the workbench shows the Codex cursor on the node it last touched and a phase chip
|
|
87
|
+
(`thinking` · `tooling` · `waiting-approval` · `idle`). `tooling` and the cursor position are
|
|
88
|
+
derived from Codex's own MCP writes — nothing per call. Even without `PMX_CANVAS_AGENT_SOURCE`,
|
|
89
|
+
writes arriving as plain `mcp` are attributed to the single attached session.
|
|
90
|
+
|
|
63
91
|
## Codex-Native Workflow
|
|
64
92
|
|
|
65
93
|
1. Open `/workbench` in the Codex in-app Browser as the first visible action. If PMX is not running
|
|
@@ -132,8 +132,8 @@ pmx-canvas --theme=light # Light theme
|
|
|
132
132
|
pmx-canvas --version # Print installed version and exit
|
|
133
133
|
```
|
|
134
134
|
|
|
135
|
-
`--theme` accepts `dark` (default), `light`, `high-contrast`, `midnight`, `sepia`, `arctic`, `ember`, `forest`, or `volt` (0.4.x; the
|
|
136
|
-
the `PMX_CANVAS_THEME` environment variable, or
|
|
135
|
+
`--theme` accepts `dark` (default), `light`, `high-contrast`, `midnight`, `sepia`, `arctic`, `ember`, `forest`, or `volt` (0.4.x; the rail's theme picker lists the same nine). Same value can be set via
|
|
136
|
+
the `PMX_CANVAS_THEME` environment variable, or picked live from the left tool rail.
|
|
137
137
|
|
|
138
138
|
Start the canvas once per session, then reuse it. Use `--no-open` when running as an agent — the
|
|
139
139
|
human can open the browser URL themselves.
|
|
@@ -202,7 +202,7 @@ server and become part of the authoritative canvas state.
|
|
|
202
202
|
and type-specific actions. (Every node type, **including `status`, has a remove `×`** in its title
|
|
203
203
|
bar and a `Close` menu item.)
|
|
204
204
|
- Right-click a group node — recolor the group using preset swatches or a custom color picker,
|
|
205
|
-
|
|
205
|
+
or ungroup it (dissolves the frame; children stay)
|
|
206
206
|
- Drag-and-drop files or URLs — add file, image, markdown, or webpage nodes directly
|
|
207
207
|
- Paste URLs — create webpage nodes from the clipboard
|
|
208
208
|
|
|
@@ -421,7 +421,7 @@ When you do set a color (group/graph), use this palette consistently to convey m
|
|
|
421
421
|
## MCP Tools Reference
|
|
422
422
|
|
|
423
423
|
PMX Canvas exposes **16 action-discriminated composites** (the whole recommended surface) plus
|
|
424
|
-
**6 first-class standalones** — 22 tools total
|
|
424
|
+
**6 first-class standalones** — 22 tools total. The composites fold the older
|
|
425
425
|
single-purpose tools behind an `action` (and, for `canvas_ax_gate`, a `kind`) discriminator —
|
|
426
426
|
**field names are unchanged**; only the tool name + the `action`/`kind` selector differ.
|
|
427
427
|
|
|
@@ -436,7 +436,7 @@ single-purpose tools behind an `action` (and, for `canvas_ax_gate`, a `kind`) di
|
|
|
436
436
|
|-----------|-----------------|--------------|
|
|
437
437
|
| `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 |
|
|
438
438
|
| `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 |
|
|
439
|
-
| `canvas_edge` | `add` · `remove` | Connect / disconnect nodes |
|
|
439
|
+
| `canvas_edge` | `add` · `update` · `remove` | Connect / edit / disconnect nodes |
|
|
440
440
|
| `canvas_group` | `create` · `add` · `ungroup` | Manage spatial group containers |
|
|
441
441
|
| `canvas_history` | `undo` · `redo` | Time travel through the mutation ring buffer |
|
|
442
442
|
| `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,7 +444,7 @@ single-purpose tools behind an `action` (and, for `canvas_ax_gate`, a `kind`) di
|
|
|
444
444
|
| `canvas_query` | `search` · `layout` · `validate` | Find nodes by keyword, read full layout, or **`validate`** the board for node collisions / group-containment / dangling edges |
|
|
445
445
|
| `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`) |
|
|
446
446
|
| `canvas_webview` | `status` · `start` · `stop` · `resize` · `evaluate` | Headless Bun.WebView automation for the workbench (folds the `canvas_webview_*` / `canvas_resize` / `canvas_evaluate` tools) |
|
|
447
|
-
| `canvas_ax_state` | `get` · `set-focus` · `set-policy` · `report-capability` | Read AX state; set AX focus; patch tool/prompt policy; report host capability |
|
|
447
|
+
| `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) |
|
|
448
448
|
| `canvas_ax_work` | `add` · `update` · `annotate` | Canvas-bound work items + review annotations |
|
|
449
449
|
| `canvas_ax_gate` | `request` · `resolve` · `await` × `kind` `approval` \| `elicitation` \| `mode` | The human-decision gate machine (request → await → resolve) |
|
|
450
450
|
| `canvas_ax_timeline` | `read` · `record-event` · `add-evidence` · `send-steering` | The bounded AX diagnostics timeline |
|
|
@@ -512,7 +512,7 @@ composite in v0.4.0 (actions `save | list | restore | delete | gc | diff`); the
|
|
|
512
512
|
standalones were removed after their deprecated 0.3.x window.
|
|
513
513
|
|
|
514
514
|
`canvas_batch` supports exactly these ops: `node.add`, `node.update`, `node.remove`, `graph.add`,
|
|
515
|
-
`edge.add`, `edge.remove`, `group.create`, `group.add`, `group.remove`, `pin.set`/`pin.add`/
|
|
515
|
+
`edge.add`, `edge.update`, `edge.remove`, `group.create`, `group.add`, `group.remove`, `pin.set`/`pin.add`/
|
|
516
516
|
`pin.remove`, `snapshot.save`, and `arrange`. Anything else fails with "Unsupported canvas_batch
|
|
517
517
|
operation" — batch is non-atomic, so earlier ops stay applied and the response carries
|
|
518
518
|
`failedIndex`. `canvas_screenshot` requires an active automation WebView: call
|
|
@@ -692,7 +692,9 @@ In batch/MCP/HTTP payloads, `height` is chart content height and `nodeHeight` is
|
|
|
692
692
|
|
|
693
693
|
- **`canvas_group { action: "create", … }`** — visual container; `title`, `childIds` (node IDs), `color`. Auto-sizes to fit children.
|
|
694
694
|
- **`canvas_group { action: "add", groupId, childIds }`** — add nodes to an existing group.
|
|
695
|
-
- **`canvas_group { action: "ungroup", groupId }`** —
|
|
695
|
+
- **`canvas_group { action: "ungroup", groupId }`** — dissolve the group: its children become
|
|
696
|
+
independent nodes (members of the enclosing group when nested) and the frame is removed, in one
|
|
697
|
+
undo step. Exactly what the human's Ungroup does.
|
|
696
698
|
|
|
697
699
|
### Group Layout Guidance
|
|
698
700
|
|
|
@@ -827,10 +829,20 @@ Useful workbench selectors:
|
|
|
827
829
|
- Annotations: `.annotation-layer path` renders human-drawn freehand ink. Use WebView
|
|
828
830
|
to inspect or screenshot annotation shapes; MCP/context resources only expose compact
|
|
829
831
|
annotation target summaries, not the raw visual shape. Humans can remove marks with
|
|
830
|
-
the eraser
|
|
832
|
+
the eraser in the rail's Annotate popover; agents can remove a known annotation ID with
|
|
831
833
|
`canvas_view { action: "remove-annotation" }`.
|
|
832
|
-
- Canvas chrome: `.
|
|
833
|
-
-
|
|
834
|
+
- Canvas chrome (rail-chrome-v2): `.tool-rail`, `.top-bar` (`.connection-dot`, `.agent-chip`,
|
|
835
|
+
`.gate-badge`, `.context-budget`, `.external-indicator`, `.start-session-btn`),
|
|
836
|
+
`.connection-banner`, `.canvas-region`, `.session-panel`, `.command-bar`, `.selection-bar`,
|
|
837
|
+
`.activity-feed`, `.writers-sheet`, `.session-receipt`, `.snapshot-panel` (the History
|
|
838
|
+
drawer), `.minimap`, `.empty-state`, `.command-palette`, `.expanded-overlay-panel`.
|
|
839
|
+
- Presence: `.agent-cursor` (attached agent), `.human-cursor` (other tabs), `.scope-fence`,
|
|
840
|
+
`.canvas-node.agent-mutating` (shimmer), `.node-yield-pill`.
|
|
841
|
+
- Groups: `.canvas-node.group-node` with `.group-name-pill`, `.group-actions`, `.group-chip`
|
|
842
|
+
(collapsed); `.drop-pill` while a dragged node would join/leave a group; `.edge-hint-pill`
|
|
843
|
+
during an edge drag.
|
|
844
|
+
- Every `.canvas-node` carries `data-node-id` and `data-node-type` (rail-chrome redesign); `canvas_query`
|
|
845
|
+
(`layout` / `search`) remains the reliable way to get ids without a browser.
|
|
834
846
|
|
|
835
847
|
Async script example:
|
|
836
848
|
|
|
@@ -1098,10 +1110,37 @@ nodes.
|
|
|
1098
1110
|
`pmx-canvas ax command invoke`; envelope `ax.command.invoke`). Unknown names
|
|
1099
1111
|
are rejected; an invocation records an `agent-event` of kind `command`.
|
|
1100
1112
|
- **Policy:** a canvas-bound, snapshotted tool/prompt policy
|
|
1101
|
-
(`tools.allowed|excluded|approvalRequired`, `prompt.systemAppend|mode
|
|
1102
|
-
into `canvas://ax-context`. Patch it
|
|
1103
|
-
(HTTP `GET|POST /api/canvas/ax/policy`;
|
|
1104
|
-
merge and are normalized server-side.
|
|
1113
|
+
(`tools.allowed|excluded|approvalRequired`, `prompt.systemAppend|mode`, and the
|
|
1114
|
+
**scope fence** `scope: { nodeIds, padding }`) read into `canvas://ax-context`. Patch it
|
|
1115
|
+
with `canvas_ax_state { action: "set-policy" }` (HTTP `GET|POST /api/canvas/ax/policy`;
|
|
1116
|
+
CLI `pmx-canvas ax policy get|set`); patches merge and are normalized server-side.
|
|
1117
|
+
While a fence is set, your canvas WRITES outside it (nodes not in the set, new nodes
|
|
1118
|
+
outside the fenced box, arrange/clear/restore) are refused with HTTP 403 naming the
|
|
1119
|
+
reason; reads are never fenced. The fence is the human's: `set-policy` does not take
|
|
1120
|
+
`scope`, and an HTTP policy call that includes it is refused (403). Read `policy.scope`
|
|
1121
|
+
before writing and ask the human to widen it rather than retrying.
|
|
1122
|
+
- **External steering (no session):** a writer with no attached session is shown passively —
|
|
1123
|
+
a top-bar indicator (avatars, "N writers · M ops"), a click-to-open activity feed (one row
|
|
1124
|
+
per write: "Created markdown “…”" under the writer's label, per-writer filters, inline Veto
|
|
1125
|
+
on your pending explicit intents) and a connected-writers sheet. Pure visibility: pmx-canvas
|
|
1126
|
+
is local-first and veto/ghosting is the safety model, not permissions. The feed is
|
|
1127
|
+
`activity` on `GET /api/canvas/ax/presence` (`canvas_ax_state { action: "presence" }`).
|
|
1128
|
+
- **Session receipt:** attaching a session (`set-presence { attached: true }` or a
|
|
1129
|
+
`session-start` activity) over a non-empty board saves a `Before session · <label> · HH:MM`
|
|
1130
|
+
snapshot; ending it (`attached: false`, `session-end`, or idle expiry) emits one
|
|
1131
|
+
`agent-session-ended` SSE frame — `{ label, endedAt, counts: { items, done, vetoed },
|
|
1132
|
+
snapshot }` — which the browser shows as a receipt with *View diff* (the session's own
|
|
1133
|
+
changes) and a restore path. End your session explicitly so the receipt is prompt. The
|
|
1134
|
+
human's *Start agent session* button is the same attach with `source: "browser"`.
|
|
1135
|
+
- **Human collaborators / user wins:** every open workbench tab is a presence (`GET
|
|
1136
|
+
/api/canvas/human-presence`); a node a human is dragging is locked — an agent write to it is
|
|
1137
|
+
refused with 409 until release (requeue and retry), and a pending explicit intent on it is
|
|
1138
|
+
vetoed with a `yield` timeline event.
|
|
1139
|
+
- **Unattended approvals:** a pending approval gate carries a TTL (`ttlMs` on the request,
|
|
1140
|
+
default 5 min). If the human does not answer in time it resolves to `held` — a
|
|
1141
|
+
non-approval: do not proceed — and a `policy` agent-event explains why. The human can
|
|
1142
|
+
reopen it from the session panel; an `await` on the gate returns when it leaves
|
|
1143
|
+
`pending`, so treat any status other than `approved` as "do not proceed".
|
|
1105
1144
|
|
|
1106
1145
|
Interactions request PMX-AX primitives only — never arbitrary shell, tool, MCP,
|
|
1107
1146
|
or host execution.
|
|
@@ -1278,7 +1317,7 @@ All POST/PATCH endpoints accept `Content-Type: application/json`. Default base U
|
|
|
1278
1317
|
| POST | `/api/canvas/web-artifact` | Build a bundled web artifact and optionally open it on canvas |
|
|
1279
1318
|
| POST | `/api/canvas/group` | Create group |
|
|
1280
1319
|
| POST | `/api/canvas/group/add` | Add nodes to group |
|
|
1281
|
-
| POST | `/api/canvas/group/ungroup` | Ungroup |
|
|
1320
|
+
| POST | `/api/canvas/group/ungroup` | Ungroup (dissolve: children released, frame removed) |
|
|
1282
1321
|
| POST | `/api/canvas/arrange` | Auto-arrange |
|
|
1283
1322
|
| POST | `/api/canvas/focus` | Center viewport on node |
|
|
1284
1323
|
| POST | `/api/canvas/fit` | Fit viewport to canvas bounds or selected nodes |
|
|
@@ -13,8 +13,9 @@ maps Copilot SDK features onto PMX AX primitives.
|
|
|
13
13
|
|
|
14
14
|
## Quick Start
|
|
15
15
|
|
|
16
|
-
1. Install the project adapter
|
|
17
|
-
|
|
16
|
+
1. Install the project adapter with `pmx-canvas copilot install-extension --yes`, or copy the
|
|
17
|
+
packaged extension directory into the repository:
|
|
18
|
+
`mkdir -p .github/extensions && cp -R node_modules/pmx-canvas/.github/extensions/pmx-canvas .github/extensions/`
|
|
18
19
|
2. Reload Copilot app extensions with `extensions_reload` so `project:pmx-canvas` is registered.
|
|
19
20
|
3. Start or confirm a PMX Canvas daemon for the workspace: `pmx-canvas serve --daemon`
|
|
20
21
|
and `pmx-canvas serve status`. The adapter can auto-start in many local sessions, but a running
|
|
@@ -38,6 +39,20 @@ Open it with:
|
|
|
38
39
|
Use a different `instanceId` for parallel panels. Reusing an `instanceId` focuses/reloads the same
|
|
39
40
|
panel.
|
|
40
41
|
|
|
42
|
+
## Agent presence (automatic)
|
|
43
|
+
|
|
44
|
+
The extension also reports Copilot's real context window: the SDK's `session.usage_info`
|
|
45
|
+
event (`currentTokens` / `tokenLimit`, root agent only) is posted to the presence as
|
|
46
|
+
`contextUsage`, so the board's top-bar meter reads **Context** with the live numbers rather
|
|
47
|
+
than the pinned-context estimate (**Pins**).
|
|
48
|
+
|
|
49
|
+
The extension attaches a Copilot session to the canvas when it joins the host session, flips the
|
|
50
|
+
phase to `thinking` on every submitted prompt, and detaches on exit. While attached the workbench
|
|
51
|
+
shows the Copilot cursor on the node the agent last touched plus a phase chip, and the agent's
|
|
52
|
+
canvas writes — which reach the server as MCP or HTTP tool calls, not as `copilot` — are attributed
|
|
53
|
+
to that session by the server. Nothing to configure; `GET /api/canvas/ax/presence` shows the live
|
|
54
|
+
state.
|
|
55
|
+
|
|
41
56
|
## What the Adapter Does
|
|
42
57
|
|
|
43
58
|
- Opens the live PMX workbench directly in a native Copilot canvas panel.
|
|
@@ -49,21 +64,28 @@ panel.
|
|
|
49
64
|
`onUserPromptSubmitted`.
|
|
50
65
|
- Exposes adapter actions for status, AX context refresh, AX focus, and explicit session steering.
|
|
51
66
|
|
|
52
|
-
### Agent behavior — steering
|
|
67
|
+
### Agent behavior — steering push with a gated fallback
|
|
68
|
+
|
|
69
|
+
The adapter long-polls the consumer-scoped delivery queue, sends each steer through
|
|
70
|
+
`copilotSession.send`, and marks it only after the host accepts the prompt. Browser-originated
|
|
71
|
+
steering therefore creates a Copilot turn without waiting for another chat message. Addressed
|
|
72
|
+
steers follow their target; broadcasts are marked only for the `copilot` consumer; Copilot-origin
|
|
73
|
+
messages are excluded by the queue's loop-safety filter.
|
|
74
|
+
|
|
75
|
+
On adapter startup, existing backlog is marked for the `copilot` consumer without creating turns.
|
|
76
|
+
Only steers created after that adapter instance started can wake it, so reopening Copilot cannot
|
|
77
|
+
flood the chat with stale instructions from prior sessions.
|
|
53
78
|
|
|
54
|
-
`onUserPromptSubmitted` injects the whole `/api/canvas/ax/context`
|
|
55
|
-
items, approval gates, and the compact `delivery` lead block) as hidden context
|
|
56
|
-
only when the **pin/focus gate is open** (`pinned.count > 0 || focus.nodeIds.length > 0`),
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
`timeline.pendingSteering`. Three consequences the adapter/agent must honor:
|
|
79
|
+
`onUserPromptSubmitted` remains the fallback. It injects the whole `/api/canvas/ax/context`
|
|
80
|
+
(pins, focus, work items, approval gates, and the compact `delivery` lead block) as hidden context,
|
|
81
|
+
but only when the **pin/focus gate is open** (`pinned.count > 0 || focus.nodeIds.length > 0`), and
|
|
82
|
+
it is clipped to a char budget. Read steering from **`delivery.pendingSteering`** (the compact,
|
|
83
|
+
count-bearing block — newest-first, capped at 10), not the full `timeline.pendingSteering`.
|
|
60
84
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
3. The agent must **act on injected `delivery.pendingSteering` / `pendingActivity` and
|
|
66
|
-
then ack** (`canvas_ax_delivery { action: "mark" }`), or it re-injects every gated turn.
|
|
85
|
+
If native send fails, the adapter leaves the steer unmarked and retries. If marking fails after a
|
|
86
|
+
successful send, the in-process pump retries the mark without sending the prompt twice. A process
|
|
87
|
+
restart establishes a new delivery baseline and marks older unacknowledged rows without replaying
|
|
88
|
+
them.
|
|
67
89
|
|
|
68
90
|
To be robust to the char clip, prefer injecting that compact loop-safe lead block from
|
|
69
91
|
`GET /api/canvas/ax/context?consumer=copilot` (`delivery.pendingSteering` +
|
|
@@ -73,15 +95,10 @@ To be robust to the char clip, prefer injecting that compact loop-safe lead bloc
|
|
|
73
95
|
|
|
74
96
|
#### Waking the agent from a canvas steer (#59) — adapter-owned
|
|
75
97
|
|
|
76
|
-
Recording a browser-origin `ax.steer`
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
`canvas_ax_delivery { action: "claim", consumer: "copilot" }`, call the host's native
|
|
81
|
-
send (`copilotSession.send` / the working `send_instruction` path) with each steer, then
|
|
82
|
-
`canvas_ax_delivery { action: "mark" }` it (loop-safe). This wake is intentionally
|
|
83
|
-
host-owned — PMX never imports the host SDK. Until the adapter wires it, a steering
|
|
84
|
-
button must be labeled "queued for the agent's next turn", not "steer now".
|
|
98
|
+
Recording a browser-origin `ax.steer` queues it in PMX; the Copilot adapter owns the wake. Its
|
|
99
|
+
delivery pump long-polls `…/delivery/pending?consumer=copilot`, calls
|
|
100
|
+
`copilotSession.send`, then marks that consumer's delivery. PMX remains host-agnostic and never
|
|
101
|
+
imports the Copilot SDK.
|
|
85
102
|
|
|
86
103
|
### Closing the loop (optional, recommended)
|
|
87
104
|
|
|
@@ -24,6 +24,23 @@ it, or the board silently diverges from reality.
|
|
|
24
24
|
their run scoping comes from the run-prefixed `agentId`, e.g. `run7:impl-auth`), so
|
|
25
25
|
consecutive orchestrations don't interleave in the bounded timeline.
|
|
26
26
|
|
|
27
|
+
## Presence (rail-chrome-v2)
|
|
28
|
+
|
|
29
|
+
- The ORCHESTRATOR attaches the session: `canvas_ax_state { action: "set-presence",
|
|
30
|
+
attached: true, label: "<run label>" }` at run start and `{ attached: false }` at the end
|
|
31
|
+
(the human gets a receipt: items done / vetoed, a diff against the pre-run snapshot, one-click
|
|
32
|
+
restore). Subagents never attach — writes they make through MCP/HTTP with no `agentId` are
|
|
33
|
+
attributed to the orchestrator's session; a subagent that passes `agentId` shows as its own
|
|
34
|
+
writer. Pick one: a single cursor for the run (no `agentId` on board writes) or one per
|
|
35
|
+
subagent (`agentId` everywhere, also in the writers sheet).
|
|
36
|
+
- The board may be fenced (`policy.scope`). A 403 from any participant means "outside the
|
|
37
|
+
fence": report it to the orchestrator, who asks the human — never widen it yourself (the
|
|
38
|
+
fence is the human's; `set-policy` cannot set it).
|
|
39
|
+
- A 409 means a human is holding that node right now: requeue that step's write and retry
|
|
40
|
+
after the next steering poll. A `yield` timeline event names who took over.
|
|
41
|
+
- Unanswered gates auto-hold (`held`) after their TTL: treat `held` as "do not proceed" and
|
|
42
|
+
keep the subagent's step `blocked` until the human reopens and answers.
|
|
43
|
+
|
|
27
44
|
## Graph semantics
|
|
28
45
|
|
|
29
46
|
- `flow` edges = planned sequence. `depends-on` edges = blocking dependency (the visual DAG).
|
|
@@ -57,11 +74,48 @@ it, or the board silently diverges from reality.
|
|
|
57
74
|
subagent, so everything is pull. Cadence: check `canvas://ax-pending-steering` (or
|
|
58
75
|
`canvas_ax_delivery { action: "claim", consumer: "<agentId or host>" }`) between every major
|
|
59
76
|
step.
|
|
77
|
+
- **Reactive loop (0.4.9+):** pass `timeoutMs` (MCP) / `?waitMs` (HTTP) to the claim and the
|
|
78
|
+
call BLOCKS until steering for your consumer arrives (capped at 120000ms per call). A host
|
|
79
|
+
that cannot be woken from outside (Copilot, Codex — their model runs only while the host
|
|
80
|
+
gives it a turn) reacts to canvas steering by looping on this inside one turn:
|
|
81
|
+
claim(timeoutMs: 120000) → act on each message → mark → repeat until the human says stop or
|
|
82
|
+
the turn budget runs out. One standing prompt in the host ("run the canvas steering loop")
|
|
83
|
+
makes the composer's addressed steers land promptly instead of sitting queued.
|
|
60
84
|
- Address a specific agent with the steering `target` field (0.4.5+). Untargeted steers are
|
|
61
|
-
broadcast
|
|
62
|
-
|
|
85
|
+
broadcast — and delivered PER CONSUMER (0.4.9+): every agent that claims with its own
|
|
86
|
+
consumer key receives the broadcast, and marking it (`consumer: "<your key>"`) removes it
|
|
87
|
+
from YOUR queue only. "All workers: stop" reaches the whole fleet; each worker marks its own
|
|
88
|
+
copy after acting.
|
|
89
|
+
- **Territories (0.4.9+):** the orchestrator fences each worker to its lane with
|
|
90
|
+
`ax.policy.set { agentScopes: { "<worker agentId>": { nodeIds, padding } } }` — that worker's
|
|
91
|
+
writes outside the territory are refused (403) while everyone else roams. An agent can fence
|
|
92
|
+
OTHER writers but never its own key; the human can set or clear any territory.
|
|
93
|
+
- **Spawn-window ghosts:** the default intent TTL (8 s) dies before a spawned worker's first
|
|
94
|
+
write — signal spawn choreography with `ttlMs: 30000-60000`. A ghost that expires QUIETLY no
|
|
95
|
+
longer blocks its linked mutation (it proceeds unlinked; only a human veto refuses), so a slow
|
|
96
|
+
spawn cannot poison a batch.
|
|
97
|
+
- **Identity discipline:** never pass a worker's `agentId` on writes YOU make on its behalf —
|
|
98
|
+
attribution follows `agentId`, so the activity feed would book your write as the worker's.
|
|
99
|
+
Create shared scaffolding (lanes, work items) under your own identity, then let each worker
|
|
100
|
+
write as itself. Workers should OMIT `attached` entirely (they are writers, not sessions).
|
|
101
|
+
- **CLI hosts (Codex CLI, Amp) become reactive with the pump:** run
|
|
102
|
+
`pmx-canvas pump --consumer codex-cli --exec 'codex exec --full-auto {message}'` — it
|
|
103
|
+
long-polls the inbox, hands each steer to a CLI task, and marks per-consumer only after success.
|
|
104
|
+
This does not steer an open Codex desktop task; desktop requires a host adapter using the app's
|
|
105
|
+
native follow-up channel. `--parent` rolls the pumped agent under its orchestrator.
|
|
106
|
+
- **No MCP in the subagent?** Workers spawned inside a host often lack the canvas MCP — the
|
|
107
|
+
plain HTTP API is the worker path: POST /api/canvas/node, /api/canvas/ax/presence,
|
|
108
|
+
/api/canvas/ax/delivery/pending?consumer=<agentId>, /:id/mark. Same identity rules apply.
|
|
109
|
+
- **Fleet chrome:** every worker should declare its orchestrator on set-presence
|
|
110
|
+
(`parentAgentId: "<orchestrator key>"`): the top bar rolls workers up into the
|
|
111
|
+
orchestrator's chip ("+N workers") and their cursors render smaller so a big fleet stays
|
|
112
|
+
legible.
|
|
63
113
|
- Mark delivery ONLY after acting on the message. The mark is compare-and-set: a
|
|
64
114
|
`delivered:false` response means another consumer owned it — drop it, do not act twice.
|
|
115
|
+
- Steering comes from more places than the command bar: a gate rejection, the human undoing
|
|
116
|
+
one of your board edits ("Undid your edit: …"), a take-over of a node you were editing, and
|
|
117
|
+
vetoed ghost intents all arrive as steering. The orchestrator re-routes them to the subagent
|
|
118
|
+
whose node they name.
|
|
65
119
|
|
|
66
120
|
## Run lifecycle
|
|
67
121
|
|