pmx-canvas 0.4.8 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/extensions/pmx-canvas/extension.mjs +154 -5
- package/.github/extensions/pmx-canvas/steering-delivery.mjs +79 -0
- package/CHANGELOG.md +139 -0
- package/Readme.md +72 -23
- package/dist/canvas/global.css +3069 -796
- package/dist/canvas/index.js +104 -104
- package/dist/json-render/index.css +1 -1
- package/dist/types/cli/agent.d.ts +1 -0
- package/dist/types/cli/commands/pump.d.ts +9 -0
- package/dist/types/client/App.d.ts +1 -1
- package/dist/types/client/canvas/AgentPresenceLayer.d.ts +11 -0
- package/dist/types/client/canvas/BarHint.d.ts +22 -0
- package/dist/types/client/canvas/CanvasNode.d.ts +5 -0
- package/dist/types/client/canvas/CanvasViewport.d.ts +4 -4
- package/dist/types/client/canvas/CommandBar.d.ts +10 -0
- package/dist/types/client/canvas/ConnectionBanner.d.ts +13 -0
- package/dist/types/client/canvas/ContextMenu.d.ts +6 -0
- package/dist/types/client/canvas/EdgeLayer.d.ts +2 -1
- package/dist/types/client/canvas/EmptyState.d.ts +11 -0
- package/dist/types/client/canvas/ExternalWriters.d.ts +5 -0
- package/dist/types/client/canvas/HumanPresenceLayer.d.ts +8 -0
- package/dist/types/client/canvas/Minimap.d.ts +12 -1
- package/dist/types/client/canvas/ScopeFenceLayer.d.ts +8 -0
- package/dist/types/client/canvas/SelectionBar.d.ts +6 -0
- package/dist/types/client/canvas/SessionPanel.d.ts +1 -0
- package/dist/types/client/canvas/SessionReceipt.d.ts +17 -0
- package/dist/types/client/canvas/TextPrompt.d.ts +25 -0
- package/dist/types/client/canvas/ToolRail.d.ts +21 -0
- package/dist/types/client/canvas/TopBar.d.ts +7 -0
- package/dist/types/client/canvas/canvas-area.d.ts +25 -0
- package/dist/types/client/canvas/create-in-view.d.ts +18 -0
- package/dist/types/client/canvas/import-files.d.ts +8 -0
- package/dist/types/client/canvas/kind-colors.d.ts +3 -0
- package/dist/types/client/canvas/use-focus-trap.d.ts +14 -0
- package/dist/types/client/canvas/use-now.d.ts +6 -0
- package/dist/types/client/icons.d.ts +11 -4
- package/dist/types/client/nodes/GroupNode.d.ts +6 -1
- package/dist/types/client/nodes/MarkdownNode.d.ts +2 -0
- package/dist/types/client/nodes/inline-editor-commands.d.ts +3 -4
- package/dist/types/client/state/canvas-store.d.ts +49 -5
- package/dist/types/client/state/human-store.d.ts +26 -0
- package/dist/types/client/state/iframe-mode.d.ts +1 -0
- package/dist/types/client/state/intent-bridge.d.ts +37 -5
- package/dist/types/client/state/node-factory.d.ts +0 -1
- package/dist/types/client/state/presence-store.d.ts +78 -0
- package/dist/types/client/state/session-store.d.ts +176 -0
- package/dist/types/client/types.d.ts +2 -1
- package/dist/types/client/utils/platform.d.ts +2 -0
- package/dist/types/server/agent-presence.d.ts +192 -0
- package/dist/types/server/ax-gate-ttl.d.ts +6 -0
- package/dist/types/server/ax-interaction.d.ts +4 -10
- package/dist/types/server/ax-state-manager.d.ts +21 -2
- package/dist/types/server/ax-state.d.ts +49 -3
- package/dist/types/server/ax-wait.d.ts +13 -0
- package/dist/types/server/canvas-db.d.ts +9 -1
- package/dist/types/server/canvas-operations.d.ts +1 -0
- package/dist/types/server/canvas-state.d.ts +27 -13
- package/dist/types/server/canvas-validation.d.ts +0 -14
- package/dist/types/server/html-surface.d.ts +1 -1
- package/dist/types/server/human-presence.d.ts +40 -0
- package/dist/types/server/index.d.ts +29 -3
- package/dist/types/server/intent-registry.d.ts +7 -0
- package/dist/types/server/mutation-history.d.ts +15 -2
- package/dist/types/server/operations/invoker.d.ts +13 -2
- package/dist/types/server/operations/ops/ax-presence.d.ts +2 -0
- package/dist/types/server/operations/ops/human-presence.d.ts +3 -0
- package/dist/types/server/operations/registry.d.ts +11 -0
- package/dist/types/server/operations/types.d.ts +1 -1
- package/dist/types/server/scope-fence.d.ts +62 -0
- package/dist/types/server/server.d.ts +0 -12
- package/dist/types/shared/agent-presence.d.ts +130 -0
- package/dist/types/shared/approval-gates.d.ts +19 -0
- package/dist/types/shared/auto-arrange.d.ts +0 -1
- package/dist/types/shared/ax-kinds.d.ts +11 -0
- package/dist/types/shared/human-presence.d.ts +31 -0
- package/dist/types/shared/iframe-probe.d.ts +8 -0
- package/dist/types/shared/scope-fence.d.ts +21 -0
- package/docs/RELEASE.md +6 -0
- package/docs/ax-host-adapter-contract.md +30 -14
- package/docs/cli.md +37 -1
- package/docs/environment.md +3 -0
- package/docs/http-api.md +222 -5
- package/docs/mcp.md +6 -5
- package/docs/node-types.md +3 -1
- package/docs/plans/plan-009-tech-debt-backlog.md +13 -0
- package/docs/screenshot.png +0 -0
- package/docs/screenshots/multi-agent-live.png +0 -0
- package/docs/screenshots/workbench-live-dark.png +0 -0
- package/docs/screenshots/workbench-live-light.png +0 -0
- package/docs/sdk.md +1 -1
- package/package.json +1 -1
- package/skills/pmx-canvas/SKILL.md +103 -16
- package/skills/pmx-canvas/references/codex-app-adapter.md +28 -0
- package/skills/pmx-canvas/references/full-reference.md +55 -16
- package/skills/pmx-canvas/references/github-copilot-app-adapter.md +41 -24
- package/skills/pmx-canvas-orchestration/SKILL.md +56 -2
- package/skills/pmx-canvas-testing/SKILL.md +17 -0
- package/src/cli/agent.ts +1 -0
- package/src/cli/commands/copilot.ts +15 -8
- package/src/cli/commands/nodes.ts +1 -19
- package/src/cli/commands/pump.ts +252 -0
- package/src/cli/index.ts +1 -5
- package/src/cli/shared.ts +1 -1
- package/src/client/App.tsx +209 -585
- package/src/client/canvas/AgentPresenceLayer.tsx +74 -0
- package/src/client/canvas/AttentionHistory.tsx +0 -10
- package/src/client/canvas/BarHint.tsx +49 -0
- package/src/client/canvas/CanvasNode.tsx +488 -136
- package/src/client/canvas/CanvasViewport.tsx +190 -94
- package/src/client/canvas/CommandBar.tsx +140 -0
- package/src/client/canvas/CommandPalette.tsx +133 -58
- package/src/client/canvas/ConnectionBanner.tsx +65 -0
- package/src/client/canvas/ContextMenu.tsx +88 -27
- package/src/client/canvas/ContextPinBar.tsx +2 -2
- package/src/client/canvas/EdgeLayer.tsx +121 -38
- package/src/client/canvas/EmptyState.tsx +93 -0
- package/src/client/canvas/ExpandedNodeOverlay.tsx +64 -118
- package/src/client/canvas/ExternalWriters.tsx +308 -0
- package/src/client/canvas/FocusFieldLayer.tsx +12 -1
- package/src/client/canvas/HumanPresenceLayer.tsx +45 -0
- package/src/client/canvas/IntentLayer.tsx +1 -1
- package/src/client/canvas/Minimap.tsx +117 -190
- package/src/client/canvas/ScopeFenceLayer.tsx +38 -0
- package/src/client/canvas/SelectionBar.tsx +175 -23
- package/src/client/canvas/SessionPanel.tsx +481 -0
- package/src/client/canvas/SessionReceipt.tsx +141 -0
- package/src/client/canvas/ShortcutOverlay.tsx +21 -6
- package/src/client/canvas/SnapshotPanel.tsx +166 -80
- package/src/client/canvas/TextPrompt.tsx +128 -0
- package/src/client/canvas/ToolRail.tsx +447 -0
- package/src/client/canvas/TopBar.tsx +308 -0
- package/src/client/canvas/auto-fit.ts +1 -5
- package/src/client/canvas/canvas-area.ts +41 -0
- package/src/client/canvas/create-in-view.ts +48 -0
- package/src/client/canvas/import-files.ts +64 -0
- package/src/client/canvas/kind-colors.ts +22 -0
- package/src/client/canvas/snap-guides.ts +1 -1
- package/src/client/canvas/use-focus-trap.ts +54 -0
- package/src/client/canvas/use-now.ts +17 -0
- package/src/client/canvas/use-pan-zoom.ts +13 -2
- package/src/client/icons.tsx +57 -18
- package/src/client/nodes/GroupNode.tsx +9 -28
- package/src/client/nodes/InlineFormatBar.tsx +4 -3
- package/src/client/nodes/InlineMarkdownEditor.tsx +27 -0
- package/src/client/nodes/MarkdownNode.tsx +59 -24
- package/src/client/nodes/MdFormatBar.tsx +2 -4
- package/src/client/nodes/inline-editor-commands.ts +17 -6
- package/src/client/nodes/use-surface-frame.ts +3 -1
- package/src/client/state/attention-bridge.ts +3 -0
- package/src/client/state/canvas-store.ts +273 -84
- package/src/client/state/human-store.ts +175 -0
- package/src/client/state/iframe-mode.ts +33 -9
- package/src/client/state/intent-bridge.ts +75 -13
- package/src/client/state/node-factory.ts +2 -8
- package/src/client/state/presence-store.ts +168 -0
- package/src/client/state/session-store.ts +419 -0
- package/src/client/state/sse-bridge.ts +93 -30
- package/src/client/theme/global.css +3069 -796
- package/src/client/types.ts +3 -1
- package/src/client/utils/platform.ts +6 -1
- package/src/mcp/canvas-access.ts +2 -2
- package/src/server/agent-presence.ts +755 -0
- package/src/server/ax-context.ts +45 -0
- package/src/server/ax-gate-ttl.ts +61 -0
- package/src/server/ax-interaction.ts +29 -1
- package/src/server/ax-state-manager.ts +78 -6
- package/src/server/ax-state.ts +82 -15
- package/src/server/ax-wait.ts +33 -0
- package/src/server/canvas-db.ts +103 -33
- package/src/server/canvas-operations.ts +17 -8
- package/src/server/canvas-state.ts +131 -58
- package/src/server/canvas-validation.ts +3 -53
- package/src/server/demo-state.json +2 -70
- package/src/server/html-surface.ts +11 -2
- package/src/server/human-presence.ts +160 -0
- package/src/server/index.ts +107 -4
- package/src/server/intent-registry.ts +13 -0
- package/src/server/mutation-history.ts +47 -2
- package/src/server/operations/composites.ts +6 -3
- package/src/server/operations/http.ts +10 -1
- package/src/server/operations/index.ts +4 -0
- package/src/server/operations/invoker.ts +33 -6
- package/src/server/operations/ops/ax-presence.ts +62 -0
- package/src/server/operations/ops/ax-read.ts +8 -0
- package/src/server/operations/ops/ax-state.ts +106 -1
- package/src/server/operations/ops/ax-timeline.ts +54 -5
- package/src/server/operations/ops/ax-work.ts +46 -0
- package/src/server/operations/ops/batch.ts +3 -1
- package/src/server/operations/ops/edges.ts +88 -2
- package/src/server/operations/ops/groups.ts +4 -3
- package/src/server/operations/ops/human-presence.ts +35 -0
- package/src/server/operations/ops/json-render.ts +11 -1
- package/src/server/operations/ops/nodes.ts +65 -16
- package/src/server/operations/ops/query.ts +1 -0
- package/src/server/operations/ops/snapshots.ts +16 -0
- package/src/server/operations/registry.ts +196 -1
- package/src/server/operations/types.ts +1 -1
- package/src/server/scope-fence.ts +207 -0
- package/src/server/server.ts +177 -79
- package/src/server/trace-manager.ts +0 -2
- package/src/server/web-artifacts.ts +0 -1
- package/src/shared/agent-presence.ts +162 -0
- package/src/shared/approval-gates.ts +35 -0
- package/src/shared/auto-arrange.ts +121 -13
- package/src/shared/ax-kinds.ts +23 -0
- package/src/shared/human-presence.ts +30 -0
- package/src/shared/iframe-probe.ts +8 -0
- package/src/shared/scope-fence.ts +31 -0
- package/dist/types/client/canvas/DockedNode.d.ts +0 -4
- package/src/client/canvas/DockedNode.tsx +0 -165
|
@@ -5,6 +5,7 @@ import { createServer as createNetServer } from "node:net";
|
|
|
5
5
|
import { dirname, resolve } from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import { CanvasError, createCanvas, joinSession } from "@github/copilot-sdk/extension";
|
|
8
|
+
import { createSteeringDeliveryPump } from "./steering-delivery.mjs";
|
|
8
9
|
|
|
9
10
|
const EXTENSION_DIR = dirname(fileURLToPath(import.meta.url));
|
|
10
11
|
const PROJECT_ROOT = resolve(EXTENSION_DIR, "../../..");
|
|
@@ -12,8 +13,10 @@ const DEFAULT_PORT = 4313;
|
|
|
12
13
|
const MAX_AX_CONTEXT_CHARS = 16_000;
|
|
13
14
|
const MANAGED_START_TIMEOUT_MS = 10_000;
|
|
14
15
|
const HEALTH_TIMEOUT_MS = 500;
|
|
16
|
+
const STEERING_WAIT_MS = 30_000;
|
|
15
17
|
|
|
16
18
|
let copilotSession;
|
|
19
|
+
let steeringDeliveryPump;
|
|
17
20
|
let managedProcess = null;
|
|
18
21
|
let managedBaseUrl = null;
|
|
19
22
|
let managedWorkspaceRoot = null;
|
|
@@ -467,12 +470,97 @@ async function postAxRecord(ctx, path, payload) {
|
|
|
467
470
|
}
|
|
468
471
|
}
|
|
469
472
|
|
|
473
|
+
// Agent presence (rail-chrome-v2): the Copilot session attaches itself so the
|
|
474
|
+
// canvas shows its cursor + phase chip, and the server attributes the agent's
|
|
475
|
+
// MCP/HTTP writes to this session. Best-effort and never blocking the host.
|
|
476
|
+
async function postPresence(workspaceRoot, payload, options = {}) {
|
|
477
|
+
try {
|
|
478
|
+
const resolved = await resolvePmxServer(
|
|
479
|
+
{ input: {}, session: { workingDirectory: workspaceRoot } },
|
|
480
|
+
{ autoStart: false },
|
|
481
|
+
);
|
|
482
|
+
if (!resolved.ok || !resolved.baseUrl) return false;
|
|
483
|
+
await fetchJson(resolved.baseUrl, "/api/canvas/ax/presence", {
|
|
484
|
+
method: "POST",
|
|
485
|
+
headers: { "Content-Type": "application/json" },
|
|
486
|
+
body: JSON.stringify({ source: "copilot", label: "GitHub Copilot", ...payload }),
|
|
487
|
+
timeoutMs: options.timeoutMs ?? 1_500,
|
|
488
|
+
});
|
|
489
|
+
return true;
|
|
490
|
+
} catch {
|
|
491
|
+
return false;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
async function getCopilotPresence() {
|
|
496
|
+
try {
|
|
497
|
+
const resolved = await resolvePmxServer(
|
|
498
|
+
{ input: {}, session: { workingDirectory: PROJECT_ROOT } },
|
|
499
|
+
{ autoStart: false },
|
|
500
|
+
);
|
|
501
|
+
if (!resolved.ok || !resolved.baseUrl) return null;
|
|
502
|
+
const snapshot = await fetchJson(resolved.baseUrl, "/api/canvas/ax/presence", { timeoutMs: 1_500 });
|
|
503
|
+
if (!Array.isArray(snapshot.presences)) return null;
|
|
504
|
+
return (
|
|
505
|
+
snapshot.presences.find(
|
|
506
|
+
(presence) =>
|
|
507
|
+
presence?.sessionId === "copilot" ||
|
|
508
|
+
(presence?.source === "copilot" && presence?.agentId == null),
|
|
509
|
+
) ?? null
|
|
510
|
+
);
|
|
511
|
+
} catch {
|
|
512
|
+
return null;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
470
516
|
// Fire-and-forget mirror of a copilot-originated action onto the AX timeline.
|
|
471
517
|
// Never throws — adapter UX must not depend on the canvas being reachable.
|
|
472
518
|
function recordCopilotSteering(ctx, message) {
|
|
473
519
|
void postAxRecord(ctx, "/api/canvas/ax/steer", { message }).catch(() => {});
|
|
474
520
|
}
|
|
475
521
|
|
|
522
|
+
async function claimCopilotSteering(consumer) {
|
|
523
|
+
const workspaceRoot = PROJECT_ROOT;
|
|
524
|
+
const resolved = await resolvePmxServer(
|
|
525
|
+
{ input: {}, session: { workingDirectory: workspaceRoot } },
|
|
526
|
+
{ autoStart: false },
|
|
527
|
+
);
|
|
528
|
+
if (!resolved.ok || !resolved.baseUrl) {
|
|
529
|
+
throw new Error(resolved.error ?? "PMX Canvas server is unavailable.");
|
|
530
|
+
}
|
|
531
|
+
const delivery = await fetchJson(
|
|
532
|
+
resolved.baseUrl,
|
|
533
|
+
`/api/canvas/ax/delivery/pending?consumer=${encodeURIComponent(consumer)}&limit=1&order=oldest&waitMs=${STEERING_WAIT_MS}`,
|
|
534
|
+
{ timeoutMs: STEERING_WAIT_MS + 2_000 },
|
|
535
|
+
);
|
|
536
|
+
return Array.isArray(delivery.pending) ? delivery.pending : [];
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
async function markCopilotSteering(id, consumer) {
|
|
540
|
+
const workspaceRoot = PROJECT_ROOT;
|
|
541
|
+
const resolved = await resolvePmxServer(
|
|
542
|
+
{ input: {}, session: { workingDirectory: workspaceRoot } },
|
|
543
|
+
{ autoStart: false },
|
|
544
|
+
);
|
|
545
|
+
if (!resolved.ok || !resolved.baseUrl) {
|
|
546
|
+
throw new Error(resolved.error ?? "PMX Canvas server is unavailable.");
|
|
547
|
+
}
|
|
548
|
+
await fetchJson(resolved.baseUrl, `/api/canvas/ax/delivery/${encodeURIComponent(id)}/mark`, {
|
|
549
|
+
method: "POST",
|
|
550
|
+
headers: { "Content-Type": "application/json" },
|
|
551
|
+
body: JSON.stringify({ consumer }),
|
|
552
|
+
timeoutMs: 2_000,
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
let lastSteeringDeliveryError = null;
|
|
557
|
+
function reportSteeringDeliveryError(error) {
|
|
558
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
559
|
+
if (message === lastSteeringDeliveryError) return;
|
|
560
|
+
lastSteeringDeliveryError = message;
|
|
561
|
+
process.stderr.write(`[pmx-canvas] Copilot steering delivery retrying: ${message}\n`);
|
|
562
|
+
}
|
|
563
|
+
|
|
476
564
|
async function getAxTimeline(ctx, limit) {
|
|
477
565
|
const resolved = await resolvePmxServer(ctx, { autoStart: false });
|
|
478
566
|
if (!resolved.ok || !resolved.baseUrl) return { ok: false, error: resolved.error };
|
|
@@ -766,6 +854,10 @@ copilotSession = await joinSession({
|
|
|
766
854
|
hooks: {
|
|
767
855
|
onUserPromptSubmitted: async (input) => {
|
|
768
856
|
const workspaceRoot = resolve(input?.workingDirectory ?? copilotSession?.workspacePath ?? PROJECT_ROOT);
|
|
857
|
+
// A submitted prompt means the agent is about to think: attach (idempotent)
|
|
858
|
+
// and flip the phase. The agent's subsequent canvas writes reach the server
|
|
859
|
+
// as MCP/HTTP tool calls and are attributed to this session.
|
|
860
|
+
void postPresence(workspaceRoot, { attached: true, phase: "thinking", detail: null });
|
|
769
861
|
const resolved = await resolvePmxServer({ input: {}, session: { workingDirectory: workspaceRoot } }, { autoStart: false });
|
|
770
862
|
if (!resolved.ok || !resolved.baseUrl) return undefined;
|
|
771
863
|
const context = await getAxContext(resolved.baseUrl, workspaceRoot, {});
|
|
@@ -775,11 +867,68 @@ copilotSession = await joinSession({
|
|
|
775
867
|
},
|
|
776
868
|
});
|
|
777
869
|
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
870
|
+
steeringDeliveryPump = createSteeringDeliveryPump({
|
|
871
|
+
consumer: "copilot",
|
|
872
|
+
claim: claimCopilotSteering,
|
|
873
|
+
send: async (message) => await copilotSession.send({ prompt: message }),
|
|
874
|
+
mark: markCopilotSteering,
|
|
875
|
+
getPresence: getCopilotPresence,
|
|
876
|
+
setPresence: async (patch) => await postPresence(PROJECT_ROOT, patch),
|
|
877
|
+
shouldSend: (() => {
|
|
878
|
+
const startedAt = Date.now();
|
|
879
|
+
return (steering) => Date.parse(steering.createdAt) >= startedAt;
|
|
880
|
+
})(),
|
|
881
|
+
pause: delay,
|
|
882
|
+
onError: reportSteeringDeliveryError,
|
|
781
883
|
});
|
|
782
|
-
|
|
884
|
+
void steeringDeliveryPump.start();
|
|
885
|
+
|
|
886
|
+
// Attach the session as soon as the host joins (the canvas may not be running
|
|
887
|
+
// yet — the prompt hook above re-attaches lazily in that case).
|
|
888
|
+
void postPresence(resolve(copilotSession?.workspacePath ?? PROJECT_ROOT), { attached: true });
|
|
889
|
+
|
|
890
|
+
// Real context window (rail-chrome-v2): Copilot emits `session.usage_info`
|
|
891
|
+
// with the live token count and the model's window after each turn. Report it
|
|
892
|
+
// on the presence so the board's top-bar meter shows the agent's ACTUAL window
|
|
893
|
+
// instead of the pinned-context estimate. Root agent only — sub-agent events
|
|
894
|
+
// carry an `agentId` and have their own windows. Coalesced to one POST per
|
|
895
|
+
// 500ms: a turn can emit several usage events back to back.
|
|
896
|
+
let usageFlush = null;
|
|
897
|
+
let pendingUsage = null;
|
|
898
|
+
function reportContextUsage(event) {
|
|
899
|
+
const data = event?.data;
|
|
900
|
+
if (event?.agentId || !data || typeof data.currentTokens !== "number" || typeof data.tokenLimit !== "number" || data.tokenLimit <= 0) {
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
pendingUsage = { used: Math.max(0, Math.round(data.currentTokens)), total: Math.round(data.tokenLimit) };
|
|
904
|
+
if (usageFlush) return;
|
|
905
|
+
usageFlush = setTimeout(() => {
|
|
906
|
+
usageFlush = null;
|
|
907
|
+
const usage = pendingUsage;
|
|
908
|
+
pendingUsage = null;
|
|
909
|
+
if (usage) void postPresence(resolve(copilotSession?.workspacePath ?? PROJECT_ROOT), { contextUsage: usage });
|
|
910
|
+
}, 500);
|
|
911
|
+
}
|
|
912
|
+
if (typeof copilotSession?.on === "function") {
|
|
913
|
+
try {
|
|
914
|
+
copilotSession.on("session.usage_info", reportContextUsage);
|
|
915
|
+
} catch {
|
|
916
|
+
// An SDK without the event keeps the estimate — the meter says so.
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
async function shutdown() {
|
|
921
|
+
steeringDeliveryPump?.stop();
|
|
922
|
+
// Detach the presence first so the canvas returns to the quiet board the
|
|
923
|
+
// moment the host goes away, then stop any managed server. The whole
|
|
924
|
+
// detach is capped at 800ms (server probing + the POST) so a host with a
|
|
925
|
+
// short SIGTERM grace window can never be held past it.
|
|
926
|
+
await Promise.race([
|
|
927
|
+
postPresence(resolve(copilotSession?.workspacePath ?? PROJECT_ROOT), { attached: false }, { timeoutMs: 500 }),
|
|
928
|
+
delay(800),
|
|
929
|
+
]);
|
|
783
930
|
stopManagedServer();
|
|
784
931
|
process.exit(0);
|
|
785
|
-
}
|
|
932
|
+
}
|
|
933
|
+
process.once("SIGTERM", () => void shutdown());
|
|
934
|
+
process.once("SIGINT", () => void shutdown());
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
export function createSteeringDeliveryPump({
|
|
2
|
+
consumer,
|
|
3
|
+
claim,
|
|
4
|
+
send,
|
|
5
|
+
mark,
|
|
6
|
+
shouldSend = () => true,
|
|
7
|
+
getPresence,
|
|
8
|
+
setPresence,
|
|
9
|
+
pause,
|
|
10
|
+
onError,
|
|
11
|
+
retryMs = 1_000,
|
|
12
|
+
}) {
|
|
13
|
+
let stopped = false;
|
|
14
|
+
let loopPromise = null;
|
|
15
|
+
const awaitingMark = [];
|
|
16
|
+
|
|
17
|
+
async function flushMarks() {
|
|
18
|
+
while (awaitingMark.length > 0) {
|
|
19
|
+
const id = awaitingMark[0];
|
|
20
|
+
await mark(id, consumer);
|
|
21
|
+
awaitingMark.shift();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function sendWithOwnedPhase(message) {
|
|
26
|
+
const detail = `steer: ${message.slice(0, 60)}`;
|
|
27
|
+
const presence = await getPresence?.();
|
|
28
|
+
const ownsPhase =
|
|
29
|
+
presence?.phase === "idle" &&
|
|
30
|
+
typeof setPresence === "function" &&
|
|
31
|
+
(await setPresence({ phase: "tooling", detail })) !== false;
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
await send(message);
|
|
35
|
+
} finally {
|
|
36
|
+
if (ownsPhase && typeof getPresence === "function" && typeof setPresence === "function") {
|
|
37
|
+
const current = await getPresence();
|
|
38
|
+
if (current?.phase === "tooling" && current.detail === detail) {
|
|
39
|
+
await setPresence({ phase: "idle", detail: null });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function runOnce() {
|
|
46
|
+
await flushMarks();
|
|
47
|
+
const pending = await claim(consumer);
|
|
48
|
+
for (const steering of pending) {
|
|
49
|
+
if (stopped) break;
|
|
50
|
+
if (shouldSend(steering)) await sendWithOwnedPhase(steering.message);
|
|
51
|
+
awaitingMark.push(steering.id);
|
|
52
|
+
await flushMarks();
|
|
53
|
+
}
|
|
54
|
+
return pending.length;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function run() {
|
|
58
|
+
while (!stopped) {
|
|
59
|
+
try {
|
|
60
|
+
const claimed = await runOnce();
|
|
61
|
+
if (claimed === 0 && !stopped) await pause(retryMs);
|
|
62
|
+
} catch (error) {
|
|
63
|
+
onError(error);
|
|
64
|
+
if (!stopped) await pause(retryMs);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
runOnce,
|
|
71
|
+
start() {
|
|
72
|
+
if (!loopPromise) loopPromise = run();
|
|
73
|
+
return loopPromise;
|
|
74
|
+
},
|
|
75
|
+
stop() {
|
|
76
|
+
stopped = true;
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
}
|
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,144 @@
|
|
|
3
3
|
All notable changes to `pmx-canvas` are documented here. This project follows
|
|
4
4
|
[Semantic Versioning](https://semver.org/).
|
|
5
5
|
|
|
6
|
+
## [0.5.0] - 2026-08-28
|
|
7
|
+
|
|
8
|
+
The multi-agent workbench release. The canvas becomes a live, shared surface
|
|
9
|
+
where several agents and a human can work at once — everyone can see who is
|
|
10
|
+
writing where, steer any connected agent, and take the board back when they
|
|
11
|
+
need it.
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### Breaking
|
|
15
|
+
|
|
16
|
+
- Node docking is gone. `dockPosition` is removed from node payloads, from
|
|
17
|
+
`canvas_update_node`'s input, and from the SDK's `CanvasNodeState` and node
|
|
18
|
+
updates; the docked HUD pills and the first-run docked status/context widgets
|
|
19
|
+
are retired. Nodes always live on the canvas now — use `position` and
|
|
20
|
+
`pinned`. Old boards load fine and previously docked nodes appear as normal
|
|
21
|
+
nodes. The validator's advisory `hiddenEdgeEndpoints` field went with them.
|
|
22
|
+
- Ungrouping now dissolves the group in one undo step: children are released
|
|
23
|
+
(into the enclosing group when nested) and the frame is removed — it no
|
|
24
|
+
longer survives to be deleted separately. A missing group now returns 404,
|
|
25
|
+
and an empty group dissolves instead of erroring. To release children while
|
|
26
|
+
keeping the frame, patch the group with `childIds: []`.
|
|
27
|
+
- Approval gates no longer wait forever: an unanswered gate auto-resolves to
|
|
28
|
+
the new `held` status (a non-approval — the action does not proceed) after
|
|
29
|
+
its TTL, default 5 minutes via `PMX_CANVAS_GATE_TTL_MS`; reopen it with
|
|
30
|
+
`POST /api/canvas/ax/approval/:id/reopen`. Consumers switching on gate status
|
|
31
|
+
must handle `held`.
|
|
32
|
+
- Broadcast steering is delivered per consumer: marking a broadcast with your
|
|
33
|
+
`consumer` clears it from your queue only, and every other consumer still
|
|
34
|
+
receives it (previously the first mark removed it for everyone — fleets must
|
|
35
|
+
no longer rely on that for dedupe). Unclaimed broadcasts leave the pending
|
|
36
|
+
queue after 15 minutes; addressed steers still wait for their consumer
|
|
37
|
+
indefinitely.
|
|
38
|
+
### Highlights
|
|
39
|
+
|
|
40
|
+
- See who is on the board in real time — every writing agent paints a named,
|
|
41
|
+
colored cursor and a phase, and a top-bar indicator shows external steering
|
|
42
|
+
even for agents that aren't attached.
|
|
43
|
+
- Steer any connected agent from the composer: address one by name or broadcast
|
|
44
|
+
to the whole fleet, and `pmx-canvas pump` turns any CLI agent into a steerable
|
|
45
|
+
board citizen.
|
|
46
|
+
- Human-wins editing: grab a node and agents are locked out of it until you let
|
|
47
|
+
go, and you can undo an agent's last edit straight from the session panel.
|
|
48
|
+
- A redesigned board — tool rail, top bar, session panel, command bar, minimap,
|
|
49
|
+
History drawer, groups, and an expanded node view.
|
|
50
|
+
- Unattended approval gates now resolve themselves safely instead of hanging
|
|
51
|
+
forever, and an orchestrator can fence each agent to its own lane.
|
|
52
|
+
|
|
53
|
+
### Added
|
|
54
|
+
|
|
55
|
+
- Agent presence: a live view of every agent writing to the board — identity
|
|
56
|
+
color, phase (thinking / working / waiting), a cursor that tracks where it is
|
|
57
|
+
working, and whether a session is attached. Read it at
|
|
58
|
+
`GET /api/canvas/ax/presence` or set it via `canvas_ax_state`
|
|
59
|
+
(`presence` / `set-presence`).
|
|
60
|
+
- Human presence and a user-wins edit lock: your cursor is shared with agents,
|
|
61
|
+
and while you are holding a node an agent write to it is refused until you
|
|
62
|
+
release it.
|
|
63
|
+
- Addressed steering: the composer picks which connected agent to steer, and a
|
|
64
|
+
broadcast reaches every consumer in the fleet; the steering timeline names the
|
|
65
|
+
agent behind each message.
|
|
66
|
+
- `pmx-canvas pump` — a new CLI command that makes any CLI agent (Codex, Amp, a
|
|
67
|
+
script) steerable: it long-polls for steers and runs your command once per
|
|
68
|
+
message. Supports `--consumer`, `--exec`, `--parent`, `--once`, and
|
|
69
|
+
`--backlog`. On Windows the command runs through `cmd.exe` and receives the
|
|
70
|
+
steer on stdin; the `{message}` / `{id}` placeholders are refused there,
|
|
71
|
+
because `cmd.exe` re-parses expanded variables and a steer could inject
|
|
72
|
+
commands.
|
|
73
|
+
- Edges can be edited in place — update an edge's label, type, style, or
|
|
74
|
+
animation without deleting and redrawing it (`PATCH /api/canvas/edge/:id`,
|
|
75
|
+
`canvas_edge { action: "update" }`).
|
|
76
|
+
- File nodes can be repointed at another file with a patch — content is re-read
|
|
77
|
+
and the watcher rewired, keeping the node's edges, pins, and position
|
|
78
|
+
(`PATCH /api/canvas/node/:id` with `path`).
|
|
79
|
+
- Shared undo of agent edits: when an agent's change tops the history stack, a
|
|
80
|
+
fixed "Undo" row appears in the session panel and taking it back posts a
|
|
81
|
+
steering note.
|
|
82
|
+
- Unattended-approval policy: an approval gate nobody answers within its TTL
|
|
83
|
+
resolves to `held` (the action does not proceed) instead of hanging; reopen it
|
|
84
|
+
with a fresh clock from the session panel or
|
|
85
|
+
`POST /api/canvas/ax/approval/:id/reopen`.
|
|
86
|
+
- Scope fence: an orchestrator can fence each agent to a set of nodes, and
|
|
87
|
+
agent writes outside the fence are refused (403) — the human is never fenced.
|
|
88
|
+
- Session receipts that say why a session ended (by you, by the agent, or idle
|
|
89
|
+
timeout), and a History drawer that stops hoarding duplicate snapshots.
|
|
90
|
+
- A redesigned board: tool rail with hover labels and shortcuts, top bar,
|
|
91
|
+
command bar, session panel, minimap v2, groups v2, an empty state, a palette,
|
|
92
|
+
a selection bar, a connection banner, and an expanded node view.
|
|
93
|
+
- An honest context meter: it shows the agent's real context window when the
|
|
94
|
+
host reports usage, and otherwise a "Pins" estimate of what your pinned
|
|
95
|
+
context would cost.
|
|
96
|
+
- In-canvas prompt entry (Shift+F / W / I everywhere), right-click Delete and
|
|
97
|
+
"Remove from ‹frame›" on member cards, and a work-item Assistant filter.
|
|
98
|
+
- New environment variables: `PMX_CANVAS_AGENT_SOURCE` (name the agent behind a
|
|
99
|
+
transport), `PMX_CANVAS_CONTEXT_BUDGET_TOKENS` (the Pins-meter ceiling,
|
|
100
|
+
default 32000), and `PMX_CANVAS_GATE_TTL_MS` (approval-gate TTL, default 5 min).
|
|
101
|
+
|
|
102
|
+
### Changed
|
|
103
|
+
|
|
104
|
+
- Create and patch now reject the dead `path` (on non-image nodes) and `src`
|
|
105
|
+
(anywhere) fields with a clear 400 instead of silently dropping them and
|
|
106
|
+
leaving an empty node — the canonical field is `content`. The image-node
|
|
107
|
+
`path` alias still works.
|
|
108
|
+
- Approval gates report a new `held` outcome (from the unattended-approval
|
|
109
|
+
policy); consumers reading gate status should tolerate it alongside
|
|
110
|
+
`approved` / `rejected`.
|
|
111
|
+
- Steering delivery claims can long-poll (`timeoutMs`) so a host that can't be
|
|
112
|
+
woken parks on the claim, and delivery is per-consumer so a broadcast reaches
|
|
113
|
+
the whole fleet without double-delivering.
|
|
114
|
+
- Auto-arrange is cluster-aware — each on-board neighborhood packs in place
|
|
115
|
+
instead of being interleaved with the others.
|
|
116
|
+
- Saving a snapshot on an unchanged board now returns the existing snapshot (marked `reused`) instead of creating a duplicate — a checkpoint-by-name on an unchanged board keeps the earlier snapshot's name.
|
|
117
|
+
- Cancelled work items no longer read as vetoed backlog in the timeline.
|
|
118
|
+
- Keyboard shortcuts are platform-aware on Windows keyboards (Ctrl+Y redo,
|
|
119
|
+
AltGr guard, platform-correct labels).
|
|
120
|
+
- Humans now draw annotations from the rail's Annotate popover (pen / text /
|
|
121
|
+
eraser) rather than separate toolbar buttons.
|
|
122
|
+
|
|
123
|
+
### Fixed
|
|
124
|
+
|
|
125
|
+
- Node headlines stay crisp at high zoom instead of blurring.
|
|
126
|
+
- HTML and mermaid surfaces render in embedded hosts that block sub-frame
|
|
127
|
+
requests or use `srcdoc` (Copilot, Codex, Claude panels).
|
|
128
|
+
- Open webpage / file / image work on every surface — no flow depends on
|
|
129
|
+
`window.prompt`, which is a silent no-op in embedded browser panes.
|
|
130
|
+
- Edges no longer draw as degenerate straight lines — side-tangent bezier
|
|
131
|
+
routing gives them a real bend, and the draw preview no longer jumps.
|
|
132
|
+
- A focus glow no longer wraps the whole board when an attention region spans
|
|
133
|
+
everything.
|
|
134
|
+
- Card checkboxes survive a human-speed press, expanded-view checkboxes tick,
|
|
135
|
+
and closing a document with Esc no longer wipes it.
|
|
136
|
+
- Stale browser tabs self-heal on a dev rebuild instead of faithfully
|
|
137
|
+
reproducing already-fixed bugs.
|
|
138
|
+
- Timeline filter hints and selection-bar tooltips render in-flow as styled
|
|
139
|
+
hints (native title tooltips never show in embedded panes), and the filter
|
|
140
|
+
chips fit one row.
|
|
141
|
+
- Presence overlay expiry always emits, held gates no longer pin the session
|
|
142
|
+
panel open, and Copilot presence is preserved during steering.
|
|
143
|
+
|
|
6
144
|
## [0.4.8] - 2026-08-20
|
|
7
145
|
|
|
8
146
|
### Fixed
|
|
@@ -3121,6 +3259,7 @@ otherwise have to discover by trial and error.
|
|
|
3121
3259
|
- Regression coverage for snapshot flat-`id` aliases on both MCP and
|
|
3122
3260
|
HTTP surfaces, plus async / top-level-`await` WebView script bodies.
|
|
3123
3261
|
|
|
3262
|
+
[0.5.0]: https://github.com/pskoett/pmx-canvas/compare/v0.4.8...v0.5.0
|
|
3124
3263
|
[0.4.8]: https://github.com/pskoett/pmx-canvas/compare/v0.4.7...v0.4.8
|
|
3125
3264
|
[0.4.7]: https://github.com/pskoett/pmx-canvas/releases/tag/v0.4.7
|
|
3126
3265
|
[0.4.6]: https://github.com/pskoett/pmx-canvas/releases/tag/v0.4.6
|
package/Readme.md
CHANGED
|
@@ -9,9 +9,10 @@ project. Pin what matters and the agent reads your spatial curation as
|
|
|
9
9
|
structured context.
|
|
10
10
|
|
|
11
11
|
<p align="center">
|
|
12
|
-
<img src="docs/screenshots/
|
|
13
|
-
<img src="docs/screenshots/
|
|
12
|
+
<img src="docs/screenshots/workbench-live-dark.png" alt="Live multi-agent workbench — dark theme: two agents on the board, review cards, an architecture diagram, and the writers pill" width="49%" />
|
|
13
|
+
<img src="docs/screenshots/workbench-live-light.png" alt="Live multi-agent workbench — light theme" width="49%" />
|
|
14
14
|
</p>
|
|
15
|
+
<p align="center"><sub>A real working board: two agents attached (the writers pill, top right), review cards they co-wrote, and a mermaid architecture diagram — dark and light themes.</sub></p>
|
|
15
16
|
|
|
16
17
|
<p align="center">
|
|
17
18
|
<img src="docs/screenshots/demo-workbench-dark.png" alt="Structured workbench demo — dark theme" width="49%" />
|
|
@@ -28,7 +29,11 @@ communication — proximity means relatedness, pinning means *focus here*.
|
|
|
28
29
|
|
|
29
30
|
### 01 / Curate
|
|
30
31
|
|
|
31
|
-
Drag, group, arrange, and **pin** nodes spatially.
|
|
32
|
+
Drag, group, arrange, and **pin** nodes spatially. Connect nodes with typed
|
|
33
|
+
edges — flow, depends-on, relation, references — drawn as smooth curves that
|
|
34
|
+
leave each card perpendicular to its border and bend toward their target.
|
|
35
|
+
Edges stay editable in place: right-click one to relabel, retype, restyle, or
|
|
36
|
+
delete it, or click it and press Delete. Curation is the channel
|
|
32
37
|
from human intent to agent context — the agent reads `canvas://pinned-context`
|
|
33
38
|
and `canvas://spatial-context` (proximity clusters, reading order, pinned
|
|
34
39
|
neighborhoods) and uses your layout to ground its next action.
|
|
@@ -38,7 +43,8 @@ neighborhoods) and uses your layout to ground its next action.
|
|
|
38
43
|
Files, web pages, screenshots, structured panels, charts, diagrams, embedded
|
|
39
44
|
MCP Apps, and bundled web artifacts all live on the same surface. A file node
|
|
40
45
|
follows the file on disk as the agent edits it — source with line numbers, a
|
|
41
|
-
CSV or TSV as a real table, a PDF inline
|
|
46
|
+
CSV or TSV as a real table, a PDF inline — and can be re-pointed at another
|
|
47
|
+
file (patch its `path`) without losing its edges, pins, or position. Anything else binary shows its name
|
|
42
48
|
and size rather than decoded gibberish, and a very large text file renders
|
|
43
49
|
truncated with a note saying so. Code review has its own node: a unified
|
|
44
50
|
diff with per-file headers and colored added and removed lines. Diagrams can
|
|
@@ -91,11 +97,16 @@ canvas into a shared workspace between you and the agent:
|
|
|
91
97
|
commands (`pmx.plan`, `pmx.review`, …), and read a tool/prompt policy.
|
|
92
98
|
- **Host capability** — adapters report what the host can do, for diagnostics.
|
|
93
99
|
|
|
94
|
-
Recent additions in this layer:
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
100
|
+
Recent additions in this layer: a **composer** steers directly from the board
|
|
101
|
+
(with a target picker when several agents are connected); the **session panel**
|
|
102
|
+
shows work items, approval gates, and a filterable timeline where every row
|
|
103
|
+
names its writer; a shared **undo stack** knows whether the human or the agent
|
|
104
|
+
made each edit, and undoing an agent's edit tells the agent as steering; a
|
|
105
|
+
**scope fence** confines an agent's writes to selected nodes; and grabbing a
|
|
106
|
+
node in the browser locks it against agent edits while you hold it. Dialogs,
|
|
107
|
+
prompts, and tooltips are all in-canvas — every flow works identically in
|
|
108
|
+
embedded browser panes (Copilot, Codex, Claude Code desktop) where native
|
|
109
|
+
browser UI doesn't exist.
|
|
99
110
|
|
|
100
111
|
Canvas-bound state (focus, work items, approvals, review annotations,
|
|
101
112
|
elicitations, mode requests, policy) rides canvas snapshots and restore; the
|
|
@@ -105,7 +116,40 @@ snapshot. Every primitive is reachable from MCP, the HTTP API, the SDK, and
|
|
|
105
116
|
GitHub Copilot app) map onto the same neutral surfaces without making PMX Canvas
|
|
106
117
|
vendor-specific.
|
|
107
118
|
|
|
108
|
-
### 05 /
|
|
119
|
+
### 05 / Run several agents on one board
|
|
120
|
+
|
|
121
|
+
The canvas is built for more than one writer. Every connected agent gets a
|
|
122
|
+
stable **identity color** carried by its cursor, its top-bar session chip, and
|
|
123
|
+
its minimap dot — you always see who is where. Workers roll up under their
|
|
124
|
+
orchestrator's chip (`+2 workers`), each session chip has its own End button,
|
|
125
|
+
and an idle board with no writers stays byte-clean.
|
|
126
|
+
|
|
127
|
+
<p align="center">
|
|
128
|
+
<img src="docs/screenshots/multi-agent-live.png" alt="Two agent sessions and three rolled-up workers live on one board — identity-colored cursors on the cards, per-chip End buttons, the attributed session timeline, and the all-agents composer" width="100%" />
|
|
129
|
+
</p>
|
|
130
|
+
<p align="center"><sub>Two agent sessions (Claude Code +3 workers, GitHub Copilot) and their cursors live on one board, with the writer-attributed timeline and the all-agents composer.</sub></p>
|
|
131
|
+
|
|
132
|
+
Steering is **addressed and reliable**: the composer's picker lists every
|
|
133
|
+
connected writer with live pump health (`· polling`, `· 2 queued`), an
|
|
134
|
+
addressed steer waits in that agent's delivery queue until the agent claims
|
|
135
|
+
and marks it, and broadcasts age out instead of greeting every future
|
|
136
|
+
consumer. Each host wakes its own way — the GitHub Copilot extension pumps
|
|
137
|
+
steers into real Copilot turns, Codex picks them up through its app-native
|
|
138
|
+
heartbeat, and any CLI agent becomes reactive with one command:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
pmx-canvas pump --consumer amp --exec 'amp -x {message}'
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
A failed hand-off is never silently swallowed: the steer stays pending and the
|
|
145
|
+
pump exits non-zero. Presence is honest too — "thinking" settles to idle after
|
|
146
|
+
silence, tool calls never clobber an explicit phase, and an attached writer
|
|
147
|
+
that has never made a spatial write still parks its cursor at its last
|
|
148
|
+
activity so it cannot be invisible. Agents coordinate with each other over the
|
|
149
|
+
same steering queue (agent-to-agent hand-offs, reviews, and territory splits),
|
|
150
|
+
and the `pmx-canvas-orchestration` skill documents the choreography.
|
|
151
|
+
|
|
152
|
+
### 06 / Save
|
|
109
153
|
|
|
110
154
|
Spatial state auto-saves to `.pmx-canvas/canvas.db` (debounced ~500 ms) —
|
|
111
155
|
git-committable, shareable across machines, and survives both browser
|
|
@@ -114,7 +158,7 @@ undo/redo, and an auto-detected code graph (JS/TS, Python, Go, Rust) make
|
|
|
114
158
|
the canvas durable rather than throwaway. Stop the server before committing
|
|
115
159
|
the DB so SQLite WAL data is checkpointed into the file.
|
|
116
160
|
|
|
117
|
-
###
|
|
161
|
+
### 07 / Any agent
|
|
118
162
|
|
|
119
163
|
Harness-agnostic. Drive the canvas from [MCP](docs/mcp.md) (22 tools,
|
|
120
164
|
14 resources, change notifications), the [CLI](docs/cli.md), the
|
|
@@ -124,7 +168,7 @@ Claude Code, the GitHub Copilot app and CLI, Codex, Amp, Cursor, Windsurf, or
|
|
|
124
168
|
any agent that can spawn an MCP stdio server, call a CLI, or hit an HTTP
|
|
125
169
|
endpoint.
|
|
126
170
|
|
|
127
|
-
###
|
|
171
|
+
### 08 / Native app adapters
|
|
128
172
|
|
|
129
173
|
PMX Canvas doesn't just run in a browser tab — it embeds **natively inside the
|
|
130
174
|
agent apps you already use**, as a thin adapter layer over the same neutral AX
|
|
@@ -153,11 +197,11 @@ surfaces (the core never imports a host SDK).
|
|
|
153
197
|
- **GitHub Copilot app** — a committed project canvas extension
|
|
154
198
|
(`.github/extensions/pmx-canvas/`) opens the live PMX workbench in a native
|
|
155
199
|
Copilot panel (light-themed by default to match the app, via the `?theme=`
|
|
156
|
-
session override),
|
|
157
|
-
|
|
158
|
-
review annotations, the AX timeline, and
|
|
159
|
-
into any repo with
|
|
160
|
-
preview).
|
|
200
|
+
session override), wakes Copilot when the board sends a steer, injects
|
|
201
|
+
pinned/focused AX context on prompt submission, and exposes actions for focus,
|
|
202
|
+
work items, approval gates, review annotations, the AX timeline, and
|
|
203
|
+
host-capability reporting. Install it into any repo with
|
|
204
|
+
`pmx-canvas copilot install-extension` (`--dry-run` to preview).
|
|
161
205
|
- **Codex app** — native through the Codex in-app Browser (opened to
|
|
162
206
|
`/workbench`) plus the PMX MCP server: agents read `canvas://ax-context` /
|
|
163
207
|
`canvas_ax_state { action: "get" }` and label Codex-originated focus with
|
|
@@ -222,9 +266,13 @@ bunx pmx-canvas --mcp # Run as MCP server (stdio)
|
|
|
222
266
|
bunx pmx-canvas --help # All commands
|
|
223
267
|
```
|
|
224
268
|
|
|
225
|
-
Themes are a
|
|
269
|
+
Themes are a tool-rail picker away too, and any one panel can override the
|
|
226
270
|
shared theme for itself with `?theme=<name|auto>`.
|
|
227
271
|
|
|
272
|
+
Press `?` on the board for the full keyboard map. Shortcuts follow the
|
|
273
|
+
platform — `⌘` on a Mac, `Ctrl` on Windows and Linux, where `Ctrl+Y` also
|
|
274
|
+
redoes and `Backspace`/`Delete` both delete the selection.
|
|
275
|
+
|
|
228
276
|
The canvas opens at `http://localhost:4313`. Try `--demo` first — it seeds a
|
|
229
277
|
showcase board with every node type on it: notes and status, files, a diff, a
|
|
230
278
|
CSV table, charts of every kind, mermaid diagrams, the HTML primitives,
|
|
@@ -307,7 +355,7 @@ services:
|
|
|
307
355
|
```bash
|
|
308
356
|
# .agents/setup — install the CLI (pin the exact version: a fresh orb running
|
|
309
357
|
# @latest can silently pick up a newer release than the one you validated)
|
|
310
|
-
npm install -g pmx-canvas@0.
|
|
358
|
+
npm install -g pmx-canvas@0.5.0
|
|
311
359
|
```
|
|
312
360
|
|
|
313
361
|
The server binds the portal-assigned `$PORT` automatically (gated on the
|
|
@@ -371,10 +419,11 @@ the agent can read `canvas://skills` and pull in companion skills
|
|
|
371
419
|
## Scope
|
|
372
420
|
|
|
373
421
|
- **Single-machine, today.** One canvas per `bunx pmx-canvas` instance, on
|
|
374
|
-
one machine. No built-in multi-user auth
|
|
375
|
-
|
|
376
|
-
pointed at the same `localhost:4313
|
|
377
|
-
|
|
422
|
+
one machine. No built-in multi-user auth — collaboration means humans and
|
|
423
|
+
agents on the same machine: every browser tab and every connected agent
|
|
424
|
+
pointed at the same `localhost:4313` shares one live board, with per-writer
|
|
425
|
+
presence (cursors, chips, minimap dots) for all of them. To share across
|
|
426
|
+
machines, commit `.pmx-canvas/canvas.db`.
|
|
378
427
|
- **What leaves your machine.** The core canvas runs entirely on
|
|
379
428
|
`localhost`. Network egress only happens for explicit, opt-in flows:
|
|
380
429
|
`webpage` nodes and remote-URL `image` nodes fetch the address you give
|