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.
Files changed (181) hide show
  1. package/.github/extensions/pmx-canvas/extension.mjs +139 -14
  2. package/CHANGELOG.md +70 -0
  3. package/Readme.md +32 -6
  4. package/dist/canvas/global.css +510 -31
  5. package/dist/canvas/index.js +92 -77
  6. package/dist/types/cli/agent.d.ts +3 -0
  7. package/dist/types/cli/commands/backup.d.ts +1 -0
  8. package/dist/types/cli/commands/boards.d.ts +1 -0
  9. package/dist/types/cli/commands/export.d.ts +1 -0
  10. package/dist/types/cli/daemon.d.ts +5 -5
  11. package/dist/types/cli/shared.d.ts +3 -1
  12. package/dist/types/client/canvas/BoardCopyDialog.d.ts +5 -0
  13. package/dist/types/client/canvas/BoardFolderDialog.d.ts +7 -0
  14. package/dist/types/client/canvas/BoardLinkDialog.d.ts +3 -0
  15. package/dist/types/client/canvas/BoardSwitcher.d.ts +8 -0
  16. package/dist/types/client/canvas/ExportDialog.d.ts +7 -0
  17. package/dist/types/client/canvas/HomeView.d.ts +9 -0
  18. package/dist/types/client/nodes/AttachmentNode.d.ts +6 -0
  19. package/dist/types/client/nodes/BoardNode.d.ts +5 -0
  20. package/dist/types/client/state/boards-store.d.ts +79 -0
  21. package/dist/types/client/state/canvas-store.d.ts +2 -0
  22. package/dist/types/client/state/intent-bridge.d.ts +1 -0
  23. package/dist/types/client/state/session-store.d.ts +7 -0
  24. package/dist/types/client/types.d.ts +2 -2
  25. package/dist/types/json-render/catalog.d.ts +1 -1
  26. package/dist/types/json-render/charts/definitions.d.ts +1 -1
  27. package/dist/types/json-render/server.d.ts +6 -0
  28. package/dist/types/mcp/canvas-access.d.ts +10 -3
  29. package/dist/types/mcp/context-uri-template.d.ts +6 -0
  30. package/dist/types/server/agent-presence.d.ts +1 -1
  31. package/dist/types/server/attribution.d.ts +11 -0
  32. package/dist/types/server/ax-context.d.ts +3 -1
  33. package/dist/types/server/ax-interaction.d.ts +2 -2
  34. package/dist/types/server/ax-state.d.ts +13 -3
  35. package/dist/types/server/backup.d.ts +36 -0
  36. package/dist/types/server/board-export.d.ts +45 -0
  37. package/dist/types/server/canvas-db.d.ts +63 -9
  38. package/dist/types/server/canvas-operations.d.ts +23 -0
  39. package/dist/types/server/canvas-provenance.d.ts +1 -1
  40. package/dist/types/server/canvas-state.d.ts +140 -3
  41. package/dist/types/server/context-brief.d.ts +111 -0
  42. package/dist/types/server/context-reads.d.ts +55 -0
  43. package/dist/types/server/document-import.d.ts +66 -0
  44. package/dist/types/server/index.d.ts +103 -4
  45. package/dist/types/server/mutation-history.d.ts +1 -0
  46. package/dist/types/server/operations/invoker.d.ts +2 -0
  47. package/dist/types/server/operations/mcp.d.ts +7 -1
  48. package/dist/types/server/operations/ops/ax-shared.d.ts +2 -2
  49. package/dist/types/server/operations/ops/backup.d.ts +2 -0
  50. package/dist/types/server/operations/ops/boards.d.ts +10 -0
  51. package/dist/types/server/operations/ops/export.d.ts +2 -0
  52. package/dist/types/server/operations/ops/imports.d.ts +2 -0
  53. package/dist/types/server/operations/ops/nodes.d.ts +1 -1
  54. package/dist/types/server/operations/ops/query.d.ts +2 -0
  55. package/dist/types/server/operations/registry.d.ts +9 -1
  56. package/dist/types/server/operations/types.d.ts +1 -1
  57. package/dist/types/server/server.d.ts +1 -0
  58. package/dist/types/server/trace-manager.d.ts +7 -0
  59. package/dist/types/server/webpage-node.d.ts +22 -1
  60. package/dist/types/server/workbench-auth.d.ts +3 -0
  61. package/dist/types/shared/boards.d.ts +6 -0
  62. package/docs/RELEASE.md +5 -0
  63. package/docs/cli.md +73 -0
  64. package/docs/http-api.md +198 -2
  65. package/docs/mcp.md +71 -6
  66. package/docs/node-types.md +26 -0
  67. package/docs/plans/plan-010-attributed-ask-answers.md +39 -0
  68. package/docs/plans/plan-011-context-read-instrumentation.md +54 -0
  69. package/docs/plans/plan-012-many-boards-stage-1.md +98 -0
  70. package/docs/plans/plan-013-static-board-export.md +47 -0
  71. package/docs/plans/plan-014-connected-board-memory.md +140 -0
  72. package/docs/product-vision-2026-09.md +118 -18
  73. package/docs/sdk.md +50 -0
  74. package/package.json +1 -1
  75. package/skills/pmx-canvas/SKILL.md +68 -11
  76. package/skills/pmx-canvas/evals/evals.json +130 -0
  77. package/skills/pmx-canvas/references/full-reference.md +43 -7
  78. package/skills/pmx-canvas/references/github-copilot-app-adapter.md +3 -0
  79. package/skills/pmx-canvas/references/installing-pmx-canvas.md +4 -4
  80. package/skills/pmx-canvas/references/tours-and-recording.md +69 -0
  81. package/skills/pmx-canvas-orchestration/SKILL.md +5 -0
  82. package/skills/pmx-canvas-testing/SKILL.md +11 -2
  83. package/src/cli/agent.ts +4 -0
  84. package/src/cli/commands/apps.ts +1 -1
  85. package/src/cli/commands/ax.ts +13 -0
  86. package/src/cli/commands/backup.ts +66 -0
  87. package/src/cli/commands/boards.ts +71 -0
  88. package/src/cli/commands/export.ts +37 -0
  89. package/src/cli/commands/nodes.ts +4 -1
  90. package/src/cli/commands/pins.ts +6 -2
  91. package/src/cli/commands/pump.ts +3 -0
  92. package/src/cli/commands/query.ts +33 -7
  93. package/src/cli/commands/view.ts +7 -5
  94. package/src/cli/daemon.ts +37 -7
  95. package/src/cli/index.ts +19 -9
  96. package/src/cli/shared.ts +11 -2
  97. package/src/client/App.tsx +21 -4
  98. package/src/client/canvas/AttentionToast.tsx +5 -3
  99. package/src/client/canvas/BoardCopyDialog.tsx +153 -0
  100. package/src/client/canvas/BoardFolderDialog.tsx +120 -0
  101. package/src/client/canvas/BoardLinkDialog.tsx +93 -0
  102. package/src/client/canvas/BoardSwitcher.tsx +149 -0
  103. package/src/client/canvas/CanvasNode.tsx +0 -1
  104. package/src/client/canvas/CanvasViewport.tsx +3 -0
  105. package/src/client/canvas/CommandPalette.tsx +107 -4
  106. package/src/client/canvas/ContextMenu.tsx +4 -1
  107. package/src/client/canvas/ExpandedNodeOverlay.tsx +3 -0
  108. package/src/client/canvas/ExportDialog.tsx +199 -0
  109. package/src/client/canvas/HomeView.tsx +277 -0
  110. package/src/client/canvas/Presentation.tsx +39 -9
  111. package/src/client/canvas/SelectionBar.tsx +0 -2
  112. package/src/client/canvas/SessionPanel.tsx +28 -1
  113. package/src/client/canvas/ToolRail.tsx +38 -3
  114. package/src/client/canvas/TopBar.tsx +34 -18
  115. package/src/client/canvas/import-files.ts +19 -1
  116. package/src/client/canvas/kind-colors.ts +1 -0
  117. package/src/client/icons.tsx +2 -0
  118. package/src/client/nodes/AttachmentNode.tsx +145 -0
  119. package/src/client/nodes/BoardNode.tsx +35 -0
  120. package/src/client/nodes/FileNode.tsx +6 -0
  121. package/src/client/nodes/MarkdownNode.tsx +47 -19
  122. package/src/client/nodes/ax-node-actions.ts +2 -5
  123. package/src/client/nodes/use-ax-surface-bridge.ts +1 -1
  124. package/src/client/state/attention-bridge.ts +3 -1
  125. package/src/client/state/boards-store.ts +167 -0
  126. package/src/client/state/canvas-store.ts +22 -0
  127. package/src/client/state/intent-bridge.ts +30 -2
  128. package/src/client/state/node-factory.ts +1 -0
  129. package/src/client/state/session-store.ts +5 -0
  130. package/src/client/state/sse-bridge.ts +52 -6
  131. package/src/client/theme/global.css +510 -31
  132. package/src/client/types.ts +2 -0
  133. package/src/json-render/server.ts +16 -9
  134. package/src/mcp/canvas-access.ts +59 -22
  135. package/src/mcp/context-uri-template.ts +28 -0
  136. package/src/mcp/server.ts +122 -15
  137. package/src/server/agent-context.ts +116 -29
  138. package/src/server/attribution.ts +29 -0
  139. package/src/server/ax-context.ts +4 -2
  140. package/src/server/ax-interaction.ts +1 -0
  141. package/src/server/ax-state-manager.ts +72 -3
  142. package/src/server/ax-state.ts +41 -0
  143. package/src/server/backup.ts +178 -0
  144. package/src/server/board-export.ts +686 -0
  145. package/src/server/canvas-db.ts +599 -107
  146. package/src/server/canvas-operations.ts +68 -9
  147. package/src/server/canvas-provenance.ts +1 -0
  148. package/src/server/canvas-schema.ts +12 -0
  149. package/src/server/canvas-state.ts +774 -45
  150. package/src/server/context-brief.ts +372 -0
  151. package/src/server/context-reads.ts +238 -0
  152. package/src/server/demo-state.json +659 -2
  153. package/src/server/document-import.ts +223 -0
  154. package/src/server/index.ts +231 -5
  155. package/src/server/mutation-history.ts +10 -13
  156. package/src/server/operations/composites.ts +26 -4
  157. package/src/server/operations/http.ts +9 -3
  158. package/src/server/operations/index.ts +8 -0
  159. package/src/server/operations/invoker.ts +14 -3
  160. package/src/server/operations/mcp.ts +50 -11
  161. package/src/server/operations/ops/app.ts +5 -0
  162. package/src/server/operations/ops/ax-read.ts +10 -6
  163. package/src/server/operations/ops/ax-timeline.ts +75 -0
  164. package/src/server/operations/ops/backup.ts +134 -0
  165. package/src/server/operations/ops/batch.ts +8 -1
  166. package/src/server/operations/ops/boards.ts +351 -0
  167. package/src/server/operations/ops/export.ts +74 -0
  168. package/src/server/operations/ops/ext-app.ts +4 -0
  169. package/src/server/operations/ops/imports.ts +236 -0
  170. package/src/server/operations/ops/nodes.ts +57 -5
  171. package/src/server/operations/ops/query.ts +206 -9
  172. package/src/server/operations/registry.ts +123 -31
  173. package/src/server/operations/types.ts +1 -1
  174. package/src/server/server.ts +146 -2
  175. package/src/server/spatial-analysis.ts +9 -17
  176. package/src/server/trace-manager.ts +19 -0
  177. package/src/server/web-artifacts.ts +2 -1
  178. package/src/server/webpage-node.ts +173 -13
  179. package/src/server/workbench-auth.ts +11 -0
  180. package/src/shared/boards.ts +18 -0
  181. 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: candidate.explicit || 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 context = await getAxContext(entry.pmx?.baseUrl, entry.workspaceRoot, entry.input);
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 result = await setAxFocus(entry.pmx?.baseUrl, entry.workspaceRoot, entry.input, body.nodeIds);
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
- if (entry.pmx?.baseUrl) {
400
- void fetchJson(entry.pmx.baseUrl, "/api/canvas/ax/steer", {
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
- return await fetchJson(resolved.baseUrl, "/api/canvas/ax/context", { timeoutMs: 2_000 });
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 resolvePmxServer(ctx, { autoStart: false });
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 resolvePmxServer(ctx, { autoStart: false });
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 resolvePmxServer(ctx, { autoStart: false });
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 resolvePmxServer(ctx, { autoStart: false });
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) => await setAxFocus(null, workspaceRootFrom(ctx), ctx.input ?? {}, ctx.input?.nodeIds),
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 resolvePmxServer(ctx, { autoStart: false });
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. Named [snapshots](docs/mcp.md), full
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) (22 tools,
187
- 14 resources, change notifications), the [CLI](docs/cli.md), the
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. Install it into any repo with
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.6.5
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)** — 22 tools, 14 core resources plus bundled
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