pmx-canvas 0.1.23 → 0.1.25

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 (54) hide show
  1. package/.github/extensions/pmx-canvas/extension.mjs +591 -0
  2. package/CHANGELOG.md +123 -0
  3. package/Readme.md +36 -5
  4. package/dist/canvas/global.css +36 -3
  5. package/dist/canvas/index.js +54 -54
  6. package/dist/types/client/nodes/ExtAppFrame.d.ts +1 -0
  7. package/dist/types/client/nodes/McpAppNode.d.ts +1 -0
  8. package/dist/types/client/nodes/iframe-document-url.d.ts +8 -0
  9. package/dist/types/client/state/intent-bridge.d.ts +4 -0
  10. package/dist/types/client/types.d.ts +1 -0
  11. package/dist/types/json-render/catalog.d.ts +1 -1
  12. package/dist/types/mcp/canvas-access.d.ts +9 -0
  13. package/dist/types/server/ax-context.d.ts +3 -0
  14. package/dist/types/server/ax-state.d.ts +43 -0
  15. package/dist/types/server/canvas-db.d.ts +5 -0
  16. package/dist/types/server/canvas-operations.d.ts +4 -0
  17. package/dist/types/server/canvas-state.d.ts +20 -3
  18. package/dist/types/server/index.d.ts +6 -0
  19. package/dist/types/server/mutation-history.d.ts +1 -1
  20. package/docs/cli.md +13 -0
  21. package/docs/http-api.md +24 -0
  22. package/docs/mcp.md +20 -2
  23. package/docs/plans/plan-004-pmx-ax-primitives.md +463 -0
  24. package/docs/screenshot.png +0 -0
  25. package/docs/sdk.md +5 -0
  26. package/package.json +2 -1
  27. package/skills/pmx-canvas/SKILL.md +14 -0
  28. package/skills/pmx-canvas/references/codex-app-adapter.md +110 -0
  29. package/skills/pmx-canvas/references/github-copilot-app-adapter.md +125 -0
  30. package/src/cli/agent.ts +34 -0
  31. package/src/cli/index.ts +2 -1
  32. package/src/client/App.tsx +2 -0
  33. package/src/client/canvas/CanvasNode.tsx +7 -0
  34. package/src/client/canvas/CommandPalette.tsx +2 -1
  35. package/src/client/canvas/use-node-drag.ts +29 -7
  36. package/src/client/canvas/use-node-resize.ts +27 -7
  37. package/src/client/nodes/ExtAppFrame.tsx +51 -10
  38. package/src/client/nodes/HtmlNode.tsx +5 -2
  39. package/src/client/nodes/McpAppNode.tsx +13 -1
  40. package/src/client/nodes/iframe-document-url.ts +58 -0
  41. package/src/client/state/intent-bridge.ts +8 -0
  42. package/src/client/state/sse-bridge.ts +3 -3
  43. package/src/client/theme/global.css +36 -3
  44. package/src/client/types.ts +1 -0
  45. package/src/mcp/canvas-access.ts +38 -0
  46. package/src/mcp/server.ts +113 -4
  47. package/src/server/ax-context.ts +38 -0
  48. package/src/server/ax-state.ts +130 -0
  49. package/src/server/canvas-db.ts +36 -1
  50. package/src/server/canvas-operations.ts +96 -4
  51. package/src/server/canvas-state.ts +123 -4
  52. package/src/server/index.ts +29 -2
  53. package/src/server/mutation-history.ts +12 -0
  54. package/src/server/server.ts +312 -14
@@ -24,6 +24,7 @@ export declare function resolveExtAppContainerDimensions(target: ExtAppHostDimen
24
24
  height: number;
25
25
  };
26
26
  export declare function shouldApplyExtAppSizeChange(height: unknown, isExpanded: boolean): height is number;
