pmx-canvas 0.9.2 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/extensions/pmx-canvas/extension.mjs +9 -3
- package/.github/extensions/pmx-canvas/runtime.json +1 -1
- package/.github/extensions/pmx-canvas/tools.json +9278 -2014
- package/CHANGELOG.md +34 -1
- package/dist/canvas/global.css +12 -17
- package/dist/canvas/index.js +58 -58
- package/dist/canvas/mcp-app.html +75 -80
- package/dist/types/client/canvas/submit-without-navigation.d.ts +3 -0
- package/dist/types/client/state/intent-bridge.d.ts +0 -3
- package/dist/types/client/state/presence-store.d.ts +3 -3
- package/dist/types/client/types.d.ts +1 -1
- package/dist/types/mcp/canvas-access.d.ts +1 -12
- package/dist/types/mcp/resource-subscriptions.d.ts +7 -0
- package/dist/types/mcp/server.d.ts +2 -0
- package/dist/types/server/ax-wait.d.ts +1 -0
- package/dist/types/server/canvas-operations.d.ts +1 -0
- package/dist/types/server/canvas-state.d.ts +2 -0
- package/dist/types/server/index.d.ts +5 -5
- package/dist/types/server/knowledge-lifecycle.d.ts +11 -2
- package/dist/types/server/lifecycle-status.d.ts +3 -1
- package/dist/types/server/operations/http.d.ts +10 -0
- package/dist/types/server/operations/index.d.ts +2 -2
- package/dist/types/server/operations/invoker.d.ts +4 -3
- package/dist/types/server/operations/mcp-contracts.d.ts +20 -0
- package/dist/types/server/operations/mcp.d.ts +2 -0
- package/dist/types/server/operations/ops/batch.d.ts +1 -0
- package/dist/types/server/operations/registry.d.ts +2 -2
- package/dist/types/server/operations/types.d.ts +12 -2
- package/dist/types/server/server.d.ts +2 -0
- package/dist/types/server/tour-control.d.ts +6 -3
- package/dist/types/server/webpage-node.d.ts +1 -0
- package/docs/api-reference.md +2 -2
- package/docs/cli.md +10 -1
- package/docs/design/Chrome.dc.html +1 -0
- package/docs/design.md +3 -1
- package/docs/http-api.md +31 -0
- package/docs/mcp.md +98 -22
- package/docs/node-types.md +3 -0
- package/docs/plans/plan-019-post-0.9-roadmap.md +187 -11
- package/docs/sdk.md +10 -0
- package/package.json +1 -1
- package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/extension.mjs +9 -3
- package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/runtime.json +1 -1
- package/plugins/pmx-canvas/plugin.json +1 -1
- package/plugins/pmx-canvas/runtime.json +1 -1
- package/plugins/pmx-canvas/tools.json +9278 -2014
- package/skills/pmx-canvas/SKILL.md +12 -7
- package/skills/pmx-canvas/references/full-reference.md +35 -2
- package/skills/pmx-canvas/references/github-copilot-app-adapter.md +2 -2
- package/skills/pmx-canvas/references/installing-pmx-canvas.md +7 -7
- package/src/client/canvas/BoardCopyDialog.tsx +2 -1
- package/src/client/canvas/KnowledgeDialog.tsx +3 -0
- package/src/client/canvas/LifecyclePanel.tsx +4 -0
- package/src/client/canvas/RelationPresetsDialog.tsx +2 -0
- package/src/client/canvas/ToolRail.tsx +2 -0
- package/src/client/canvas/submit-without-navigation.ts +14 -0
- package/src/client/state/canvas-store.ts +12 -24
- package/src/client/state/intent-bridge.ts +0 -6
- package/src/client/state/presence-store.ts +3 -3
- package/src/client/state/sse-bridge.ts +11 -6
- package/src/client/theme/global.css +12 -17
- package/src/mcp/canvas-access.ts +6 -47
- package/src/mcp/resource-subscriptions.ts +181 -0
- package/src/mcp/server.ts +185 -219
- package/src/server/ax-wait.ts +21 -6
- package/src/server/canvas-operations.ts +6 -2
- package/src/server/canvas-state.ts +10 -0
- package/src/server/index.ts +20 -32
- package/src/server/knowledge-lifecycle.ts +66 -0
- package/src/server/lifecycle-status.ts +79 -7
- package/src/server/operations/http.ts +69 -21
- package/src/server/operations/index.ts +8 -1
- package/src/server/operations/invoker.ts +53 -5
- package/src/server/operations/mcp-contracts.ts +339 -0
- package/src/server/operations/mcp.ts +153 -18
- package/src/server/operations/ops/ax-await.ts +18 -10
- package/src/server/operations/ops/ax-read.ts +2 -0
- package/src/server/operations/ops/ax-timeline.ts +2 -2
- package/src/server/operations/ops/batch.ts +27 -8
- package/src/server/operations/ops/lifecycle.ts +3 -7
- package/src/server/operations/ops/nodes.ts +20 -8
- package/src/server/operations/ops/viewport.ts +3 -7
- package/src/server/operations/registry.ts +24 -4
- package/src/server/operations/types.ts +16 -2
- package/src/server/server.ts +23 -10
- package/src/server/tour-control.ts +17 -8
- package/src/server/webpage-node.ts +9 -4
|
@@ -106,7 +106,7 @@ for the authoritative stop, viewport, persistence, reset, group-order, and captu
|
|
|
106
106
|
|
|
107
107
|
## Runtime prerequisites
|
|
108
108
|
|
|
109
|
-
PMX Canvas 0.
|
|
109
|
+
PMX Canvas 0.10.0 requires **Bun >=1.4.4**, including when installed through npm.
|
|
110
110
|
Check `bun --version` and ensure Bun is on the MCP host's PATH, not just your shell's;
|
|
111
111
|
use an absolute executable path if needed. Install/configure only when requested.
|
|
112
112
|
For pinned installation, MCP configuration, managed services, and disposable verification,
|
|
@@ -264,10 +264,15 @@ json-render, a graph, or an HTML primitive is sufficient.
|
|
|
264
264
|
|
|
265
265
|
## Current MCP Composites
|
|
266
266
|
|
|
267
|
-
The live MCP surface is **
|
|
267
|
+
The live MCP surface is **25 tools**: the 18 composites below plus 7 standalones. This table is
|
|
268
268
|
kept in sync with the server's composite registry; `tools/list` on a fresh `pmx-canvas --mcp` is
|
|
269
269
|
always authoritative, independent of the installed release version.
|
|
270
270
|
|
|
271
|
+
Use `canvas_help {}` to list MCP registry operation names, then `canvas_help { op: "node.add" }`
|
|
272
|
+
for that operation's public tool, argument schema and guidance. Help does not start or read a
|
|
273
|
+
canvas. The schema includes routing selectors and required fields; handlers still enforce
|
|
274
|
+
conditional requirements. Use `tools/list` for non-registry tools such as `canvas_screenshot`.
|
|
275
|
+
|
|
271
276
|
| Composite | Actions |
|
|
272
277
|
|-----------|---------|
|
|
273
278
|
| `canvas_import` | `list`, `read`, `submit`, `unavailable` |
|
|
@@ -303,7 +308,7 @@ Important routing:
|
|
|
303
308
|
As of v0.3.0, the 57 legacy single-purpose tools from the v0.2 compatibility window are removed.
|
|
304
309
|
The composites above plus the retained standalones are now the whole MCP surface: `canvas_batch`,
|
|
305
310
|
`canvas_pin_nodes`, `canvas_screenshot`, `canvas_ax_interaction`, `canvas_ingest_activity`, and
|
|
306
|
-
`canvas_invoke_command`. Snapshots are the `canvas_snapshot` composite (actions
|
|
311
|
+
`canvas_invoke_command`, plus `canvas_help`. Snapshots are the `canvas_snapshot` composite (actions
|
|
307
312
|
`save | list | restore | restore-node | delete | gc | diff`); the 6 legacy snapshot standalones were removed in
|
|
308
313
|
v0.4.0 after their deprecated 0.3.x window.
|
|
309
314
|
|
|
@@ -389,9 +394,9 @@ If the documented receipts or warnings are unexpectedly absent, do not guess aro
|
|
|
389
394
|
payload reads: verify that the CLI/MCP host and daemon use the same 0.7 installation and workspace,
|
|
390
395
|
restart the matching daemon after upgrade, and re-check `/health`.
|
|
391
396
|
|
|
392
|
-
**Token hygiene.** For routine state checks use
|
|
393
|
-
`
|
|
394
|
-
actually consuming
|
|
397
|
+
**Token hygiene.** For routine state checks use
|
|
398
|
+
`canvas_ax_state { action: "get", includeContext: false }` to omit the serialized AX context.
|
|
399
|
+
Omitting `includeContext` defaults to `true`; request full context only when actually consuming it.
|
|
395
400
|
|
|
396
401
|
## Context Pins
|
|
397
402
|
|
|
@@ -683,7 +688,7 @@ Single-card and layout reads record content delivery, not incident relations.
|
|
|
683
688
|
Graph cards and complete brief relations carry a captured `linksRevision`: reads
|
|
684
689
|
acknowledge that version, not later changes. Brief delivery is qualified by source board.
|
|
685
690
|
Reads never switch boards or mark full content read; pull needed cards in full separately. MCP remains
|
|
686
|
-
|
|
691
|
+
25 tools and 15 static canvas resources, with two canvas templates (context/graph).
|
|
687
692
|
|
|
688
693
|
Markdown saves bind `[[Board]]`, `[[Board#Card]]`, and `|alias` forms to durable
|
|
689
694
|
IDs; code spans/fences are ignored. Ambiguous/missing targets never guess or
|
|
@@ -431,10 +431,15 @@ When you do set a color (group/graph), use this palette consistently to convey m
|
|
|
431
431
|
## MCP Tools Reference
|
|
432
432
|
|
|
433
433
|
PMX Canvas exposes **18 action-discriminated composites** (the whole recommended surface) plus
|
|
434
|
-
**
|
|
434
|
+
**7 first-class standalones** — 25 tools total. The composites fold the older
|
|
435
435
|
single-purpose tools behind an `action` (and, for `canvas_ax_gate`, a `kind`) discriminator —
|
|
436
436
|
**field names are unchanged**; only the tool name + the `action`/`kind` selector differ.
|
|
437
437
|
|
|
438
|
+
`canvas_help {}` lists MCP registry operation names. `canvas_help { op: "node.add" }`
|
|
439
|
+
returns that operation's public tool, description and JSON argument schema, including
|
|
440
|
+
routing selectors and required fields. It does not start or read a canvas. Handlers still
|
|
441
|
+
enforce conditional requirements. Non-registry tools use `tools/list` instead.
|
|
442
|
+
|
|
438
443
|
> **Legacy single-purpose tools were removed in v0.3.0.** The old names (`canvas_add_node`,
|
|
439
444
|
> `canvas_update_node`, `canvas_request_approval`, `canvas_add_work_item`, …) are no longer
|
|
440
445
|
> registered — use the composites below and fresh MCP `tools/list` for the installed runtime's
|
|
@@ -531,7 +536,7 @@ AX excerpts and annotation anchors, including `canvas://ax-context` and
|
|
|
531
536
|
or brief relation entry instead.
|
|
532
537
|
Single-card and layout reads record content delivery, not incident relations.
|
|
533
538
|
Reads never switch boards or record full content reads. Pull cards in full
|
|
534
|
-
when needed. Counts
|
|
539
|
+
when needed. Counts are 25 tools, 15 static canvas resources; there are now two
|
|
535
540
|
canvas templates, context and graph, besides the skill file template.
|
|
536
541
|
|
|
537
542
|
Markdown save resolves `[[Board]]`, `[[Board#Card]]`, and `|alias` forms into
|
|
@@ -1124,11 +1129,39 @@ Use native `json-render` and `graph` nodes when the output should stay fully ins
|
|
|
1124
1129
|
|
|
1125
1130
|
Spec elements support an `on` map (`on.press`, `on.change`, …) binding events to actions (`{ action, params }`) — built-in actions (`setState`, `pushState`, …) or, when named after an AX interaction type, a capability-gated AX emit. e.g. a Button with `on: { press: { action: 'ax.work.create', params: { title: '…' } } }` lets a human turn a panel control into a tracked work item; the viewer forwards it to the canvas, which validates and submits it server-side (clamped to the node's own id). See **Node AX Interactions** above.
|
|
1126
1131
|
|
|
1132
|
+
## MCP results and cancellation (0.10)
|
|
1133
|
+
|
|
1134
|
+
The 25-tool catalog remains on MCP SDK v1. Each tool advertises `outputSchema`
|
|
1135
|
+
and effect annotations. Help returns `structuredContent.result` with operation
|
|
1136
|
+
metadata (or the name list); it records no content reads. Registry-dispatched tools expose
|
|
1137
|
+
`structuredContent.result = { operation, outcome, data }`, where `outcome` is
|
|
1138
|
+
`success`, `error` or `partial`; `data` matches the existing compact text JSON,
|
|
1139
|
+
or `{message}` for prose. Exceptions use `operation: null`; they do not prove no
|
|
1140
|
+
writes occurred. Interaction, activity-ingestion and screenshot tools instead
|
|
1141
|
+
use direct structured objects matching their schemas. Check `isError` and domain
|
|
1142
|
+
outcomes. A composite's annotations cover all its actions, not just reads.
|
|
1143
|
+
|
|
1144
|
+
Cancellation releases supported AX waits, aborts webpage fetches before late
|
|
1145
|
+
content writes, and stops subsequent batch operations. Completed writes remain;
|
|
1146
|
+
batch partial results retain `results` and `refs`. A created webpage can remain
|
|
1147
|
+
with `fetch.cancelled: true`. External app builds/runtime calls and local WebView
|
|
1148
|
+
captures are not interruptible mid-call. After a disconnect, inspect state before
|
|
1149
|
+
retrying because the response may not arrive. `_meta.progressToken` enables batch
|
|
1150
|
+
and supported AX wait progress notifications, including through the daemon connection. Without
|
|
1151
|
+
a token, calls return their normal result without progress notifications.
|
|
1152
|
+
|
|
1127
1153
|
## MCP Resources
|
|
1128
1154
|
|
|
1129
1155
|
These resources give you read access to canvas intelligence. Read them to understand
|
|
1130
1156
|
what the human has set up and what they're focusing on.
|
|
1131
1157
|
|
|
1158
|
+
As of 0.10, live resource updates require `resources/subscribe` on each connection.
|
|
1159
|
+
Subscribe to the exact URI you read, including context/graph query parameters;
|
|
1160
|
+
`resources/unsubscribe` stops it, and reconnecting requires a new subscription.
|
|
1161
|
+
Notifications invalidate cached data but do not deliver content, record a read, or
|
|
1162
|
+
advance a consumer cursor. Explicit `resources/read` remains available without
|
|
1163
|
+
subscriptions. Static schema and bundled skills do not support subscriptions.
|
|
1164
|
+
|
|
1132
1165
|
| Resource | What it provides |
|
|
1133
1166
|
|----------|-----------------|
|
|
1134
1167
|
| `canvas://context{?budget,consumer,since}` | Budgeted cross-board map brief with optional durable consumer cursor |
|
|
@@ -4,7 +4,7 @@ Use this reference when PMX Canvas is running inside the GitHub Copilot app as a
|
|
|
4
4
|
extension. The adapter is intentionally thin: PMX Canvas remains the state owner, and the extension
|
|
5
5
|
maps Copilot SDK features onto PMX AX primitives.
|
|
6
6
|
|
|
7
|
-
The [standalone agent plugin](
|
|
7
|
+
The [standalone agent plugin](https://github.com/pskoett/pmx-canvas/blob/main/docs/agent-plugin.md) bundles this adapter and PMX's
|
|
8
8
|
MCP launcher. Its native canvas is Copilot-only; other Agent Plugins-compatible clients use
|
|
9
9
|
the portable MCP tools and browser workbench.
|
|
10
10
|
|
|
@@ -125,7 +125,7 @@ imports the Copilot SDK.
|
|
|
125
125
|
- **Await gates** with `canvas_ax_gate { kind, action: "await", id }` (or surface a native modal
|
|
126
126
|
and await the PMX result) so an approval gate actually blocks the agent until the human resolves it.
|
|
127
127
|
|
|
128
|
-
See [`docs/ax-host-adapter-contract.md`](
|
|
128
|
+
See [`docs/ax-host-adapter-contract.md`](https://github.com/pskoett/pmx-canvas/blob/main/docs/ax-host-adapter-contract.md).
|
|
129
129
|
- Keeps all persistent PMX state in `.pmx-canvas/canvas.db`; the extension does not own canvas
|
|
130
130
|
state.
|
|
131
131
|
|
|
@@ -4,7 +4,7 @@ Use this reference when the `pmx-canvas` skill is installed but the `pmx-canvas`
|
|
|
4
4
|
|
|
5
5
|
## Prerequisites and version
|
|
6
6
|
|
|
7
|
-
The examples pin **PMX Canvas 0.
|
|
7
|
+
The examples pin **PMX Canvas 0.10.0**, which requires **Bun >=1.4.4** even when
|
|
8
8
|
installed through npm: the CLI runs through Bun, not Node. Check `bun --version`
|
|
9
9
|
first. If missing or older, follow the [Bun installation instructions](https://bun.sh/docs/installation)
|
|
10
10
|
with the user's authorization. Ensure Bun is on the MCP host's PATH as well as the
|
|
@@ -22,11 +22,11 @@ in `SKILL.md`. Do not pair newer checkout guidance with an older runtime unknowi
|
|
|
22
22
|
## Install from npm
|
|
23
23
|
|
|
24
24
|
```bash
|
|
25
|
-
npm install -g pmx-canvas@0.
|
|
25
|
+
npm install -g pmx-canvas@0.10.0
|
|
26
26
|
pmx-canvas --version
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
Without a global install, use `bunx pmx-canvas@0.
|
|
29
|
+
Without a global install, use `bunx pmx-canvas@0.10.0` in place of `pmx-canvas` in
|
|
30
30
|
the commands below. Do not use unpinned `bunx pmx-canvas` for a version-specific check.
|
|
31
31
|
|
|
32
32
|
## Choose the workspace and target
|
|
@@ -96,7 +96,7 @@ discards empty env values, remove `PMX_CANVAS_URL` from its launch environment i
|
|
|
96
96
|
"mcpServers": {
|
|
97
97
|
"canvas": {
|
|
98
98
|
"command": "bunx",
|
|
99
|
-
"args": ["pmx-canvas@0.
|
|
99
|
+
"args": ["pmx-canvas@0.10.0", "--mcp"],
|
|
100
100
|
"env": {
|
|
101
101
|
"PMX_CANVAS_WORKSPACE_ROOT": "/absolute/path/to/project",
|
|
102
102
|
"PMX_CANVAS_PORT": "14313",
|
|
@@ -140,7 +140,7 @@ Amp executes, not only on the user's laptop. Merge this into that workspace's
|
|
|
140
140
|
"amp.mcpServers": {
|
|
141
141
|
"canvas": {
|
|
142
142
|
"command": "bunx",
|
|
143
|
-
"args": ["pmx-canvas@0.
|
|
143
|
+
"args": ["pmx-canvas@0.10.0", "--mcp"],
|
|
144
144
|
"includeTools": ["canvas_*"],
|
|
145
145
|
"env": {
|
|
146
146
|
"PMX_CANVAS_WORKSPACE_ROOT": "/absolute/path/to/project",
|
|
@@ -174,8 +174,8 @@ The agent must not run this approval itself or move the command to global settin
|
|
|
174
174
|
`--mcp-config` to bypass workspace trust. Approval authorizes execution of this MCP
|
|
175
175
|
command; it is not a new Canvas import consent or permission to mutate arbitrary boards.
|
|
176
176
|
After approval, reload MCP connections (`reload_mcp` in Amp) and require `connected`.
|
|
177
|
-
Discover `canvas` tools with `tool_search`;
|
|
178
|
-
|
|
177
|
+
Discover `canvas` tools with `tool_search`; the full unfiltered 0.10.0 server has 25 tools,
|
|
178
|
+
including `canvas_help`. If still absent, inspect the server status, command/PATH and target instead
|
|
179
179
|
of reinstalling blindly. See [Amp MCP documentation](https://ampcode.com/docs/customize/mcp).
|
|
180
180
|
|
|
181
181
|
### Verify native tools, then steering
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
type BoardSummary,
|
|
8
8
|
} from '../state/boards-store';
|
|
9
9
|
import { useFocusTrap } from './use-focus-trap';
|
|
10
|
+
import { submitWithoutNavigation } from './submit-without-navigation';
|
|
10
11
|
|
|
11
12
|
export function BoardCopyDialog({ board, onClose }: { board: BoardSummary; onClose: () => void }) {
|
|
12
13
|
const dialogRef = useRef<HTMLDivElement>(null);
|
|
@@ -63,7 +64,7 @@ export function BoardCopyDialog({ board, onClose }: { board: BoardSummary; onClo
|
|
|
63
64
|
aria-labelledby="board-copy-title"
|
|
64
65
|
onMouseDown={(e) => e.stopPropagation()}
|
|
65
66
|
>
|
|
66
|
-
<form onSubmit={submit}>
|
|
67
|
+
<form onClick={submitWithoutNavigation} onSubmit={submit}>
|
|
67
68
|
<h2 id="board-copy-title">Create from {board.name}</h2>
|
|
68
69
|
<label class="board-copy-field">
|
|
69
70
|
Board name
|
|
@@ -8,6 +8,7 @@ import { requestJson } from '../state/intent-bridge';
|
|
|
8
8
|
import { cleanLifecycle, DEFAULT_LIFECYCLE, LifecycleFields } from './LifecycleFields';
|
|
9
9
|
import { useFocusTrap } from './use-focus-trap';
|
|
10
10
|
import { ChoiceMenu } from './ChoiceMenu';
|
|
11
|
+
import { submitWithoutNavigation } from './submit-without-navigation';
|
|
11
12
|
|
|
12
13
|
export function KnowledgeDialog({ folder, onClose }: { folder?: string; onClose: () => void }) {
|
|
13
14
|
const enabled = knowledgeLifecycleEnabled.value;
|
|
@@ -158,6 +159,7 @@ export function KnowledgeDialog({ folder, onClose }: { folder?: string; onClose:
|
|
|
158
159
|
<p>Knowledge settings unavailable. Close and retry.</p>
|
|
159
160
|
) : folder ? (
|
|
160
161
|
<form
|
|
162
|
+
onClick={submitWithoutNavigation}
|
|
161
163
|
onSubmit={async (event) => {
|
|
162
164
|
event.preventDefault();
|
|
163
165
|
if (!knowledgeLifecycleEnabled.value || busy) return;
|
|
@@ -286,6 +288,7 @@ export function KnowledgeDialog({ folder, onClose }: { folder?: string; onClose:
|
|
|
286
288
|
<form
|
|
287
289
|
class="category-inspector"
|
|
288
290
|
noValidate
|
|
291
|
+
onClick={submitWithoutNavigation}
|
|
289
292
|
onSubmit={async (event) => {
|
|
290
293
|
event.preventDefault();
|
|
291
294
|
if (!knowledgeLifecycleEnabled.value || busy) return;
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
import { sendSteering } from '../state/session-store';
|
|
21
21
|
import { hasUnsavedMarkdown } from '../nodes/MarkdownNode';
|
|
22
22
|
import { cleanLifecycle, DEFAULT_LIFECYCLE, LifecycleFields } from './LifecycleFields';
|
|
23
|
+
import { submitWithoutNavigation } from './submit-without-navigation';
|
|
23
24
|
|
|
24
25
|
export function LifecyclePanel() {
|
|
25
26
|
const target = lifecycleTarget.value;
|
|
@@ -264,6 +265,7 @@ function LifecycleDialog({ target }: { target: NonNullable<typeof lifecycleTarge
|
|
|
264
265
|
<form
|
|
265
266
|
class="lifecycle-quick-review"
|
|
266
267
|
noValidate
|
|
268
|
+
onClick={submitWithoutNavigation}
|
|
267
269
|
onSubmit={(event) => {
|
|
268
270
|
event.preventDefault();
|
|
269
271
|
void recordReview('retain', true).then((ok) => {
|
|
@@ -345,6 +347,7 @@ function LifecycleDialog({ target }: { target: NonNullable<typeof lifecycleTarge
|
|
|
345
347
|
· category belongs to the folder
|
|
346
348
|
</p>
|
|
347
349
|
<form
|
|
350
|
+
onClick={submitWithoutNavigation}
|
|
348
351
|
onSubmit={(event) => {
|
|
349
352
|
event.preventDefault();
|
|
350
353
|
void mutate('/lifecycle', { lifecycleOverride: override ? cleanLifecycle(policy) : null });
|
|
@@ -446,6 +449,7 @@ function LifecycleDialog({ target }: { target: NonNullable<typeof lifecycleTarge
|
|
|
446
449
|
) : (
|
|
447
450
|
<form
|
|
448
451
|
class="lifecycle-review"
|
|
452
|
+
onClick={submitWithoutNavigation}
|
|
449
453
|
onSubmit={(event) => {
|
|
450
454
|
event.preventDefault();
|
|
451
455
|
void recordReview(outcome, applied);
|
|
@@ -4,6 +4,7 @@ import { boardList, updateBoard } from '../state/boards-store';
|
|
|
4
4
|
import { requestJson } from '../state/intent-bridge';
|
|
5
5
|
import { knowledge, refreshKnowledge } from '../state/knowledge-store';
|
|
6
6
|
import { useFocusTrap } from './use-focus-trap';
|
|
7
|
+
import { submitWithoutNavigation } from './submit-without-navigation';
|
|
7
8
|
|
|
8
9
|
/** Shared templates live in Home; applying one never rewrites connection kinds. */
|
|
9
10
|
export function RelationPresetsDialog({ onClose }: { onClose: () => void }) {
|
|
@@ -152,6 +153,7 @@ export function RelationPresetsDialog({ onClose }: { onClose: () => void }) {
|
|
|
152
153
|
{editing ? (
|
|
153
154
|
<form
|
|
154
155
|
class="relation-vocabulary"
|
|
156
|
+
onClick={submitWithoutNavigation}
|
|
155
157
|
onSubmit={async (event) => {
|
|
156
158
|
event.preventDefault();
|
|
157
159
|
if (busy) return;
|
|
@@ -97,6 +97,8 @@ function RailButton({
|
|
|
97
97
|
}) {
|
|
98
98
|
const [hint, setHint] = useState<{ left: number; top: number } | null>(null);
|
|
99
99
|
const show = (e: Event) => {
|
|
100
|
+
// Closing the menu restores focus before it unmounts; that must not reopen the hint.
|
|
101
|
+
if (menuOpen) return;
|
|
100
102
|
const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
|
|
101
103
|
setHint({ left: rect.right + 10, top: rect.top + rect.height / 2 });
|
|
102
104
|
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { JSX } from 'preact';
|
|
2
|
+
|
|
3
|
+
/** A scripts-only host blocks native submission before onSubmit can run. */
|
|
4
|
+
export function submitWithoutNavigation(event: JSX.TargetedMouseEvent<HTMLFormElement>): void {
|
|
5
|
+
if (event.defaultPrevented || !(event.target instanceof Element)) return;
|
|
6
|
+
const button = event.target.closest('button');
|
|
7
|
+
const form = event.currentTarget;
|
|
8
|
+
if (button?.type !== 'submit' || button.form !== form || button.matches(':disabled')) return;
|
|
9
|
+
// Enter in a form input also activates its submit button. Keep native
|
|
10
|
+
// validation, but dispatch only the JS event, never a navigation/submission.
|
|
11
|
+
event.preventDefault();
|
|
12
|
+
if (!form.noValidate && !button.formNoValidate && !form.reportValidity()) return;
|
|
13
|
+
form.dispatchEvent(new SubmitEvent('submit', { bubbles: true, cancelable: true, submitter: button }));
|
|
14
|
+
}
|
|
@@ -768,31 +768,19 @@ export function animateViewport(
|
|
|
768
768
|
duration = 300,
|
|
769
769
|
options: { recordHistory?: boolean } = {},
|
|
770
770
|
): void {
|
|
771
|
-
if (animationId !== null) cancelAnimationFrame(animationId);
|
|
772
|
-
|
|
773
771
|
const from = { ...viewport.value };
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
if (t < 1) {
|
|
788
|
-
animationId = requestAnimationFrame(tick);
|
|
789
|
-
} else {
|
|
790
|
-
animationId = null;
|
|
791
|
-
commitViewport(target, options);
|
|
792
|
-
}
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
animationId = requestAnimationFrame(tick);
|
|
772
|
+
animateCameraPath(
|
|
773
|
+
(t) => {
|
|
774
|
+
const e = easeOutCubic(t);
|
|
775
|
+
return {
|
|
776
|
+
x: from.x + (target.x - from.x) * e,
|
|
777
|
+
y: from.y + (target.y - from.y) * e,
|
|
778
|
+
scale: from.scale + (target.scale - from.scale) * e,
|
|
779
|
+
};
|
|
780
|
+
},
|
|
781
|
+
duration,
|
|
782
|
+
() => commitViewport(target, options),
|
|
783
|
+
);
|
|
796
784
|
}
|
|
797
785
|
|
|
798
786
|
/**
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { canvasArea, canvasFitInsets } from '../canvas/canvas-area';
|
|
2
2
|
import { showToast } from './attention-bridge';
|
|
3
3
|
import { isHostedWorkbench, workbenchFetch } from './workbench-transport';
|
|
4
|
-
import type { AgentPresenceSnapshot } from '../../shared/agent-presence.js';
|
|
5
4
|
import type { CanvasEdge, CanvasNodeState } from '../types';
|
|
6
5
|
import { addEdge, canvasInteractionEpoch, nodes } from './canvas-store';
|
|
7
6
|
import { activeBoardId, boardsLoaded } from './boards-store';
|
|
@@ -401,11 +400,6 @@ export async function fetchAxSurfaceState(): Promise<unknown> {
|
|
|
401
400
|
return requestJson<unknown>('fetchAxSurfaceState', '/api/canvas/ax/surface-snapshot', null);
|
|
402
401
|
}
|
|
403
402
|
|
|
404
|
-
/** Connect-time agent-presence read; live updates arrive as `agent-presence` SSE frames. */
|
|
405
|
-
export async function fetchAgentPresence(): Promise<Partial<AgentPresenceSnapshot> | null> {
|
|
406
|
-
return requestJson<Partial<AgentPresenceSnapshot> | null>('fetchAgentPresence', '/api/canvas/ax/presence', null);
|
|
407
|
-
}
|
|
408
|
-
|
|
409
403
|
/** Ask the server to open a node's surface in the system browser. */
|
|
410
404
|
export async function openNodeInSystemBrowserRequest(
|
|
411
405
|
nodeId: string,
|
|
@@ -12,9 +12,9 @@ import {
|
|
|
12
12
|
} from '../../shared/agent-presence.js';
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
* Agent presence (rail-chrome-v2 phase 2). Fed by the server's
|
|
16
|
-
*
|
|
17
|
-
*
|
|
15
|
+
* Agent presence (rail-chrome-v2 phase 2). Fed by the server's ordered
|
|
16
|
+
* `agent-presence` frames: a connect snapshot and every change, including TTL
|
|
17
|
+
* expiry, over SSE or polling. This store never runs its own expiry ticker.
|
|
18
18
|
*
|
|
19
19
|
* `sessionActive` is the ONE selector every agent surface reads — the session
|
|
20
20
|
* panel, command bar, presence layer, and top-bar chip all mount on it.
|
|
@@ -14,7 +14,6 @@ import {
|
|
|
14
14
|
axSurfaceState,
|
|
15
15
|
bringToFront,
|
|
16
16
|
cancelViewportAnimation,
|
|
17
|
-
commitViewport,
|
|
18
17
|
canvasTheme,
|
|
19
18
|
connectionStatus,
|
|
20
19
|
contextPinsRevision,
|
|
@@ -50,12 +49,12 @@ import {
|
|
|
50
49
|
import { applyContextStatus, refreshContextStatus } from './context-status-store';
|
|
51
50
|
import { refreshContextChip } from './context-chip-store';
|
|
52
51
|
import {
|
|
53
|
-
fetchAgentPresence,
|
|
54
52
|
fetchAxSurfaceState,
|
|
55
53
|
fetchCanvasState,
|
|
56
54
|
reportClientViewportSize,
|
|
57
55
|
requestJson,
|
|
58
56
|
tabAgentKey,
|
|
57
|
+
updateViewportFromClient,
|
|
59
58
|
} from './intent-bridge';
|
|
60
59
|
import { agentActivity, applyPresenceSnapshot, sessionActive } from './presence-store';
|
|
61
60
|
import { applyHumanSnapshot, humanClientId, startHumanPresence, stopHumanPresence } from './human-store';
|
|
@@ -577,9 +576,8 @@ function handleConnected(data: Record<string, unknown>): void {
|
|
|
577
576
|
// Reconnect marker for holders of server-minted URLs (Finding S).
|
|
578
577
|
workbenchConnectionEpoch.value += 1;
|
|
579
578
|
reloadIfServerUpgraded(data.version, data.bundleStamp);
|
|
580
|
-
//
|
|
581
|
-
//
|
|
582
|
-
void fetchAgentPresence().then(applyPresenceSnapshot);
|
|
579
|
+
// Both transports send an ordered `agent-presence` connect snapshot, then
|
|
580
|
+
// live frames. A separate HTTP read can arrive late and overwrite newer state.
|
|
583
581
|
void refreshContextStatus();
|
|
584
582
|
refreshContextChip();
|
|
585
583
|
// Human collaborators (phase 8): announce this tab and read who else is here.
|
|
@@ -1123,7 +1121,14 @@ function handleCanvasCameraMove(data: Record<string, unknown>): void {
|
|
|
1123
1121
|
try {
|
|
1124
1122
|
// The presentation camera is local; otherwise the end camera becomes the
|
|
1125
1123
|
// board viewport — unrecorded, so a camera move is never an undo step.
|
|
1126
|
-
playStop(
|
|
1124
|
+
playStop(
|
|
1125
|
+
stop.data,
|
|
1126
|
+
presenting.value
|
|
1127
|
+
? undefined
|
|
1128
|
+
: (end) => {
|
|
1129
|
+
void updateViewportFromClient(end, { recordHistory: false });
|
|
1130
|
+
},
|
|
1131
|
+
);
|
|
1127
1132
|
} catch {
|
|
1128
1133
|
// Target not in this tab's layout yet: take the server's resolved camera.
|
|
1129
1134
|
const fallback = cameraSchema.safeParse(data.viewport);
|
|
@@ -4050,8 +4050,14 @@ kbd.toolbar-menu-hint {
|
|
|
4050
4050
|
}
|
|
4051
4051
|
|
|
4052
4052
|
/* ── Markdown reader mode (expanded view) ──────────────────── */
|
|
4053
|
+
.md-expanded-document {
|
|
4054
|
+
display: flex;
|
|
4055
|
+
flex-direction: column;
|
|
4056
|
+
}
|
|
4057
|
+
|
|
4053
4058
|
.md-reader {
|
|
4054
|
-
|
|
4059
|
+
flex: 1;
|
|
4060
|
+
min-height: 0;
|
|
4055
4061
|
overflow-y: auto;
|
|
4056
4062
|
display: flex;
|
|
4057
4063
|
justify-content: center;
|
|
@@ -4205,11 +4211,11 @@ kbd.toolbar-menu-hint {
|
|
|
4205
4211
|
background: var(--c-surface-subtle);
|
|
4206
4212
|
}
|
|
4207
4213
|
|
|
4208
|
-
/* Source
|
|
4214
|
+
/* Source stays below the scroll viewport, never over the document text. */
|
|
4209
4215
|
.md-edit-fab {
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4216
|
+
align-self: flex-end;
|
|
4217
|
+
flex-shrink: 0;
|
|
4218
|
+
margin: 16px;
|
|
4213
4219
|
padding: 8px 18px;
|
|
4214
4220
|
font-size: 13px;
|
|
4215
4221
|
font-weight: 600;
|
|
@@ -5991,21 +5997,10 @@ html.is-node-resizing .ext-app-preview-catcher {
|
|
|
5991
5997
|
/* Its sticky/backdrop-filter context must paint hints above the Source FAB. */
|
|
5992
5998
|
z-index: 2;
|
|
5993
5999
|
}
|
|
5994
|
-
.expanded-overlay-panel:has(.ax-step-dock) .md-expanded-document {
|
|
5995
|
-
display: flex;
|
|
5996
|
-
flex-direction: column;
|
|
5997
|
-
}
|
|
5998
|
-
.expanded-overlay-panel:has(.ax-step-dock) .md-reader {
|
|
5999
|
-
flex: 1;
|
|
6000
|
-
min-height: 0;
|
|
6001
|
-
}
|
|
6002
6000
|
.expanded-overlay-panel:has(.ax-step-dock) .md-edit-fab {
|
|
6003
6001
|
/* Keep Source and the document out of the bottom dock's hint area. */
|
|
6004
|
-
position: static;
|
|
6005
6002
|
order: -1;
|
|
6006
|
-
|
|
6007
|
-
flex-shrink: 0;
|
|
6008
|
-
margin-bottom: 16px;
|
|
6003
|
+
margin: 0 0 16px;
|
|
6009
6004
|
}
|
|
6010
6005
|
.ax-step-dock:has(.toolbar-tooltip-anchor:hover) .toolbar-tooltip-anchor:not(:hover) > :focus-visible + .toolbar-tooltip {
|
|
6011
6006
|
/* Pointer exploration temporarily takes precedence without moving keyboard focus. */
|
package/src/mcp/canvas-access.ts
CHANGED
|
@@ -2,7 +2,6 @@ import { realpathSync } from 'node:fs';
|
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
3
|
import { basename, dirname, resolve } from 'node:path';
|
|
4
4
|
import { createCanvas, canvasState, type CanvasLayout, type CanvasNodeState, type PmxCanvas } from '../server/index.js';
|
|
5
|
-
import type { PmxAxSource } from '../server/ax-state.js';
|
|
6
5
|
import type { ContextReadInput } from '../server/context-reads.js';
|
|
7
6
|
import { HttpOperationInvoker, LocalOperationInvoker, type OperationInvoker } from '../server/operations/index.js';
|
|
8
7
|
|
|
@@ -14,13 +13,9 @@ import { HttpOperationInvoker, LocalOperationInvoker, type OperationInvoker } fr
|
|
|
14
13
|
// not CanvasAccess. The public SDK PmxCanvas methods are unchanged.
|
|
15
14
|
type AxStateResult = ReturnType<PmxCanvas['getAxState']>;
|
|
16
15
|
type AxContextResult = ReturnType<PmxCanvas['getAxContext']>;
|
|
17
|
-
type SubmitAxInteractionInput = Parameters<PmxCanvas['submitAxInteraction']>[0];
|
|
18
|
-
type SubmitAxInteractionResult = ReturnType<PmxCanvas['submitAxInteraction']>;
|
|
19
16
|
type GetPendingSteeringResult = ReturnType<PmxCanvas['getPendingSteering']>;
|
|
20
17
|
type ListElicitationsResult = ReturnType<PmxCanvas['listElicitations']>;
|
|
21
18
|
type ListModeRequestsResult = ReturnType<PmxCanvas['listModeRequests']>;
|
|
22
|
-
type IngestActivityInput = Parameters<PmxCanvas['ingestActivity']>[0];
|
|
23
|
-
type IngestActivityResult = ReturnType<PmxCanvas['ingestActivity']>;
|
|
24
19
|
type GetPolicyResult = ReturnType<PmxCanvas['getPolicy']>;
|
|
25
20
|
type GetAxTimelineQuery = Parameters<PmxCanvas['getAxTimeline']>[0];
|
|
26
21
|
type GetAxTimelineResult = ReturnType<PmxCanvas['getAxTimeline']>;
|
|
@@ -56,14 +51,9 @@ export interface CanvasAccess {
|
|
|
56
51
|
listWorkItems(): Promise<ListWorkItemsResult>;
|
|
57
52
|
listApprovalGates(): Promise<ListApprovalGatesResult>;
|
|
58
53
|
listReviewAnnotations(): Promise<ListReviewAnnotationsResult>;
|
|
59
|
-
submitAxInteraction(
|
|
60
|
-
input: SubmitAxInteractionInput,
|
|
61
|
-
options?: { source?: PmxAxSource },
|
|
62
|
-
): Promise<SubmitAxInteractionResult>;
|
|
63
54
|
getPendingSteering(options?: { consumer?: string; limit?: number }): Promise<GetPendingSteeringResult>;
|
|
64
55
|
listElicitations(): Promise<ListElicitationsResult>;
|
|
65
56
|
listModeRequests(): Promise<ListModeRequestsResult>;
|
|
66
|
-
ingestActivity(input: IngestActivityInput, options?: { source?: PmxAxSource }): Promise<IngestActivityResult>;
|
|
67
57
|
getPolicy(): Promise<GetPolicyResult>;
|
|
68
58
|
getHistory(): Promise<HistoryResult>;
|
|
69
59
|
getPinnedNodeIds(board?: string): Promise<string[]>;
|
|
@@ -75,7 +65,7 @@ export interface CanvasAccess {
|
|
|
75
65
|
// canvas_screenshot (still hand-written — binary payload) is the only webview
|
|
76
66
|
// tool left on CanvasAccess; it needs the status + screenshot accessors.
|
|
77
67
|
getAutomationWebViewStatus(): Promise<AutomationWebViewStatus>;
|
|
78
|
-
screenshotAutomationWebView(options?: AutomationScreenshotOptions): Promise<Uint8Array>;
|
|
68
|
+
screenshotAutomationWebView(options?: AutomationScreenshotOptions, signal?: AbortSignal): Promise<Uint8Array>;
|
|
79
69
|
}
|
|
80
70
|
|
|
81
71
|
class LocalCanvasAccess implements CanvasAccess {
|
|
@@ -123,13 +113,6 @@ class LocalCanvasAccess implements CanvasAccess {
|
|
|
123
113
|
return this.canvas.getAxTimeline(query);
|
|
124
114
|
}
|
|
125
115
|
|
|
126
|
-
async submitAxInteraction(
|
|
127
|
-
input: SubmitAxInteractionInput,
|
|
128
|
-
options?: { source?: PmxAxSource },
|
|
129
|
-
): Promise<SubmitAxInteractionResult> {
|
|
130
|
-
return this.canvas.submitAxInteraction(input, { source: options?.source ?? 'mcp' });
|
|
131
|
-
}
|
|
132
|
-
|
|
133
116
|
async getPendingSteering(options?: { consumer?: string; limit?: number }): Promise<GetPendingSteeringResult> {
|
|
134
117
|
return this.canvas.getPendingSteering(options);
|
|
135
118
|
}
|
|
@@ -142,10 +125,6 @@ class LocalCanvasAccess implements CanvasAccess {
|
|
|
142
125
|
return this.canvas.listModeRequests();
|
|
143
126
|
}
|
|
144
127
|
|
|
145
|
-
async ingestActivity(input: IngestActivityInput, options?: { source?: PmxAxSource }): Promise<IngestActivityResult> {
|
|
146
|
-
return this.canvas.ingestActivity(input, { source: options?.source ?? 'mcp' });
|
|
147
|
-
}
|
|
148
|
-
|
|
149
128
|
async getPolicy(): Promise<GetPolicyResult> {
|
|
150
129
|
return this.canvas.getPolicy();
|
|
151
130
|
}
|
|
@@ -299,23 +278,6 @@ class RemoteCanvasAccess implements CanvasAccess {
|
|
|
299
278
|
return await this.requestJson<GetAxTimelineResult>('GET', `/api/canvas/ax/timeline${qs}`);
|
|
300
279
|
}
|
|
301
280
|
|
|
302
|
-
async submitAxInteraction(
|
|
303
|
-
input: SubmitAxInteractionInput,
|
|
304
|
-
options?: { source?: PmxAxSource },
|
|
305
|
-
): Promise<SubmitAxInteractionResult> {
|
|
306
|
-
// The interaction endpoint returns its structured outcome (ok/code/error) in
|
|
307
|
-
// the body for both accepted and rejected interactions, so read the body
|
|
308
|
-
// regardless of HTTP status rather than throwing on a denial.
|
|
309
|
-
const response = await fetch(`${this.remoteBaseUrl}/api/canvas/ax/interaction`, {
|
|
310
|
-
method: 'POST',
|
|
311
|
-
headers: { 'Content-Type': 'application/json' },
|
|
312
|
-
body: JSON.stringify({ ...input, source: options?.source ?? 'mcp' }),
|
|
313
|
-
});
|
|
314
|
-
const body = await response.json().catch(() => null);
|
|
315
|
-
if (body && typeof body === 'object') return body as SubmitAxInteractionResult;
|
|
316
|
-
throw new Error(`Remote canvas interaction failed with HTTP ${response.status}`);
|
|
317
|
-
}
|
|
318
|
-
|
|
319
281
|
async getPendingSteering(options?: { consumer?: string; limit?: number }): Promise<GetPendingSteeringResult> {
|
|
320
282
|
const params = new URLSearchParams();
|
|
321
283
|
if (options?.consumer) params.set('consumer', options.consumer);
|
|
@@ -338,13 +300,6 @@ class RemoteCanvasAccess implements CanvasAccess {
|
|
|
338
300
|
return r.modeRequests ?? [];
|
|
339
301
|
}
|
|
340
302
|
|
|
341
|
-
async ingestActivity(input: IngestActivityInput, options?: { source?: PmxAxSource }): Promise<IngestActivityResult> {
|
|
342
|
-
return await this.requestJson<IngestActivityResult>('POST', '/api/canvas/ax/activity', {
|
|
343
|
-
...input,
|
|
344
|
-
source: options?.source ?? 'mcp',
|
|
345
|
-
});
|
|
346
|
-
}
|
|
347
|
-
|
|
348
303
|
async getPolicy(): Promise<GetPolicyResult> {
|
|
349
304
|
const r = await this.requestJson<{ policy?: GetPolicyResult }>('GET', '/api/canvas/ax/policy');
|
|
350
305
|
if (!r.policy) throw new Error('Remote canvas did not return a policy.');
|
|
@@ -404,11 +359,15 @@ class RemoteCanvasAccess implements CanvasAccess {
|
|
|
404
359
|
return await this.requestJson<AutomationWebViewStatus>('GET', '/api/workbench/webview');
|
|
405
360
|
}
|
|
406
361
|
|
|
407
|
-
async screenshotAutomationWebView(
|
|
362
|
+
async screenshotAutomationWebView(
|
|
363
|
+
options: AutomationScreenshotOptions = {},
|
|
364
|
+
signal?: AbortSignal,
|
|
365
|
+
): Promise<Uint8Array> {
|
|
408
366
|
const response = await fetch(`${this.remoteBaseUrl}/api/workbench/webview/screenshot`, {
|
|
409
367
|
method: 'POST',
|
|
410
368
|
headers: { 'Content-Type': 'application/json' },
|
|
411
369
|
body: JSON.stringify(options),
|
|
370
|
+
signal,
|
|
412
371
|
});
|
|
413
372
|
if (!response.ok) {
|
|
414
373
|
const text = await response.text();
|