pmx-canvas 0.3.3 → 0.4.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.
Files changed (87) hide show
  1. package/CHANGELOG.md +99 -0
  2. package/Readme.md +13 -4
  3. package/dist/canvas/index.js +71 -71
  4. package/dist/json-render/index.js +47 -47
  5. package/dist/types/cli/agent.d.ts +13 -8
  6. package/dist/types/cli/commands/apps.d.ts +1 -0
  7. package/dist/types/cli/commands/ax.d.ts +1 -0
  8. package/dist/types/cli/commands/copilot.d.ts +1 -0
  9. package/dist/types/cli/commands/edges.d.ts +1 -0
  10. package/dist/types/cli/commands/groups.d.ts +1 -0
  11. package/dist/types/cli/commands/history.d.ts +1 -0
  12. package/dist/types/cli/commands/nodes.d.ts +1 -0
  13. package/dist/types/cli/commands/pins.d.ts +1 -0
  14. package/dist/types/cli/commands/query.d.ts +1 -0
  15. package/dist/types/cli/commands/view.d.ts +1 -0
  16. package/dist/types/cli/commands/webview.d.ts +1 -0
  17. package/dist/types/cli/shared.d.ts +73 -0
  18. package/dist/types/client/canvas/ExpandedNodeOverlay.d.ts +1 -1
  19. package/dist/types/client/index.d.ts +1 -1
  20. package/dist/types/client/nodes/ExtAppFrame.d.ts +10 -0
  21. package/dist/types/client/nodes/HtmlNode.d.ts +1 -1
  22. package/dist/types/client/nodes/McpAppNode.d.ts +1 -1
  23. package/dist/types/client/nodes/use-ax-surface-bridge.d.ts +20 -0
  24. package/dist/types/client/state/node-factory.d.ts +29 -0
  25. package/dist/types/client/uuid-polyfill.d.ts +0 -0
  26. package/dist/types/server/canvas-db.d.ts +0 -1
  27. package/dist/types/server/canvas-operations.d.ts +2 -0
  28. package/dist/types/server/canvas-state.d.ts +14 -15
  29. package/dist/types/server/diagram-presets.d.ts +8 -0
  30. package/dist/types/server/operations/composites.d.ts +6 -30
  31. package/dist/types/server/server.d.ts +1 -0
  32. package/dist/types/shared/ax-surface-protocol.d.ts +18 -0
  33. package/docs/bun-webview-integration.md +5 -0
  34. package/docs/cli.md +5 -0
  35. package/docs/environment.md +48 -0
  36. package/docs/http-api.md +25 -1
  37. package/docs/mcp.md +21 -30
  38. package/docs/node-types.md +5 -0
  39. package/docs/plans/plan-009-tech-debt-backlog.md +34 -15
  40. package/docs/screenshot.png +0 -0
  41. package/docs/tech-debt-assessment-2026-06.md +5 -5
  42. package/package.json +13 -10
  43. package/skills/pmx-canvas/SKILL.md +16 -6
  44. package/skills/pmx-canvas/evals/evals.json +7 -7
  45. package/skills/pmx-canvas/references/full-reference.md +25 -16
  46. package/src/cli/agent.ts +22 -3512
  47. package/src/cli/commands/apps.ts +165 -0
  48. package/src/cli/commands/ax.ts +604 -0
  49. package/src/cli/commands/copilot.ts +43 -0
  50. package/src/cli/commands/edges.ts +73 -0
  51. package/src/cli/commands/groups.ts +81 -0
  52. package/src/cli/commands/history.ts +220 -0
  53. package/src/cli/commands/nodes.ts +984 -0
  54. package/src/cli/commands/pins.ts +42 -0
  55. package/src/cli/commands/query.ts +223 -0
  56. package/src/cli/commands/view.ts +186 -0
  57. package/src/cli/commands/webview.ts +227 -0
  58. package/src/cli/index.ts +8 -2
  59. package/src/cli/shared.ts +861 -0
  60. package/src/client/canvas/ExpandedNodeOverlay.tsx +3 -2
  61. package/src/client/index.tsx +2 -0
  62. package/src/client/nodes/ExtAppFrame.tsx +70 -51
  63. package/src/client/nodes/HtmlNode.tsx +8 -51
  64. package/src/client/nodes/McpAppNode.tsx +12 -47
  65. package/src/client/nodes/use-ax-surface-bridge.ts +71 -0
  66. package/src/client/state/node-factory.ts +72 -0
  67. package/src/client/state/sse-bridge.ts +209 -86
  68. package/src/client/uuid-polyfill.ts +14 -0
  69. package/src/json-render/renderer/index.tsx +3 -2
  70. package/src/mcp/server.ts +0 -47
  71. package/src/server/canvas-db.ts +0 -8
  72. package/src/server/canvas-operations.ts +8 -0
  73. package/src/server/canvas-schema.ts +2 -8
  74. package/src/server/canvas-state.ts +51 -151
  75. package/src/server/diagram-presets.ts +15 -1
  76. package/src/server/html-surface.ts +11 -6
  77. package/src/server/index.ts +3 -9
  78. package/src/server/operations/composites.ts +25 -34
  79. package/src/server/operations/invoker.ts +10 -3
  80. package/src/server/operations/mcp.ts +51 -1
  81. package/src/server/operations/ops/app.ts +5 -0
  82. package/src/server/operations/ops/snapshots.ts +6 -7
  83. package/src/server/operations/registry.ts +4 -1
  84. package/src/server/server.ts +172 -74
  85. package/src/shared/ax-surface-protocol.ts +22 -0
  86. package/dist/types/client/canvas/ContextPinHud.d.ts +0 -1
  87. package/src/client/canvas/ContextPinHud.tsx +0 -17
