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.
Files changed (212) hide show
  1. package/.github/extensions/pmx-canvas/extension.mjs +154 -5
  2. package/.github/extensions/pmx-canvas/steering-delivery.mjs +79 -0
  3. package/CHANGELOG.md +156 -0
  4. package/Readme.md +72 -23
  5. package/dist/canvas/global.css +3069 -796
  6. package/dist/canvas/index.js +104 -104
  7. package/dist/json-render/index.css +1 -1
  8. package/dist/types/cli/agent.d.ts +1 -0
  9. package/dist/types/cli/commands/pump.d.ts +9 -0
  10. package/dist/types/client/App.d.ts +1 -1
  11. package/dist/types/client/canvas/AgentPresenceLayer.d.ts +11 -0
  12. package/dist/types/client/canvas/BarHint.d.ts +22 -0
  13. package/dist/types/client/canvas/CanvasNode.d.ts +5 -0
  14. package/dist/types/client/canvas/CanvasViewport.d.ts +4 -4
  15. package/dist/types/client/canvas/CommandBar.d.ts +10 -0
  16. package/dist/types/client/canvas/ConnectionBanner.d.ts +13 -0
  17. package/dist/types/client/canvas/ContextMenu.d.ts +6 -0
  18. package/dist/types/client/canvas/EdgeLayer.d.ts +2 -1
  19. package/dist/types/client/canvas/EmptyState.d.ts +11 -0
  20. package/dist/types/client/canvas/ExternalWriters.d.ts +5 -0
  21. package/dist/types/client/canvas/HumanPresenceLayer.d.ts +8 -0
  22. package/dist/types/client/canvas/Minimap.d.ts +12 -1
  23. package/dist/types/client/canvas/ScopeFenceLayer.d.ts +8 -0
  24. package/dist/types/client/canvas/SelectionBar.d.ts +6 -0
  25. package/dist/types/client/canvas/SessionPanel.d.ts +1 -0
  26. package/dist/types/client/canvas/SessionReceipt.d.ts +17 -0
  27. package/dist/types/client/canvas/TextPrompt.d.ts +25 -0
  28. package/dist/types/client/canvas/ToolRail.d.ts +21 -0
  29. package/dist/types/client/canvas/TopBar.d.ts +7 -0
  30. package/dist/types/client/canvas/canvas-area.d.ts +25 -0
  31. package/dist/types/client/canvas/create-in-view.d.ts +18 -0
  32. package/dist/types/client/canvas/import-files.d.ts +8 -0
  33. package/dist/types/client/canvas/kind-colors.d.ts +3 -0
  34. package/dist/types/client/canvas/use-focus-trap.d.ts +14 -0
  35. package/dist/types/client/canvas/use-now.d.ts +6 -0
  36. package/dist/types/client/icons.d.ts +11 -4
  37. package/dist/types/client/nodes/ExtAppFrame.d.ts +32 -0
  38. package/dist/types/client/nodes/GroupNode.d.ts +6 -1
  39. package/dist/types/client/nodes/MarkdownNode.d.ts +2 -0
  40. package/dist/types/client/nodes/inline-editor-commands.d.ts +3 -4
  41. package/dist/types/client/state/canvas-store.d.ts +49 -5
  42. package/dist/types/client/state/human-store.d.ts +26 -0
  43. package/dist/types/client/state/iframe-mode.d.ts +1 -0
  44. package/dist/types/client/state/intent-bridge.d.ts +37 -5
  45. package/dist/types/client/state/node-factory.d.ts +0 -1
  46. package/dist/types/client/state/presence-store.d.ts +78 -0
  47. package/dist/types/client/state/session-store.d.ts +176 -0
  48. package/dist/types/client/types.d.ts +2 -1
  49. package/dist/types/client/utils/platform.d.ts +2 -0
  50. package/dist/types/server/agent-presence.d.ts +192 -0
  51. package/dist/types/server/ax-gate-ttl.d.ts +6 -0
  52. package/dist/types/server/ax-interaction.d.ts +4 -10
  53. package/dist/types/server/ax-state-manager.d.ts +21 -2
  54. package/dist/types/server/ax-state.d.ts +49 -3
  55. package/dist/types/server/ax-wait.d.ts +13 -0
  56. package/dist/types/server/canvas-db.d.ts +23 -1
  57. package/dist/types/server/canvas-operations.d.ts +1 -0
  58. package/dist/types/server/canvas-state.d.ts +27 -13
  59. package/dist/types/server/canvas-validation.d.ts +0 -14
  60. package/dist/types/server/html-surface.d.ts +1 -1
  61. package/dist/types/server/human-presence.d.ts +40 -0
  62. package/dist/types/server/index.d.ts +29 -3
  63. package/dist/types/server/intent-registry.d.ts +7 -0
  64. package/dist/types/server/mutation-history.d.ts +15 -2
  65. package/dist/types/server/operations/invoker.d.ts +13 -2
  66. package/dist/types/server/operations/ops/ax-presence.d.ts +2 -0
  67. package/dist/types/server/operations/ops/human-presence.d.ts +3 -0
  68. package/dist/types/server/operations/registry.d.ts +11 -0
  69. package/dist/types/server/operations/types.d.ts +1 -1
  70. package/dist/types/server/scope-fence.d.ts +62 -0
  71. package/dist/types/server/server.d.ts +0 -12
  72. package/dist/types/shared/agent-presence.d.ts +130 -0
  73. package/dist/types/shared/approval-gates.d.ts +19 -0
  74. package/dist/types/shared/auto-arrange.d.ts +0 -1
  75. package/dist/types/shared/ax-kinds.d.ts +11 -0
  76. package/dist/types/shared/human-presence.d.ts +31 -0
  77. package/dist/types/shared/iframe-probe.d.ts +8 -0
  78. package/dist/types/shared/scope-fence.d.ts +21 -0
  79. package/docs/RELEASE.md +54 -0
  80. package/docs/ax-host-adapter-contract.md +30 -14
  81. package/docs/cli.md +37 -1
  82. package/docs/environment.md +3 -0
  83. package/docs/http-api.md +222 -5
  84. package/docs/mcp.md +6 -5
  85. package/docs/node-types.md +3 -1
  86. package/docs/plans/plan-009-tech-debt-backlog.md +13 -0
  87. package/docs/screenshot.png +0 -0
  88. package/docs/screenshots/multi-agent-live.png +0 -0
  89. package/docs/screenshots/workbench-live-dark.png +0 -0
  90. package/docs/screenshots/workbench-live-light.png +0 -0
  91. package/docs/sdk.md +1 -1
  92. package/package.json +1 -1
  93. package/skills/pmx-canvas/SKILL.md +109 -18
  94. package/skills/pmx-canvas/references/codex-app-adapter.md +28 -0
  95. package/skills/pmx-canvas/references/full-reference.md +55 -16
  96. package/skills/pmx-canvas/references/github-copilot-app-adapter.md +41 -24
  97. package/skills/pmx-canvas-orchestration/SKILL.md +56 -2
  98. package/skills/pmx-canvas-testing/SKILL.md +57 -0
  99. package/src/cli/agent.ts +1 -0
  100. package/src/cli/commands/copilot.ts +15 -8
  101. package/src/cli/commands/nodes.ts +1 -19
  102. package/src/cli/commands/pump.ts +252 -0
  103. package/src/cli/index.ts +1 -5
  104. package/src/cli/shared.ts +1 -1
  105. package/src/client/App.tsx +209 -585
  106. package/src/client/canvas/AgentPresenceLayer.tsx +74 -0
  107. package/src/client/canvas/AttentionHistory.tsx +0 -10
  108. package/src/client/canvas/BarHint.tsx +49 -0
  109. package/src/client/canvas/CanvasNode.tsx +488 -136
  110. package/src/client/canvas/CanvasViewport.tsx +190 -94
  111. package/src/client/canvas/CommandBar.tsx +140 -0
  112. package/src/client/canvas/CommandPalette.tsx +133 -58
  113. package/src/client/canvas/ConnectionBanner.tsx +65 -0
  114. package/src/client/canvas/ContextMenu.tsx +88 -27
  115. package/src/client/canvas/ContextPinBar.tsx +2 -2
  116. package/src/client/canvas/EdgeLayer.tsx +121 -38
  117. package/src/client/canvas/EmptyState.tsx +93 -0
  118. package/src/client/canvas/ExpandedNodeOverlay.tsx +64 -118
  119. package/src/client/canvas/ExternalWriters.tsx +308 -0
  120. package/src/client/canvas/FocusFieldLayer.tsx +12 -1
  121. package/src/client/canvas/HumanPresenceLayer.tsx +45 -0
  122. package/src/client/canvas/IntentLayer.tsx +1 -1
  123. package/src/client/canvas/Minimap.tsx +117 -190
  124. package/src/client/canvas/ScopeFenceLayer.tsx +38 -0
  125. package/src/client/canvas/SelectionBar.tsx +175 -23
  126. package/src/client/canvas/SessionPanel.tsx +481 -0
  127. package/src/client/canvas/SessionReceipt.tsx +141 -0
  128. package/src/client/canvas/ShortcutOverlay.tsx +21 -6
  129. package/src/client/canvas/SnapshotPanel.tsx +166 -80
  130. package/src/client/canvas/TextPrompt.tsx +128 -0
  131. package/src/client/canvas/ToolRail.tsx +447 -0
  132. package/src/client/canvas/TopBar.tsx +308 -0
  133. package/src/client/canvas/auto-fit.ts +1 -5
  134. package/src/client/canvas/canvas-area.ts +41 -0
  135. package/src/client/canvas/create-in-view.ts +48 -0
  136. package/src/client/canvas/import-files.ts +64 -0
  137. package/src/client/canvas/kind-colors.ts +22 -0
  138. package/src/client/canvas/snap-guides.ts +1 -1
  139. package/src/client/canvas/use-focus-trap.ts +54 -0
  140. package/src/client/canvas/use-now.ts +17 -0
  141. package/src/client/canvas/use-pan-zoom.ts +13 -2
  142. package/src/client/icons.tsx +57 -18
  143. package/src/client/nodes/ExtAppFrame.tsx +79 -2
  144. package/src/client/nodes/GroupNode.tsx +9 -28
  145. package/src/client/nodes/InlineFormatBar.tsx +4 -3
  146. package/src/client/nodes/InlineMarkdownEditor.tsx +27 -0
  147. package/src/client/nodes/MarkdownNode.tsx +59 -24
  148. package/src/client/nodes/MdFormatBar.tsx +2 -4
  149. package/src/client/nodes/inline-editor-commands.ts +17 -6
  150. package/src/client/nodes/use-surface-frame.ts +3 -1
  151. package/src/client/state/attention-bridge.ts +3 -0
  152. package/src/client/state/canvas-store.ts +273 -84
  153. package/src/client/state/human-store.ts +175 -0
  154. package/src/client/state/iframe-mode.ts +33 -9
  155. package/src/client/state/intent-bridge.ts +75 -13
  156. package/src/client/state/node-factory.ts +2 -8
  157. package/src/client/state/presence-store.ts +168 -0
  158. package/src/client/state/session-store.ts +419 -0
  159. package/src/client/state/sse-bridge.ts +93 -30
  160. package/src/client/theme/global.css +3069 -796
  161. package/src/client/types.ts +3 -1
  162. package/src/client/utils/platform.ts +6 -1
  163. package/src/mcp/canvas-access.ts +2 -2
  164. package/src/server/agent-presence.ts +755 -0
  165. package/src/server/ax-context.ts +45 -0
  166. package/src/server/ax-gate-ttl.ts +61 -0
  167. package/src/server/ax-interaction.ts +29 -1
  168. package/src/server/ax-state-manager.ts +78 -6
  169. package/src/server/ax-state.ts +82 -15
  170. package/src/server/ax-wait.ts +33 -0
  171. package/src/server/canvas-db.ts +237 -61
  172. package/src/server/canvas-operations.ts +17 -8
  173. package/src/server/canvas-state.ts +131 -58
  174. package/src/server/canvas-validation.ts +3 -53
  175. package/src/server/demo-state.json +2 -70
  176. package/src/server/html-surface.ts +11 -2
  177. package/src/server/human-presence.ts +160 -0
  178. package/src/server/index.ts +107 -4
  179. package/src/server/intent-registry.ts +13 -0
  180. package/src/server/mutation-history.ts +47 -2
  181. package/src/server/operations/composites.ts +6 -3
  182. package/src/server/operations/http.ts +10 -1
  183. package/src/server/operations/index.ts +4 -0
  184. package/src/server/operations/invoker.ts +33 -6
  185. package/src/server/operations/ops/ax-presence.ts +62 -0
  186. package/src/server/operations/ops/ax-read.ts +8 -0
  187. package/src/server/operations/ops/ax-state.ts +106 -1
  188. package/src/server/operations/ops/ax-timeline.ts +54 -5
  189. package/src/server/operations/ops/ax-work.ts +46 -0
  190. package/src/server/operations/ops/batch.ts +3 -1
  191. package/src/server/operations/ops/edges.ts +88 -2
  192. package/src/server/operations/ops/groups.ts +4 -3
  193. package/src/server/operations/ops/human-presence.ts +35 -0
  194. package/src/server/operations/ops/json-render.ts +11 -1
  195. package/src/server/operations/ops/nodes.ts +65 -16
  196. package/src/server/operations/ops/query.ts +1 -0
  197. package/src/server/operations/ops/snapshots.ts +16 -0
  198. package/src/server/operations/registry.ts +196 -1
  199. package/src/server/operations/types.ts +1 -1
  200. package/src/server/scope-fence.ts +207 -0
  201. package/src/server/server.ts +177 -79
  202. package/src/server/trace-manager.ts +0 -2
  203. package/src/server/web-artifacts.ts +0 -1
  204. package/src/shared/agent-presence.ts +162 -0
  205. package/src/shared/approval-gates.ts +35 -0
  206. package/src/shared/auto-arrange.ts +121 -13
  207. package/src/shared/ax-kinds.ts +23 -0
  208. package/src/shared/human-presence.ts +30 -0
  209. package/src/shared/iframe-probe.ts +8 -0
  210. package/src/shared/scope-fence.ts +31 -0
  211. package/dist/types/client/canvas/DockedNode.d.ts +0 -4
  212. package/src/client/canvas/DockedNode.tsx +0 -165
