pmx-canvas 0.6.5 → 0.7.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 +139 -14
- package/CHANGELOG.md +70 -0
- package/Readme.md +32 -6
- package/dist/canvas/global.css +510 -31
- package/dist/canvas/index.js +92 -77
- package/dist/types/cli/agent.d.ts +3 -0
- package/dist/types/cli/commands/backup.d.ts +1 -0
- package/dist/types/cli/commands/boards.d.ts +1 -0
- package/dist/types/cli/commands/export.d.ts +1 -0
- package/dist/types/cli/daemon.d.ts +5 -5
- package/dist/types/cli/shared.d.ts +3 -1
- package/dist/types/client/canvas/BoardCopyDialog.d.ts +5 -0
- package/dist/types/client/canvas/BoardFolderDialog.d.ts +7 -0
- package/dist/types/client/canvas/BoardLinkDialog.d.ts +3 -0
- package/dist/types/client/canvas/BoardSwitcher.d.ts +8 -0
- package/dist/types/client/canvas/ExportDialog.d.ts +7 -0
- package/dist/types/client/canvas/HomeView.d.ts +9 -0
- package/dist/types/client/nodes/AttachmentNode.d.ts +6 -0
- package/dist/types/client/nodes/BoardNode.d.ts +5 -0
- package/dist/types/client/state/boards-store.d.ts +79 -0
- package/dist/types/client/state/canvas-store.d.ts +2 -0
- package/dist/types/client/state/intent-bridge.d.ts +1 -0
- package/dist/types/client/state/session-store.d.ts +7 -0
- package/dist/types/client/types.d.ts +2 -2
- package/dist/types/json-render/catalog.d.ts +1 -1
- package/dist/types/json-render/charts/definitions.d.ts +1 -1
- package/dist/types/json-render/server.d.ts +6 -0
- package/dist/types/mcp/canvas-access.d.ts +10 -3
- package/dist/types/mcp/context-uri-template.d.ts +6 -0
- package/dist/types/server/agent-presence.d.ts +1 -1
- package/dist/types/server/attribution.d.ts +11 -0
- package/dist/types/server/ax-context.d.ts +3 -1
- package/dist/types/server/ax-interaction.d.ts +2 -2
- package/dist/types/server/ax-state.d.ts +13 -3
- package/dist/types/server/backup.d.ts +36 -0
- package/dist/types/server/board-export.d.ts +45 -0
- package/dist/types/server/canvas-db.d.ts +63 -9
- package/dist/types/server/canvas-operations.d.ts +23 -0
- package/dist/types/server/canvas-provenance.d.ts +1 -1
- package/dist/types/server/canvas-state.d.ts +140 -3
- package/dist/types/server/context-brief.d.ts +111 -0
- package/dist/types/server/context-reads.d.ts +55 -0
- package/dist/types/server/document-import.d.ts +66 -0
- package/dist/types/server/index.d.ts +103 -4
- package/dist/types/server/mutation-history.d.ts +1 -0
- package/dist/types/server/operations/invoker.d.ts +2 -0
- package/dist/types/server/operations/mcp.d.ts +7 -1
- package/dist/types/server/operations/ops/ax-shared.d.ts +2 -2
- package/dist/types/server/operations/ops/backup.d.ts +2 -0
- package/dist/types/server/operations/ops/boards.d.ts +10 -0
- package/dist/types/server/operations/ops/export.d.ts +2 -0
- package/dist/types/server/operations/ops/imports.d.ts +2 -0
- package/dist/types/server/operations/ops/nodes.d.ts +1 -1
- package/dist/types/server/operations/ops/query.d.ts +2 -0
- package/dist/types/server/operations/registry.d.ts +9 -1
- package/dist/types/server/operations/types.d.ts +1 -1
- package/dist/types/server/server.d.ts +1 -0
- package/dist/types/server/trace-manager.d.ts +7 -0
- package/dist/types/server/webpage-node.d.ts +22 -1
- package/dist/types/server/workbench-auth.d.ts +3 -0
- package/dist/types/shared/boards.d.ts +6 -0
- package/docs/RELEASE.md +5 -0
- package/docs/cli.md +73 -0
- package/docs/http-api.md +198 -2
- package/docs/mcp.md +71 -6
- package/docs/node-types.md +26 -0
- package/docs/plans/plan-010-attributed-ask-answers.md +39 -0
- package/docs/plans/plan-011-context-read-instrumentation.md +54 -0
- package/docs/plans/plan-012-many-boards-stage-1.md +98 -0
- package/docs/plans/plan-013-static-board-export.md +47 -0
- package/docs/plans/plan-014-connected-board-memory.md +140 -0
- package/docs/product-vision-2026-09.md +118 -18
- package/docs/sdk.md +50 -0
- package/package.json +1 -1
- package/skills/pmx-canvas/SKILL.md +68 -11
- package/skills/pmx-canvas/evals/evals.json +130 -0
- package/skills/pmx-canvas/references/full-reference.md +43 -7
- package/skills/pmx-canvas/references/github-copilot-app-adapter.md +3 -0
- package/skills/pmx-canvas/references/installing-pmx-canvas.md +4 -4
- package/skills/pmx-canvas/references/tours-and-recording.md +69 -0
- package/skills/pmx-canvas-orchestration/SKILL.md +5 -0
- package/skills/pmx-canvas-testing/SKILL.md +11 -2
- package/src/cli/agent.ts +4 -0
- package/src/cli/commands/apps.ts +1 -1
- package/src/cli/commands/ax.ts +13 -0
- package/src/cli/commands/backup.ts +66 -0
- package/src/cli/commands/boards.ts +71 -0
- package/src/cli/commands/export.ts +37 -0
- package/src/cli/commands/nodes.ts +4 -1
- package/src/cli/commands/pins.ts +6 -2
- package/src/cli/commands/pump.ts +3 -0
- package/src/cli/commands/query.ts +33 -7
- package/src/cli/commands/view.ts +7 -5
- package/src/cli/daemon.ts +37 -7
- package/src/cli/index.ts +19 -9
- package/src/cli/shared.ts +11 -2
- package/src/client/App.tsx +21 -4
- package/src/client/canvas/AttentionToast.tsx +5 -3
- package/src/client/canvas/BoardCopyDialog.tsx +153 -0
- package/src/client/canvas/BoardFolderDialog.tsx +120 -0
- package/src/client/canvas/BoardLinkDialog.tsx +93 -0
- package/src/client/canvas/BoardSwitcher.tsx +149 -0
- package/src/client/canvas/CanvasNode.tsx +0 -1
- package/src/client/canvas/CanvasViewport.tsx +3 -0
- package/src/client/canvas/CommandPalette.tsx +107 -4
- package/src/client/canvas/ContextMenu.tsx +4 -1
- package/src/client/canvas/ExpandedNodeOverlay.tsx +3 -0
- package/src/client/canvas/ExportDialog.tsx +199 -0
- package/src/client/canvas/HomeView.tsx +277 -0
- package/src/client/canvas/Presentation.tsx +39 -9
- package/src/client/canvas/SelectionBar.tsx +0 -2
- package/src/client/canvas/SessionPanel.tsx +28 -1
- package/src/client/canvas/ToolRail.tsx +38 -3
- package/src/client/canvas/TopBar.tsx +34 -18
- package/src/client/canvas/import-files.ts +19 -1
- package/src/client/canvas/kind-colors.ts +1 -0
- package/src/client/icons.tsx +2 -0
- package/src/client/nodes/AttachmentNode.tsx +145 -0
- package/src/client/nodes/BoardNode.tsx +35 -0
- package/src/client/nodes/FileNode.tsx +6 -0
- package/src/client/nodes/MarkdownNode.tsx +47 -19
- package/src/client/nodes/ax-node-actions.ts +2 -5
- package/src/client/nodes/use-ax-surface-bridge.ts +1 -1
- package/src/client/state/attention-bridge.ts +3 -1
- package/src/client/state/boards-store.ts +167 -0
- package/src/client/state/canvas-store.ts +22 -0
- package/src/client/state/intent-bridge.ts +30 -2
- package/src/client/state/node-factory.ts +1 -0
- package/src/client/state/session-store.ts +5 -0
- package/src/client/state/sse-bridge.ts +52 -6
- package/src/client/theme/global.css +510 -31
- package/src/client/types.ts +2 -0
- package/src/json-render/server.ts +16 -9
- package/src/mcp/canvas-access.ts +59 -22
- package/src/mcp/context-uri-template.ts +28 -0
- package/src/mcp/server.ts +122 -15
- package/src/server/agent-context.ts +116 -29
- package/src/server/attribution.ts +29 -0
- package/src/server/ax-context.ts +4 -2
- package/src/server/ax-interaction.ts +1 -0
- package/src/server/ax-state-manager.ts +72 -3
- package/src/server/ax-state.ts +41 -0
- package/src/server/backup.ts +178 -0
- package/src/server/board-export.ts +686 -0
- package/src/server/canvas-db.ts +599 -107
- package/src/server/canvas-operations.ts +68 -9
- package/src/server/canvas-provenance.ts +1 -0
- package/src/server/canvas-schema.ts +12 -0
- package/src/server/canvas-state.ts +774 -45
- package/src/server/context-brief.ts +372 -0
- package/src/server/context-reads.ts +238 -0
- package/src/server/demo-state.json +659 -2
- package/src/server/document-import.ts +223 -0
- package/src/server/index.ts +231 -5
- package/src/server/mutation-history.ts +10 -13
- package/src/server/operations/composites.ts +26 -4
- package/src/server/operations/http.ts +9 -3
- package/src/server/operations/index.ts +8 -0
- package/src/server/operations/invoker.ts +14 -3
- package/src/server/operations/mcp.ts +50 -11
- package/src/server/operations/ops/app.ts +5 -0
- package/src/server/operations/ops/ax-read.ts +10 -6
- package/src/server/operations/ops/ax-timeline.ts +75 -0
- package/src/server/operations/ops/backup.ts +134 -0
- package/src/server/operations/ops/batch.ts +8 -1
- package/src/server/operations/ops/boards.ts +351 -0
- package/src/server/operations/ops/export.ts +74 -0
- package/src/server/operations/ops/ext-app.ts +4 -0
- package/src/server/operations/ops/imports.ts +236 -0
- package/src/server/operations/ops/nodes.ts +57 -5
- package/src/server/operations/ops/query.ts +206 -9
- package/src/server/operations/registry.ts +123 -31
- package/src/server/operations/types.ts +1 -1
- package/src/server/server.ts +146 -2
- package/src/server/spatial-analysis.ts +9 -17
- package/src/server/trace-manager.ts +19 -0
- package/src/server/web-artifacts.ts +2 -1
- package/src/server/webpage-node.ts +173 -13
- package/src/server/workbench-auth.ts +11 -0
- package/src/shared/boards.ts +18 -0
- package/src/shared/node-sizes.ts +1 -0
|
@@ -23,6 +23,10 @@ let managedWorkspaceRoot = null;
|
|
|
23
23
|
let managedPort = null;
|
|
24
24
|
const managedLogs = [];
|
|
25
25
|
const panelServers = new Map();
|
|
26
|
+
// Canvas actions are invoked separately from `open`, and their context can
|
|
27
|
+
// omit the canvas input. Keep the server selected for each visible instance so
|
|
28
|
+
// an action cannot fall back to the process/default port behind the panel.
|
|
29
|
+
const panelTargets = new Map();
|
|
26
30
|
|
|
27
31
|
function normalizeBaseUrl(value) {
|
|
28
32
|
if (typeof value !== "string" || value.trim() === "") return null;
|
|
@@ -227,7 +231,7 @@ async function resolvePmxServer(ctxOrInput, options = {}) {
|
|
|
227
231
|
const allowWorkspaceMismatch = input?.allowWorkspaceMismatch === true;
|
|
228
232
|
for (const candidate of candidateBaseUrls(input)) {
|
|
229
233
|
const probe = await probeServer(candidate.baseUrl, workspaceRoot, {
|
|
230
|
-
allowWorkspaceMismatch
|
|
234
|
+
allowWorkspaceMismatch,
|
|
231
235
|
});
|
|
232
236
|
if (probe.ok) return probe;
|
|
233
237
|
}
|
|
@@ -245,6 +249,29 @@ async function resolvePmxServer(ctxOrInput, options = {}) {
|
|
|
245
249
|
return await startManagedServer(workspaceRoot, input);
|
|
246
250
|
}
|
|
247
251
|
|
|
252
|
+
async function resolvePanelTarget(ctx, options = {}) {
|
|
253
|
+
const selected = panelTargets.get(ctx?.instanceId);
|
|
254
|
+
if (!selected) return await resolvePmxServer(ctx, options);
|
|
255
|
+
|
|
256
|
+
const probe = await probeServer(selected.baseUrl, selected.workspaceRoot, {
|
|
257
|
+
timeoutMs: options.timeoutMs ?? 2_000,
|
|
258
|
+
});
|
|
259
|
+
if (probe.ok) return probe;
|
|
260
|
+
return {
|
|
261
|
+
...probe,
|
|
262
|
+
baseUrl: null,
|
|
263
|
+
error: `The displayed PMX Canvas target is no longer valid: ${probe.error ?? "health check failed"}. Action withheld.`,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function rememberPanelTarget(ctx, pmx) {
|
|
268
|
+
if (!ctx?.instanceId || !pmx?.ok || !pmx.baseUrl) return;
|
|
269
|
+
panelTargets.set(ctx.instanceId, {
|
|
270
|
+
baseUrl: pmx.baseUrl,
|
|
271
|
+
workspaceRoot: pmx.health.workspace,
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
248
275
|
function escapeHtml(value) {
|
|
249
276
|
return String(value)
|
|
250
277
|
.replaceAll("&", "&")
|
|
@@ -350,6 +377,19 @@ async function readRequestJson(req) {
|
|
|
350
377
|
return JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
351
378
|
}
|
|
352
379
|
|
|
380
|
+
async function verifiedEntryTarget(entry) {
|
|
381
|
+
if (!entry.pmx?.baseUrl) return { ok: false, baseUrl: null, error: "PMX Canvas server is unavailable." };
|
|
382
|
+
const probe = await probeServer(entry.pmx.baseUrl, entry.pmx.health.workspace, {
|
|
383
|
+
timeoutMs: 2_000,
|
|
384
|
+
});
|
|
385
|
+
if (probe.ok) return probe;
|
|
386
|
+
return {
|
|
387
|
+
...probe,
|
|
388
|
+
baseUrl: null,
|
|
389
|
+
error: `The displayed PMX Canvas target is no longer valid: ${probe.error ?? "health check failed"}. Action withheld.`,
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
|
|
353
393
|
function jsonResponse(res, statusCode, body) {
|
|
354
394
|
res.writeHead(statusCode, { "Content-Type": "application/json; charset=utf-8" });
|
|
355
395
|
res.end(JSON.stringify(body));
|
|
@@ -377,13 +417,23 @@ async function startPanelServer(instanceId, ctx, pmx) {
|
|
|
377
417
|
return;
|
|
378
418
|
}
|
|
379
419
|
if (req.method === "GET" && url.pathname === "/context") {
|
|
380
|
-
const
|
|
420
|
+
const target = await verifiedEntryTarget(entry);
|
|
421
|
+
if (!target.ok || !target.baseUrl) {
|
|
422
|
+
jsonResponse(res, 409, target);
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
const context = await getAxContext(target.baseUrl, entry.workspaceRoot, entry.input);
|
|
381
426
|
jsonResponse(res, 200, context);
|
|
382
427
|
return;
|
|
383
428
|
}
|
|
384
429
|
if (req.method === "POST" && url.pathname === "/focus") {
|
|
385
430
|
const body = await readRequestJson(req);
|
|
386
|
-
const
|
|
431
|
+
const target = await verifiedEntryTarget(entry);
|
|
432
|
+
if (!target.ok || !target.baseUrl) {
|
|
433
|
+
jsonResponse(res, 409, target);
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
const result = await setAxFocus(target.baseUrl, entry.workspaceRoot, entry.input, body.nodeIds);
|
|
387
437
|
jsonResponse(res, 200, result);
|
|
388
438
|
return;
|
|
389
439
|
}
|
|
@@ -396,8 +446,9 @@ async function startPanelServer(instanceId, ctx, pmx) {
|
|
|
396
446
|
await copilotSession?.send({ prompt: body.prompt });
|
|
397
447
|
// Explicit user instruction from the panel → mirror onto the AX
|
|
398
448
|
// timeline as a steering message (fire-and-forget).
|
|
399
|
-
|
|
400
|
-
|
|
449
|
+
const target = await verifiedEntryTarget(entry);
|
|
450
|
+
if (target.ok && target.baseUrl) {
|
|
451
|
+
void fetchJson(target.baseUrl, "/api/canvas/ax/steer", {
|
|
401
452
|
method: "POST",
|
|
402
453
|
headers: { "Content-Type": "application/json" },
|
|
403
454
|
body: JSON.stringify({ message: body.prompt, source: "copilot" }),
|
|
@@ -418,18 +469,81 @@ async function startPanelServer(instanceId, ctx, pmx) {
|
|
|
418
469
|
return { server, url: `http://127.0.0.1:${port}/`, entry };
|
|
419
470
|
}
|
|
420
471
|
|
|
421
|
-
async function getAxContext(baseUrl, workspaceRoot, input = {}) {
|
|
472
|
+
async function getAxContext(baseUrl, workspaceRoot, input = {}, options = {}) {
|
|
422
473
|
const resolved = baseUrl
|
|
423
474
|
? { ok: true, baseUrl }
|
|
424
475
|
: await resolvePmxServer({ input, session: { workingDirectory: workspaceRoot } }, { autoStart: false });
|
|
425
476
|
if (!resolved.ok || !resolved.baseUrl) {
|
|
426
477
|
return { ok: false, error: resolved.error ?? "PMX Canvas server is unavailable." };
|
|
427
478
|
}
|
|
428
|
-
|
|
479
|
+
// A proxied read is one this extension records itself with what it actually
|
|
480
|
+
// injected (after truncation), so the server does not record it again.
|
|
481
|
+
const headers = { "x-pmx-source": "copilot", ...(options.proxied ? { "x-pmx-proxied-read": "1" } : {}) };
|
|
482
|
+
return await fetchJson(resolved.baseUrl, "/api/canvas/ax/context", { headers, timeoutMs: 2_000 });
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function deliveredPinnedIds(pinnedNodeIds, payloadText) {
|
|
486
|
+
const pinned = new Set(pinnedNodeIds);
|
|
487
|
+
const delivered = new Set();
|
|
488
|
+
const objectStarts = [];
|
|
489
|
+
let inString = false;
|
|
490
|
+
let escaped = false;
|
|
491
|
+
for (let index = 0; index < payloadText.length; index += 1) {
|
|
492
|
+
const character = payloadText[index];
|
|
493
|
+
if (inString) {
|
|
494
|
+
if (escaped) escaped = false;
|
|
495
|
+
else if (character === "\\") escaped = true;
|
|
496
|
+
else if (character === '"') inString = false;
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
if (character === '"') {
|
|
500
|
+
inString = true;
|
|
501
|
+
} else if (character === "{") {
|
|
502
|
+
objectStarts.push(index);
|
|
503
|
+
} else if (character === "}" && objectStarts.length > 0) {
|
|
504
|
+
const start = objectStarts.pop();
|
|
505
|
+
try {
|
|
506
|
+
const value = JSON.parse(payloadText.slice(start, index + 1));
|
|
507
|
+
if (value && !Array.isArray(value) && typeof value === "object" &&
|
|
508
|
+
Object.hasOwn(value, "id") && typeof value.id === "string" && pinned.has(value.id)) {
|
|
509
|
+
delivered.add(value.id);
|
|
510
|
+
}
|
|
511
|
+
} catch {
|
|
512
|
+
// An inner object may be complete even when its containing object was clipped.
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
return pinnedNodeIds.filter((id) => delivered.has(id));
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/** Records what the per-prompt hook injected, so delivery is measured on what Copilot received. */
|
|
520
|
+
async function recordInjectedContext(baseUrl, context, injected) {
|
|
521
|
+
const pinnedNodeIds = Array.isArray(context?.pinned?.nodeIds) ? context.pinned.nodeIds : [];
|
|
522
|
+
const text = injected ?? "";
|
|
523
|
+
try {
|
|
524
|
+
await fetchJson(baseUrl, "/api/canvas/ax/context-reads", {
|
|
525
|
+
method: "POST",
|
|
526
|
+
headers: { "Content-Type": "application/json", "x-pmx-source": "copilot" },
|
|
527
|
+
body: JSON.stringify({
|
|
528
|
+
channel: "adapter",
|
|
529
|
+
resource: "copilot:prompt-context",
|
|
530
|
+
source: "copilot",
|
|
531
|
+
consumer: "copilot",
|
|
532
|
+
boardId: context.boardId,
|
|
533
|
+
pinnedNodeIds,
|
|
534
|
+
// Same rule as the server (context-reads.ts): a node object with that id, not a bare id list.
|
|
535
|
+
deliveredNodeIds: deliveredPinnedIds(pinnedNodeIds, text),
|
|
536
|
+
bytes: Buffer.byteLength(text, "utf-8"),
|
|
537
|
+
}),
|
|
538
|
+
timeoutMs: 1_500,
|
|
539
|
+
});
|
|
540
|
+
} catch {
|
|
541
|
+
// Instrumentation must never block the prompt.
|
|
542
|
+
}
|
|
429
543
|
}
|
|
430
544
|
|
|
431
545
|
async function getAxStatus(ctx) {
|
|
432
|
-
const resolved = await
|
|
546
|
+
const resolved = await resolvePanelTarget(ctx, { autoStart: false });
|
|
433
547
|
if (!resolved.ok || !resolved.baseUrl) return { ok: false, server: resolved };
|
|
434
548
|
const state = await fetchJson(resolved.baseUrl, "/api/canvas/ax", { timeoutMs: 2_000 });
|
|
435
549
|
return { ok: true, server: resolved, ax: state };
|
|
@@ -454,7 +568,7 @@ async function setAxFocus(baseUrl, workspaceRoot, input = {}, nodeIds = []) {
|
|
|
454
568
|
// Resolves the server first so the action returns an actionable error when the
|
|
455
569
|
// canvas is unavailable, then maps the neutral primitive over plain HTTP.
|
|
456
570
|
async function postAxRecord(ctx, path, payload) {
|
|
457
|
-
const resolved = await
|
|
571
|
+
const resolved = await resolvePanelTarget(ctx, { autoStart: false });
|
|
458
572
|
if (!resolved.ok || !resolved.baseUrl) {
|
|
459
573
|
return { ok: false, error: resolved.error ?? "PMX Canvas server is unavailable." };
|
|
460
574
|
}
|
|
@@ -562,7 +676,7 @@ function reportSteeringDeliveryError(error) {
|
|
|
562
676
|
}
|
|
563
677
|
|
|
564
678
|
async function getAxTimeline(ctx, limit) {
|
|
565
|
-
const resolved = await
|
|
679
|
+
const resolved = await resolvePanelTarget(ctx, { autoStart: false });
|
|
566
680
|
if (!resolved.ok || !resolved.baseUrl) return { ok: false, error: resolved.error };
|
|
567
681
|
const query = typeof limit === "number" && limit > 0 ? `?limit=${limit}` : "";
|
|
568
682
|
return await fetchJson(resolved.baseUrl, `/api/canvas/ax/timeline${query}`, { timeoutMs: 2_000 });
|
|
@@ -611,7 +725,7 @@ const pmxCanvas = createCanvas({
|
|
|
611
725
|
name: "get_ax_context",
|
|
612
726
|
description: "Return the current PMX Canvas AX pinned and focused context.",
|
|
613
727
|
handler: async (ctx) => {
|
|
614
|
-
const resolved = await
|
|
728
|
+
const resolved = await resolvePanelTarget(ctx, { autoStart: false });
|
|
615
729
|
if (!resolved.ok || !resolved.baseUrl) return { ok: false, error: resolved.error };
|
|
616
730
|
return await getAxContext(resolved.baseUrl, workspaceRootFrom(ctx), ctx.input ?? {});
|
|
617
731
|
},
|
|
@@ -630,7 +744,13 @@ const pmxCanvas = createCanvas({
|
|
|
630
744
|
required: ["nodeIds"],
|
|
631
745
|
additionalProperties: false,
|
|
632
746
|
},
|
|
633
|
-
handler: async (ctx) =>
|
|
747
|
+
handler: async (ctx) => {
|
|
748
|
+
const resolved = await resolvePanelTarget(ctx, { autoStart: false });
|
|
749
|
+
if (!resolved.ok || !resolved.baseUrl) {
|
|
750
|
+
throw new CanvasError("pmx_unavailable", resolved.error ?? "PMX Canvas server is unavailable.");
|
|
751
|
+
}
|
|
752
|
+
return await setAxFocus(resolved.baseUrl, workspaceRootFrom(ctx), ctx.input ?? {}, ctx.input?.nodeIds);
|
|
753
|
+
},
|
|
634
754
|
},
|
|
635
755
|
{
|
|
636
756
|
name: "send_instruction",
|
|
@@ -772,7 +892,7 @@ const pmxCanvas = createCanvas({
|
|
|
772
892
|
additionalProperties: false,
|
|
773
893
|
},
|
|
774
894
|
handler: async (ctx) => {
|
|
775
|
-
const resolved = await
|
|
895
|
+
const resolved = await resolvePanelTarget(ctx, { autoStart: false });
|
|
776
896
|
if (!resolved.ok || !resolved.baseUrl) {
|
|
777
897
|
return { ok: false, error: resolved.error ?? "PMX Canvas server is unavailable." };
|
|
778
898
|
}
|
|
@@ -800,6 +920,7 @@ const pmxCanvas = createCanvas({
|
|
|
800
920
|
},
|
|
801
921
|
],
|
|
802
922
|
open: async (ctx) => {
|
|
923
|
+
panelTargets.delete(ctx.instanceId);
|
|
803
924
|
let pmx;
|
|
804
925
|
try {
|
|
805
926
|
pmx = await resolvePmxServer(ctx);
|
|
@@ -814,6 +935,7 @@ const pmxCanvas = createCanvas({
|
|
|
814
935
|
}
|
|
815
936
|
|
|
816
937
|
if (pmx.ok && pmx.baseUrl) {
|
|
938
|
+
rememberPanelTarget(ctx, pmx);
|
|
817
939
|
const fallbackPanel = panelServers.get(ctx.instanceId);
|
|
818
940
|
if (fallbackPanel) {
|
|
819
941
|
panelServers.delete(ctx.instanceId);
|
|
@@ -835,6 +957,7 @@ const pmxCanvas = createCanvas({
|
|
|
835
957
|
panel.entry.workspaceRoot = workspaceRootFrom(ctx);
|
|
836
958
|
panel.entry.input = ctx.input ?? {};
|
|
837
959
|
}
|
|
960
|
+
rememberPanelTarget(ctx, pmx);
|
|
838
961
|
return {
|
|
839
962
|
title: "PMX Canvas",
|
|
840
963
|
status: pmx.ok ? "Connected" : "Needs server",
|
|
@@ -842,6 +965,7 @@ const pmxCanvas = createCanvas({
|
|
|
842
965
|
};
|
|
843
966
|
},
|
|
844
967
|
onClose: async (ctx) => {
|
|
968
|
+
panelTargets.delete(ctx.instanceId);
|
|
845
969
|
const panel = panelServers.get(ctx.instanceId);
|
|
846
970
|
if (!panel) return;
|
|
847
971
|
panelServers.delete(ctx.instanceId);
|
|
@@ -860,8 +984,9 @@ copilotSession = await joinSession({
|
|
|
860
984
|
void postPresence(workspaceRoot, { attached: true, phase: "thinking", detail: null });
|
|
861
985
|
const resolved = await resolvePmxServer({ input: {}, session: { workingDirectory: workspaceRoot } }, { autoStart: false });
|
|
862
986
|
if (!resolved.ok || !resolved.baseUrl) return undefined;
|
|
863
|
-
const context = await getAxContext(resolved.baseUrl, workspaceRoot, {});
|
|
987
|
+
const context = await getAxContext(resolved.baseUrl, workspaceRoot, {}, { proxied: true });
|
|
864
988
|
const additionalContext = formatAdditionalContext(context, resolved.baseUrl);
|
|
989
|
+
void recordInjectedContext(resolved.baseUrl, context, additionalContext);
|
|
865
990
|
return additionalContext ? { additionalContext } : undefined;
|
|
866
991
|
},
|
|
867
992
|
},
|
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,76 @@
|
|
|
3
3
|
All notable changes to `pmx-canvas` are documented here. This project follows
|
|
4
4
|
[Semantic Versioning](https://semver.org/).
|
|
5
5
|
|
|
6
|
+
## [Unreleased]
|
|
7
|
+
|
|
8
|
+
## [0.7.1] - 2026-09-30
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- Imported cards avoid existing content and multi-card imports form a group named after the source file.
|
|
13
|
+
- Asking an agent to import now includes processing consent in one click; review actions have clearer buttons and a separate sticky footer.
|
|
14
|
+
- Presentations support right-drag panning and a visible exit button; export Open and Download actions look like buttons.
|
|
15
|
+
- Board navigation no longer reports removed cards or carries old session receipts onto Home.
|
|
16
|
+
- Refused edits show their reason without a tooltip, keep the reason in Updates, and leave refused deletions visible.
|
|
17
|
+
- Copilot panel actions use the displayed server and refuse writes if its workspace changes.
|
|
18
|
+
- Inline batch JSON works with either spaced or equals-style flags.
|
|
19
|
+
- CLI exports support an independent `--include-derived-text` opt-in and reject unknown flags.
|
|
20
|
+
- Stopping from a different installation preserves live daemon PID files and reports the mismatch.
|
|
21
|
+
- Daemon stop verifies the process on Windows and refuses to stop a live process whose installation cannot be verified.
|
|
22
|
+
- Bundled skills include a retrievable tour reference, conditional browser-host checks, and eval scenarios for the new board and import workflows.
|
|
23
|
+
|
|
24
|
+
## [0.7.0] - 2026-09-29
|
|
25
|
+
|
|
26
|
+
### Highlights
|
|
27
|
+
|
|
28
|
+
- Keep your work in named boards, organized into nested folders and connected by board links.
|
|
29
|
+
- Search across your library and give agents source-labelled context from related boards.
|
|
30
|
+
- Turn document attachments into reviewable Markdown using your connected agent's tools.
|
|
31
|
+
- Share a board as a standalone HTML file and back up the whole library.
|
|
32
|
+
|
|
33
|
+
### Breaking
|
|
34
|
+
|
|
35
|
+
- The database migrates to multi-board storage and cannot be opened by older versions. Before downgrading, restore the automatic `canvas.db.pre-boards` copy or a pre-upgrade backup; changes made after migration are not in that copy.
|
|
36
|
+
|
|
37
|
+
### Changed
|
|
38
|
+
|
|
39
|
+
- The canvas database now stores many named boards. On first start your current board becomes a board named after the workspace, every old snapshot becomes a board of its own, and a copy of the old file is kept as `canvas.db.pre-boards` (older versions cannot open the migrated file).
|
|
40
|
+
- Agent context and search now read chart values, diagram labels and structured card text instead of raw renderer configuration.
|
|
41
|
+
- Questions, approvals and mode requests record who asked and answered them, including self-answers; this attribution describes the writer and does not grant permission.
|
|
42
|
+
|
|
43
|
+
### Added
|
|
44
|
+
|
|
45
|
+
- Sort boards into categories on Home; each category is its own section you can fold away, and boards migrated from old snapshots start in a folded "From old snapshots" section.
|
|
46
|
+
- Export a board as one HTML file anyone can open without installing anything, from **Export** in the top bar or `pmx-canvas export`; the dialog lists what goes into the file first, and local file contents stay out unless you include them.
|
|
47
|
+
- Back up every board with `pmx-canvas backup`, schedule server-run copies with `pmx-canvas backup schedule --every 24h`, and bring a backup back with `pmx-canvas restore` (the replaced file is kept); Home has no backup button.
|
|
48
|
+
- The top bar shows the open board; click it to switch to a recent board, create a new one, or go Home, where you can open, rename, and delete boards (deleting asks first).
|
|
49
|
+
- Agents can list, read and create boards with `canvas_board`, `canvas://boards`, `pmx-canvas board list|create|rename`, and the `/api/canvas/boards` routes; only you open, switch or delete a board.
|
|
50
|
+
- Agent writes go to the board you have open; on Home, an agent's first write opens your most recent board, or creates one if there are none.
|
|
51
|
+
- The canvas now records which context each agent reads and whether your pinned nodes were in it, readable with `pmx-canvas ax reads`, `canvas_ax_timeline { action: "reads" }`, or `GET /api/canvas/ax/context-reads`.
|
|
52
|
+
- Designate a Markdown card as a board README, add text-only board-link cards, inspect outgoing links and backlinks, and search cards across the library without switching boards.
|
|
53
|
+
- Preview and create an inactive board from selected reusable cards and optional groups/README; copied cards receive fresh IDs and omit pins, AX state and history.
|
|
54
|
+
- Read a character-bounded cross-board brief with source IDs over MCP, HTTP, CLI or SDK; a durable cursor is used only when a stable consumer ID is supplied.
|
|
55
|
+
- Attach PDF and Office originals up to 20 MiB, explicitly ask the connected agent to draft source-linked Markdown, and review it before adding it to the board; Canvas does not bundle conversion or launch an agent automatically.
|
|
56
|
+
|
|
57
|
+
### Known limitations
|
|
58
|
+
|
|
59
|
+
- Document extraction depends on the connected agent's available tools; remote byte transfer, OCR, unreadable charts, and some host formats may be unavailable.
|
|
60
|
+
- A graph map, direct card-to-card edges across boards, and wiki-link syntax are not implemented.
|
|
61
|
+
|
|
62
|
+
### Fixed
|
|
63
|
+
|
|
64
|
+
- Snapshots can only be read, restored, renamed or deleted on their owning board.
|
|
65
|
+
- Slow file imports no longer land on a board opened after the drop, and rapid board switches keep their intended order.
|
|
66
|
+
- Recent boards remain correctly ordered when multiple opens happen within one millisecond.
|
|
67
|
+
- HTTP board switching and deletion require the trusted workbench token, not just the UI marker.
|
|
68
|
+
- `pmx-canvas pump` no longer crashes when its command exits without reading the steer; the exit code decides, as intended.
|
|
69
|
+
- A change the server refuses in the browser now shows a toast with the reason instead of failing silently.
|
|
70
|
+
- Webpage nodes refuse redirects that move a public page onto a private or loopback address, and never fetch link-local (cloud metadata) addresses.
|
|
71
|
+
|
|
72
|
+
### Internal
|
|
73
|
+
|
|
74
|
+
- The e2e gate now checks that every node type paints in a 600 px Chromium pane, the width of the agent-host panels the workbench lives in.
|
|
75
|
+
|
|
6
76
|
## [0.6.5] - 2026-09-26
|
|
7
77
|
|
|
8
78
|
### Added
|
package/Readme.md
CHANGED
|
@@ -41,6 +41,15 @@ in real time. Either side adds material; the human curates spatial structure
|
|
|
41
41
|
`canvas://pinned-context` and acts on it. Spatial arrangement is
|
|
42
42
|
communication — proximity means relatedness, pinning means *focus here*.
|
|
43
43
|
|
|
44
|
+
The database holds many named boards. **Home is the library view, not a board**:
|
|
45
|
+
it lists recent boards and nested category paths (up to eight 60-character
|
|
46
|
+
segments). Empty folders are not independent objects; they exist only while a
|
|
47
|
+
board is filed beneath them. Only a human opens/switches or deletes a board.
|
|
48
|
+
Agents may list, read, create, rename, categorize, search, and create an inactive
|
|
49
|
+
board from selected reusable cards without disturbing the board in view. Home
|
|
50
|
+
provides a copy-preview dialog; an open board's command palette offers a
|
|
51
|
+
board-link picker. Neither depends on native browser prompts.
|
|
52
|
+
|
|
44
53
|
## Main features
|
|
45
54
|
|
|
46
55
|
### 01 / Curate
|
|
@@ -73,6 +82,14 @@ the canvas is the union of its [built-in node types](docs/node-types.md) and
|
|
|
73
82
|
**whatever your agent's harness already has access to** — MCP servers, CLIs,
|
|
74
83
|
file reads, web fetch, anything on its toolbelt.
|
|
75
84
|
|
|
85
|
+
Dropping a PDF or Office document attaches the original (up to 20 MiB). It does
|
|
86
|
+
not bundle a converter or automatically launch an agent. Clicking **Ask agent to
|
|
87
|
+
import** also authorizes processing by the connected host's tools/model provider;
|
|
88
|
+
there is no separate consent confirmation. The agent returns Markdown sections
|
|
89
|
+
with references and warnings for review. Multi-card imports are placed together
|
|
90
|
+
in a free area, in a group named after the source file. Remote hosts may require
|
|
91
|
+
manual transfer; OCR, charts, and host format support are not guaranteed.
|
|
92
|
+
|
|
76
93
|
JSON-render panels support bound form state, nested repeats, watchers, and Card
|
|
77
94
|
header/footer slots. Form edits remain viewer-local until explicitly submitted;
|
|
78
95
|
see [JSON-render nodes](docs/node-types.md#json-render-nodes). Authored iframe cards
|
|
@@ -176,15 +193,21 @@ and the `pmx-canvas-orchestration` skill documents the choreography.
|
|
|
176
193
|
|
|
177
194
|
Spatial state auto-saves to `.pmx-canvas/canvas.db` (debounced ~500 ms) —
|
|
178
195
|
git-committable, shareable across machines, and survives both browser
|
|
179
|
-
refresh and server restart.
|
|
196
|
+
refresh and server restart. Autosave is not a backup: `pmx-canvas backup` and
|
|
197
|
+
its optional server-run schedule copy the whole board library, while restore
|
|
198
|
+
keeps the replaced database. Backup and scheduling are backend/CLI operations;
|
|
199
|
+
there is no Home backup button. A board can also be exported as a self-contained
|
|
200
|
+
HTML file. Export previews what is included, omits local file contents unless
|
|
201
|
+
explicitly requested, and locks board-link cards because an exported file has
|
|
202
|
+
no live target library (the target title is not embedded). Named [snapshots](docs/mcp.md), full
|
|
180
203
|
undo/redo, and an auto-detected code graph (JS/TS, Python, Go, Rust) make
|
|
181
204
|
the canvas durable rather than throwaway. Stop the server before committing
|
|
182
205
|
the DB so SQLite WAL data is checkpointed into the file.
|
|
183
206
|
|
|
184
207
|
### 07 / Any agent
|
|
185
208
|
|
|
186
|
-
Harness-agnostic. Drive the canvas from [MCP](docs/mcp.md) (
|
|
187
|
-
|
|
209
|
+
Harness-agnostic. Drive the canvas from [MCP](docs/mcp.md) (24 tools,
|
|
210
|
+
15 static canvas resources plus one context resource template, change notifications), the [CLI](docs/cli.md), the
|
|
188
211
|
[HTTP API](docs/http-api.md), or the [Bun SDK](docs/sdk.md) — all
|
|
189
212
|
[environment variables documented here](docs/environment.md). Works with
|
|
190
213
|
Claude Code, the GitHub Copilot app and CLI, Codex, Amp, Cursor, Windsurf, or
|
|
@@ -223,7 +246,9 @@ surfaces (the core never imports a host SDK).
|
|
|
223
246
|
session override), wakes Copilot when the board sends a steer, injects
|
|
224
247
|
pinned/focused AX context on prompt submission, and exposes actions for focus,
|
|
225
248
|
work items, approval gates, review annotations, the AX timeline, and
|
|
226
|
-
host-capability reporting.
|
|
249
|
+
host-capability reporting. Actions stay bound to the server shown by their
|
|
250
|
+
panel and are withheld if that server no longer matches the panel workspace.
|
|
251
|
+
Install it into any repo with
|
|
227
252
|
`pmx-canvas copilot install-extension` (`--dry-run` to preview).
|
|
228
253
|
- **Codex app** — native through the Codex in-app Browser (opened to
|
|
229
254
|
`/workbench`) plus the PMX MCP server: agents read `canvas://ax-context` /
|
|
@@ -378,7 +403,7 @@ services:
|
|
|
378
403
|
```bash
|
|
379
404
|
# .agents/setup — install the CLI (pin the exact version: a fresh orb running
|
|
380
405
|
# @latest can silently pick up a newer release than the one you validated)
|
|
381
|
-
npm install -g pmx-canvas@0.
|
|
406
|
+
npm install -g pmx-canvas@0.7.1
|
|
382
407
|
```
|
|
383
408
|
|
|
384
409
|
The server binds the portal-assigned `$PORT` automatically (gated on the
|
|
@@ -444,7 +469,8 @@ paths, screenshots, or logs are attached automatically.
|
|
|
444
469
|
the three-tier visual matrix (json-render → html → web-artifact)
|
|
445
470
|
- **[CLI reference](docs/cli.md)** — full command surface, daemon mode,
|
|
446
471
|
watch streams, WebView automation
|
|
447
|
-
- **[MCP reference](docs/mcp.md)** —
|
|
472
|
+
- **[MCP reference](docs/mcp.md)** — 24 tools, 15 static canvas resources, one
|
|
473
|
+
context resource template, plus bundled
|
|
448
474
|
skill resources, Skills extension, change notifications, node-type routing
|
|
449
475
|
- **[HTTP API](docs/http-api.md)** — REST endpoints, SSE, batch operations
|
|
450
476
|
- **[AX host-adapter contract](docs/ax-host-adapter-contract.md)** — how native
|