pmx-canvas 0.4.8 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/extensions/pmx-canvas/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 +3071 -796
- package/dist/canvas/index.js +122 -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 +39 -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 +2 -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 +9 -1
- package/dist/types/server/canvas-operations.d.ts +11 -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 +20 -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 +4 -4
- package/skills/pmx-canvas/SKILL.md +112 -17
- 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 +17 -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 +263 -0
- package/src/cli/index.ts +2 -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 +74 -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 +86 -20
- 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 +285 -90
- 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 +3071 -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 +103 -33
- package/src/server/canvas-operations.ts +58 -14
- 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 +111 -6
- 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 +77 -16
- package/src/server/operations/ops/query.ts +1 -0
- package/src/server/operations/ops/snapshots.ts +7 -4
- 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
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unattended approval policy (rail-chrome-v2 phase 4, design item 3).
|
|
3
|
+
*
|
|
4
|
+
* A pending approval gate carries a TTL. If the human does not answer in time
|
|
5
|
+
* the gate resolves to `held` — the safe default: the action does NOT proceed,
|
|
6
|
+
* a `policy` entry lands in the timeline, and the gate can be reopened from the
|
|
7
|
+
* session panel. Shared so the server's sweeper and the client's countdown read
|
|
8
|
+
* one clock.
|
|
9
|
+
*/
|
|
10
|
+
export declare const DEFAULT_GATE_TTL_MS: number;
|
|
11
|
+
export declare const MIN_GATE_TTL_MS = 1000;
|
|
12
|
+
export declare const MAX_GATE_TTL_MS: number;
|
|
13
|
+
export declare function clampGateTtlMs(value: unknown, fallback?: number): number;
|
|
14
|
+
/** Milliseconds until a gate auto-holds (0 when expired, null when it has no TTL). */
|
|
15
|
+
export declare function gateRemainingMs(gate: {
|
|
16
|
+
expiresAt: string | null;
|
|
17
|
+
}, now?: number): number | null;
|
|
18
|
+
/** `M:SS` for the gate countdown ("auto-holds in 4:31"). */
|
|
19
|
+
export declare function formatCountdown(ms: number): string;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AX status and kind unions shared by the server's state model and the
|
|
3
|
+
* client's session panel, so a new status cannot be added on one side and
|
|
4
|
+
* silently render unrecognized on the other.
|
|
5
|
+
*/
|
|
6
|
+
export type AxWorkItemStatus = 'todo' | 'in-progress' | 'blocked' | 'done' | 'cancelled';
|
|
7
|
+
/** `held` = auto-held by the unattended-approval policy: the action did NOT proceed. */
|
|
8
|
+
export type AxApprovalStatus = 'pending' | 'approved' | 'rejected' | 'held';
|
|
9
|
+
export type AxEventKind = 'prompt' | 'assistant-message' | 'tool-start' | 'tool-result' | 'failure' | 'approval' | 'steering' | 'command' | 'note' | 'policy'
|
|
10
|
+
/** A human took over a node the agent was editing; the agent yielded (rail-chrome-v2 item 6). */
|
|
11
|
+
| 'yield';
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Human collaborator presence (rail-chrome-v2 phase 8, design items 5 and 6).
|
|
3
|
+
* Every open workbench tab is a client; the server fans the set out over SSE
|
|
4
|
+
* so tabs see each other's cursors, and an agent write to a node a human is
|
|
5
|
+
* holding is refused until the grab ends (user wins). In-memory, TTL-swept,
|
|
6
|
+
* never persisted — like agent presence.
|
|
7
|
+
*/
|
|
8
|
+
export interface HumanPresence {
|
|
9
|
+
/** Per-tab id (sessionStorage), never an identity. */
|
|
10
|
+
clientId: string;
|
|
11
|
+
/** Display name shown on the cursor tag. */
|
|
12
|
+
name: string;
|
|
13
|
+
/** World coordinates of the pointer, null when it left the canvas. */
|
|
14
|
+
cursor: {
|
|
15
|
+
x: number;
|
|
16
|
+
y: number;
|
|
17
|
+
} | null;
|
|
18
|
+
/** The node this human is dragging or editing right now. */
|
|
19
|
+
grabbingNodeId: string | null;
|
|
20
|
+
lastSeenAt: string;
|
|
21
|
+
}
|
|
22
|
+
export interface HumanPresenceSnapshot {
|
|
23
|
+
humans: HumanPresence[];
|
|
24
|
+
}
|
|
25
|
+
/** A tab that stops reporting is gone after this. */
|
|
26
|
+
export declare const HUMAN_PRESENCE_TTL_MS = 30000;
|
|
27
|
+
/** A grab the tab stops renewing (crash, tab closed mid-drag) releases after this. */
|
|
28
|
+
export declare const HUMAN_GRAB_TTL_MS = 8000;
|
|
29
|
+
/** Cursor updates are throttled to this on the client. */
|
|
30
|
+
export declare const HUMAN_CURSOR_THROTTLE_MS = 80;
|
|
31
|
+
export declare const MAX_HUMANS = 32;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handshake contract between /api/canvas/iframe-probe (server document) and
|
|
3
|
+
* probeSrcIframes (client). The probe document posts this marker to its parent;
|
|
4
|
+
* receiving it is the ONLY proof that src-URL iframes truly load — hosts that
|
|
5
|
+
* block sub-frame requests (the Claude Code desktop browser) still fire `load`
|
|
6
|
+
* on the error page they commit instead.
|
|
7
|
+
*/
|
|
8
|
+
export declare const IFRAME_PROBE_MESSAGE_SOURCE = "pmx-canvas-iframe-probe";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scope fence geometry shared by the server's enforcement and the client's
|
|
3
|
+
* rendering so both draw the same box: the bounding box of the fenced nodes
|
|
4
|
+
* plus `padding` px. Null when none of the fenced nodes exist.
|
|
5
|
+
*/
|
|
6
|
+
export interface FenceRect {
|
|
7
|
+
x: number;
|
|
8
|
+
y: number;
|
|
9
|
+
width: number;
|
|
10
|
+
height: number;
|
|
11
|
+
}
|
|
12
|
+
export declare function fenceRectFromNodes(nodes: Iterable<{
|
|
13
|
+
position: {
|
|
14
|
+
x: number;
|
|
15
|
+
y: number;
|
|
16
|
+
};
|
|
17
|
+
size: {
|
|
18
|
+
width: number;
|
|
19
|
+
height: number;
|
|
20
|
+
};
|
|
21
|
+
}>, padding: number): FenceRect | null;
|
package/docs/RELEASE.md
CHANGED
|
@@ -241,6 +241,26 @@ with `ok: true`, the published tarball is intact end-to-end.
|
|
|
241
241
|
in `Readme.md` actually needs the refresh.
|
|
242
242
|
- **CHANGELOG dates**: use the actual publish date in the
|
|
243
243
|
`## [version] - YYYY-MM-DD` header, not the day you wrote the entry.
|
|
244
|
+
- **npm publish is asynchronous now**: a successful publish prints "Your
|
|
245
|
+
package is being processed and may take a few minutes to become
|
|
246
|
+
available" — `npm view` 404s on the new version and `latest` stays on
|
|
247
|
+
the old one for several minutes (~4 min observed for 0.4.8). Poll
|
|
248
|
+
before diagnosing a failed publish; the Publish CI step log's
|
|
249
|
+
`+ pmx-canvas@X.Y.Z` line is the proof it landed.
|
|
244
250
|
- **Node 24 deprecation timeline**: GitHub Actions is migrating
|
|
245
251
|
`actions/checkout`, `setup-node`, `upload-artifact` to Node 24 by
|
|
246
252
|
June 2, 2026. The publish workflow already pins `@v5` of all three.
|
|
253
|
+
- **npm token failures wear two disguises** (both hit the 0.5.0 publish,
|
|
254
|
+
after build/tests/provenance all succeeded):
|
|
255
|
+
- `404 Not Found - PUT …/pmx-canvas` on a package that exists = the
|
|
256
|
+
token was rejected (npm masks permission refusals as 404). Rotate the
|
|
257
|
+
`NPM_TOKEN` repo secret.
|
|
258
|
+
- `EOTP: This operation requires a one-time password` = the account
|
|
259
|
+
enforces 2FA on writes and the token doesn't have **Bypass 2FA**
|
|
260
|
+
ticked — CI cannot answer an OTP. A granular token needs that box
|
|
261
|
+
checked to publish from Actions.
|
|
262
|
+
- Both are fixed without re-tagging: update the secret, then
|
|
263
|
+
`gh run rerun <publish-run-id> --failed` re-publishes the same tag.
|
|
264
|
+
- npm restricts bypass-2FA tokens for direct publishing by **Jan 2027**
|
|
265
|
+
— migrate to Trusted Publishing (OIDC; `publish.yml` already has
|
|
266
|
+
`id-token: write`) and delete the token before then.
|
|
@@ -24,11 +24,28 @@ surface each one binds to.
|
|
|
24
24
|
| `ingestActivity(event)` | `POST /api/canvas/ax/activity` · `canvas_ingest_activity` — board auto-reacts | Forwarding the host's tool/session hooks |
|
|
25
25
|
| `awaitGate(id)` | `GET /api/canvas/ax/{approval\|elicitation\|mode}/<id>?waitMs=` · `canvas_ax_gate { kind: "approval"\|"elicitation"\|"mode", action: "await" }` | Optionally surfacing a native modal; the agent must await PMX |
|
|
26
26
|
| `mirrorLog(event)` *(optional)* | `GET /api/canvas/ax/timeline` · `canvas://ax-timeline` | Writing AX events into the host's own chat/session log |
|
|
27
|
+
| `presence()` | `POST /api/canvas/ax/presence` · `canvas_ax_state { action: "set-presence" }` — or simply `ingestActivity` with `session-start` / `session-end` / `tool-start` / `tool-result` | Calling it from the host's session and tool hooks. Attach saves a pre-session snapshot; detach emits the `agent-session-ended` receipt — detach on shutdown (bounded, best-effort) so the human gets it promptly. If the host knows the model's token usage, report it as `contextUsage: { used, total }` — the top-bar meter then shows the real window instead of the pinned-context estimate |
|
|
27
28
|
|
|
28
|
-
|
|
29
|
+
### Presence: the cursor works for every adapter
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
An adapter does not need per-call labelling for the canvas to show its agent's
|
|
32
|
+
cursor. Attach once (`attached: true`, or a `session-start` activity) and detach
|
|
33
|
+
on exit; from then on every canvas write the agent makes — through MCP, HTTP,
|
|
34
|
+
the CLI or the SDK, under any transport label — is attributed to that session
|
|
35
|
+
while it is the only one attached. The cursor moves to the node each write
|
|
36
|
+
touched and the phase reads `tooling` during work, `waiting-approval` while a
|
|
37
|
+
gate is pending. Report `thinking` from a prompt/turn hook when the host has one
|
|
38
|
+
(the Copilot extension does); name the agent with `PMX_CANVAS_AGENT_SOURCE` on
|
|
39
|
+
the MCP server when the host runs several agents side by side, and pass
|
|
40
|
+
`agentId` for sub-agents that should keep their own cursor.
|
|
41
|
+
|
|
42
|
+
## Steering is queued first, then pushed by capable adapters (#54)
|
|
43
|
+
|
|
44
|
+
A board action (e.g. an `ax.steer` emit from a surface button) always enqueues a steering message
|
|
45
|
+
first. A capable adapter can immediately claim it, call the host's native send, and mark it after
|
|
46
|
+
the host accepts the prompt. The GitHub Copilot reference adapter implements this delivery loop.
|
|
47
|
+
|
|
48
|
+
When a host cannot wake a turn, queued steering reaches the next turn when:
|
|
32
49
|
|
|
33
50
|
1. **The pin/focus gate is open.** A typical adapter injects `/api/canvas/ax/context`
|
|
34
51
|
only when something is pinned or focused (`pinned.count > 0 || focus.nodeIds.length > 0`).
|
|
@@ -50,17 +67,16 @@ many more are queued so it can drain the FIFO `…/delivery/pending` endpoint wh
|
|
|
50
67
|
count is non-zero. **Adapters should read `delivery.pendingSteering`** (this compact,
|
|
51
68
|
count-bearing block), not `timeline.pendingSteering`.
|
|
52
69
|
|
|
53
|
-
### Canvas-origin steering
|
|
54
|
-
|
|
55
|
-
Recording a browser-origin `ax.steer` (and the `ok:true` ack a surface button gets —
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
turn"), never imply it interrupts the agent now.
|
|
70
|
+
### Canvas-origin steering requires an adapter-owned wake (#59)
|
|
71
|
+
|
|
72
|
+
Recording a browser-origin `ax.steer` (and the `ok:true` ack a surface button gets — report #55)
|
|
73
|
+
means the steer is durably queued on the timeline. PMX deliberately does not import a host SDK, so
|
|
74
|
+
the *wake* is adapter-owned: a cooperating host adapter drains
|
|
75
|
+
`…/delivery/pending?consumer=<id>`, calls its native send, then marks the delivery. The GitHub
|
|
76
|
+
Copilot adapter does this with a long-polling pump and `copilotSession.send`. It establishes a
|
|
77
|
+
baseline at adapter startup, marking older backlog without replaying it so stale instructions from
|
|
78
|
+
prior sessions cannot flood a newly opened chat. Adapters without a native send retain
|
|
79
|
+
next-human-turn delivery.
|
|
64
80
|
|
|
65
81
|
## The two primitives that close the loop
|
|
66
82
|
|
package/docs/cli.md
CHANGED
|
@@ -13,6 +13,42 @@ pmx-canvas node list --port 4750 # target a non-default dae
|
|
|
13
13
|
pmx-canvas --server-url http://127.0.0.1:4750 status # same, by URL
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
+
## pump — reactive steering for CLI agents
|
|
17
|
+
|
|
18
|
+
`pmx-canvas pump` turns any CLI agent (Codex, Amp, a script) into a steerable
|
|
19
|
+
board citizen: it long-polls the per-consumer delivery claim and runs your
|
|
20
|
+
command once per steer, marking per-consumer on success.
|
|
21
|
+
|
|
22
|
+
On Windows the exec runs through `cmd.exe` and receives the steer message on
|
|
23
|
+
stdin. The `{message}` and `{id}` placeholders are refused there: `cmd.exe`
|
|
24
|
+
expands `%VAR%` and re-parses the result, so a steer containing shell
|
|
25
|
+
metacharacters could inject commands. Read stdin, or reference
|
|
26
|
+
`PMX_STEER_MESSAGE` / `PMX_STEER_ID` inside your own script.
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pmx-canvas pump --consumer codex-cli --exec 'codex exec --full-auto {message}'
|
|
30
|
+
pmx-canvas pump --consumer amp --exec 'amp -x {message}' --parent claude-code
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The Codex example starts a non-interactive CLI task. It does not steer an open
|
|
34
|
+
Codex desktop task: desktop owns that thread's writer and requires a host
|
|
35
|
+
adapter that uses the app's native follow-up channel.
|
|
36
|
+
|
|
37
|
+
- The steer text reaches the command as `$PMX_STEER_MESSAGE` (env) and stdin;
|
|
38
|
+
`{message}` in the template expands to the quoted env reference — steer
|
|
39
|
+
content is never spliced into the shell line.
|
|
40
|
+
- The delivery envelope rides along as env: `$PMX_STEER_ID`, `$PMX_STEER_SOURCE`
|
|
41
|
+
(who sent it), `$PMX_STEER_TARGET` (your consumer key, or `ALL` for a
|
|
42
|
+
broadcast), and `$PMX_STEER_CREATED_AT` — so the host turn can say who
|
|
43
|
+
steered, whether it was addressed, and how old it is.
|
|
44
|
+
- Startup backlog is marked silently (pass `--backlog deliver` to process it).
|
|
45
|
+
- A failing command is retried twice, then the pump exits non-zero and leaves
|
|
46
|
+
the steer pending so a failed host injection cannot look delivered.
|
|
47
|
+
- `--parent <key>` rolls the pumped agent up under an orchestrator's chip;
|
|
48
|
+
`--once` processes a single steer (useful in scripts and tests).
|
|
49
|
+
- Survives daemon restarts; backs off against pre-0.4.9 servers that ignore
|
|
50
|
+
`waitMs`.
|
|
51
|
+
|
|
16
52
|
## Server lifecycle
|
|
17
53
|
|
|
18
54
|
```bash
|
|
@@ -281,7 +317,7 @@ reports annotation targets and bounds; it does not describe whether the human
|
|
|
281
317
|
drew an arrow, line, circle, or other shape. Inspect `.annotation-layer path` or
|
|
282
318
|
take a screenshot when the drawn form matters.
|
|
283
319
|
|
|
284
|
-
Humans draw with the pen
|
|
320
|
+
Humans draw with the rail's Annotate popover (pen / text) and remove marks with its eraser.
|
|
285
321
|
If an agent already knows the annotation ID from context, it can remove it through
|
|
286
322
|
MCP with `canvas_view { action: "remove-annotation", id }`.
|
|
287
323
|
|
package/docs/environment.md
CHANGED
|
@@ -20,6 +20,9 @@ optional; defaults are listed.
|
|
|
20
20
|
| `PMX_CANVAS_DB_PATH` | server | Overrides the SQLite path (default `.pmx-canvas/canvas.db`). |
|
|
21
21
|
| `PMX_CANVAS_STATE_FILE` | server | `.db`-path alias only as of 0.4.0 (non-`.db` values are ignored with a boot warning; the pre-0.2 JSON import was removed). |
|
|
22
22
|
| `PMX_CANVAS_BLOB_THRESHOLD_BYTES` | server | Node payloads above this many JSON bytes are stored as blobs (default `2048`). |
|
|
23
|
+
| `PMX_CANVAS_AGENT_SOURCE` | MCP transport, CLI | Names the agent behind this transport for presence (`codex`, `claude-code`, …) so its writes, attach and cursor share one identity; default is the transport label (`mcp` / `cli`). |
|
|
24
|
+
| `PMX_CANVAS_GATE_TTL_MS` | server | Default TTL for approval gates before the unattended-approval policy auto-holds them (default `300000` = 5 min, clamped to 1 s – 24 h). |
|
|
25
|
+
| `PMX_CANVAS_CONTEXT_BUDGET_TOKENS` | server | `budget.total` in agent presence — the ceiling the top bar's **Pins** meter measures the pinned-context token estimate against (default `32000`). It is not the agent's context window; a host reports that on the presence as `contextUsage`, and the meter then reads **Context**. |
|
|
23
26
|
|
|
24
27
|
## Server behavior
|
|
25
28
|
|
package/docs/http-api.md
CHANGED
|
@@ -29,6 +29,12 @@ curl -X POST http://localhost:4313/api/canvas/schema/validate \
|
|
|
29
29
|
-H "Content-Type: application/json" \
|
|
30
30
|
-d '{"type":"json-render","spec":{"root":"card","elements":{"card":{"type":"Card","props":{"title":"Preview"},"children":[]}}}}'
|
|
31
31
|
|
|
32
|
+
# Create a json-render node from a spec. `height` and `nodeHeight` are both
|
|
33
|
+
# accepted for the node frame (nodeHeight matches the graph/stream actions).
|
|
34
|
+
curl -X POST http://localhost:4313/api/canvas/json-render \
|
|
35
|
+
-H "Content-Type: application/json" \
|
|
36
|
+
-d '{"title":"Panel","nodeHeight":280,"spec":{"root":"card","elements":{"card":{"type":"Card","props":{"title":"Live"},"children":[]}}}}'
|
|
37
|
+
|
|
32
38
|
# Validate an HTML primitive without creating a node
|
|
33
39
|
curl -X POST http://localhost:4313/api/canvas/schema/validate \
|
|
34
40
|
-H "Content-Type: application/json" \
|
|
@@ -73,6 +79,17 @@ with no `fileContent`, plus `data.mimeType` when the type is known
|
|
|
73
79
|
(`application/pdf` for PDFs). A text file larger than 2 MB is stored truncated
|
|
74
80
|
with `data.truncated: true`.
|
|
75
81
|
|
|
82
|
+
```bash
|
|
83
|
+
# Repoint a file node at another file — content re-read, watcher rewired,
|
|
84
|
+
# edges/pins/position kept (recreating the node loses all three)
|
|
85
|
+
curl -X PATCH http://localhost:4313/api/canvas/node/node-abc123 \
|
|
86
|
+
-H "Content-Type: application/json" \
|
|
87
|
+
-d '{"path":"src/server/canvas-state.ts"}'
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
The title follows the new filename unless the node was renamed away from the
|
|
91
|
+
old one (or the patch sets `title` explicitly).
|
|
92
|
+
|
|
76
93
|
```bash
|
|
77
94
|
# Raw bytes for a binary file node (PDF viewer, downloads)
|
|
78
95
|
curl http://localhost:4313/api/canvas/file-bytes?nodeId=node-abc123
|
|
@@ -99,6 +116,16 @@ Search-based edge creation is intentionally strict: `fromSearch` and
|
|
|
99
116
|
`toSearch` must each resolve to exactly one node. Broad queries that match
|
|
100
117
|
multiple nodes fail; use the full visible title.
|
|
101
118
|
|
|
119
|
+
```bash
|
|
120
|
+
# Update an edge in place (label, type, style, animated) — empty label clears it
|
|
121
|
+
curl -X PATCH http://localhost:4313/api/canvas/edge/edge-123 \
|
|
122
|
+
-H "Content-Type: application/json" \
|
|
123
|
+
-d '{"label":"retries","type":"depends-on","style":"dashed"}'
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Retyping an edge onto a from/to/type triple that already exists is refused
|
|
127
|
+
(400) — the same duplicate rule edge creation enforces.
|
|
128
|
+
|
|
102
129
|
## Annotations
|
|
103
130
|
|
|
104
131
|
```bash
|
|
@@ -164,10 +191,10 @@ curl -X POST http://localhost:4313/api/canvas/ax/event \
|
|
|
164
191
|
-H "Content-Type: application/json" \
|
|
165
192
|
-d '{"kind":"tool-start","summary":"ran tests","source":"api"}'
|
|
166
193
|
|
|
167
|
-
# Timeline — send a steering message
|
|
194
|
+
# Timeline — send a steering message; `target` addresses ONE consumer, omit to broadcast
|
|
168
195
|
curl -X POST http://localhost:4313/api/canvas/ax/steer \
|
|
169
196
|
-H "Content-Type: application/json" \
|
|
170
|
-
-d '{"message":"focus on the failing test first","source":"api"}'
|
|
197
|
+
-d '{"message":"focus on the failing test first","source":"api","target":"copilot"}'
|
|
171
198
|
|
|
172
199
|
# Timeline — record an evidence item (logs/tool-result/screenshot/file/diff/test-output)
|
|
173
200
|
curl -X POST http://localhost:4313/api/canvas/ax/evidence \
|
|
@@ -186,14 +213,21 @@ curl -X PATCH http://localhost:4313/api/canvas/ax/work/<id> \
|
|
|
186
213
|
-d '{"status":"done"}'
|
|
187
214
|
curl http://localhost:4313/api/canvas/ax/work
|
|
188
215
|
|
|
189
|
-
# Canvas-bound — request / resolve an approval gate (pending → approved/rejected)
|
|
216
|
+
# Canvas-bound — request / resolve an approval gate (pending → approved/rejected/held)
|
|
190
217
|
curl -X POST http://localhost:4313/api/canvas/ax/approval \
|
|
191
218
|
-H "Content-Type: application/json" \
|
|
192
|
-
-d '{"title":"Deploy to prod","action":"deploy.prod","source":"api"}'
|
|
219
|
+
-d '{"title":"Deploy to prod","action":"deploy.prod","ttlMs":300000,"source":"api"}'
|
|
193
220
|
curl -X POST http://localhost:4313/api/canvas/ax/approval/<id>/resolve \
|
|
194
221
|
-H "Content-Type: application/json" \
|
|
195
222
|
-d '{"decision":"approved","source":"api"}'
|
|
196
223
|
curl http://localhost:4313/api/canvas/ax/approval
|
|
224
|
+
# Unattended-approval policy: a gate nobody answers within its TTL (default
|
|
225
|
+
# PMX_CANVAS_GATE_TTL_MS = 5 min, max 24 h) resolves to `held` — the action does
|
|
226
|
+
# NOT proceed, an awaiting agent is released with a non-approval, and a
|
|
227
|
+
# `policy` agent-event explains why. Reopen it (fresh TTL) from the session
|
|
228
|
+
# panel or here; this is the human's path, not an MCP tool.
|
|
229
|
+
curl -X POST http://localhost:4313/api/canvas/ax/approval/<id>/reopen \
|
|
230
|
+
-H "Content-Type: application/json" -d '{"source":"browser"}'
|
|
197
231
|
|
|
198
232
|
# Canvas-bound — add a review annotation (comment/finding) anchored to node/file/region
|
|
199
233
|
curl -X POST http://localhost:4313/api/canvas/ax/review \
|
|
@@ -216,6 +250,152 @@ reject an unknown `status` with 400 (the tokens are `todo`, `in-progress`,
|
|
|
216
250
|
and `PATCH /ax/review/:id` return 404 for unknown IDs; approval resolve returns
|
|
217
251
|
404 if the gate is missing or already resolved.
|
|
218
252
|
|
|
253
|
+
## Agent presence
|
|
254
|
+
|
|
255
|
+
Who is writing to the board right now, in what phase, and whether a session
|
|
256
|
+
is attached. Presence is *derived*: every agent-originated mutation (anything
|
|
257
|
+
without the workbench's own `x-pmx-workbench: 1` marker) registers its caller
|
|
258
|
+
as a `tooling` writer, the activity feed drives attach/detach and phase
|
|
259
|
+
(`session-start`, `session-end`, `tool-start`, `tool-result`), and adapters
|
|
260
|
+
with richer hooks can set a phase, cursor, or focus explicitly. Writers fade
|
|
261
|
+
90 s after their last write; attached sessions expire after 30 min of quiet
|
|
262
|
+
without a `session-end`.
|
|
263
|
+
|
|
264
|
+
```bash
|
|
265
|
+
# Read the snapshot (the browser does this on connect)
|
|
266
|
+
curl http://localhost:4313/api/canvas/ax/presence
|
|
267
|
+
# → { ok, presences: [{ sessionId, source, agentId, label, phase, detail,
|
|
268
|
+
# focusNodeId, cursor, attached, opCount, contextUsage, lastSeenAt }],
|
|
269
|
+
# budget: { used, total }, sessionActive,
|
|
270
|
+
# activity: [{ id, at, sessionId, label, op, summary, nodeId }] } # newest first, last 50
|
|
271
|
+
|
|
272
|
+
# Attach a session and report a phase (idle | thinking | tooling | waiting-approval)
|
|
273
|
+
curl -X POST http://localhost:4313/api/canvas/ax/presence \
|
|
274
|
+
-H "Content-Type: application/json" \
|
|
275
|
+
-d '{"source":"copilot","attached":true,"phase":"thinking","detail":"planning"}'
|
|
276
|
+
|
|
277
|
+
# Point the agent cursor at a node, then detach
|
|
278
|
+
curl -X POST http://localhost:4313/api/canvas/ax/presence \
|
|
279
|
+
-H "Content-Type: application/json" \
|
|
280
|
+
-d '{"source":"copilot","focusNodeId":"<node-id>","cursor":{"x":320,"y":140}}'
|
|
281
|
+
curl -X POST http://localhost:4313/api/canvas/ax/presence \
|
|
282
|
+
-H "Content-Type: application/json" -d '{"source":"copilot","attached":false}'
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
`sessionActive` is true when any presence is attached — it is the single gate
|
|
286
|
+
for the agent chrome (session panel, command bar, presence layer). Live but
|
|
287
|
+
unattached writers are the *external steering* case: the browser shows a
|
|
288
|
+
passive top-bar indicator (writers + op count) with an activity feed and a
|
|
289
|
+
connected-writers sheet — visibility only, never permissions. `activity` is
|
|
290
|
+
that feed: one entry per agent write with a human summary ("Created markdown
|
|
291
|
+
“Release plan”"), attributed to the writer (re-attributed to the session when
|
|
292
|
+
a transport writer folds into it), bounded to the last 50 and kept after a
|
|
293
|
+
writer fades. Detaching (`attached: false`, `session-end`) removes the
|
|
294
|
+
presence outright — an ended session never lingers as an external writer.
|
|
295
|
+
|
|
296
|
+
**The context meter is an estimate unless the host says otherwise.** `budget`
|
|
297
|
+
is a token estimate (chars ÷ 4) of the `pinned-context` payload against
|
|
298
|
+
`PMX_CANVAS_CONTEXT_BUDGET_TOKENS` (default 32000) — what the human's pins
|
|
299
|
+
would cost the agent, not the agent's live window; the top bar labels it
|
|
300
|
+
**Pins**. A host that knows the agent's real usage reports it on the presence
|
|
301
|
+
(`contextUsage: { used, total }` on `POST /api/canvas/ax/presence` /
|
|
302
|
+
`set-presence`); the top bar then shows **Context** with those numbers. The
|
|
303
|
+
bundled Copilot extension reports it from the SDK's `session.usage_info`
|
|
304
|
+
event (root agent, coalesced to one report per 500 ms); the legacy
|
|
305
|
+
`context-usage` workbench event feeds the single attached session the same
|
|
306
|
+
way.
|
|
307
|
+
|
|
308
|
+
```bash
|
|
309
|
+
curl -X POST http://localhost:4313/api/canvas/ax/presence \
|
|
310
|
+
-H "Content-Type: application/json" \
|
|
311
|
+
-d '{"source":"copilot","contextUsage":{"used":42800,"total":128000}}'
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
**Writer identity and attribution.** Plain HTTP callers read as source `api`,
|
|
315
|
+
the CLI sends `x-pmx-source: cli`, the MCP server sends `mcp` (or
|
|
316
|
+
`PMX_CANVAS_AGENT_SOURCE` when set), the SDK is `sdk`. Those are *transport*
|
|
317
|
+
labels, not agents — so while exactly one session is attached, a transport
|
|
318
|
+
write with no `agentId` is attributed to that session: its cursor moves to
|
|
319
|
+
the touched node, its phase reads `tooling`, and no second writer appears.
|
|
320
|
+
This is what lets a Copilot/Codex/Claude Code session that writes through MCP
|
|
321
|
+
keep one cursor. Pass `agentId` (sub-agents) or a host label to keep a writer
|
|
322
|
+
separate; with several sessions attached, transport writes stay on their own
|
|
323
|
+
label. A session the human started (*Start agent session*, `source: "browser"`)
|
|
324
|
+
is a placeholder for whichever agent comes next: it absorbs agent-less writes
|
|
325
|
+
under *any* label — a host label included — and takes that agent's name. If
|
|
326
|
+
exactly one such writer is already on the board when the human starts the
|
|
327
|
+
session, it is adopted on the spot. One agent arriving over two channels —
|
|
328
|
+
an adapter presence plus its own MCP server, both announcing the same label —
|
|
329
|
+
merges into one session at attach, and the merged channel's writes and detach
|
|
330
|
+
keep landing on it.
|
|
331
|
+
|
|
332
|
+
**Session lifecycle: pre-session snapshot and receipt.** When a session
|
|
333
|
+
attaches (`attached: true`, or `session-start` on the activity feed) over a
|
|
334
|
+
non-empty board, the server saves a snapshot named
|
|
335
|
+
`Before session · <label> · HH:MM` — the board before the agent touched it.
|
|
336
|
+
When that session ends (`attached: false`, `session-end`, or the idle expiry)
|
|
337
|
+
the stream carries one `agent-session-ended` frame:
|
|
338
|
+
|
|
339
|
+
```json
|
|
340
|
+
{ "label": "Copilot", "endedAt": "…", "counts": { "items": 4, "done": 3, "vetoed": 1 },
|
|
341
|
+
"snapshot": { "id": "…", "name": "Before session · Copilot · 14:00" } }
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
`items`/`done` count the work items on the board; `vetoed` counts cancelled
|
|
345
|
+
items plus rejected or held gates. `snapshot` is null when the board was
|
|
346
|
+
empty at attach. The browser renders this as the session receipt; its *View
|
|
347
|
+
diff* is `GET /api/canvas/snapshots/<id>/diff` against that snapshot, and
|
|
348
|
+
restoring the snapshot undoes the session. Adapters should end their session
|
|
349
|
+
explicitly so the human gets the receipt promptly rather than after the idle
|
|
350
|
+
expiry. The browser's *Start agent session* button is this same endpoint
|
|
351
|
+
(`source: "browser"`, `attached: true`) — subsequent agent-less writes are
|
|
352
|
+
attributed to that session, and the first named agent renames it.
|
|
353
|
+
|
|
354
|
+
## Human presence (collaborators)
|
|
355
|
+
|
|
356
|
+
Every open workbench tab reports itself; the server fans the set out as
|
|
357
|
+
`human-presence` SSE frames so tabs see each other's cursors. A node a human
|
|
358
|
+
is holding (dragging) is an edit lock: an agent write targeting it is refused
|
|
359
|
+
with **409** ("being edited by <name> — requeue") until the grab is released
|
|
360
|
+
or goes stale (8 s without renewal). Membership changes count as targeting the
|
|
361
|
+
member: dissolving the group a held node belongs to (`group.remove`, or
|
|
362
|
+
`node.remove` on the group) or pulling it into another group is refused the
|
|
363
|
+
same way. Human heartbeats never count as agent activity.
|
|
364
|
+
|
|
365
|
+
```bash
|
|
366
|
+
curl http://localhost:4313/api/canvas/human-presence
|
|
367
|
+
# → { ok, humans: [{ clientId, name, cursor, grabbingNodeId, lastSeenAt }] }
|
|
368
|
+
curl -X POST http://localhost:4313/api/canvas/human-presence \
|
|
369
|
+
-H "Content-Type: application/json" -H "x-pmx-workbench: 1" \
|
|
370
|
+
-d '{"clientId":"tab-1","name":"mia","cursor":{"x":320,"y":140},"grabbingNodeId":"<node-id>"}'
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
## Scope fence (agent writes)
|
|
374
|
+
|
|
375
|
+
The policy's `scope` limits what an attached agent may WRITE: existing-node
|
|
376
|
+
writes must target fenced nodes, new nodes must land inside the fenced
|
|
377
|
+
nodes' bounding box plus `padding` px (default 40), and board-wide writes
|
|
378
|
+
(arrange, clear, restore) are refused. Every refusal is HTTP 403 with a reason
|
|
379
|
+
naming the node or position. Reads are never fenced, nor are the human's own
|
|
380
|
+
workbench writes. A fence is a granted region: a group id in `nodeIds` expands
|
|
381
|
+
to the frame plus every member (nested groups included) when the scope is set.
|
|
382
|
+
The fence is visible to the agent in `canvas://ax-context` (`policy.scope`) and
|
|
383
|
+
drawn on the canvas while a session is attached.
|
|
384
|
+
|
|
385
|
+
The fence belongs to the human: only workbench calls (`x-pmx-workbench: 1`) may
|
|
386
|
+
set, replace, or clear `scope`. An agent call to `POST /api/canvas/ax/policy`
|
|
387
|
+
that includes `scope` is refused with 403, and the MCP `set-policy` action does
|
|
388
|
+
not take it.
|
|
389
|
+
|
|
390
|
+
```bash
|
|
391
|
+
# Grant (replace semantics) and clear — workbench calls
|
|
392
|
+
curl -X POST http://localhost:4313/api/canvas/ax/policy \
|
|
393
|
+
-H "Content-Type: application/json" -H "x-pmx-workbench: 1" \
|
|
394
|
+
-d '{"scope":{"nodeIds":["<node-a>","<node-b>"],"padding":40},"source":"browser"}'
|
|
395
|
+
curl -X POST http://localhost:4313/api/canvas/ax/policy \
|
|
396
|
+
-H "Content-Type: application/json" -H "x-pmx-workbench: 1" -d '{"scope":null}'
|
|
397
|
+
```
|
|
398
|
+
|
|
219
399
|
## AX interactions, delivery, elicitation, mode, commands & policy
|
|
220
400
|
|
|
221
401
|
Node interactions are one normalized, capability-gated envelope that maps onto an
|
|
@@ -229,12 +409,32 @@ curl -X POST http://localhost:4313/api/canvas/ax/interaction \
|
|
|
229
409
|
-H "Content-Type: application/json" \
|
|
230
410
|
-d '{"type":"ax.work.create","sourceNodeId":"node-1","payload":{"title":"Wire auth"}}'
|
|
231
411
|
|
|
232
|
-
# Delivery — claim pending steering for a consumer (loop-safe), then mark delivered
|
|
412
|
+
# Delivery — claim pending steering for a consumer (loop-safe), then mark delivered.
|
|
413
|
+
# The claim returns broadcasts plus steering addressed to THIS consumer; a claim
|
|
414
|
+
# with no `consumer` sees broadcasts only. With several agents connected, the
|
|
415
|
+
# browser composer offers a target picker fed by live presence — always claim
|
|
416
|
+
# with your own label or addressed steering never reaches you.
|
|
233
417
|
curl "http://localhost:4313/api/canvas/ax/delivery/pending?consumer=copilot&limit=20"
|
|
418
|
+
|
|
419
|
+
# Long-poll: `waitMs` parks the request until steering for this consumer
|
|
420
|
+
# arrives or the timeout elapses (capped at 120000ms) — the reactive loop for
|
|
421
|
+
# hosts whose model only runs while the host gives it a turn. Loop on this
|
|
422
|
+
# instead of tight polling.
|
|
423
|
+
curl "http://localhost:4313/api/canvas/ax/delivery/pending?consumer=copilot&waitMs=120000"
|
|
424
|
+
# Marks are PER CONSUMER for broadcasts: your mark removes the message from
|
|
425
|
+
# YOUR queue only — every other consumer still receives it ("all workers:
|
|
426
|
+
# stop" reaches the whole fleet). Addressed steers stay single-recipient
|
|
427
|
+
# compare-and-set; a consumer-less mark is the anonymous global ack.
|
|
234
428
|
curl -X POST http://localhost:4313/api/canvas/ax/delivery/<steering-id>/mark \
|
|
235
429
|
-H "Content-Type: application/json" \
|
|
236
430
|
-d '{"consumer":"copilot"}'
|
|
237
431
|
|
|
432
|
+
# Per-agent territories (fleet orchestration): fence a worker to its lane.
|
|
433
|
+
# An agent may fence OTHER writers, never its own key; the human may set any.
|
|
434
|
+
curl -X POST http://localhost:4313/api/canvas/ax/policy \
|
|
435
|
+
-H "Content-Type: application/json" \
|
|
436
|
+
-d '{"agentScopes":{"run1:impl":{"nodeIds":["node-a"],"padding":80}}}'
|
|
437
|
+
|
|
238
438
|
# Elicitation — request structured human input, then respond
|
|
239
439
|
curl -X POST http://localhost:4313/api/canvas/ax/elicitation \
|
|
240
440
|
-H "Content-Type: application/json" \
|
|
@@ -352,6 +552,15 @@ curl -N http://localhost:4313/api/workbench/events
|
|
|
352
552
|
The browser, the CLI `watch` command, and the MCP resource notifications
|
|
353
553
|
all consume this stream. Auto-reconnect with exponential backoff.
|
|
354
554
|
|
|
555
|
+
`agent-presence` frames carry the full presence snapshot (the same body as
|
|
556
|
+
`GET /api/canvas/ax/presence`) on every change, including TTL expiry — a
|
|
557
|
+
client never needs its own expiry timer. `agent-session-ended` carries the
|
|
558
|
+
session receipt (see [Agent presence](#agent-presence)).
|
|
559
|
+
|
|
560
|
+
Every frame's payload is wrapped in an envelope that sets `sessionId` (the
|
|
561
|
+
workbench session, used for reconnect/reload detection) and `timestamp` —
|
|
562
|
+
payload keys of those names are overwritten, so event bodies never use them.
|
|
563
|
+
|
|
355
564
|
## Polling transport (proxy-safe)
|
|
356
565
|
|
|
357
566
|
Some proxies (e.g. the Amp orb portal) buffer streaming responses and only
|
|
@@ -420,8 +629,16 @@ re-mint them automatically (Finding S).
|
|
|
420
629
|
curl -X POST http://localhost:4313/api/canvas/undo
|
|
421
630
|
curl -X POST http://localhost:4313/api/canvas/redo
|
|
422
631
|
curl http://localhost:4313/api/canvas/history
|
|
632
|
+
# → { text, entries: [{ id, timestamp, description, actor, operationType, isCurrent, isUndone }],
|
|
633
|
+
# top: { …the entry undo would revert next… } | null, canUndo, canRedo }
|
|
423
634
|
```
|
|
424
635
|
|
|
636
|
+
One shared undo stack, agent and human alike. Each entry's `actor` is `human`
|
|
637
|
+
for workbench writes (`x-pmx-workbench: 1`) and `agent` for everything else;
|
|
638
|
+
`top` is what the next undo reverts. The session panel uses this to offer
|
|
639
|
+
"undo this edit" on the agent's latest board write and tells the agent through
|
|
640
|
+
steering when the human takes it.
|
|
641
|
+
|
|
425
642
|
## WebView automation
|
|
426
643
|
|
|
427
644
|
```bash
|
package/docs/mcp.md
CHANGED
|
@@ -39,18 +39,18 @@ its `action` to the same operation the legacy tool used, so results are identica
|
|
|
39
39
|
|-----------|-----------------|----------|
|
|
40
40
|
| `canvas_node` | `add` · `get` · `update` · `remove` | `canvas_add_node`, `canvas_get_node`, `canvas_update_node`, `canvas_remove_node`, `canvas_add_html_node` (`add` + `type:"html"`), `canvas_add_html_primitive` (`add` + `type:"html"`, `primitive:"<kind>"`), `canvas_refresh_webpage_node` (`update` + `refresh:true`) |
|
|
41
41
|
| `canvas_render` | `describe-schema` · `validate` · `add-json-render` · `stream-json-render` · `add-graph` · `workboard` | `canvas_describe_schema`, `canvas_validate_spec`, `canvas_add_json_render_node`, `canvas_stream_json_render_node`, `canvas_add_graph_node` |
|
|
42
|
-
| `canvas_edge` | `add` · `remove` | `canvas_add_edge`, `canvas_remove_edge` |
|
|
42
|
+
| `canvas_edge` | `add` · `update` · `remove` | `canvas_add_edge`, `canvas_update_edge`, `canvas_remove_edge` |
|
|
43
43
|
| `canvas_group` | `create` · `add` · `ungroup` | `canvas_create_group`, `canvas_group_nodes`, `canvas_ungroup` |
|
|
44
44
|
| `canvas_history` | `undo` · `redo` | `canvas_undo`, `canvas_redo` |
|
|
45
45
|
| `canvas_view` | `arrange` · `focus` · `fit` · `clear` · `remove-annotation` | `canvas_arrange`, `canvas_focus_node`, `canvas_fit_view`, `canvas_clear`, `canvas_remove_annotation` |
|
|
46
46
|
| `canvas_query` | `search` · `layout` · `validate` | `canvas_search`, `canvas_get_layout`, `canvas_validate` |
|
|
47
47
|
| `canvas_webview` | `status` · `start` · `stop` · `resize` · `evaluate` | `canvas_webview_status`, `canvas_webview_start`, `canvas_webview_stop`, `canvas_resize`, `canvas_evaluate` |
|
|
48
48
|
| `canvas_app` | `open-mcp-app` · `diagram` · `build-artifact` | `canvas_open_mcp_app`, `canvas_add_diagram`, `canvas_build_web_artifact` |
|
|
49
|
-
| `canvas_ax_state` | `get` · `set-focus` · `set-policy` · `report-capability` | `canvas_get_ax`, `canvas_set_ax_focus`, `canvas_set_ax_policy`, `canvas_report_host_capability` |
|
|
49
|
+
| `canvas_ax_state` | `get` · `set-focus` · `set-policy` · `report-capability` · `presence` · `set-presence` | `canvas_get_ax`, `canvas_set_ax_focus`, `canvas_set_ax_policy`, `canvas_report_host_capability` |
|
|
50
50
|
| `canvas_ax_work` | `add` · `update` · `annotate` | `canvas_add_work_item`, `canvas_update_work_item`, `canvas_add_review_annotation` |
|
|
51
51
|
| `canvas_ax_gate` | `request` · `resolve` · `await` × kind `approval` \| `elicitation` \| `mode` | `canvas_request_approval`, `canvas_resolve_approval`, `canvas_await_approval`, `canvas_request_elicitation`, `canvas_respond_elicitation`, `canvas_await_elicitation`, `canvas_request_mode`, `canvas_resolve_mode`, `canvas_await_mode` (9 → 1) |
|
|
52
52
|
| `canvas_ax_timeline` | `read` · `record-event` · `add-evidence` · `send-steering` | `canvas_get_ax_timeline`, `canvas_record_ax_event`, `canvas_add_evidence`, `canvas_send_steering` |
|
|
53
|
-
| `canvas_ax_delivery` | `claim` · `mark` | `canvas_claim_ax_delivery`, `canvas_mark_ax_delivery` |
|
|
53
|
+
| `canvas_ax_delivery` | `claim` (long-polls with `timeoutMs`) · `mark` | `canvas_claim_ax_delivery`, `canvas_mark_ax_delivery` |
|
|
54
54
|
| `canvas_intent` | `signal` · `update` · `clear` | _(new — Ghost Cursor of Intent; no legacy standalone tool)_ |
|
|
55
55
|
| `canvas_snapshot` | `save` · `list` · `restore` · `delete` · `gc` · `diff` | `canvas_snapshot` (legacy save tool), `canvas_list_snapshots`, `canvas_restore`, `canvas_delete_snapshot`, `canvas_gc_snapshots`, `canvas_diff` — removed in v0.4.0 after one deprecated minor |
|
|
56
56
|
|
|
@@ -135,6 +135,7 @@ migrating an older integration.
|
|
|
135
135
|
| `canvas_stream_json_render_node` | `canvas_render { action: "stream-json-render" }` |
|
|
136
136
|
| `canvas_add_graph_node` | `canvas_render { action: "add-graph" }` |
|
|
137
137
|
| `canvas_add_edge` | `canvas_edge { action: "add" }` |
|
|
138
|
+
| `canvas_update_edge` | `canvas_edge { action: "update" }` |
|
|
138
139
|
| `canvas_remove_edge` | `canvas_edge { action: "remove" }` |
|
|
139
140
|
| `canvas_create_group` | `canvas_group { action: "create" }` |
|
|
140
141
|
| `canvas_group_nodes` | `canvas_group { action: "add" }` |
|
|
@@ -277,8 +278,8 @@ keep annotation context compact: agents see annotation counts, bounds, and targe
|
|
|
277
278
|
summaries such as the node or empty canvas region that was marked, but not the
|
|
278
279
|
raw stroke geometry or visual shape.
|
|
279
280
|
|
|
280
|
-
Annotations are a browser-visible markup layer.
|
|
281
|
-
|
|
281
|
+
Annotations are a browser-visible markup layer. Humans draw with the rail's
|
|
282
|
+
Annotate popover (pen / text / eraser — the eraser removes a mark); agents can also
|
|
282
283
|
remove a known annotation ID with `canvas_view { action: "remove-annotation", id }`.
|
|
283
284
|
|
|
284
285
|
Use WebView automation when an agent needs to actually see annotations as drawn.
|
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
|