@@ -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
- - Right-click a group to ungroup
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. |
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, { consumer: 'copilot' });
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.4.7",
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. **Mutate through current composites.** Prefer the 16 composite MCP tools below.
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. **Docking is explicit (0.4.6+).** Nodes render on the canvas where you put them; a node is
203
- only a HUD pill if you set `dockPosition`. An edge whose endpoint is docked cannot be drawn —
204
- `validate` reports it as `hiddenEdgeEndpoints` and fails `ok`, because the edge visually
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
- - Shift+drag empty space to lasso-select.
242
- - Use the selection bar for Pin as context, Group, Connect, and Clear.
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
- - Use toolbar snapshots before experiments and restore only after confirmation.
248
- - The toolbar carries view controls (fit, reset, zoom), arrange, minimap, a theme picker (nine
249
- themes: dark, light, high-contrast, midnight, sepia, arctic, ember, forest, volt), snapshots, trace,
250
- the annotation tools, search (Cmd+K), and shortcuts. On narrow viewports (≤900px — most
251
- embedded host panes and mobile) the secondary controls collapse into a single "⋯" More menu at
252
- the end of the bar; every action is still there as a labeled row, including the theme list. If
253
- an automation script can't find a toolbar button at panel width, open the More menu first.
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`, and `recovery-exhausted` — when
360
- diagnosing, trust `paint-ok`/`paint-fail`, and never assert health from `settled` alone. If a
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 browser toolbar has a matching theme picker). Same value can be set via
136
- the `PMX_CANVAS_THEME` environment variable, or toggled live in the browser toolbar.
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
- and ungroup its children
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 (v0.4.x). The composites fold the older
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 }`** — release all children from a group.
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 toolbar button; agents can remove a known annotation ID with
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: `.hud-layer`, `.canvas-toolbar`, `.connection-dot`, `.canvas-bootstrap-card`
833
- - Nodes do not expose stable `data-node-id` attributes. Use `canvas_query` (`layout` / `search`) or MCP resource data for exact node IDs.
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`) read
1102
- into `canvas://ax-context`. Patch it with `canvas_ax_state { action: "set-policy" }`
1103
- (HTTP `GET|POST /api/canvas/ax/policy`; CLI `pmx-canvas ax policy get|set`); patches
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 by copying the packaged extension into the repository:
17
- `mkdir -p .github/extensions/pmx-canvas && cp node_modules/pmx-canvas/.github/extensions/pmx-canvas/extension.mjs .github/extensions/pmx-canvas/extension.mjs`
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 is gated, not pushed
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` (pins, focus, work
55
- items, approval gates, and the compact `delivery` lead block) as hidden context — but
56
- only when the **pin/focus gate is open** (`pinned.count > 0 || focus.nodeIds.length > 0`),
57
- and it is clipped to a char budget. Read steering from **`delivery.pendingSteering`**
58
- (the compact, count-bearing block — newest-first, capped at 10), not the full
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
- 1. A steering board must **stay pinned** (or its button must also emit `ax.focus.set`
62
- on the board node) to hold the gate open.
63
- 2. A sandbox button click does **not** wake a turn — a human message does. The click
64
- only enqueues the steer.
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` does **not** wake the active session by itself
77
- (report #59); PMX only queues it (the `ok:true` emit ack = "recorded", not "delivered").
78
- To make a canvas **Steer** button actually create a visible turn, the adapter must, on
79
- its own cadence (e.g. an SSE subscription or poll), **drain**
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: only the ORCHESTRATOR claims them and re-routes; subagents claim only steers
62
- targeted at their own `agentId`.
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