@@ -10,12 +10,17 @@
10
10
  * - Idempotent operations where possible
11
11
  * - --yes for destructive actions, --dry-run for preview
12
12
  */
13
- /**
14
- * Extract the global `--port <n>` / `--server-url <url>` flags (any position,
15
- * `=` or space-separated value) and set the invocation's target override.
16
- * Returns the remaining args for command dispatch. Invalid values are a loud
17
- * `die` — never a silent fallback to the default port. `--server-url` wins
18
- * over `--port` when both are given.
19
- */
20
- export declare function extractGlobalTargetFlags(args: string[]): string[];
13
+ import { extractGlobalTargetFlags } from './shared.js';
14
+ import './commands/nodes.js';
15
+ import './commands/edges.js';
16
+ import './commands/groups.js';
17
+ import './commands/view.js';
18
+ import './commands/query.js';
19
+ import './commands/pins.js';
20
+ import './commands/history.js';
21
+ import './commands/ax.js';
22
+ import './commands/webview.js';
23
+ import './commands/apps.js';
24
+ import './commands/copilot.js';
25
+ export { extractGlobalTargetFlags };
21
26
  export declare function runAgentCli(rawArgs: string[]): Promise<void>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Shared core for the agent-native CLI.
3
+ *
4
+ * Holds the command registry (COMMANDS + cmd()), flag parsing, output/die,
5
+ * target resolution (getBaseUrl + global --port/--server-url handling), the
6
+ * operation invoker, and every helper used by two or more command domains.
7
+ * Command modules in src/cli/commands/ import from here only — this module
8
+ * must never import from agent.ts or any command module (cycle-free).
9
+ */
10
+ declare function getBaseUrl(): string;
11
+ /**
12
+ * Extract the global `--port <n>` / `--server-url <url>` flags (any position,
13
+ * `=` or space-separated value) and set the invocation's target override.
14
+ * Returns the remaining args for command dispatch. Invalid values are a loud
15
+ * `die` — never a silent fallback to the default port. `--server-url` wins
16
+ * over `--port` when both are given.
17
+ */
18
+ export declare function extractGlobalTargetFlags(args: string[]): string[];
19
+ declare function die(message: string, hint?: string): never;
20
+ declare function output(data: unknown): void;
21
+ declare function invokeOperation(name: string, input: Record<string, unknown>): Promise<unknown>;
22
+ declare function parseFlags(args: string[]): {
23
+ positional: string[];
24
+ flags: Record<string, string | true>;
25
+ };
26
+ declare function requireFlag(flags: Record<string, string | true>, name: string, hint: string): string;
27
+ declare function getStringFlag(flags: Record<string, string | true>, ...names: string[]): string | undefined;
28
+ declare function optionalNumberFlag(flags: Record<string, string | true>, name: string, hint: string): number | undefined;
29
+ /**
30
+ * AX `source` for a CLI-originated action. Defaults to `cli`, but honors an
31
+ * explicit `--source <label>` so an adapterless agent using the CLI as a fallback
32
+ * transport (e.g. `--source codex`) attributes its actions correctly — keeping
33
+ * loop-safety (a consumer never gets back its own steering) accurate (report #69).
34
+ */
35
+ declare function resolveAxSource(flags: Record<string, string | true>): string;
36
+ declare function optionalFiniteFlag(flags: Record<string, string | true>, name: string, hint: string): number | undefined;
37
+ declare function optionalPositiveFiniteFlag(flags: Record<string, string | true>, name: string, hint: string): number | undefined;
38
+ declare function optionalPositiveFiniteFlagWithAliases(flags: Record<string, string | true>, hint: string, ...names: string[]): number | undefined;
39
+ declare function optionalBooleanFlag(flags: Record<string, string | true>, name: string, hint: string): boolean | undefined;
40
+ declare function applyStrictSizeFlags(body: Record<string, unknown>, flags: Record<string, string | true>): void;
41
+ declare function isRecord(value: unknown): value is Record<string, unknown>;
42
+ declare function parseJsonValue(raw: string, label: string, hint: string): unknown;
43
+ declare function readOptionalTextInput(flags: Record<string, string | true>, options: {
44
+ fileFlags?: string[];
45
+ valueFlags?: string[];
46
+ allowStdin?: boolean;
47
+ label: string;
48
+ hint: string;
49
+ }): Promise<string | undefined>;
50
+ declare function applyCommonGeometryFlags(body: Record<string, unknown>, flags: Record<string, string | true>, hints: {
51
+ x: string;
52
+ y: string;
53
+ width: string;
54
+ height: string;
55
+ }): void;
56
+ declare function buildJsonRenderRequestBody(flags: Record<string, string | true>): Promise<Record<string, unknown>>;
57
+ declare function buildHtmlPrimitiveRequestBody(flags: Record<string, string | true>): Promise<Record<string, unknown>>;
58
+ declare function buildGraphRequestBody(flags: Record<string, string | true>, options?: {
59
+ requireData?: boolean;
60
+ allowStdin?: boolean;
61
+ }): Promise<Record<string, unknown>>;
62
+ declare function runWebArtifactBuildCommand(flags: Record<string, string | true>): Promise<void>;
63
+ declare const COMMANDS: Record<string, {
64
+ run: (args: string[]) => Promise<void>;
65
+ help: string;
66
+ examples: string[];
67
+ }>;
68
+ declare const RESOURCE_COMMAND_ALIASES: Record<string, Record<string, string>>;
69
+ declare const RESOURCE_SUBCOMMAND_HINTS: Record<string, Record<string, string>>;
70
+ declare function cmd(name: string, help: string, examples: string[], run: (args: string[]) => Promise<void>): void;
71
+ declare function showCommandHelp(name: string): void;
72
+ declare function readStdin(): Promise<string>;
73
+ export { COMMANDS, RESOURCE_COMMAND_ALIASES, RESOURCE_SUBCOMMAND_HINTS, applyCommonGeometryFlags, applyStrictSizeFlags, buildGraphRequestBody, buildHtmlPrimitiveRequestBody, buildJsonRenderRequestBody, cmd, die, getBaseUrl, getStringFlag, invokeOperation, isRecord, optionalBooleanFlag, optionalFiniteFlag, optionalNumberFlag, optionalPositiveFiniteFlag, optionalPositiveFiniteFlagWithAliases, output, parseFlags, parseJsonValue, readOptionalTextInput, readStdin, requireFlag, resolveAxSource, runWebArtifactBuildCommand, showCommandHelp, };
@@ -1 +1 @@
1
- export declare function ExpandedNodeOverlay(): import("preact/src").JSX.Element | null;
1
+ export declare function ExpandedNodeOverlay(): import("preact/jsx-runtime").JSX.Element | null;
@@ -1 +1 @@
1
- export {};
1
+ import './uuid-polyfill';
@@ -51,6 +51,16 @@ export declare function buildExtAppAxBridgeScript(axToken: string, nodeId: strin
51
51
  * the host also checks event.source against the live iframe.
52
52
  */
53
53
  export declare function buildExtAppBootBeaconScript(frameToken: string, nodeId: string): string;
54
+ /**
55
+ * Base styles injected into every ext-app document (Finding Q, 0.3.4 report):
56
+ * apps aspect-fit their drawn surface to the frame width, and on narrow/tall
57
+ * tiles the leftover region shows the app document's own body background —
58
+ * pure black for the hosted Excalidraw bundle, in every engine. Making the
59
+ * app document transparent lets the host iframe's theme background
60
+ * (var(--c-panel)) show through the letterbox instead. Background only — app
61
+ * content, its drawn scene, and its own foreground styling are untouched.
62
+ */
63
+ export declare function buildExtAppSurfaceBaseStyles(): string;
54
64
  export declare function injectExtAppAxBridgeScript(html: string, axBridgeScript: string): string;
55
65
  export declare function resolveExtAppContainerDimensions(target: ExtAppHostDimensionsTarget | null | undefined, fallback: {
56
66
  width: number;
@@ -6,4 +6,4 @@ export declare function HtmlNode({ node, expanded, presentation, presentationExi
6
6
  presentation?: boolean;
7
7
  presentationExitToken?: string;
8
8
  autoFocus?: boolean;
9
- }): import("preact/src").JSX.Element;
9
+ }): import("preact/jsx-runtime").JSX.Element;
@@ -3,4 +3,4 @@ export declare function isSameOriginFrameDocumentUrl(url: string, origin?: strin
3
3
  export declare function McpAppNode({ node, expanded }: {
4
4
  node: CanvasNodeState;
5
5
  expanded?: boolean;
6
- }): import("preact/src").JSX.Element;
6
+ }): import("preact/jsx-runtime").JSX.Element;
@@ -0,0 +1,20 @@
1
+ import type { RefObject } from 'preact';
2
+ /** Sandboxed surfaces that emit through this bridge (clamped to their own node server-side). */
3
+ export type AxSurfaceBridgeSurface = 'json-render' | 'html-node' | 'mcp-app';
4
+ /**
5
+ * The sandboxed-surface AX trust boundary, single-sited (plan-009 M2; the
6
+ * listener was copy-pasted across HtmlNode, McpAppNode, and ExtAppFrame — a
7
+ * prior near-miss lived exactly in that duplication). Receives a
8
+ * `window.PMX_AX.emit(...)` message from THIS node's iframe, validates the
9
+ * event source against the live contentWindow plus the per-mount nonce token
10
+ * and node id, submits the interaction through the capability-gated endpoint
11
+ * (the server re-validates capabilities and clamps sandboxed surfaces to their
12
+ * own node), and acks the result back so the surface can self-confirm (#55).
13
+ */
14
+ export declare function useAxSurfaceBridge(options: {
15
+ enabled: boolean;
16
+ token: string;
17
+ nodeId: string;
18
+ sourceSurface: AxSurfaceBridgeSurface;
19
+ iframeRef: RefObject<HTMLIFrameElement>;
20
+ }): void;
@@ -0,0 +1,29 @@
1
+ import type { CanvasNodeState } from '../types';
2
+ export declare const DEFAULT_POSITIONS: Record<CanvasNodeState['type'], {
3
+ x: number;
4
+ y: number;
5
+ w: number;
6
+ h: number;
7
+ }> & Record<'prompt' | 'response', {
8
+ x: number;
9
+ y: number;
10
+ w: number;
11
+ h: number;
12
+ }>;
13
+ /**
14
+ * Build a canvas node with the shared client defaults. `position`/`size`
15
+ * default to the type's DEFAULT_POSITIONS entry; callers that compute
16
+ * placement (auto-placement, event-supplied geometry) pass explicit values.
17
+ * Status nodes sit at zIndex 0 (background chrome), everything else at 1.
18
+ */
19
+ export declare function makeNodeState(id: string, type: CanvasNodeState['type'], data: Record<string, unknown>, options?: {
20
+ position?: {
21
+ x: number;
22
+ y: number;
23
+ };
24
+ size?: {
25
+ width: number;
26
+ height: number;
27
+ };
28
+ dockPosition?: 'left' | 'right' | null;
29
+ }): CanvasNodeState;
File without changes
@@ -35,7 +35,6 @@ export declare function listSnapshotsFromDB(db: Database, options?: CanvasSnapsh
35
35
  export declare function deleteSnapshotFromDB(db: Database, id: string): boolean;
36
36
  export declare function writeBlobToDB(db: Database, sha256: string, jsonValue: string): number;
37
37
  export declare function readBlobFromDB(db: Database, sha256: string): string | null;
38
- export declare function hasBlobInDB(db: Database, sha256: string): boolean;
39
38
  export interface AxTimelineQuery {
40
39
  limit?: number;
41
40
  sessionId?: string;
@@ -128,6 +128,8 @@ export declare function validateCanvasNodePatch(patch: {
128
128
  export declare function mergeTraceNodeDataFields(base: Record<string, unknown>, input: Record<string, unknown>): Record<string, unknown>;
129
129
  export declare function hasTraceNodeDataFields(input: Record<string, unknown>): boolean;
130
130
  export declare function scheduleCodeGraphRecompute(onComplete?: () => void): void;
131
+ /** Clear a pending recompute so the timer cannot outlive its server (start/stop cycles, tests). */
132
+ export declare function cancelCodeGraphRecompute(): void;
131
133
  /**
132
134
  * Resolve an html-node `html` field that may be a path to a local .html/.htm file.
133
135
  *
@@ -7,7 +7,9 @@
7
7
  *
8
8
  * Persistence: canvas state auto-saves to `.pmx-canvas/canvas.db` (SQLite WAL mode)
9
9
  * in the workspace root on every mutation (debounced). Auto-loads on `loadFromDisk()`.
10
- * Legacy `.pmx-canvas/state.json` is auto-migrated on first boot.
10
+ * Legacy pre-0.2 formats (`.pmx-canvas/state.json`, `.pmx-canvas.json`,
11
+ * `.pmx-canvas-snapshots/`, loose blob files) are no longer imported as of 0.4.0 —
12
+ * the one-shot boot migration into SQLite was retired.
11
13
  */
12
14
  import { type PersistedCanvasState, type CanvasTheme, type AxTimelineQuery } from './canvas-db.js';
13
15
  import { type PmxAxActivityKind, type PmxAxElicitation, type PmxAxModeRequest, type PmxAxMode, type PmxAxCommandDescriptor, type PmxAxPolicy, type PmxAxFocusState, type PmxAxSource, type PmxAxState, type PmxAxWorkItem, type PmxAxWorkItemStatus, type PmxAxApprovalGate, type PmxAxReviewAnnotation, type PmxAxReviewKind, type PmxAxReviewSeverity, type PmxAxReviewStatus, type PmxAxReviewAnchorType, type PmxAxReviewRegion, type PmxAxEvent, type PmxAxEventKind, type PmxAxEvidence, type PmxAxEvidenceKind, type PmxAxSteeringMessage, type PmxAxHostCapability, type PmxAxTimelineSummary } from './ax-state.js';
@@ -155,7 +157,7 @@ declare class CanvasStateManager {
155
157
  private notifyChange;
156
158
  private _mutationRecorder;
157
159
  private _suppressRecordingDepth;
158
- /** Register a mutation recorder. Used by mutation-history to capture undo/redo closures. */
160
+ /** Register THE mutation recorder (single slot, last-write-wins — see note above). */
159
161
  onMutation(cb: (info: MutationRecordInfo) => void): void;
160
162
  /** Run a function with mutation recording suppressed (for undo/redo replay and computed edges). */
161
163
  withSuppressedRecording(fn: () => void): void;
@@ -171,7 +173,6 @@ declare class CanvasStateManager {
171
173
  private translateGroupChildren;
172
174
  private recomputeParentGroupBounds;
173
175
  private compactGroupChildren;
174
- private _stateFilePath;
175
176
  private _db;
176
177
  private _saveTimer;
177
178
  /** Workspace root backing persistence and workspace-relative path resolution. */
@@ -188,20 +189,9 @@ declare class CanvasStateManager {
188
189
  isBlobReference(value: unknown): value is PersistedBlobRef;
189
190
  resolveBlobReference(value: unknown): unknown;
190
191
  private externalizePersistedStateBlobs;
191
- /**
192
- * One-time migration: rename files from the pre-consolidation layout
193
- * (`.pmx-canvas.json` + `.pmx-canvas-snapshots/`) into `.pmx-canvas/`.
194
- * No-op when the new layout already exists.
195
- */
196
- private migrateLegacyLayout;
197
- /**
198
- * One-time migration: import state.json + snapshot JSON files + blob files
199
- * into the SQLite database. Renames originals to `.bak`.
200
- */
201
- private migrateJsonToSqlite;
202
192
  getWorkspaceRoot(): string;
203
193
  private emptyPersistedState;
204
- /** Load canvas state from SQLite (or legacy JSON fallback). Call once on server startup. */
194
+ /** Load canvas state from SQLite. Call once on server startup. */
205
195
  loadFromDisk(options?: LoadFromDiskOptions): boolean;
206
196
  /**
207
197
  * Whether this workspace's canvas DB already holds saved state. Used to gate
@@ -215,6 +205,15 @@ declare class CanvasStateManager {
215
205
  flushToDisk(): void;
216
206
  /** Write current state to SQLite immediately. */
217
207
  private saveToDisk;
208
+ private _lastPersistenceError;
209
+ /** Health view of state persistence: ok until a save fails, ok again after the next success. */
210
+ get persistenceHealth(): {
211
+ ok: boolean;
212
+ lastError: {
213
+ message: string;
214
+ at: string;
215
+ } | null;
216
+ };
218
217
  /** Close the SQLite database cleanly. Call on server shutdown. */
219
218
  close(): void;
220
219
  private get snapshotsDir();
@@ -7,6 +7,14 @@ export declare const EXCALIDRAW_SAVE_CHECKPOINT_TOOL = "save_checkpoint";
7
7
  export declare const EXCALIDRAW_READ_CHECKPOINT_TOOL = "read_checkpoint";
8
8
  export declare const DEFAULT_EXCALIDRAW_ELEMENTS: ReadonlyArray<Record<string, unknown>>;
9
9
  export declare const EXCALIDRAW_MCP_TRANSPORT: ExternalMcpTransportConfig;
10
+ /**
11
+ * The diagram preset targets the hosted Excalidraw MCP server. Tests set
12
+ * PMX_CANVAS_EXCALIDRAW_MCP_COMMAND (a stdio command line, e.g.
13
+ * "bun run tests/fixtures/mcp-app-fixture.ts") so the unit suite never does a
14
+ * real network round-trip — the hosted dependency made diagram tests time out
15
+ * under parallel load (plan-009 M8).
16
+ */
17
+ export declare function resolveExcalidrawTransport(): ExternalMcpTransportConfig;
10
18
  export interface DiagramPresetOpenInput {
11
19
  elements: unknown;
12
20
  nodeId?: string;
@@ -16,27 +16,12 @@
16
16
  * operation (plan-005 slices 1–7 + plan-008 Wave 1) that is now reachable ONLY
17
17
  * through its composite (or `canvas_batch`).
18
18
  *
19
- * Deferred to v0.4: the `canvas_snapshot` composite (still a name collision —
20
- * see below). Its 6 legacy snapshot standalones (`canvas_snapshot`,
21
- * `canvas_list_snapshots`, `canvas_restore`, `canvas_delete_snapshot`,
22
- * `canvas_gc_snapshots`, `canvas_diff`) stay registered in v0.3.0 but are marked
23
- * deprecated (description-prefixed) per docs/api-stability.md's
24
- * deprecate-one-minor-before-removal rule. (`canvas_webview` shipped in plan-008
25
- * Wave 3 via runner injection; `canvas_app` shipped in Wave 4 — open-mcp-app /
26
- * diagram / build-artifact. Wave 5 folded the last three legacy tools
27
- * deprecate-only — NO per-action input-injection mechanism was needed:
28
- * `canvas_add_html_node` / `canvas_add_html_primitive` → `canvas_node` action
29
- * "add" (type:"html" [+ primitive]); `canvas_refresh_webpage_node` → `canvas_node`
30
- * action "update" (refresh:true). `canvas_screenshot` stays standalone — it
31
- * returns a binary image payload the composite/registry JSON wire shape does not
32
- * model.)
33
- *
34
- * Not shipped here: the `canvas_snapshot` composite (plan-006 #7). Its target
35
- * name is ALREADY a legacy standalone tool (the save-snapshot tool, op
36
- * `snapshot.save`), so it cannot be added additively without a name clash, and
37
- * repurposing `canvas_snapshot` to be action-discriminated now would break
38
- * existing callers. It lands in v0.4, in the same change that removes the 6
39
- * kept-but-deprecated legacy snapshot tools and frees the name.
19
+ * v0.4.0 completed the fold: the `canvas_snapshot` composite shipped (its name
20
+ * was held by the legacy save-snapshot standalone through v0.3.x, which — with
21
+ * the other 5 snapshot standalones — was deprecated in 0.3.0 and removed here
22
+ * per docs/api-stability.md's deprecate-one-minor-before-removal rule).
23
+ * `canvas_screenshot` stays standalone — it returns a binary image payload the
24
+ * composite/registry JSON wire shape does not model.
40
25
  *
41
26
  * This module must never import server.ts or index.ts.
42
27
  */
@@ -95,15 +80,6 @@ export interface CompositeToolDefinition {
95
80
  kind: string;
96
81
  action: string;
97
82
  }) => string | undefined;
98
- /**
99
- * Two-discriminator extension: human-readable `(kind, action)` for a member op
100
- * (the inverse of `resolveOp`), used to build that op's deprecation note. The
101
- * `kind` field-collision is resolved here (see `gateFieldRemap`).
102
- */
103
- describeOp?: (opName: string) => {
104
- kind: string;
105
- action: string;
106
- } | undefined;
107
83
  /**
108
84
  * Field-name remap applied to the composite's advertised schema and undone at
109
85
  * dispatch. Resolves a collision between a discriminator name and a member-op
@@ -24,6 +24,7 @@
24
24
  * - GET /api/canvas/json-render/view?nodeId=... -> local json-render viewer
25
25
  * - POST /api/canvas/web-artifact -> build bundled HTML artifact + optional canvas node
26
26
  * - GET /api/workbench/events -> SSE event stream
27
+ * - GET /api/workbench/poll -> proxy-safe polling transport (same events, JSON)
27
28
  * - GET /api/workbench/state -> workbench state snapshot
28
29
  * - POST /api/workbench/intent -> workbench intents
29
30
  * - GET /api/workbench/webview -> Bun.WebView automation status
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Wire-protocol source tags for sandboxed-surface ↔ canvas postMessage traffic
3
+ * (plan-009 M2): one module owns the strings that used to be spread across the
4
+ * client node components, the server-side surface script generators, the
5
+ * ext-app script builders, and the json-render viewer. These are the trust
6
+ * boundary between opaque-origin iframes and the canvas — a typo'd tag fails
7
+ * SILENTLY (the listener just ignores the message), so single-siting them is a
8
+ * correctness measure, not cosmetics. The values are a persisted wire contract
9
+ * with already-generated surface documents: never change them.
10
+ */
11
+ /** A sandboxed surface emitting an AX interaction to the parent canvas. */
12
+ export declare const AX_SURFACE_EMIT_SOURCE = "pmx-canvas-ax";
13
+ /** The parent canvas acking an AX interaction back to the emitting surface (#55). */
14
+ export declare const AX_SURFACE_ACK_SOURCE = "pmx-canvas-ax-ack";
15
+ /** Parent→surface pushes (theme updates, live AX state) into html/viewer iframes. */
16
+ export declare const HTML_SURFACE_PUSH_SOURCE = "pmx-canvas-html-node";
17
+ /** Ext-app boot beacon: the iframe's scripts executed (WebKit watchdog liveness). */
18
+ export declare const EXT_APP_BOOT_BEACON_SOURCE = "pmx-canvas-ext-app-alive";
@@ -173,6 +173,11 @@ PMX Canvas follows Bun's current stable backend reality.
173
173
  - required on Linux and Windows
174
174
  - also supported on macOS
175
175
  - preferred when backend consistency or future CDP-based tooling matters
176
+ - can trigger a native "Allow remote debugging?" dialog on first start in some
177
+ hosts — approve it promptly or the start times out with `ok:false`. Raise
178
+ `PMX_CANVAS_WEBVIEW_TIMEOUT_MS` (e.g. `30000`) when the prompt or a slow
179
+ Chrome launch is expected, and retry once before classifying a timeout as a
180
+ failure.
176
181
 
177
182
  ### Current default behavior
178
183
 
package/docs/cli.md CHANGED
@@ -51,6 +51,11 @@ pmx-canvas node add --help --type webpage --json
51
51
  pmx-canvas external-app add --kind excalidraw --title "Diagram"
52
52
 
53
53
  pmx-canvas edge add --from-search "DVT O3 — GitOps" --to-search "deep work trend" --type relation
54
+
55
+ # Updates return a compact { ok, id, position, size, updatedFields } envelope;
56
+ # pass --full for the complete node payload (ext-app payloads can be huge).
57
+ pmx-canvas node update <node-id> --width 840 --height 620
58
+ pmx-canvas node update <node-id> --title "Renamed" --full
54
59
  ```
55
60
 
56
61
  `--from-search` / `--to-search` must each resolve to exactly one node — broad
@@ -0,0 +1,48 @@
1
+ # Environment variables
2
+
3
+ Every `PMX_*` variable the server, CLI, MCP transport, and build read. All are
4
+ optional; defaults are listed.
5
+
6
+ ## Ports and workspace targeting
7
+
8
+ | Variable | Read by | Effect |
9
+ |---|---|---|
10
+ | `PMX_WEB_CANVAS_PORT` | server, CLI `serve` | Preferred server port (default `4313`). CLI `--port` wins over it. |
11
+ | `PMX_CANVAS_PORT` | all entry points | Client-side target port for CLI commands and the `--mcp` lookup; as of 0.4.0 the server also falls back to it when `--port`/`PMX_WEB_CANVAS_PORT` are unset, so one var can drive the whole stack. |
12
+ | `PMX_CANVAS_URL` | agent CLI, MCP transport | Full base URL target (wins over `PMX_CANVAS_PORT`). |
13
+ | `PMX_CANVAS_WORKSPACE_ROOT` | server, MCP transport | Pins the workspace root for daemon binds and the MCP same-workspace lookup, overriding the launch cwd. Set it when a host spawns `--mcp` from an incidental dir. |
14
+ | `PMX_CANVAS_ALLOW_WORKSPACE_SPLIT` | MCP transport | `1` forces a separate canvas instead of attaching to a different-workspace daemon on the preferred port. |
15
+
16
+ ## Persistence
17
+
18
+ | Variable | Read by | Effect |
19
+ |---|---|---|
20
+ | `PMX_CANVAS_DB_PATH` | server | Overrides the SQLite path (default `.pmx-canvas/canvas.db`). |
21
+ | `PMX_CANVAS_STATE_FILE` | server | `.db`-path alias only as of 0.4.0 (non-`.db` values are ignored with a boot warning; the pre-0.2 JSON import was removed). |
22
+ | `PMX_CANVAS_BLOB_THRESHOLD_BYTES` | server | Node payloads above this many JSON bytes are stored as blobs (default `2048`). |
23
+
24
+ ## Server behavior
25
+
26
+ | Variable | Read by | Effect |
27
+ |---|---|---|
28
+ | `PMX_CANVAS_THEME` | server | Startup theme: `dark` (default), `light`, `high-contrast`. |
29
+ | `PMX_CANVAS_DISABLE_BROWSER_OPEN` | server | `1` suppresses auto-opening the browser (used by tests/CI). |
30
+ | `PMX_CANVAS_DIST` | server | Explicit client bundle directory to serve instead of the packaged `dist/canvas`. |
31
+ | `PMX_CANVAS_WEBVIEW_TIMEOUT_MS` | server | Startup timeout for the Bun.WebView automation session. |
32
+
33
+ ## MCP app host (security-relevant)
34
+
35
+ | Variable | Read by | Effect |
36
+ |---|---|---|
37
+ | `PMX_MCP_APP_HOST_MODE` | server | Gates which hosted MCP app servers may be opened. |
38
+ | `PMX_MCP_APP_HOST_ALLOWLIST` | server | Additional allowed MCP app server origins. |
39
+ | `PMX_MCP_APP_HOST_STATE_FILE` | server | Overrides the app-host session state file. |
40
+
41
+ ## Build and diagnostics
42
+
43
+ | Variable | Read by | Effect |
44
+ |---|---|---|
45
+ | `PMX_CANVAS_JSON_RENDER_DEVTOOLS` | server | `1` (plus `?devtools=1` on the viewer URL) enables the json-render devtools overlay. |
46
+ | `PMX_CANVAS_FORCE_JSON_RENDER_REBUILD` | json-render build | `1` forces the viewer bundle rebuild even when outputs exist. |
47
+ | `PMX_CANVAS_EXCALIDRAW_MCP_COMMAND` | server (tests) | Stdio command replacing the hosted Excalidraw MCP server for the diagram preset, e.g. `bun run tests/fixtures/mcp-app-fixture.ts`. |
48
+ | `PMX_SESSION_LOG` / `PMX_TEST_LOG` | server | Path for MCP-app host session diagnostics logging (session wins over test). |
package/docs/http-api.md CHANGED
@@ -5,7 +5,9 @@ any language. Default base URL: `http://localhost:4313`.
5
5
 
6
6
  A non-empty request body that is not valid JSON returns
7
7
  `400 { "ok": false, "error": "Malformed JSON body." }` on every route; empty
8
- bodies are treated as an empty request.
8
+ bodies are treated as an empty request. As of 0.4.0 every error response is a
9
+ JSON envelope of that same shape (`{ ok: false, error }`, status unchanged) —
10
+ no plain-text errors remain.
9
11
 
10
12
  ## Canvas state
11
13
 
@@ -321,6 +323,28 @@ curl -N http://localhost:4313/api/workbench/events
321
323
  The browser, the CLI `watch` command, and the MCP resource notifications
322
324
  all consume this stream. Auto-reconnect with exponential backoff.
323
325
 
326
+ ## Polling transport (proxy-safe)
327
+
328
+ Some proxies (e.g. the Amp orb portal) buffer streaming responses and only
329
+ flush them on close, so the SSE stream above delivers nothing. The polling
330
+ transport returns the same events as short-lived JSON responses that pass
331
+ through any proxy:
332
+
333
+ ```bash
334
+ # No `since`: a full connect snapshot (same events an SSE connect sends) + the
335
+ # current cursor. With `since=<seq>`: only events after that cursor.
336
+ curl "http://localhost:4313/api/workbench/poll"
337
+ curl "http://localhost:4313/api/workbench/poll?since=42"
338
+ ```
339
+
340
+ Responses are `{ ok, transport: "poll", snapshot, seq, events }` — poll again
341
+ with `since=<seq>` every couple of seconds. A `since` from a previous server
342
+ run or one that fell off the bounded event ring recovers with a fresh
343
+ snapshot. The browser client uses this automatically: if the SSE stream
344
+ produces no event within 3 seconds of connecting, it switches to polling.
345
+ Force a transport with `/workbench?transport=poll` (or `transport=sse` to
346
+ disable the fallback).
347
+
324
348
  ## Time travel
325
349
 
326
350
  ```bash
package/docs/mcp.md CHANGED
@@ -1,19 +1,17 @@
1
1
  # MCP reference
2
2
 
3
- PMX Canvas ships an MCP stdio server with **27 tools** + **14 core resources**,
3
+ PMX Canvas ships an MCP stdio server with **22 tools** + **14 core resources**,
4
4
  plus per-skill resources at `canvas://skills/<name>`. The server emits
5
5
  `notifications/resources/updated` when canvas state changes — humans pin
6
6
  nodes in the browser, agents are notified immediately.
7
7
 
8
- > **Consolidation completed in v0.3.0 (plan-006/008).** The MCP surface shrank
9
- > from 84 tools to 27: 15 action-discriminated **composites** (recommended —
10
- > see below) plus 12 standalone tools. The 57 legacy single-purpose tools that
11
- > the composites replaced were removed in v0.3.0 per
12
- > [`api-stability.md`](api-stability.md); their descriptions carried
13
- > `Deprecated:` prefixes for one minor before removal. The 6 snapshot tools
14
- > remain registered as deprecated standalones — they fold into a
15
- > `canvas_snapshot` composite in v0.4 (see [Standalone tools](#standalone-tools)
16
- > below). **Prefer the composites.**
8
+ > **Consolidation complete (plan-006/008).** The MCP surface shrank from 84
9
+ > tools to 22: 16 action-discriminated **composites** (recommended — see
10
+ > below) plus 6 standalone tools. The 57 legacy single-purpose tools the
11
+ > composites replaced were removed in v0.3.0, and v0.4.0 finished the fold by
12
+ > shipping the `canvas_snapshot` composite and removing the 6 deprecated
13
+ > snapshot standalones — each step per [`api-stability.md`](api-stability.md)'s
14
+ > deprecate-one-minor-before-removal rule. **Prefer the composites.**
17
15
 
18
16
  ## Connect
19
17
 
@@ -54,6 +52,7 @@ its `action` to the same operation the legacy tool used, so results are identica
54
52
  | `canvas_ax_timeline` | `read` · `record-event` · `add-evidence` · `send-steering` | `canvas_get_ax_timeline`, `canvas_record_ax_event`, `canvas_add_evidence`, `canvas_send_steering` |
55
53
  | `canvas_ax_delivery` | `claim` · `mark` | `canvas_claim_ax_delivery`, `canvas_mark_ax_delivery` |
56
54
  | `canvas_intent` | `signal` · `update` · `clear` | _(new — Ghost Cursor of Intent; no legacy standalone tool)_ |
55
+ | `canvas_snapshot` | `save` · `list` · `restore` · `delete` · `gc` · `diff` | `canvas_snapshot` (legacy save tool), `canvas_list_snapshots`, `canvas_restore`, `canvas_delete_snapshot`, `canvas_gc_snapshots`, `canvas_diff` — removed in v0.4.0 after one deprecated minor |
57
56
 
58
57
  ### `canvas_intent` — Ghost Cursor of Intent
59
58
 
@@ -89,20 +88,16 @@ tools). `canvas_screenshot` also stays standalone — it returns a binary image
89
88
  the composite/registry JSON wire shape does not model. (Wave 5 folded
90
89
  `canvas_refresh_webpage_node` → `canvas_node { action: "update", refresh: true }` after
91
90
  fixing `node.update`'s `formatResult` to surface a FAILED refresh as `isError` +
92
- `{ ok:false, error }` instead of masking it as a false `{ ok:true }`.) The 6
93
- snapshot tools are the one deferred fold: a `canvas_snapshot` composite is
94
- planned for v0.4 (the name collides with the legacy `canvas_snapshot` save
95
- tool, and [`api-stability.md`](api-stability.md) requires deprecating a tool
96
- for one minor before removing it) — they stay registered as deprecated
97
- standalones until then.
91
+ `{ ok:false, error }` instead of masking it as a false `{ ok:true }`.) The snapshot
92
+ fold completed in v0.4.0: `canvas_snapshot { action: "save" | "list" | "restore" |
93
+ "delete" | "gc" | "diff" }` replaced the 6 deprecated snapshot standalones.
98
94
 
99
95
  ## Standalone tools
100
96
 
101
- 12 tools that intentionally stay outside the composites — either because
102
- folding them would hurt (distinct callers, binary payloads, trust-boundary or
103
- execution-intent semantics), or because they're mid-deprecation pending a
104
- v0.4 composite. See [Migration reference](#migration-reference) below for the
105
- 57 tools these composites replaced.
97
+ 6 tools that intentionally stay outside the composites — folding them would
98
+ hurt (distinct callers, binary payloads, trust-boundary or execution-intent
99
+ semantics). See [Migration reference](#migration-reference) below for the
100
+ legacy tools the composites replaced.
106
101
 
107
102
  | Tool | Description |
108
103
  |------|-------------|
@@ -112,12 +107,6 @@ v0.4 composite. See [Migration reference](#migration-reference) below for the
112
107
  | `canvas_ax_interaction` | Submit one capability-gated AX interaction envelope (`{ type, sourceNodeId, payload }`) that maps onto an AX operation; the server re-validates and clamps sandboxed surfaces to their own node |
113
108
  | `canvas_ingest_activity` | Ingest a harness-forwarded agent activity (tool/session event); the board auto-reacts with kind-driven, overridable defaults (failure → work item + review + evidence; `tool-result`+success → evidence). Makes AX bidirectional |
114
109
  | `canvas_screenshot` | Capture a screenshot from the active workbench automation session |
115
- | `canvas_snapshot` | **Deprecated** → folds into the `canvas_snapshot` composite in v0.4 (actions `save\|list\|restore\|delete\|gc\|diff`). Save current canvas as a named snapshot |
116
- | `canvas_list_snapshots` | **Deprecated** → folds into the `canvas_snapshot` composite in v0.4 (actions `save\|list\|restore\|delete\|gc\|diff`). List saved snapshots, bounded to the newest 20 by default |
117
- | `canvas_restore` | **Deprecated** → folds into the `canvas_snapshot` composite in v0.4 (actions `save\|list\|restore\|delete\|gc\|diff`). Restore canvas from a saved snapshot |
118
- | `canvas_delete_snapshot` | **Deprecated** → folds into the `canvas_snapshot` composite in v0.4 (actions `save\|list\|restore\|delete\|gc\|diff`). Delete a saved snapshot |
119
- | `canvas_gc_snapshots` | **Deprecated** → folds into the `canvas_snapshot` composite in v0.4 (actions `save\|list\|restore\|delete\|gc\|diff`). Delete old snapshots while keeping the newest N |
120
- | `canvas_diff` | **Deprecated** → folds into the `canvas_snapshot` composite in v0.4 (actions `save\|list\|restore\|delete\|gc\|diff`). Compare current canvas vs a saved snapshot |
121
110
 
122
111
  `canvas_node { action: "add", type: "html" }` accepts optional `summary`, `agentSummary`,
123
112
  `embeddedNodeIds`, and `embeddedUrls`. PMX also derives a bounded text summary from visible
@@ -125,7 +114,9 @@ HTML, so rich HTML nodes stay searchable and readable in pinned/spatial context.
125
114
 
126
115
  ## Migration reference
127
116
 
128
- The 57 legacy single-purpose tools below were removed in v0.3.0. Each row is
117
+ The 57 legacy single-purpose tools below were removed in v0.3.0, and v0.4.0
118
+ additionally removed the 6 deprecated snapshot standalones (see the
119
+ `canvas_snapshot` composite row above for their replacements). Each row is
129
120
  the composite call that replaces it — kept as a lookup table for anyone
130
121
  migrating an older integration.
131
122
 
@@ -304,5 +295,5 @@ CLI and MCP are kept aligned for the main canvas operations: node and edge
304
295
  creation, graph/json-render/html/html-primitive nodes, web artifacts, external apps, groups,
305
296
  batch builds, layout validation, snapshots, search, focus, pins, undo/redo,
306
297
  semantic watch streams, WebView automation, and daemon/server control where
307
- it applies. A few agent-native capabilities — resource subscriptions and
308
- `canvas_diff` — remain MCP-only.
298
+ it applies. A few agent-native capabilities — such as resource
299
+ subscriptions — remain MCP-only.