27
+ export declare function resolveExtAppInlineFrameHeight(appHeight: number, hostHeight: number): number;
27
28
  export declare function ExtAppFrame({ node, expanded }: {
28
29
  node: CanvasNodeState;
29
30
  expanded?: boolean;
@@ -1,4 +1,5 @@
1
1
  import type { CanvasNodeState } from '../types';
2
+ export declare function isSameOriginFrameDocumentUrl(url: string, origin?: string): boolean;
2
3
  export declare function McpAppNode({ node, expanded }: {
3
4
  node: CanvasNodeState;
4
5
  expanded?: boolean;
@@ -0,0 +1,8 @@
1
+ export declare function createIframeDocumentUrl(html: string, sandbox: string): Promise<string>;
2
+ export declare function useIframeDocument(html: string, sandbox: string): {
3
+ attributes: {
4
+ src?: string;
5
+ };
6
+ ready: boolean;
7
+ key: string;
8
+ };
@@ -22,6 +22,10 @@ export declare function openWorkbenchFile(path: string): Promise<{
22
22
  }>;
23
23
  /** Fetch canvas state from server. */
24
24
  export declare function fetchCanvasState(): Promise<Record<string, unknown>>;
25
+ export declare function saveCanvasTheme(theme: string): Promise<{
26
+ ok: boolean;
27
+ theme?: string;
28
+ }>;
25
29
  /** Fetch available slash commands for prompt completion. */
26
30
  export declare function fetchSlashCommands(): Promise<Array<{
27
31
  name: string;
@@ -58,6 +58,7 @@ export declare const EXCALIDRAW_CREATE_VIEW_TOOL = "create_view";
58
58
  export declare function isExcalidrawNode(node: CanvasNodeState): boolean;
59
59
  export interface CanvasLayout {
60
60
  viewport: ViewportState;
61
+ theme?: 'dark' | 'light' | 'high-contrast';
61
62
  nodes: CanvasNodeState[];
62
63
  edges: CanvasEdge[];
63
64
  annotations?: CanvasAnnotation[];
@@ -287,8 +287,8 @@ export declare const allComponentDefinitions: {
287
287
  props: z.ZodObject<{
288
288
  text: z.ZodString;
289
289
  variant: z.ZodNullable<z.ZodEnum<{
290
- default: "default";
291
290
  error: "error";
291
+ default: "default";
292
292
  success: "success";
293
293
  secondary: "secondary";
294
294
  destructive: "destructive";
@@ -1,4 +1,5 @@
1
1
  import { type CanvasLayout, type CanvasNodeState, type CanvasSnapshot, type PmxCanvas } from '../server/index.js';
2
+ import type { PmxAxSource } from '../server/ax-state.js';
2
3
  type AddNodeInput = Parameters<PmxCanvas['addNode']>[0];
3
4
  type AddWebpageNodeInput = Parameters<PmxCanvas['addWebpageNode']>[0];
4
5
  type RefreshWebpageNodeResult = Awaited<ReturnType<PmxCanvas['refreshWebpageNode']>>;
@@ -20,6 +21,9 @@ type ArrangeLayout = Parameters<PmxCanvas['arrange']>[0];
20
21
  type FocusNodeResult = ReturnType<PmxCanvas['focusNode']>;
21
22
  type FitViewOptions = Parameters<PmxCanvas['fitView']>[0];
22
23
  type FitViewResult = ReturnType<PmxCanvas['fitView']>;
24
+ type AxStateResult = ReturnType<PmxCanvas['getAxState']>;
25
+ type AxContextResult = ReturnType<PmxCanvas['getAxContext']>;
26
+ type SetAxFocusResult = ReturnType<PmxCanvas['setAxFocus']>;
23
27
  type SearchResult = ReturnType<PmxCanvas['search']>;
24
28
  type UndoRedoResult = Awaited<ReturnType<PmxCanvas['undo']>>;
25
29
  type HistoryResult = ReturnType<PmxCanvas['getHistory']>;
@@ -68,6 +72,11 @@ export interface CanvasAccess {
68
72
  noPan?: boolean;
69
73
  }): Promise<FocusNodeResult>;
70
74
  fitView(options?: FitViewOptions): Promise<FitViewResult>;
75
+ getAxState(): Promise<AxStateResult>;
76
+ getAxContext(): Promise<AxContextResult>;
77
+ setAxFocus(nodeIds: string[], options?: {
78
+ source?: PmxAxSource;
79
+ }): Promise<SetAxFocusResult>;
71
80
  clear(): Promise<void>;
72
81
  search(query: string): Promise<SearchResult>;
73
82
  undo(): Promise<UndoRedoResult>;
@@ -0,0 +1,3 @@
1
+ import { type PmxAxContext, type PmxAxPinnedContext } from './ax-state.js';
2
+ export declare function buildCanvasAxPinnedContext(): PmxAxPinnedContext;
3
+ export declare function buildCanvasAxContext(): PmxAxContext;
@@ -0,0 +1,43 @@
1
+ import type { CanvasLayout, CanvasNodeState } from './canvas-state.js';
2
+ import type { AgentContextNode } from './agent-context.js';
3
+ export type PmxAxSource = 'agent' | 'api' | 'browser' | 'cli' | 'codex' | 'copilot' | 'mcp' | 'sdk' | 'system';
4
+ export interface PmxAxFocusState {
5
+ nodeIds: string[];
6
+ primaryNodeId: string | null;
7
+ updatedAt: string | null;
8
+ source: PmxAxSource | null;
9
+ }
10
+ export interface PmxAxState {
11
+ version: 1;
12
+ focus: PmxAxFocusState;
13
+ }
14
+ export interface PmxAxPinnedContext {
15
+ preamble: string;
16
+ nodeIds: string[];
17
+ count: number;
18
+ nodes: AgentContextNode[];
19
+ }
20
+ export interface PmxAxFocusContext extends PmxAxFocusState {
21
+ nodes: AgentContextNode[];
22
+ }
23
+ export interface PmxAxContext {
24
+ version: 1;
25
+ generatedAt: string;
26
+ surface: {
27
+ nodeCount: number;
28
+ edgeCount: number;
29
+ };
30
+ pinned: PmxAxPinnedContext;
31
+ focus: PmxAxFocusContext;
32
+ }
33
+ export declare function createEmptyAxFocusState(): PmxAxFocusState;
34
+ export declare function createEmptyAxState(): PmxAxState;
35
+ export declare function normalizeAxFocusState(input: unknown, validNodeIds?: Set<string>): PmxAxFocusState;
36
+ export declare function normalizeAxState(input: unknown, validNodeIds?: Set<string>): PmxAxState;
37
+ export declare function buildAxContext(input: {
38
+ layout: CanvasLayout;
39
+ pinned: PmxAxPinnedContext;
40
+ focus: PmxAxFocusState;
41
+ focusNodes: AgentContextNode[];
42
+ }): PmxAxContext;
43
+ export declare function nodeSetFromLayout(nodes: CanvasNodeState[]): Set<string>;
@@ -6,13 +6,18 @@
6
6
  */
7
7
  import { Database } from 'bun:sqlite';
8
8
  import type { CanvasAnnotation, CanvasEdge, CanvasNodeState, CanvasSnapshot, CanvasSnapshotListOptions, ViewportState } from './canvas-state.js';
9
+ import { type PmxAxState } from './ax-state.js';
10
+ export type CanvasTheme = 'dark' | 'light' | 'high-contrast';
11
+ export declare function normalizeCanvasTheme(value: unknown, fallback?: CanvasTheme): CanvasTheme;
9
12
  export interface PersistedCanvasState {
10
13
  version: number;
14
+ theme?: CanvasTheme;
11
15
  viewport: ViewportState;
12
16
  nodes: CanvasNodeState[];
13
17
  edges: CanvasEdge[];
14
18
  annotations?: CanvasAnnotation[];
15
19
  contextPins: string[];
20
+ ax?: PmxAxState;
16
21
  }
17
22
  export declare function openCanvasDb(dbPath: string): Database;
18
23
  export declare function checkpointCanvasDb(db: Database): void;
@@ -60,6 +60,10 @@ export declare const MARKDOWN_NODE_DEFAULT_SIZE: {
60
60
  width: number;
61
61
  height: number;
62
62
  };
63
+ export declare const MCP_APP_NODE_DEFAULT_SIZE: {
64
+ width: number;
65
+ height: number;
66
+ };
63
67
  interface CanvasCreateGroupInput {
64
68
  title?: string;
65
69
  childIds?: string[];
@@ -9,7 +9,8 @@
9
9
  * in the workspace root on every mutation (debounced). Auto-loads on `loadFromDisk()`.
10
10
  * Legacy `.pmx-canvas/state.json` is auto-migrated on first boot.
11
11
  */
12
- import { type PersistedCanvasState } from './canvas-db.js';
12
+ import { type PersistedCanvasState, type CanvasTheme } from './canvas-db.js';
13
+ import { type PmxAxFocusState, type PmxAxSource, type PmxAxState } from './ax-state.js';
13
14
  export declare const PMX_CANVAS_DIR = ".pmx-canvas";
14
15
  export interface PersistedBlobRef {
15
16
  __pmxCanvasBlob: 'v1';
@@ -101,6 +102,7 @@ export interface CanvasAnnotation {
101
102
  }
102
103
  export interface CanvasLayout {
103
104
  viewport: ViewportState;
105
+ theme: CanvasTheme;
104
106
  nodes: CanvasNodeState[];
105
107
  edges: CanvasEdge[];
106
108
  annotations: CanvasAnnotation[];
@@ -118,9 +120,9 @@ export interface CanvasNodeUpdate {
118
120
  collapsed?: boolean;
119
121
  dockPosition?: 'left' | 'right' | null;
120
122
  }
121
- export type CanvasChangeType = 'pins' | 'nodes';
123
+ export type CanvasChangeType = 'pins' | 'nodes' | 'ax';
122
124
  export interface MutationRecordInfo {
123
- operationType: 'addNode' | 'updateNode' | 'removeNode' | 'addEdge' | 'removeEdge' | 'addAnnotation' | 'removeAnnotation' | 'clear' | 'restoreSnapshot' | 'setPins' | 'arrange' | 'batch' | 'groupNodes' | 'ungroupNodes' | 'viewport';
125
+ operationType: 'addNode' | 'updateNode' | 'removeNode' | 'addEdge' | 'removeEdge' | 'addAnnotation' | 'removeAnnotation' | 'clear' | 'restoreSnapshot' | 'setPins' | 'setAxFocus' | 'arrange' | 'batch' | 'groupNodes' | 'ungroupNodes' | 'viewport';
124
126
  description: string;
125
127
  forward: () => void;
126
128
  inverse: () => void;
@@ -138,7 +140,9 @@ declare class CanvasStateManager {
138
140
  private edges;
139
141
  private annotations;
140
142
  private _viewport;
143
+ private _theme;
141
144
  private _contextPinnedNodeIds;
145
+ private _axState;
142
146
  private _workspaceRoot;
143
147
  private _changeListeners;
144
148
  /** Register a listener for state changes. Used by MCP server to emit resource notifications. */
@@ -153,9 +157,13 @@ declare class CanvasStateManager {
153
157
  /** Create a closure that runs with recording suppressed. */
154
158
  private suppressed;
155
159
  private recordMutation;
160
+ private currentNodeIdSet;
161
+ private normalizeAxForCurrentNodes;
162
+ private applyAxState;
156
163
  private applyResolvedGroupBounds;
157
164
  private getGroupSnapshot;
158
165
  private normalizeNode;
166
+ private nodeForRead;
159
167
  private reflowAllGroups;
160
168
  private translateGroupChildren;
161
169
  private recomputeParentGroupBounds;
@@ -245,7 +253,16 @@ declare class CanvasStateManager {
245
253
  skipped: number;
246
254
  };
247
255
  setViewport(v: Partial<ViewportState>): void;
256
+ get theme(): CanvasTheme;
257
+ setTheme(theme: CanvasTheme): CanvasTheme;
248
258
  get contextPinnedNodeIds(): Set<string>;
259
+ getAxState(): PmxAxState;
260
+ getAxFocus(): PmxAxFocusState;
261
+ setAxFocus(nodeIds: string[], options?: {
262
+ source?: PmxAxSource;
263
+ recordHistory?: boolean;
264
+ }): PmxAxFocusState;
265
+ clearAxFocus(): PmxAxFocusState;
249
266
  setContextPins(nodeIds: string[]): void;
250
267
  clearContextPins(): void;
251
268
  /** Move child nodes into a group. Sets data.parentGroup on children and data.children on the group. */
@@ -1,5 +1,6 @@
1
1
  import { EventEmitter } from 'node:events';
2
2
  import type { CanvasAnnotation, CanvasNodeState, CanvasEdge, CanvasLayout } from './canvas-state.js';
3
+ import type { PmxAxContext, PmxAxFocusState, PmxAxSource, PmxAxState } from './ax-state.js';
3
4
  import { searchNodes } from './spatial-analysis.js';
4
5
  import { diffLayouts } from './mutation-history.js';
5
6
  import { fitCanvasView, gcCanvasSnapshots, listCanvasSnapshots } from './canvas-operations.js';
@@ -104,6 +105,11 @@ export declare class PmxCanvas extends EventEmitter {
104
105
  focused: string;
105
106
  panned: boolean;
106
107
  } | null;
108
+ getAxState(): PmxAxState;
109
+ getAxContext(): PmxAxContext;
110
+ setAxFocus(nodeIds: string[], options?: {
111
+ source?: PmxAxSource;
112
+ }): PmxAxFocusState;
107
113
  fitView(options?: {
108
114
  width?: number;
109
115
  height?: number;
@@ -12,7 +12,7 @@
12
12
  * - _replaying flag prevents undo/redo from recording new entries
13
13
  */
14
14
  import type { CanvasNodeState, CanvasEdge } from './canvas-state.js';
15
- export type MutationOp = 'addNode' | 'updateNode' | 'removeNode' | 'addEdge' | 'removeEdge' | 'addAnnotation' | 'removeAnnotation' | 'clear' | 'arrange' | 'restoreSnapshot' | 'setPins' | 'batch' | 'viewport' | 'groupNodes' | 'ungroupNodes';
15
+ export type MutationOp = 'addNode' | 'updateNode' | 'removeNode' | 'addEdge' | 'removeEdge' | 'addAnnotation' | 'removeAnnotation' | 'clear' | 'arrange' | 'restoreSnapshot' | 'setPins' | 'setAxFocus' | 'batch' | 'viewport' | 'groupNodes' | 'ungroupNodes';
16
16
  export interface MutationEntry {
17
17
  id: string;
18
18
  timestamp: string;
package/docs/cli.md CHANGED
@@ -112,6 +112,19 @@ pmx-canvas focus <node-id> # Pan viewport to a node
112
112
  pmx-canvas focus <node-id> --no-pan # Select/raise without panning
113
113
  ```
114
114
 
115
+ ## AX context
116
+
117
+ AX commands expose the host-agnostic context contract used by adapters. Pins
118
+ remain the explicit human-curated context set; AX focus is the current
119
+ attention target and persists with canvas state and snapshots.
120
+
121
+ ```bash
122
+ pmx-canvas ax status # Persisted AX state
123
+ pmx-canvas ax context # Pinned + focused agent context
124
+ pmx-canvas ax focus node-1 node-2 # Set AX focus
125
+ pmx-canvas ax focus --clear # Clear AX focus
126
+ ```
127
+
115
128
  ## WebView automation
116
129
 
117
130
  Drive a headless Bun.WebView (Chromium or WebKit) pointed at the workbench:
package/docs/http-api.md CHANGED
@@ -93,6 +93,30 @@ curl -X POST http://localhost:4313/api/canvas/context-pins \
93
93
  curl http://localhost:4313/api/canvas/pinned-context
94
94
  ```
95
95
 
96
+ ## AX context and focus
97
+
98
+ AX context is the host-agnostic agent-experience layer. It combines existing
99
+ context pins with a persisted focus node set that adapters can inject into
100
+ their native prompt/context hooks.
101
+
102
+ ```bash
103
+ # Get persisted AX state
104
+ curl http://localhost:4313/api/canvas/ax
105
+
106
+ # Get agent-readable pinned + focused context
107
+ curl http://localhost:4313/api/canvas/ax/context
108
+
109
+ # Set AX focus
110
+ curl -X POST http://localhost:4313/api/canvas/ax/focus \
111
+ -H "Content-Type: application/json" \
112
+ -d '{"nodeIds":["node-1"],"source":"api"}'
113
+
114
+ # Patch AX focus through the state endpoint
115
+ curl -X PATCH http://localhost:4313/api/canvas/ax \
116
+ -H "Content-Type: application/json" \
117
+ -d '{"focus":{"nodeIds":["node-1"],"source":"api"}}'
118
+ ```
119
+
96
120
  ## Diagrams (Excalidraw preset)
97
121
 
98
122
  ```bash
package/docs/mcp.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # MCP reference
2
2
 
3
- PMX Canvas ships an MCP stdio server with **42 tools** + **8 core resources**,
3
+ PMX Canvas ships an MCP stdio server with **45 tools** + **9 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.
@@ -51,6 +51,8 @@ searchable and readable in pinned/spatial context.
51
51
  | `canvas_arrange` | Auto-arrange (grid/column/flow) |
52
52
  | `canvas_validate` | Validate collisions, containment, and missing edge endpoints |
53
53
  | `canvas_focus_node` | Pan viewport to a node; use CLI `focus --no-pan` when you only need to select/raise |
54
+ | `canvas_get_ax` | Read the PMX AX state plus pinned/focused context |
55
+ | `canvas_set_ax_focus` | Set the host-agnostic AX focus node set; adapters can pass a source such as `codex` |
54
56
  | `canvas_pin_nodes` | Pin nodes to include in agent context |
55
57
  | `canvas_clear` | Clear all nodes and edges |
56
58
  | `canvas_snapshot` | Save current canvas as a named snapshot |
@@ -80,6 +82,8 @@ Individual bundled skills are also readable at `canvas://skills/<name>`.
80
82
  | Resource | Description |
81
83
  |----------|-------------|
82
84
  | `canvas://pinned-context` | Content of pinned nodes + nearby unpinned neighbors |
85
+ | `canvas://ax` | PMX AX state, currently including persisted focus |
86
+ | `canvas://ax-context` | Agent-readable pinned and focused AX context |
83
87
  | `canvas://schema` | Running-server create schemas and json-render catalog metadata |
84
88
  | `canvas://layout` | Full canvas state (all nodes, edges, viewport) |
85
89
  | `canvas://summary` | Compact overview: counts, pinned titles, viewport |
@@ -93,13 +97,27 @@ Individual bundled skills are also readable at `canvas://skills/<name>`.
93
97
  The MCP server emits `notifications/resources/updated` whenever canvas state
94
98
  changes:
95
99
 
96
- - Pin changes notify `canvas://pinned-context`
100
+ - Pin changes notify `canvas://pinned-context`, `canvas://ax`, and `canvas://ax-context`
101
+ - AX focus changes notify `canvas://ax` and `canvas://ax-context`
97
102
  - All mutations notify `canvas://layout`, `canvas://summary`,
98
103
  `canvas://spatial-context`, `canvas://history`, and `canvas://code-graph`
99
104
 
100
105
  This closes the human-to-agent loop: spatial curation in the browser becomes
101
106
  an immediate signal in the agent's context.
102
107
 
108
+ ## Codex App Adapter
109
+
110
+ In the Codex app, PMX Canvas runs natively through the existing Codex surfaces:
111
+ MCP for tools/resources and the in-app Browser for the live `/workbench` view.
112
+ No separate PMX renderer is needed. Prefer MCP over the CLI for Codex-native
113
+ operation; keep the CLI for fallback scripts and manual debugging.
114
+
115
+ Use `canvas://ax-context` or `canvas_get_ax` to read pinned/focused context.
116
+ When Codex-hosted steering sets the current attention target, call
117
+ `canvas_set_ax_focus` with `source: "codex"` so the AX state records where the
118
+ focus came from. The full workflow lives in
119
+ `skills/pmx-canvas/references/codex-app-adapter.md`.
120
+
103
121
  ## Annotation Visibility
104
122
 
105
123
  Human-drawn canvas annotations are rendered as browser SVG ink. MCP resources