pmx-canvas 0.7.1 → 0.7.2
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 +79 -61
- package/CHANGELOG.md +16 -1
- package/Readme.md +31 -6
- package/dist/canvas/global.css +21 -5
- package/dist/canvas/index.js +73 -73
- package/dist/types/client/canvas/ExternalWriters.d.ts +3 -3
- package/dist/types/client/icons.d.ts +15 -0
- package/dist/types/client/nodes/ExtAppFrame.d.ts +1 -1
- package/dist/types/client/nodes/FileNode.d.ts +0 -12
- package/dist/types/shared/delimited-text.d.ts +14 -0
- package/docs/cli.md +9 -2
- package/docs/http-api.md +12 -8
- package/docs/mcp.md +8 -1
- package/docs/sdk.md +2 -2
- package/package.json +1 -1
- package/skills/pmx-canvas/SKILL.md +19 -6
- package/skills/pmx-canvas/evals/evals.json +43 -2
- package/skills/pmx-canvas/references/full-reference.md +6 -1
- package/skills/pmx-canvas/references/github-copilot-app-adapter.md +17 -3
- package/skills/pmx-canvas/references/installing-pmx-canvas.md +4 -4
- package/src/cli/commands/backup.ts +15 -4
- package/src/cli/commands/boards.ts +16 -3
- package/src/client/canvas/AttentionHistory.tsx +2 -1
- package/src/client/canvas/BoardSwitcher.tsx +3 -3
- package/src/client/canvas/CanvasNode.tsx +17 -8
- package/src/client/canvas/CommandBar.tsx +2 -2
- package/src/client/canvas/ContextPinBar.tsx +3 -1
- package/src/client/canvas/ExpandedNodeOverlay.tsx +3 -3
- package/src/client/canvas/ExternalWriters.tsx +4 -3
- package/src/client/canvas/FeedbackDialog.tsx +2 -1
- package/src/client/canvas/IntentLayer.tsx +2 -2
- package/src/client/canvas/SelectionBar.tsx +2 -2
- package/src/client/canvas/SessionPanel.tsx +6 -4
- package/src/client/canvas/SessionReceipt.tsx +2 -1
- package/src/client/canvas/SnapshotPanel.tsx +3 -2
- package/src/client/canvas/ToolRail.tsx +2 -1
- package/src/client/canvas/TopBar.tsx +2 -2
- package/src/client/icons.tsx +118 -1
- package/src/client/nodes/BoardNode.tsx +2 -1
- package/src/client/nodes/ExtAppFrame.tsx +3 -1
- package/src/client/nodes/FileNode.tsx +4 -78
- package/src/client/nodes/ImageNode.tsx +2 -1
- package/src/client/nodes/MarkdownNode.tsx +2 -1
- package/src/client/nodes/PromptNode.tsx +3 -1
- package/src/client/theme/global.css +21 -5
- package/src/mcp/server.ts +2 -2
- package/src/server/agent-presence.ts +17 -11
- package/src/server/board-export.ts +97 -32
- package/src/server/index.ts +9 -7
- package/src/server/operations/composites.ts +3 -2
- package/src/server/operations/ops/ax-presence.ts +7 -3
- package/src/server/operations/ops/boards.ts +12 -5
- package/src/server/operations/registry.ts +4 -13
- package/src/server/server.ts +10 -3
- package/src/shared/delimited-text.ts +75 -0
|
@@ -251,7 +251,19 @@ async function resolvePmxServer(ctxOrInput, options = {}) {
|
|
|
251
251
|
|
|
252
252
|
async function resolvePanelTarget(ctx, options = {}) {
|
|
253
253
|
const selected = panelTargets.get(ctx?.instanceId);
|
|
254
|
-
if (!selected)
|
|
254
|
+
if (!selected) {
|
|
255
|
+
const panel = panelServers.get(ctx?.instanceId);
|
|
256
|
+
if (!panel) return await resolvePmxServer(ctx, options);
|
|
257
|
+
// A fallback panel still owns its original target. Action inputs may
|
|
258
|
+
// contain neither that URL nor its workspace, so never rediscover from them.
|
|
259
|
+
const resolved = await resolvePmxServer({
|
|
260
|
+
input: { ...panel.entry.input, ...(options.autoStart === true ? { autoStart: true } : {}) },
|
|
261
|
+
session: { workingDirectory: panel.entry.workspaceRoot },
|
|
262
|
+
}, options);
|
|
263
|
+
panel.entry.pmx = resolved;
|
|
264
|
+
rememberPanelTarget(ctx, resolved);
|
|
265
|
+
return resolved;
|
|
266
|
+
}
|
|
255
267
|
|
|
256
268
|
const probe = await probeServer(selected.baseUrl, selected.workspaceRoot, {
|
|
257
269
|
timeoutMs: options.timeoutMs ?? 2_000,
|
|
@@ -280,15 +292,9 @@ function escapeHtml(value) {
|
|
|
280
292
|
.replaceAll('"', """);
|
|
281
293
|
}
|
|
282
294
|
|
|
283
|
-
function renderShell(
|
|
284
|
-
const status = entry.pmx?.ok ? "Connected" : "Not connected";
|
|
285
|
-
// theme=light is a client-local session default matching the Copilot app's
|
|
286
|
-
// light chrome — it never changes the server-global theme other clients
|
|
287
|
-
// see, and the in-panel theme picker still overrides it.
|
|
288
|
-
const frameSrc = entry.pmx?.baseUrl ? `${entry.pmx.baseUrl}/workbench?theme=light` : "about:blank";
|
|
289
|
-
const error = entry.pmx?.error ? `<p class="error">${escapeHtml(entry.pmx.error)}</p>` : "";
|
|
295
|
+
function renderShell(entry) {
|
|
290
296
|
return `<!doctype html>
|
|
291
|
-
<html>
|
|
297
|
+
<html lang="en">
|
|
292
298
|
<head>
|
|
293
299
|
<meta charset="utf-8" />
|
|
294
300
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
@@ -301,70 +307,83 @@ function renderShell(instanceId, entry) {
|
|
|
301
307
|
font-family: var(--font-sans, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif);
|
|
302
308
|
}
|
|
303
309
|
.shell {
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
310
|
+
max-width: 560px;
|
|
311
|
+
margin: 12vh auto;
|
|
312
|
+
padding: 24px;
|
|
307
313
|
}
|
|
308
|
-
|
|
314
|
+
.actions {
|
|
309
315
|
display: flex;
|
|
310
|
-
|
|
316
|
+
flex-wrap: wrap;
|
|
311
317
|
gap: 12px;
|
|
312
|
-
|
|
313
|
-
border-bottom: 1px solid var(--border-color-default, #30363d);
|
|
314
|
-
background: var(--background-color-muted, rgba(22, 27, 34, 0.95));
|
|
318
|
+
margin-top: 24px;
|
|
315
319
|
}
|
|
316
|
-
|
|
317
|
-
.
|
|
320
|
+
p { line-height: 1.6; overflow-wrap: anywhere; }
|
|
321
|
+
.workspace {
|
|
318
322
|
color: var(--text-color-muted, #8b949e);
|
|
319
|
-
font-size:
|
|
323
|
+
font-size: 13px;
|
|
320
324
|
}
|
|
321
325
|
.error {
|
|
322
|
-
margin: 0;
|
|
323
326
|
color: var(--true-color-red, #ff7b72);
|
|
324
|
-
|
|
327
|
+
min-height: 24px;
|
|
325
328
|
}
|
|
326
329
|
button {
|
|
327
|
-
border: 1px solid var(--border-color-default, #
|
|
330
|
+
border: 1px solid var(--border-color-default, #6e7681);
|
|
328
331
|
border-radius: 8px;
|
|
329
|
-
background: var(--background-color-
|
|
332
|
+
background: var(--background-color-muted, #21262d);
|
|
330
333
|
color: inherit;
|
|
334
|
+
font: inherit;
|
|
331
335
|
cursor: pointer;
|
|
332
|
-
|
|
336
|
+
min-height: 40px;
|
|
337
|
+
padding: 8px 16px;
|
|
333
338
|
}
|
|
334
|
-
button:hover {
|
|
339
|
+
button:hover, button:focus-visible {
|
|
335
340
|
border-color: var(--color-focus-outline, #2f81f7);
|
|
336
341
|
}
|
|
337
|
-
|
|
338
|
-
width: 100%;
|
|
339
|
-
height: 100%;
|
|
340
|
-
border: 0;
|
|
341
|
-
background: #0d1117;
|
|
342
|
-
}
|
|
343
|
-
.spacer { flex: 1; }
|
|
342
|
+
button:disabled { opacity: 0.6; cursor: wait; }
|
|
344
343
|
</style>
|
|
345
344
|
</head>
|
|
346
345
|
<body>
|
|
347
|
-
<
|
|
348
|
-
<
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
<button type="button" onclick="
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
346
|
+
<main class="shell">
|
|
347
|
+
<h1>Connect to PMX Canvas</h1>
|
|
348
|
+
<p>The workbench is not connected yet. Home and recent boards will appear when the server is available.</p>
|
|
349
|
+
<p class="workspace">Workspace: ${escapeHtml(entry.workspaceRoot)}</p>
|
|
350
|
+
<p class="workspace">Server: ${escapeHtml(candidateBaseUrls(entry.input)[0].baseUrl)}</p>
|
|
351
|
+
<p id="connection-status" class="error" role="status">${escapeHtml(entry.pmx?.error ?? "Checking connection…")}</p>
|
|
352
|
+
<div class="actions">
|
|
353
|
+
<button type="button" onclick="connect(false)">Check connection</button>
|
|
354
|
+
<button type="button" onclick="connect(true)">Start server</button>
|
|
355
|
+
</div>
|
|
356
|
+
<p class="workspace">This page checks for the server automatically. Start server launches a local server for this workspace.</p>
|
|
357
|
+
</main>
|
|
358
358
|
<script>
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
const
|
|
366
|
-
|
|
359
|
+
let busy = false;
|
|
360
|
+
let timer;
|
|
361
|
+
async function connect(start) {
|
|
362
|
+
if (busy) return;
|
|
363
|
+
busy = true;
|
|
364
|
+
clearTimeout(timer);
|
|
365
|
+
const buttons = document.querySelectorAll('button');
|
|
366
|
+
buttons.forEach(button => button.disabled = true);
|
|
367
|
+
const status = document.getElementById('connection-status');
|
|
368
|
+
status.textContent = start ? 'Starting server…' : 'Checking connection…';
|
|
369
|
+
try {
|
|
370
|
+
const response = await fetch(start ? '/start' : '/status', { method: start ? 'POST' : 'GET', cache: 'no-store' });
|
|
371
|
+
const target = await response.json();
|
|
372
|
+
if (response.ok && target.ok && target.baseUrl) {
|
|
373
|
+
// Load the real workbench at its own origin, including its token,
|
|
374
|
+
// Home, boards, and relative API paths. The shell is not a proxy.
|
|
375
|
+
window.location.replace(target.baseUrl + '/workbench?theme=light');
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
status.textContent = target.error || 'No matching PMX Canvas server is running.';
|
|
379
|
+
} catch (error) {
|
|
380
|
+
status.textContent = 'Connection check failed. Reopen the canvas if the adapter has stopped.';
|
|
381
|
+
}
|
|
382
|
+
busy = false;
|
|
383
|
+
buttons.forEach(button => button.disabled = false);
|
|
384
|
+
timer = setTimeout(() => connect(false), 2000);
|
|
367
385
|
}
|
|
386
|
+
connect(false);
|
|
368
387
|
</script>
|
|
369
388
|
</body>
|
|
370
389
|
</html>`;
|
|
@@ -378,7 +397,7 @@ async function readRequestJson(req) {
|
|
|
378
397
|
}
|
|
379
398
|
|
|
380
399
|
async function verifiedEntryTarget(entry) {
|
|
381
|
-
if (!entry.pmx?.baseUrl) return { ok: false, baseUrl: null, error: "PMX Canvas server is unavailable." };
|
|
400
|
+
if (!entry.pmx?.ok || !entry.pmx.baseUrl) return { ok: false, baseUrl: null, error: "PMX Canvas server is unavailable." };
|
|
382
401
|
const probe = await probeServer(entry.pmx.baseUrl, entry.pmx.health.workspace, {
|
|
383
402
|
timeoutMs: 2_000,
|
|
384
403
|
});
|
|
@@ -401,18 +420,17 @@ async function startPanelServer(instanceId, ctx, pmx) {
|
|
|
401
420
|
try {
|
|
402
421
|
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
403
422
|
if (req.method === "GET" && url.pathname === "/") {
|
|
404
|
-
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
405
|
-
res.end(renderShell(
|
|
423
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-store" });
|
|
424
|
+
res.end(renderShell(entry));
|
|
406
425
|
return;
|
|
407
426
|
}
|
|
408
427
|
if (req.method === "POST" && url.pathname === "/start") {
|
|
409
|
-
|
|
410
|
-
jsonResponse(res, 200,
|
|
428
|
+
const latest = await resolvePanelTarget({ instanceId }, { autoStart: true });
|
|
429
|
+
jsonResponse(res, 200, latest);
|
|
411
430
|
return;
|
|
412
431
|
}
|
|
413
432
|
if (req.method === "GET" && url.pathname === "/status") {
|
|
414
|
-
const latest = await
|
|
415
|
-
entry.pmx = latest.ok ? latest : entry.pmx;
|
|
433
|
+
const latest = await resolvePanelTarget({ instanceId }, { autoStart: false });
|
|
416
434
|
jsonResponse(res, 200, latest);
|
|
417
435
|
return;
|
|
418
436
|
}
|
|
@@ -545,7 +563,7 @@ async function recordInjectedContext(baseUrl, context, injected) {
|
|
|
545
563
|
async function getAxStatus(ctx) {
|
|
546
564
|
const resolved = await resolvePanelTarget(ctx, { autoStart: false });
|
|
547
565
|
if (!resolved.ok || !resolved.baseUrl) return { ok: false, server: resolved };
|
|
548
|
-
const state = await fetchJson(resolved.baseUrl, "/api/canvas/ax", { timeoutMs: 2_000 });
|
|
566
|
+
const state = await fetchJson(resolved.baseUrl, "/api/canvas/ax?includeContext=false", { timeoutMs: 2_000 });
|
|
549
567
|
return { ok: true, server: resolved, ax: state };
|
|
550
568
|
}
|
|
551
569
|
|
|
@@ -718,7 +736,7 @@ const pmxCanvas = createCanvas({
|
|
|
718
736
|
actions: [
|
|
719
737
|
{
|
|
720
738
|
name: "status",
|
|
721
|
-
description: "Return PMX Canvas server and AX state
|
|
739
|
+
description: "Return PMX Canvas server and AX state without serialized node context. Use get_ax_context for full pinned/focused context.",
|
|
722
740
|
handler: async (ctx) => await getAxStatus(ctx),
|
|
723
741
|
},
|
|
724
742
|
{
|
package/CHANGELOG.md
CHANGED
|
@@ -3,7 +3,22 @@
|
|
|
3
3
|
All notable changes to `pmx-canvas` are documented here. This project follows
|
|
4
4
|
[Semantic Versioning](https://semver.org/).
|
|
5
5
|
|
|
6
|
-
## [
|
|
6
|
+
## [0.7.2] - 2026-09-30
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- Agents can explicitly open a board before writing through MCP, CLI, HTTP, and SDK; board deletion remains human-only.
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Main-agent transport attaches reuse the attached host session, avoiding duplicate Copilot agents while keeping identified subagents separate.
|
|
15
|
+
- HTML exports have clearer Markdown typography and tables, readable CSV/TSV previews, complete status and context text, and scrollable cards with keyboard-accessible reading views.
|
|
16
|
+
- Original document attachments now avoid existing cards and each other without moving existing content.
|
|
17
|
+
- Backup and restore commands reject unknown flags instead of silently ignoring them.
|
|
18
|
+
- Amp setup instructions distinguish native MCP configuration from the browser portal.
|
|
19
|
+
- Copilot's fallback panel reconnects to the full workbench and keeps adapter actions on the recovered server.
|
|
20
|
+
- Copilot status omits serialized node context; full context remains available through `get_ax_context`.
|
|
21
|
+
- Attachment, board-menu, card, and panel controls use consistently sized SVG icons instead of small font symbols.
|
|
7
22
|
|
|
8
23
|
## [0.7.1] - 2026-09-30
|
|
9
24
|
|
package/Readme.md
CHANGED
|
@@ -44,9 +44,11 @@ communication — proximity means relatedness, pinning means *focus here*.
|
|
|
44
44
|
The database holds many named boards. **Home is the library view, not a board**:
|
|
45
45
|
it lists recent boards and nested category paths (up to eight 60-character
|
|
46
46
|
segments). Empty folders are not independent objects; they exist only while a
|
|
47
|
-
board is filed beneath them.
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
board is filed beneath them. Agents can explicitly open/switch boards; deletion
|
|
48
|
+
remains human-only. Creating a board or a copy from reusable cards leaves it
|
|
49
|
+
inactive. Agents open its returned ID before writing; this also switches the
|
|
50
|
+
shared workbench the human sees. Agents may list, read, rename, categorize, and
|
|
51
|
+
search boards without switching. Home
|
|
50
52
|
provides a copy-preview dialog; an open board's command palette offers a
|
|
51
53
|
board-link picker. Neither depends on native browser prompts.
|
|
52
54
|
|
|
@@ -387,9 +389,32 @@ the full workflow and live-test checklist.
|
|
|
387
389
|
|
|
388
390
|
### Use inside AMPCode orbs and the Amp portal
|
|
389
391
|
|
|
390
|
-
|
|
392
|
+
The portal and the agent's MCP connection are separate. For native Canvas tools,
|
|
393
|
+
merge this into the workspace's `.amp/settings.json` (not `.mcp.json`):
|
|
394
|
+
|
|
395
|
+
```json
|
|
396
|
+
{
|
|
397
|
+
"amp.mcpServers": {
|
|
398
|
+
"canvas": {
|
|
399
|
+
"command": "bunx",
|
|
400
|
+
"args": ["pmx-canvas@0.7.2", "--mcp"]
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
In the PMX product checkout, use `"command": "bun"` and
|
|
407
|
+
`"args": ["run", "src/mcp/server.ts"]` to test the checked-out source instead.
|
|
408
|
+
Reload MCP connections and complete any host trust prompt. Confirm Canvas tools
|
|
409
|
+
are discoverable and the target `/health.workspace` and version match before
|
|
410
|
+
mutating a board. If discovery is empty, inspect Amp's MCP connection status;
|
|
411
|
+
an open portal or a successful standalone MCP handshake does not prove a native
|
|
412
|
+
connection, skill activation, or steering delivery. See
|
|
413
|
+
[Amp MCP configuration](https://ampcode.com/docs/customize/mcp).
|
|
414
|
+
|
|
415
|
+
For the visible workbench, run `pmx-canvas` as an orb service (the orb sets
|
|
391
416
|
`AMP_ORB` in the environment automatically) and open the workbench through the
|
|
392
|
-
thread's portal URL.
|
|
417
|
+
thread's portal URL. A consumer-workspace service recipe:
|
|
393
418
|
|
|
394
419
|
```yaml
|
|
395
420
|
# .amp/services.yaml — supervised portal service
|
|
@@ -403,7 +428,7 @@ services:
|
|
|
403
428
|
```bash
|
|
404
429
|
# .agents/setup — install the CLI (pin the exact version: a fresh orb running
|
|
405
430
|
# @latest can silently pick up a newer release than the one you validated)
|
|
406
|
-
npm install -g pmx-canvas@0.7.
|
|
431
|
+
npm install -g pmx-canvas@0.7.2
|
|
407
432
|
```
|
|
408
433
|
|
|
409
434
|
The server binds the portal-assigned `$PORT` automatically (gated on the
|
package/dist/canvas/global.css
CHANGED
|
@@ -760,6 +760,11 @@ body,
|
|
|
760
760
|
border-radius: calc(3px * var(--node-chrome-scale, 1));
|
|
761
761
|
}
|
|
762
762
|
|
|
763
|
+
.canvas-node .node-controls button svg {
|
|
764
|
+
width: 1em;
|
|
765
|
+
height: 1em;
|
|
766
|
+
}
|
|
767
|
+
|
|
763
768
|
.canvas-node .node-controls button:hover {
|
|
764
769
|
background: var(--c-surface-hover);
|
|
765
770
|
color: var(--c-text);
|
|
@@ -1466,6 +1471,10 @@ body,
|
|
|
1466
1471
|
animation: presence-pulse 1.4s ease-in-out infinite;
|
|
1467
1472
|
}
|
|
1468
1473
|
.session-collapse {
|
|
1474
|
+
display: inline-flex;
|
|
1475
|
+
align-items: center;
|
|
1476
|
+
justify-content: center;
|
|
1477
|
+
padding: 0;
|
|
1469
1478
|
width: 26px;
|
|
1470
1479
|
height: 26px;
|
|
1471
1480
|
border: 1px solid transparent;
|
|
@@ -1682,8 +1691,9 @@ body,
|
|
|
1682
1691
|
}
|
|
1683
1692
|
}
|
|
1684
1693
|
.session-section-chevron {
|
|
1694
|
+
display: inline-flex;
|
|
1695
|
+
flex-shrink: 0;
|
|
1685
1696
|
color: var(--c-muted);
|
|
1686
|
-
font-size: 13px;
|
|
1687
1697
|
transition: transform 0.15s ease;
|
|
1688
1698
|
}
|
|
1689
1699
|
.session-section-chevron.is-open {
|
|
@@ -1966,8 +1976,13 @@ body,
|
|
|
1966
1976
|
white-space: nowrap;
|
|
1967
1977
|
}
|
|
1968
1978
|
.command-bar-chip-unpin {
|
|
1969
|
-
|
|
1970
|
-
|
|
1979
|
+
display: inline-flex;
|
|
1980
|
+
align-items: center;
|
|
1981
|
+
justify-content: center;
|
|
1982
|
+
flex-shrink: 0;
|
|
1983
|
+
padding: 0;
|
|
1984
|
+
width: 20px;
|
|
1985
|
+
height: 20px;
|
|
1971
1986
|
border: none;
|
|
1972
1987
|
border-radius: 50%;
|
|
1973
1988
|
background: none;
|
|
@@ -6896,7 +6911,7 @@ html.is-node-dragging .intent-line-layer {
|
|
|
6896
6911
|
align-items: center;
|
|
6897
6912
|
gap: 6px;
|
|
6898
6913
|
max-width: 280px;
|
|
6899
|
-
padding:
|
|
6914
|
+
padding: 6px 8px;
|
|
6900
6915
|
margin-left: -8px;
|
|
6901
6916
|
background: none;
|
|
6902
6917
|
border: 1px solid transparent;
|
|
@@ -6914,7 +6929,8 @@ html.is-node-dragging .intent-line-layer {
|
|
|
6914
6929
|
white-space: nowrap;
|
|
6915
6930
|
}
|
|
6916
6931
|
.board-switcher-caret {
|
|
6917
|
-
|
|
6932
|
+
display: inline-flex;
|
|
6933
|
+
flex-shrink: 0;
|
|
6918
6934
|
color: var(--c-text-soft);
|
|
6919
6935
|
}
|
|
6920
6936
|
.board-switcher-menu {
|