pmx-canvas 0.1.36 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/CHANGELOG.md +447 -0
  2. package/Readme.md +2 -2
  3. package/dist/json-render/index.js +89 -334
  4. package/dist/types/mcp/canvas-access.d.ts +5 -171
  5. package/dist/types/server/ax-state-manager.d.ts +267 -0
  6. package/dist/types/server/ax-state.d.ts +3 -1
  7. package/dist/types/server/canvas-db.d.ts +13 -0
  8. package/dist/types/server/canvas-operations.d.ts +1 -12
  9. package/dist/types/server/canvas-state.d.ts +8 -23
  10. package/dist/types/server/index.d.ts +6 -24
  11. package/dist/types/server/operations/composites.d.ts +121 -0
  12. package/dist/types/server/operations/http.d.ts +7 -0
  13. package/dist/types/server/operations/index.d.ts +8 -0
  14. package/dist/types/server/operations/invoker.d.ts +13 -0
  15. package/dist/types/server/operations/mcp.d.ts +15 -0
  16. package/dist/types/server/operations/ops/annotation.d.ts +2 -0
  17. package/dist/types/server/operations/ops/app.d.ts +33 -0
  18. package/dist/types/server/operations/ops/ax-await.d.ts +2 -0
  19. package/dist/types/server/operations/ops/ax-shared.d.ts +31 -0
  20. package/dist/types/server/operations/ops/ax-state.d.ts +2 -0
  21. package/dist/types/server/operations/ops/ax-timeline.d.ts +2 -0
  22. package/dist/types/server/operations/ops/ax-work.d.ts +2 -0
  23. package/dist/types/server/operations/ops/batch.d.ts +19 -0
  24. package/dist/types/server/operations/ops/edges.d.ts +2 -0
  25. package/dist/types/server/operations/ops/groups.d.ts +2 -0
  26. package/dist/types/server/operations/ops/json-render.d.ts +31 -0
  27. package/dist/types/server/operations/ops/nodes.d.ts +62 -0
  28. package/dist/types/server/operations/ops/query.d.ts +2 -0
  29. package/dist/types/server/operations/ops/snapshots.d.ts +2 -0
  30. package/dist/types/server/operations/ops/validate.d.ts +2 -0
  31. package/dist/types/server/operations/ops/viewport.d.ts +2 -0
  32. package/dist/types/server/operations/ops/webview.d.ts +2 -0
  33. package/dist/types/server/operations/registry.d.ts +15 -0
  34. package/dist/types/server/operations/types.d.ts +116 -0
  35. package/dist/types/server/operations/webview-runner.d.ts +69 -0
  36. package/docs/RELEASE.md +5 -0
  37. package/docs/adr-001-bun-only-runtime.md +46 -0
  38. package/docs/api-stability.md +57 -0
  39. package/docs/ax-host-adapter-contract.md +19 -1
  40. package/docs/ax-state-contract.md +72 -0
  41. package/docs/http-api.md +4 -0
  42. package/docs/mcp.md +61 -12
  43. package/docs/plans/plan-005-operation-registry.md +84 -0
  44. package/docs/plans/plan-006-mcp-tool-consolidation.md +109 -0
  45. package/docs/plans/plan-007-ax-domain.md +99 -0
  46. package/docs/plans/plan-008-registry-finish.md +91 -0
  47. package/docs/tech-debt-assessment-2026-06.md +90 -0
  48. package/package.json +3 -3
  49. package/skills/pmx-canvas/SKILL.md +221 -193
  50. package/skills/pmx-canvas/evals/evals.json +3 -3
  51. package/skills/pmx-canvas/references/ax-html-control-surface.md +93 -0
  52. package/skills/pmx-canvas/references/codex-app-adapter.md +13 -14
  53. package/skills/pmx-canvas/references/github-copilot-app-adapter.md +26 -11
  54. package/src/cli/agent.ts +52 -31
  55. package/src/mcp/canvas-access.ts +30 -830
  56. package/src/mcp/server.ts +162 -2014
  57. package/src/server/ax-context.ts +8 -1
  58. package/src/server/ax-state-manager.ts +826 -0
  59. package/src/server/ax-state.ts +10 -2
  60. package/src/server/canvas-db.ts +35 -0
  61. package/src/server/canvas-operations.ts +2 -328
  62. package/src/server/canvas-schema.ts +2 -2
  63. package/src/server/canvas-state.ts +103 -465
  64. package/src/server/index.ts +54 -190
  65. package/src/server/operations/composites.ts +355 -0
  66. package/src/server/operations/http.ts +103 -0
  67. package/src/server/operations/index.ts +65 -0
  68. package/src/server/operations/invoker.ts +87 -0
  69. package/src/server/operations/mcp.ts +221 -0
  70. package/src/server/operations/ops/annotation.ts +60 -0
  71. package/src/server/operations/ops/app.ts +447 -0
  72. package/src/server/operations/ops/ax-await.ts +216 -0
  73. package/src/server/operations/ops/ax-shared.ts +38 -0
  74. package/src/server/operations/ops/ax-state.ts +249 -0
  75. package/src/server/operations/ops/ax-timeline.ts +381 -0
  76. package/src/server/operations/ops/ax-work.ts +635 -0
  77. package/src/server/operations/ops/batch.ts +365 -0
  78. package/src/server/operations/ops/edges.ts +166 -0
  79. package/src/server/operations/ops/groups.ts +176 -0
  80. package/src/server/operations/ops/json-render.ts +691 -0
  81. package/src/server/operations/ops/nodes.ts +1047 -0
  82. package/src/server/operations/ops/query.ts +281 -0
  83. package/src/server/operations/ops/snapshots.ts +366 -0
  84. package/src/server/operations/ops/validate.ts +37 -0
  85. package/src/server/operations/ops/viewport.ts +219 -0
  86. package/src/server/operations/ops/webview.ts +339 -0
  87. package/src/server/operations/registry.ts +79 -0
  88. package/src/server/operations/types.ts +150 -0
  89. package/src/server/operations/webview-runner.ts +77 -0
  90. package/src/server/server.ts +158 -2255
  91. package/src/server/web-artifacts.ts +6 -2
@@ -6,100 +6,48 @@ import {
6
6
  type CanvasEdge,
7
7
  type CanvasLayout,
8
8
  type CanvasNodeState,
9
- type CanvasSnapshot,
10
9
  type PmxCanvas,
11
10
  } from '../server/index.js';
12
11
  import type { PmxAxSource } from '../server/ax-state.js';
12
+ import {
13
+ HttpOperationInvoker,
14
+ LocalOperationInvoker,
15
+ type OperationInvoker,
16
+ } from '../server/operations/index.js';
13
17
 
14
- type AddNodeInput = Parameters<PmxCanvas['addNode']>[0];
15
- type AddWebpageNodeInput = Parameters<PmxCanvas['addWebpageNode']>[0];
16
18
  type RefreshWebpageNodeResult = Awaited<ReturnType<PmxCanvas['refreshWebpageNode']>>;
17
- type OpenMcpAppInput = Parameters<PmxCanvas['openMcpApp']>[0];
18
- type OpenMcpAppResult = Awaited<ReturnType<PmxCanvas['openMcpApp']>>;
19
- type AddDiagramInput = Parameters<PmxCanvas['addDiagram']>[0];
20
- type AddJsonRenderNodeInput = Parameters<PmxCanvas['addJsonRenderNode']>[0];
21
- type AddJsonRenderNodeResult = ReturnType<PmxCanvas['addJsonRenderNode']>;
22
- type StreamJsonRenderNodeInput = Parameters<PmxCanvas['streamJsonRenderNode']>[0];
23
- type StreamJsonRenderNodeResult = ReturnType<PmxCanvas['streamJsonRenderNode']>;
19
+ // openMcpApp / addDiagram / buildWebArtifact CanvasAccess methods + their type
20
+ // aliases removed with the standalone MCP tools (plan-008 Wave 4): those tools
21
+ // migrated to the operation registry (mcpapp.open / diagram.open /
22
+ // webartifact.build) and the composite/registry tools dispatch via the invoker,
23
+ // not CanvasAccess. The public SDK PmxCanvas methods are unchanged.
24
24
  type AddHtmlNodeInput = Parameters<PmxCanvas['addHtmlNode']>[0];
25
25
  type AddHtmlPrimitiveInput = Parameters<PmxCanvas['addHtmlPrimitive']>[0];
26
26
  type AddHtmlPrimitiveResult = ReturnType<PmxCanvas['addHtmlPrimitive']>;
27
- type AddGraphNodeInput = Parameters<PmxCanvas['addGraphNode']>[0];
28
- type AddGraphNodeResult = ReturnType<PmxCanvas['addGraphNode']>;
29
- type UpdateNodePatch = Parameters<PmxCanvas['updateNode']>[1];
30
- type AddEdgeInput = Parameters<PmxCanvas['addEdge']>[0];
31
- type CreateGroupInput = Parameters<PmxCanvas['createGroup']>[0];
32
- type GroupNodesOptions = Parameters<PmxCanvas['groupNodes']>[2];
33
- type ArrangeLayout = Parameters<PmxCanvas['arrange']>[0];
34
- type FocusNodeResult = ReturnType<PmxCanvas['focusNode']>;
35
- type FitViewOptions = Parameters<PmxCanvas['fitView']>[0];
36
- type FitViewResult = ReturnType<PmxCanvas['fitView']>;
37
27
  type AxStateResult = ReturnType<PmxCanvas['getAxState']>;
38
28
  type AxContextResult = ReturnType<PmxCanvas['getAxContext']>;
39
- type SetAxFocusResult = ReturnType<PmxCanvas['setAxFocus']>;
40
- type RecordAxEventInput = Parameters<PmxCanvas['recordAxEvent']>[0];
41
- type RecordAxEventResult = ReturnType<PmxCanvas['recordAxEvent']>;
42
- type SendSteeringResult = ReturnType<PmxCanvas['sendSteering']>;
43
29
  type SubmitAxInteractionInput = Parameters<PmxCanvas['submitAxInteraction']>[0];
44
30
  type SubmitAxInteractionResult = ReturnType<PmxCanvas['submitAxInteraction']>;
45
31
  type GetPendingSteeringResult = ReturnType<PmxCanvas['getPendingSteering']>;
46
32
  type ListElicitationsResult = ReturnType<PmxCanvas['listElicitations']>;
47
- type RequestElicitationInput = Parameters<PmxCanvas['requestElicitation']>[0];
48
- type RequestElicitationResult = ReturnType<PmxCanvas['requestElicitation']>;
49
- type RespondElicitationResult = ReturnType<PmxCanvas['respondElicitation']>;
50
33
  type ListModeRequestsResult = ReturnType<PmxCanvas['listModeRequests']>;
51
- type RequestModeInput = Parameters<PmxCanvas['requestMode']>[0];
52
- type RequestModeResult = ReturnType<PmxCanvas['requestMode']>;
53
- type ResolveModeRequestResult = ReturnType<PmxCanvas['resolveModeRequest']>;
54
34
  type IngestActivityInput = Parameters<PmxCanvas['ingestActivity']>[0];
55
35
  type IngestActivityResult = ReturnType<PmxCanvas['ingestActivity']>;
56
- type AwaitApprovalResult = Awaited<ReturnType<PmxCanvas['awaitApproval']>>;
57
- type AwaitElicitationResult = Awaited<ReturnType<PmxCanvas['awaitElicitation']>>;
58
- type AwaitModeResult = Awaited<ReturnType<PmxCanvas['awaitMode']>>;
59
- type GetCommandRegistryResult = ReturnType<PmxCanvas['getCommandRegistry']>;
60
- type InvokeCommandResult = ReturnType<PmxCanvas['invokeCommand']>;
61
36
  type GetPolicyResult = ReturnType<PmxCanvas['getPolicy']>;
62
- type SetPolicyInput = Parameters<PmxCanvas['setPolicy']>[0];
63
- type SetPolicyResult = ReturnType<PmxCanvas['setPolicy']>;
64
37
  type GetAxTimelineQuery = Parameters<PmxCanvas['getAxTimeline']>[0];
65
38
  type GetAxTimelineResult = ReturnType<PmxCanvas['getAxTimeline']>;
66
- type AddWorkItemInput = Parameters<PmxCanvas['addWorkItem']>[0];
67
- type AddWorkItemResult = ReturnType<PmxCanvas['addWorkItem']>;
68
- type UpdateWorkItemPatch = Parameters<PmxCanvas['updateWorkItem']>[1];
69
- type UpdateWorkItemResult = ReturnType<PmxCanvas['updateWorkItem']>;
70
39
  type ListWorkItemsResult = ReturnType<PmxCanvas['listWorkItems']>;
71
- type RequestApprovalInput = Parameters<PmxCanvas['requestApproval']>[0];
72
- type RequestApprovalResult = ReturnType<PmxCanvas['requestApproval']>;
73
- type ResolveApprovalResult = ReturnType<PmxCanvas['resolveApproval']>;
74
40
  type ListApprovalGatesResult = ReturnType<PmxCanvas['listApprovalGates']>;
75
- type AddEvidenceInput = Parameters<PmxCanvas['addEvidence']>[0];
76
- type AddEvidenceResult = ReturnType<PmxCanvas['addEvidence']>;
77
- type AddReviewAnnotationInput = Parameters<PmxCanvas['addReviewAnnotation']>[0];
78
- type AddReviewAnnotationResult = ReturnType<PmxCanvas['addReviewAnnotation']>;
79
- type UpdateReviewAnnotationPatch = Parameters<PmxCanvas['updateReviewAnnotation']>[1];
80
- type UpdateReviewAnnotationResult = ReturnType<PmxCanvas['updateReviewAnnotation']>;
81
41
  type ListReviewAnnotationsResult = ReturnType<PmxCanvas['listReviewAnnotations']>;
82
- type GetHostCapabilityResult = ReturnType<PmxCanvas['getHostCapability']>;
83
- type ReportHostCapabilityResult = ReturnType<PmxCanvas['reportHostCapability']>;
84
- type SearchResult = ReturnType<PmxCanvas['search']>;
85
- type UndoRedoResult = Awaited<ReturnType<PmxCanvas['undo']>>;
86
42
  type HistoryResult = ReturnType<PmxCanvas['getHistory']>;
87
- type SetContextPinsResult = ReturnType<PmxCanvas['setContextPins']>;
88
43
  type RunBatchInput = Parameters<PmxCanvas['runBatch']>[0];
89
44
  type RunBatchResult = Awaited<ReturnType<PmxCanvas['runBatch']>>;
90
- type SnapshotListOptions = Parameters<PmxCanvas['listSnapshots']>[0];
91
- type SnapshotList = ReturnType<PmxCanvas['listSnapshots']>;
92
- type DeleteSnapshotResult = ReturnType<PmxCanvas['deleteSnapshot']>;
93
- type GcSnapshotsOptions = Parameters<PmxCanvas['gcSnapshots']>[0];
94
- type GcSnapshotsResult = ReturnType<PmxCanvas['gcSnapshots']>;
95
- type DiffSnapshotResult = ReturnType<PmxCanvas['diffSnapshot']>;
96
45
  type CodeGraphResult = ReturnType<PmxCanvas['getCodeGraph']>;
97
- type ValidationResult = ReturnType<PmxCanvas['validate']>;
98
- type WebArtifactInput = Parameters<PmxCanvas['buildWebArtifact']>[0];
99
- type WebArtifactResult = Awaited<ReturnType<PmxCanvas['buildWebArtifact']>>;
100
- type AutomationWebViewOptions = Parameters<PmxCanvas['startAutomationWebView']>[0];
101
- type AutomationWebViewStatus = Awaited<ReturnType<PmxCanvas['startAutomationWebView']>>;
102
- type AutomationEvaluateResult = Awaited<ReturnType<PmxCanvas['evaluateAutomationWebView']>>;
46
+ // canvas_screenshot (the only webview tool still hand-written) needs the status
47
+ // + screenshot accessors; the other four webview methods (start/stop/evaluate/
48
+ // resize) migrated to the operation registry (plan-008 Wave 3) and were removed
49
+ // from CanvasAccess.
50
+ type AutomationWebViewStatus = Awaited<ReturnType<PmxCanvas['getAutomationWebViewStatus']>>;
103
51
  type AutomationScreenshotOptions = Parameters<PmxCanvas['screenshotAutomationWebView']>[0];
104
52
 
105
53
  interface HealthResponse {
@@ -112,124 +60,41 @@ interface NodeResponse {
112
60
  node?: { id?: string };
113
61
  }
114
62
 
115
- interface JsonRenderNodeResponse extends NodeResponse {
116
- url: string;
117
- spec: AddJsonRenderNodeResult['spec'];
118
- }
119
-
120
- interface GraphNodeResponse extends NodeResponse {
121
- url: string;
122
- spec: AddGraphNodeResult['spec'];
123
- }
124
-
125
- interface SearchResponse {
126
- results?: SearchResult;
127
- }
128
-
129
- interface SnapshotSaveResponse {
130
- snapshot?: CanvasSnapshot;
131
- }
132
-
133
- interface WebViewEnvelope {
134
- webview?: AutomationWebViewStatus;
135
- }
136
-
137
- interface WebViewStopEnvelope extends WebViewEnvelope {
138
- stopped?: boolean;
139
- }
140
-
141
- interface WebViewEvaluateEnvelope {
142
- value?: AutomationEvaluateResult;
143
- }
144
-
145
63
  export interface CanvasAccess {
146
64
  readonly port: number;
147
65
  readonly remoteBaseUrl: string | null;
66
+ /** Operation-registry invoker (plan-005): local in-process or HTTP, matching the access mode. */
67
+ invoker(): OperationInvoker;
148
68
  getLayout(): Promise<CanvasLayout>;
149
69
  getNode(id: string): Promise<CanvasNodeState | undefined>;
150
- addNode(input: AddNodeInput): Promise<string>;
151
- addWebpageNode(input: AddWebpageNodeInput): Promise<Awaited<ReturnType<PmxCanvas['addWebpageNode']>>>;
152
70
  refreshWebpageNode(id: string, url?: string): Promise<RefreshWebpageNodeResult>;
153
- openMcpApp(input: OpenMcpAppInput): Promise<OpenMcpAppResult>;
154
- addDiagram(input: AddDiagramInput): Promise<OpenMcpAppResult>;
155
- addJsonRenderNode(input: AddJsonRenderNodeInput): Promise<AddJsonRenderNodeResult>;
156
- streamJsonRenderNode(input: StreamJsonRenderNodeInput): Promise<StreamJsonRenderNodeResult>;
157
71
  addHtmlNode(input: AddHtmlNodeInput): Promise<string>;
158
72
  addHtmlPrimitive(input: AddHtmlPrimitiveInput): Promise<AddHtmlPrimitiveResult>;
159
- addGraphNode(input: AddGraphNodeInput): Promise<AddGraphNodeResult>;
160
- buildWebArtifact(input: WebArtifactInput): Promise<WebArtifactResult>;
161
- updateNode(id: string, patch: UpdateNodePatch): Promise<void>;
162
- removeNode(id: string): Promise<void>;
163
- removeAnnotation(id: string): Promise<boolean>;
164
- addEdge(input: AddEdgeInput): Promise<string>;
165
- removeEdge(id: string): Promise<void>;
166
- createGroup(input: CreateGroupInput): Promise<string>;
167
- groupNodes(groupId: string, childIds: string[], options?: GroupNodesOptions): Promise<boolean>;
168
- ungroupNodes(groupId: string): Promise<boolean>;
169
- arrange(layout?: ArrangeLayout): Promise<void>;
170
- focusNode(id: string, options?: { noPan?: boolean }): Promise<FocusNodeResult>;
171
- fitView(options?: FitViewOptions): Promise<FitViewResult>;
172
73
  getAxState(): Promise<AxStateResult>;
173
74
  getAxContext(options?: { consumer?: string }): Promise<AxContextResult>;
174
- setAxFocus(nodeIds: string[], options?: { source?: PmxAxSource }): Promise<SetAxFocusResult>;
175
- recordAxEvent(input: RecordAxEventInput, options?: { source?: PmxAxSource }): Promise<RecordAxEventResult>;
176
- sendSteering(message: string, options?: { source?: PmxAxSource }): Promise<SendSteeringResult>;
177
75
  getAxTimeline(query?: GetAxTimelineQuery): Promise<GetAxTimelineResult>;
178
- addWorkItem(input: AddWorkItemInput, options?: { source?: PmxAxSource }): Promise<AddWorkItemResult>;
179
- updateWorkItem(id: string, patch: UpdateWorkItemPatch, options?: { source?: PmxAxSource }): Promise<UpdateWorkItemResult>;
180
76
  listWorkItems(): Promise<ListWorkItemsResult>;
181
- requestApproval(input: RequestApprovalInput, options?: { source?: PmxAxSource }): Promise<RequestApprovalResult>;
182
- resolveApproval(id: string, decision: 'approved' | 'rejected', options?: { resolution?: string; source?: PmxAxSource }): Promise<ResolveApprovalResult>;
183
77
  listApprovalGates(): Promise<ListApprovalGatesResult>;
184
- addEvidence(input: AddEvidenceInput, options?: { source?: PmxAxSource }): Promise<AddEvidenceResult>;
185
- addReviewAnnotation(input: AddReviewAnnotationInput, options?: { source?: PmxAxSource }): Promise<AddReviewAnnotationResult>;
186
- updateReviewAnnotation(id: string, patch: UpdateReviewAnnotationPatch, options?: { source?: PmxAxSource }): Promise<UpdateReviewAnnotationResult>;
187
78
  listReviewAnnotations(): Promise<ListReviewAnnotationsResult>;
188
- getHostCapability(): Promise<GetHostCapabilityResult>;
189
- reportHostCapability(input: unknown, options?: { source?: PmxAxSource }): Promise<ReportHostCapabilityResult>;
190
79
  submitAxInteraction(input: SubmitAxInteractionInput, options?: { source?: PmxAxSource }): Promise<SubmitAxInteractionResult>;
191
80
  getPendingSteering(options?: { consumer?: string; limit?: number }): Promise<GetPendingSteeringResult>;
192
- markSteeringDelivered(id: string): Promise<boolean>;
193
81
  listElicitations(): Promise<ListElicitationsResult>;
194
- requestElicitation(input: RequestElicitationInput, options?: { source?: PmxAxSource }): Promise<RequestElicitationResult>;
195
- respondElicitation(id: string, response: Record<string, unknown>, options?: { source?: PmxAxSource }): Promise<RespondElicitationResult>;
196
82
  listModeRequests(): Promise<ListModeRequestsResult>;
197
- requestMode(input: RequestModeInput, options?: { source?: PmxAxSource }): Promise<RequestModeResult>;
198
- resolveModeRequest(id: string, decision: 'approved' | 'rejected', options?: { resolution?: string; source?: PmxAxSource }): Promise<ResolveModeRequestResult>;
199
83
  ingestActivity(input: IngestActivityInput, options?: { source?: PmxAxSource }): Promise<IngestActivityResult>;
200
- awaitApproval(id: string, options?: { timeoutMs?: number }): Promise<AwaitApprovalResult>;
201
- awaitElicitation(id: string, options?: { timeoutMs?: number }): Promise<AwaitElicitationResult>;
202
- awaitMode(id: string, options?: { timeoutMs?: number }): Promise<AwaitModeResult>;
203
- getCommandRegistry(): Promise<GetCommandRegistryResult>;
204
- invokeCommand(name: string, args?: Record<string, unknown> | null, options?: { source?: PmxAxSource }): Promise<InvokeCommandResult>;
205
84
  getPolicy(): Promise<GetPolicyResult>;
206
- setPolicy(patch: SetPolicyInput, options?: { source?: PmxAxSource }): Promise<SetPolicyResult>;
207
- clear(): Promise<void>;
208
- search(query: string): Promise<SearchResult>;
209
- undo(): Promise<UndoRedoResult>;
210
- redo(): Promise<UndoRedoResult>;
211
85
  getHistory(): Promise<HistoryResult>;
212
- setContextPins(nodeIds: string[], mode?: 'set' | 'add' | 'remove'): Promise<SetContextPinsResult>;
213
86
  getPinnedNodeIds(): Promise<string[]>;
214
87
  runBatch(operations: RunBatchInput): Promise<RunBatchResult>;
215
- listSnapshots(options?: SnapshotListOptions): Promise<SnapshotList>;
216
- saveSnapshot(name: string): Promise<CanvasSnapshot | null>;
217
- restoreSnapshot(id: string): Promise<{ ok: boolean }>;
218
- deleteSnapshot(id: string): Promise<DeleteSnapshotResult>;
219
- gcSnapshots(options?: GcSnapshotsOptions): Promise<GcSnapshotsResult>;
220
- diffSnapshot(idOrName: string): Promise<DiffSnapshotResult>;
221
88
  getCodeGraph(): Promise<CodeGraphResult>;
222
- validate(): Promise<ValidationResult>;
89
+ // canvas_screenshot (still hand-written — binary payload) is the only webview
90
+ // tool left on CanvasAccess; it needs the status + screenshot accessors.
223
91
  getAutomationWebViewStatus(): Promise<AutomationWebViewStatus>;
224
- startAutomationWebView(options?: AutomationWebViewOptions): Promise<AutomationWebViewStatus>;
225
- stopAutomationWebView(): Promise<boolean>;
226
- evaluateAutomationWebView(expression: string): Promise<AutomationEvaluateResult>;
227
- resizeAutomationWebView(width: number, height: number): Promise<AutomationWebViewStatus>;
228
92
  screenshotAutomationWebView(options?: AutomationScreenshotOptions): Promise<Uint8Array>;
229
93
  }
230
94
 
231
95
  class LocalCanvasAccess implements CanvasAccess {
232
96
  readonly remoteBaseUrl = null;
97
+ private readonly operationInvoker = new LocalOperationInvoker();
233
98
 
234
99
  constructor(
235
100
  private readonly canvas: PmxCanvas,
@@ -241,6 +106,10 @@ class LocalCanvasAccess implements CanvasAccess {
241
106
  return this.canvas.port;
242
107
  }
243
108
 
109
+ invoker(): OperationInvoker {
110
+ return this.operationInvoker;
111
+ }
112
+
244
113
  async getLayout(): Promise<CanvasLayout> {
245
114
  return this.canvas.getLayout();
246
115
  }
@@ -249,36 +118,10 @@ class LocalCanvasAccess implements CanvasAccess {
249
118
  return this.canvas.getNode(id);
250
119
  }
251
120
 
252
- async addNode(input: AddNodeInput): Promise<string> {
253
- // PmxCanvas.addNode returns the created node; the CanvasAccess contract
254
- // (shared with the remote proxy + MCP) stays id-only.
255
- return this.canvas.addNode(input).id;
256
- }
257
-
258
- async addWebpageNode(input: AddWebpageNodeInput): Promise<Awaited<ReturnType<PmxCanvas['addWebpageNode']>>> {
259
- return await this.canvas.addWebpageNode(input);
260
- }
261
-
262
121
  async refreshWebpageNode(id: string, url?: string): Promise<RefreshWebpageNodeResult> {
263
122
  return await this.canvas.refreshWebpageNode(id, url);
264
123
  }
265
124
 
266
- async openMcpApp(input: OpenMcpAppInput): Promise<OpenMcpAppResult> {
267
- return await this.canvas.openMcpApp(input);
268
- }
269
-
270
- async addDiagram(input: AddDiagramInput): Promise<OpenMcpAppResult> {
271
- return await this.canvas.addDiagram(input);
272
- }
273
-
274
- async addJsonRenderNode(input: AddJsonRenderNodeInput): Promise<AddJsonRenderNodeResult> {
275
- return this.canvas.addJsonRenderNode(input);
276
- }
277
-
278
- async streamJsonRenderNode(input: StreamJsonRenderNodeInput): Promise<StreamJsonRenderNodeResult> {
279
- return this.canvas.streamJsonRenderNode(input);
280
- }
281
-
282
125
  async addHtmlNode(input: AddHtmlNodeInput): Promise<string> {
283
126
  // PmxCanvas.addHtmlNode returns the created node; the CanvasAccess contract
284
127
  // is a bare id string, so extract it (mirrors addNode above).
@@ -289,58 +132,6 @@ class LocalCanvasAccess implements CanvasAccess {
289
132
  return this.canvas.addHtmlPrimitive(input);
290
133
  }
291
134
 
292
- async addGraphNode(input: AddGraphNodeInput): Promise<AddGraphNodeResult> {
293
- return this.canvas.addGraphNode(input);
294
- }
295
-
296
- async buildWebArtifact(input: WebArtifactInput): Promise<WebArtifactResult> {
297
- return await this.canvas.buildWebArtifact(input);
298
- }
299
-
300
- async updateNode(id: string, patch: UpdateNodePatch): Promise<void> {
301
- this.canvas.updateNode(id, patch);
302
- }
303
-
304
- async removeNode(id: string): Promise<void> {
305
- this.canvas.removeNode(id);
306
- }
307
-
308
- async removeAnnotation(id: string): Promise<boolean> {
309
- return this.canvas.removeAnnotation(id);
310
- }
311
-
312
- async addEdge(input: AddEdgeInput): Promise<string> {
313
- return this.canvas.addEdge(input);
314
- }
315
-
316
- async removeEdge(id: string): Promise<void> {
317
- this.canvas.removeEdge(id);
318
- }
319
-
320
- async createGroup(input: CreateGroupInput): Promise<string> {
321
- return this.canvas.createGroup(input);
322
- }
323
-
324
- async groupNodes(groupId: string, childIds: string[], options?: GroupNodesOptions): Promise<boolean> {
325
- return this.canvas.groupNodes(groupId, childIds, options);
326
- }
327
-
328
- async ungroupNodes(groupId: string): Promise<boolean> {
329
- return this.canvas.ungroupNodes(groupId);
330
- }
331
-
332
- async arrange(layout?: ArrangeLayout): Promise<void> {
333
- this.canvas.arrange(layout);
334
- }
335
-
336
- async focusNode(id: string, options?: { noPan?: boolean }): Promise<FocusNodeResult> {
337
- return this.canvas.focusNode(id, options);
338
- }
339
-
340
- async fitView(options?: FitViewOptions): Promise<FitViewResult> {
341
- return this.canvas.fitView(options);
342
- }
343
-
344
135
  async getAxState(): Promise<AxStateResult> {
345
136
  return this.canvas.getAxState();
346
137
  }
@@ -349,26 +140,10 @@ class LocalCanvasAccess implements CanvasAccess {
349
140
  return this.canvas.getAxContext(options);
350
141
  }
351
142
 
352
- async setAxFocus(nodeIds: string[], options?: { source?: PmxAxSource }): Promise<SetAxFocusResult> {
353
- return this.canvas.setAxFocus(nodeIds, { source: options?.source ?? 'mcp' });
354
- }
355
-
356
- async recordAxEvent(input: RecordAxEventInput, options?: { source?: PmxAxSource }): Promise<RecordAxEventResult> {
357
- return this.canvas.recordAxEvent(input, { source: options?.source ?? 'mcp' });
358
- }
359
-
360
- async sendSteering(message: string, options?: { source?: PmxAxSource }): Promise<SendSteeringResult> {
361
- return this.canvas.sendSteering(message, { source: options?.source ?? 'mcp' });
362
- }
363
-
364
143
  async getAxTimeline(query?: GetAxTimelineQuery): Promise<GetAxTimelineResult> {
365
144
  return this.canvas.getAxTimeline(query);
366
145
  }
367
146
 
368
- async addWorkItem(input: AddWorkItemInput, options?: { source?: PmxAxSource }): Promise<AddWorkItemResult> {
369
- return this.canvas.addWorkItem(input, { source: options?.source ?? 'mcp' });
370
- }
371
-
372
147
  async submitAxInteraction(input: SubmitAxInteractionInput, options?: { source?: PmxAxSource }): Promise<SubmitAxInteractionResult> {
373
148
  return this.canvas.submitAxInteraction(input, { source: options?.source ?? 'mcp' });
374
149
  }
@@ -377,137 +152,38 @@ class LocalCanvasAccess implements CanvasAccess {
377
152
  return this.canvas.getPendingSteering(options);
378
153
  }
379
154
 
380
- async markSteeringDelivered(id: string): Promise<boolean> {
381
- return this.canvas.markSteeringDelivered(id);
382
- }
383
-
384
155
  async listElicitations(): Promise<ListElicitationsResult> {
385
156
  return this.canvas.listElicitations();
386
157
  }
387
158
 
388
- async requestElicitation(input: RequestElicitationInput, options?: { source?: PmxAxSource }): Promise<RequestElicitationResult> {
389
- return this.canvas.requestElicitation(input, { source: options?.source ?? 'mcp' });
390
- }
391
-
392
- async respondElicitation(id: string, response: Record<string, unknown>, options?: { source?: PmxAxSource }): Promise<RespondElicitationResult> {
393
- return this.canvas.respondElicitation(id, response, { source: options?.source ?? 'mcp' });
394
- }
395
-
396
159
  async listModeRequests(): Promise<ListModeRequestsResult> {
397
160
  return this.canvas.listModeRequests();
398
161
  }
399
162
 
400
- async requestMode(input: RequestModeInput, options?: { source?: PmxAxSource }): Promise<RequestModeResult> {
401
- return this.canvas.requestMode(input, { source: options?.source ?? 'mcp' });
402
- }
403
-
404
- async resolveModeRequest(id: string, decision: 'approved' | 'rejected', options?: { resolution?: string; source?: PmxAxSource }): Promise<ResolveModeRequestResult> {
405
- return this.canvas.resolveModeRequest(id, decision, { ...(options ?? {}), source: options?.source ?? 'mcp' });
406
- }
407
-
408
163
  async ingestActivity(input: IngestActivityInput, options?: { source?: PmxAxSource }): Promise<IngestActivityResult> {
409
164
  return this.canvas.ingestActivity(input, { source: options?.source ?? 'mcp' });
410
165
  }
411
166
 
412
- async awaitApproval(id: string, options?: { timeoutMs?: number }): Promise<AwaitApprovalResult> {
413
- return this.canvas.awaitApproval(id, options);
414
- }
415
-
416
- async awaitElicitation(id: string, options?: { timeoutMs?: number }): Promise<AwaitElicitationResult> {
417
- return this.canvas.awaitElicitation(id, options);
418
- }
419
-
420
- async awaitMode(id: string, options?: { timeoutMs?: number }): Promise<AwaitModeResult> {
421
- return this.canvas.awaitMode(id, options);
422
- }
423
-
424
- async getCommandRegistry(): Promise<GetCommandRegistryResult> {
425
- return this.canvas.getCommandRegistry();
426
- }
427
-
428
- async invokeCommand(name: string, args?: Record<string, unknown> | null, options?: { source?: PmxAxSource }): Promise<InvokeCommandResult> {
429
- return this.canvas.invokeCommand(name, args ?? null, { source: options?.source ?? 'mcp' });
430
- }
431
-
432
167
  async getPolicy(): Promise<GetPolicyResult> {
433
168
  return this.canvas.getPolicy();
434
169
  }
435
170
 
436
- async setPolicy(patch: SetPolicyInput, options?: { source?: PmxAxSource }): Promise<SetPolicyResult> {
437
- return this.canvas.setPolicy(patch, { source: options?.source ?? 'mcp' });
438
- }
439
-
440
- async updateWorkItem(id: string, patch: UpdateWorkItemPatch, options?: { source?: PmxAxSource }): Promise<UpdateWorkItemResult> {
441
- return this.canvas.updateWorkItem(id, patch, { source: options?.source ?? 'mcp' });
442
- }
443
-
444
171
  async listWorkItems(): Promise<ListWorkItemsResult> {
445
172
  return this.canvas.listWorkItems();
446
173
  }
447
174
 
448
- async requestApproval(input: RequestApprovalInput, options?: { source?: PmxAxSource }): Promise<RequestApprovalResult> {
449
- return this.canvas.requestApproval(input, { source: options?.source ?? 'mcp' });
450
- }
451
-
452
- async resolveApproval(id: string, decision: 'approved' | 'rejected', options?: { resolution?: string; source?: PmxAxSource }): Promise<ResolveApprovalResult> {
453
- return this.canvas.resolveApproval(id, decision, {
454
- ...(options?.resolution !== undefined ? { resolution: options.resolution } : {}),
455
- source: options?.source ?? 'mcp',
456
- });
457
- }
458
-
459
175
  async listApprovalGates(): Promise<ListApprovalGatesResult> {
460
176
  return this.canvas.listApprovalGates();
461
177
  }
462
178
 
463
- async addEvidence(input: AddEvidenceInput, options?: { source?: PmxAxSource }): Promise<AddEvidenceResult> {
464
- return this.canvas.addEvidence(input, { source: options?.source ?? 'mcp' });
465
- }
466
-
467
- async addReviewAnnotation(input: AddReviewAnnotationInput, options?: { source?: PmxAxSource }): Promise<AddReviewAnnotationResult> {
468
- return this.canvas.addReviewAnnotation(input, { source: options?.source ?? 'mcp' });
469
- }
470
-
471
- async updateReviewAnnotation(id: string, patch: UpdateReviewAnnotationPatch, options?: { source?: PmxAxSource }): Promise<UpdateReviewAnnotationResult> {
472
- return this.canvas.updateReviewAnnotation(id, patch, { source: options?.source ?? 'mcp' });
473
- }
474
-
475
179
  async listReviewAnnotations(): Promise<ListReviewAnnotationsResult> {
476
180
  return this.canvas.listReviewAnnotations();
477
181
  }
478
182
 
479
- async getHostCapability(): Promise<GetHostCapabilityResult> {
480
- return this.canvas.getHostCapability();
481
- }
482
-
483
- async reportHostCapability(input: unknown, options?: { source?: PmxAxSource }): Promise<ReportHostCapabilityResult> {
484
- return this.canvas.reportHostCapability(input, { source: options?.source ?? 'mcp' });
485
- }
486
-
487
- async clear(): Promise<void> {
488
- this.canvas.clear();
489
- }
490
-
491
- async search(query: string): Promise<SearchResult> {
492
- return this.canvas.search(query);
493
- }
494
-
495
- async undo(): Promise<UndoRedoResult> {
496
- return await this.canvas.undo();
497
- }
498
-
499
- async redo(): Promise<UndoRedoResult> {
500
- return await this.canvas.redo();
501
- }
502
-
503
183
  async getHistory(): Promise<HistoryResult> {
504
184
  return this.canvas.getHistory();
505
185
  }
506
186
 
507
- async setContextPins(nodeIds: string[], mode: 'set' | 'add' | 'remove' = 'set'): Promise<SetContextPinsResult> {
508
- return this.canvas.setContextPins(nodeIds, mode);
509
- }
510
-
511
187
  async getPinnedNodeIds(): Promise<string[]> {
512
188
  return Array.from(canvasState.contextPinnedNodeIds);
513
189
  }
@@ -516,58 +192,14 @@ class LocalCanvasAccess implements CanvasAccess {
516
192
  return await this.canvas.runBatch(operations);
517
193
  }
518
194
 
519
- async listSnapshots(options?: SnapshotListOptions): Promise<SnapshotList> {
520
- return this.canvas.listSnapshots(options);
521
- }
522
-
523
- async saveSnapshot(name: string): Promise<CanvasSnapshot | null> {
524
- return this.canvas.saveSnapshot(name);
525
- }
526
-
527
- async restoreSnapshot(id: string): Promise<{ ok: boolean }> {
528
- return await this.canvas.restoreSnapshot(id);
529
- }
530
-
531
- async deleteSnapshot(id: string): Promise<DeleteSnapshotResult> {
532
- return this.canvas.deleteSnapshot(id);
533
- }
534
-
535
- async gcSnapshots(options?: GcSnapshotsOptions): Promise<GcSnapshotsResult> {
536
- return this.canvas.gcSnapshots(options);
537
- }
538
-
539
- async diffSnapshot(idOrName: string): Promise<DiffSnapshotResult> {
540
- return this.canvas.diffSnapshot(idOrName);
541
- }
542
-
543
195
  async getCodeGraph(): Promise<CodeGraphResult> {
544
196
  return this.canvas.getCodeGraph();
545
197
  }
546
198
 
547
- async validate(): Promise<ValidationResult> {
548
- return this.canvas.validate();
549
- }
550
-
551
199
  async getAutomationWebViewStatus(): Promise<AutomationWebViewStatus> {
552
200
  return this.canvas.getAutomationWebViewStatus();
553
201
  }
554
202
 
555
- async startAutomationWebView(options: AutomationWebViewOptions = {}): Promise<AutomationWebViewStatus> {
556
- return await this.canvas.startAutomationWebView(options);
557
- }
558
-
559
- async stopAutomationWebView(): Promise<boolean> {
560
- return await this.canvas.stopAutomationWebView();
561
- }
562
-
563
- async evaluateAutomationWebView(expression: string): Promise<AutomationEvaluateResult> {
564
- return await this.canvas.evaluateAutomationWebView(expression);
565
- }
566
-
567
- async resizeAutomationWebView(width: number, height: number): Promise<AutomationWebViewStatus> {
568
- return await this.canvas.resizeAutomationWebView(width, height);
569
- }
570
-
571
203
  async screenshotAutomationWebView(options: AutomationScreenshotOptions = {}): Promise<Uint8Array> {
572
204
  return await this.canvas.screenshotAutomationWebView(options);
573
205
  }
@@ -576,11 +208,17 @@ class LocalCanvasAccess implements CanvasAccess {
576
208
  class RemoteCanvasAccess implements CanvasAccess {
577
209
  readonly remoteBaseUrl: string;
578
210
  readonly port: number;
211
+ private readonly operationInvoker: HttpOperationInvoker;
579
212
 
580
213
  constructor(baseUrl: string) {
581
214
  this.remoteBaseUrl = baseUrl.replace(/\/$/, '');
582
215
  const parsed = new URL(this.remoteBaseUrl);
583
216
  this.port = Number(parsed.port || '80');
217
+ this.operationInvoker = new HttpOperationInvoker(this.remoteBaseUrl);
218
+ }
219
+
220
+ invoker(): OperationInvoker {
221
+ return this.operationInvoker;
584
222
  }
585
223
 
586
224
  private async requestJson<T>(method: string, path: string, body?: unknown): Promise<T> {
@@ -646,61 +284,12 @@ class RemoteCanvasAccess implements CanvasAccess {
646
284
  return parsed as CanvasNodeState;
647
285
  }
648
286
 
649
- async addNode(input: AddNodeInput): Promise<string> {
650
- return await this.requestNodeId('POST', '/api/canvas/node', input);
651
- }
652
-
653
- async addWebpageNode(input: AddWebpageNodeInput): Promise<Awaited<ReturnType<PmxCanvas['addWebpageNode']>>> {
654
- return await this.requestJson<Awaited<ReturnType<PmxCanvas['addWebpageNode']>>>('POST', '/api/canvas/node', {
655
- type: 'webpage',
656
- ...input,
657
- });
658
- }
659
-
660
287
  async refreshWebpageNode(id: string, url?: string): Promise<RefreshWebpageNodeResult> {
661
288
  return await this.requestJson<RefreshWebpageNodeResult>('POST', `/api/canvas/node/${encodeURIComponent(id)}/refresh`, {
662
289
  ...(url ? { url } : {}),
663
290
  });
664
291
  }
665
292
 
666
- async openMcpApp(input: OpenMcpAppInput): Promise<OpenMcpAppResult> {
667
- return await this.requestJson<OpenMcpAppResult>('POST', '/api/canvas/mcp-app/open', input);
668
- }
669
-
670
- async addDiagram(input: AddDiagramInput): Promise<OpenMcpAppResult> {
671
- return await this.requestJson<OpenMcpAppResult>('POST', '/api/canvas/diagram', input);
672
- }
673
-
674
- async addJsonRenderNode(input: AddJsonRenderNodeInput): Promise<AddJsonRenderNodeResult> {
675
- const response = await this.requestJson<JsonRenderNodeResponse>('POST', '/api/canvas/json-render', input);
676
- const id = typeof response.id === 'string' ? response.id : response.node?.id;
677
- if (!id) throw new Error('json-render response did not include a node id.');
678
- return { id, url: response.url, spec: response.spec };
679
- }
680
-
681
- async streamJsonRenderNode(input: StreamJsonRenderNodeInput): Promise<StreamJsonRenderNodeResult> {
682
- const response = await this.requestJson<{
683
- id?: string;
684
- url?: string;
685
- applied?: number;
686
- skipped?: number;
687
- specVersion?: number;
688
- elementCount?: number;
689
- streamStatus?: 'open' | 'closed';
690
- }>('POST', '/api/canvas/json-render/stream', input);
691
- const id = typeof response.id === 'string' ? response.id : undefined;
692
- if (!id) throw new Error('json-render stream response did not include a node id.');
693
- return {
694
- id,
695
- url: response.url ?? '',
696
- applied: response.applied ?? 0,
697
- skipped: response.skipped ?? 0,
698
- specVersion: response.specVersion ?? 0,
699
- elementCount: response.elementCount ?? 0,
700
- streamStatus: response.streamStatus ?? 'open',
701
- };
702
- }
703
-
704
293
  async addHtmlNode(input: AddHtmlNodeInput): Promise<string> {
705
294
  const {
706
295
  summary,
@@ -754,98 +343,6 @@ class RemoteCanvasAccess implements CanvasAccess {
754
343
  };
755
344
  }
756
345
 
757
- async addGraphNode(input: AddGraphNodeInput): Promise<AddGraphNodeResult> {
758
- const response = await this.requestJson<GraphNodeResponse>('POST', '/api/canvas/graph', {
759
- ...input,
760
- ...(typeof input.heightPx === 'number' ? { nodeHeight: input.heightPx } : {}),
761
- });
762
- const id = typeof response.id === 'string' ? response.id : response.node?.id;
763
- if (!id) throw new Error('graph response did not include a node id.');
764
- return { id, url: response.url, spec: response.spec };
765
- }
766
-
767
- async buildWebArtifact(input: WebArtifactInput): Promise<WebArtifactResult> {
768
- return await this.requestJson<WebArtifactResult>('POST', '/api/canvas/web-artifact', input);
769
- }
770
-
771
- async updateNode(id: string, patch: UpdateNodePatch): Promise<void> {
772
- await this.requestJson<unknown>('PATCH', `/api/canvas/node/${encodeURIComponent(id)}`, patch);
773
- }
774
-
775
- async removeNode(id: string): Promise<void> {
776
- await this.requestJson<unknown>('DELETE', `/api/canvas/node/${encodeURIComponent(id)}`);
777
- }
778
-
779
- async removeAnnotation(id: string): Promise<boolean> {
780
- const response = await this.requestJson<{ ok?: boolean }>('DELETE', `/api/canvas/annotation/${encodeURIComponent(id)}`);
781
- return response.ok === true;
782
- }
783
-
784
- async addEdge(input: AddEdgeInput): Promise<string> {
785
- const response = await this.requestJson<{ id?: string }>('POST', '/api/canvas/edge', input);
786
- if (!response.id) throw new Error('Canvas edge response did not include an edge id.');
787
- return response.id;
788
- }
789
-
790
- async removeEdge(id: string): Promise<void> {
791
- await this.requestJson<unknown>('DELETE', '/api/canvas/edge', { edge_id: id });
792
- }
793
-
794
- async createGroup(input: CreateGroupInput): Promise<string> {
795
- return await this.requestNodeId('POST', '/api/canvas/group', input);
796
- }
797
-
798
- async groupNodes(groupId: string, childIds: string[], options?: GroupNodesOptions): Promise<boolean> {
799
- const response = await this.requestJson<{ ok?: boolean }>('POST', '/api/canvas/group/add', {
800
- groupId,
801
- childIds,
802
- ...(options?.childLayout ? { childLayout: options.childLayout } : {}),
803
- });
804
- return response.ok === true;
805
- }
806
-
807
- async ungroupNodes(groupId: string): Promise<boolean> {
808
- const response = await this.requestJson<{ ok?: boolean }>('POST', '/api/canvas/group/ungroup', { groupId });
809
- return response.ok === true;
810
- }
811
-
812
- async arrange(layout?: ArrangeLayout): Promise<void> {
813
- await this.requestJson<unknown>('POST', '/api/canvas/arrange', { ...(layout ? { layout } : {}) });
814
- }
815
-
816
- async focusNode(id: string, options?: { noPan?: boolean }): Promise<FocusNodeResult> {
817
- const response = await fetch(`${this.remoteBaseUrl}/api/canvas/focus`, {
818
- method: 'POST',
819
- headers: { 'Content-Type': 'application/json' },
820
- body: JSON.stringify({ id, ...(options?.noPan === true ? { noPan: true } : {}) }),
821
- });
822
- if (response.status === 404) return null;
823
- const parsed = await response.json() as { focused?: string; panned?: boolean };
824
- if (!response.ok || typeof parsed.focused !== 'string' || typeof parsed.panned !== 'boolean') return null;
825
- return { focused: parsed.focused, panned: parsed.panned };
826
- }
827
-
828
- async fitView(options?: FitViewOptions): Promise<FitViewResult> {
829
- return await this.requestJson<FitViewResult>('POST', '/api/canvas/fit', options ?? {});
830
- }
831
-
832
- async clear(): Promise<void> {
833
- await this.requestJson<unknown>('POST', '/api/canvas/clear', {});
834
- }
835
-
836
- async search(query: string): Promise<SearchResult> {
837
- const response = await this.requestJson<SearchResponse>('GET', `/api/canvas/search?q=${encodeURIComponent(query)}`);
838
- return response.results ?? [];
839
- }
840
-
841
- async undo(): Promise<UndoRedoResult> {
842
- return await this.requestJson<UndoRedoResult>('POST', '/api/canvas/undo', {});
843
- }
844
-
845
- async redo(): Promise<UndoRedoResult> {
846
- return await this.requestJson<UndoRedoResult>('POST', '/api/canvas/redo', {});
847
- }
848
-
849
346
  async getHistory(): Promise<HistoryResult> {
850
347
  return await this.requestJson<HistoryResult>('GET', '/api/canvas/history');
851
348
  }
@@ -861,47 +358,11 @@ class RemoteCanvasAccess implements CanvasAccess {
861
358
  return await this.requestJson<AxContextResult>('GET', `/api/canvas/ax/context${qs}`);
862
359
  }
863
360
 
864
- async setAxFocus(nodeIds: string[], options?: { source?: PmxAxSource }): Promise<SetAxFocusResult> {
865
- const response = await this.requestJson<{ focus?: SetAxFocusResult }>('POST', '/api/canvas/ax/focus', {
866
- nodeIds,
867
- source: options?.source ?? 'mcp',
868
- });
869
- if (!response.focus) throw new Error('Remote canvas did not return AX focus.');
870
- return response.focus;
871
- }
872
-
873
- async recordAxEvent(input: RecordAxEventInput, options?: { source?: PmxAxSource }): Promise<RecordAxEventResult> {
874
- const response = await this.requestJson<{ event?: RecordAxEventResult }>('POST', '/api/canvas/ax/event', {
875
- ...input,
876
- source: options?.source ?? 'mcp',
877
- });
878
- if (!response.event) throw new Error('Remote canvas did not return an AX event.');
879
- return response.event;
880
- }
881
-
882
- async sendSteering(message: string, options?: { source?: PmxAxSource }): Promise<SendSteeringResult> {
883
- const response = await this.requestJson<{ steering?: SendSteeringResult }>('POST', '/api/canvas/ax/steer', {
884
- message,
885
- source: options?.source ?? 'mcp',
886
- });
887
- if (!response.steering) throw new Error('Remote canvas did not return a steering message.');
888
- return response.steering;
889
- }
890
-
891
361
  async getAxTimeline(query?: GetAxTimelineQuery): Promise<GetAxTimelineResult> {
892
362
  const qs = query?.limit ? `?limit=${query.limit}` : '';
893
363
  return await this.requestJson<GetAxTimelineResult>('GET', `/api/canvas/ax/timeline${qs}`);
894
364
  }
895
365
 
896
- async addWorkItem(input: AddWorkItemInput, options?: { source?: PmxAxSource }): Promise<AddWorkItemResult> {
897
- const response = await this.requestJson<{ workItem?: AddWorkItemResult }>('POST', '/api/canvas/ax/work', {
898
- ...input,
899
- source: options?.source ?? 'mcp',
900
- });
901
- if (!response.workItem) throw new Error('Remote canvas did not return a work item.');
902
- return response.workItem;
903
- }
904
-
905
366
  async submitAxInteraction(input: SubmitAxInteractionInput, options?: { source?: PmxAxSource }): Promise<SubmitAxInteractionResult> {
906
367
  // The interaction endpoint returns its structured outcome (ok/code/error) in
907
368
  // the body for both accepted and rejected interactions, so read the body
@@ -928,65 +389,16 @@ class RemoteCanvasAccess implements CanvasAccess {
928
389
  return response.pending ?? [];
929
390
  }
930
391
 
931
- async markSteeringDelivered(id: string): Promise<boolean> {
932
- const response = await this.requestJson<{ delivered?: boolean }>(
933
- 'POST',
934
- `/api/canvas/ax/delivery/${encodeURIComponent(id)}/mark`,
935
- {},
936
- );
937
- return response.delivered ?? false;
938
- }
939
-
940
392
  async listElicitations(): Promise<ListElicitationsResult> {
941
393
  const r = await this.requestJson<{ elicitations?: ListElicitationsResult }>('GET', '/api/canvas/ax/elicitation');
942
394
  return r.elicitations ?? [];
943
395
  }
944
396
 
945
- async requestElicitation(input: RequestElicitationInput, options?: { source?: PmxAxSource }): Promise<RequestElicitationResult> {
946
- const r = await this.requestJson<{ elicitation?: RequestElicitationResult }>('POST', '/api/canvas/ax/elicitation', {
947
- ...input,
948
- source: options?.source ?? 'mcp',
949
- });
950
- if (!r.elicitation) throw new Error('Remote canvas did not return an elicitation.');
951
- return r.elicitation;
952
- }
953
-
954
- async respondElicitation(id: string, response: Record<string, unknown>, options?: { source?: PmxAxSource }): Promise<RespondElicitationResult> {
955
- const res = await fetch(`${this.remoteBaseUrl}/api/canvas/ax/elicitation/${encodeURIComponent(id)}/respond`, {
956
- method: 'POST',
957
- headers: { 'Content-Type': 'application/json' },
958
- body: JSON.stringify({ response, source: options?.source ?? 'mcp' }),
959
- });
960
- if (res.status === 404) return null;
961
- if (!res.ok) throw new Error(`HTTP ${res.status}`);
962
- return (await res.json() as { elicitation?: RespondElicitationResult }).elicitation ?? null;
963
- }
964
-
965
397
  async listModeRequests(): Promise<ListModeRequestsResult> {
966
398
  const r = await this.requestJson<{ modeRequests?: ListModeRequestsResult }>('GET', '/api/canvas/ax/mode');
967
399
  return r.modeRequests ?? [];
968
400
  }
969
401
 
970
- async requestMode(input: RequestModeInput, options?: { source?: PmxAxSource }): Promise<RequestModeResult> {
971
- const r = await this.requestJson<{ modeRequest?: RequestModeResult }>('POST', '/api/canvas/ax/mode', {
972
- ...input,
973
- source: options?.source ?? 'mcp',
974
- });
975
- if (!r.modeRequest) throw new Error('Remote canvas did not return a mode request.');
976
- return r.modeRequest;
977
- }
978
-
979
- async resolveModeRequest(id: string, decision: 'approved' | 'rejected', options?: { resolution?: string; source?: PmxAxSource }): Promise<ResolveModeRequestResult> {
980
- const res = await fetch(`${this.remoteBaseUrl}/api/canvas/ax/mode/${encodeURIComponent(id)}/resolve`, {
981
- method: 'POST',
982
- headers: { 'Content-Type': 'application/json' },
983
- body: JSON.stringify({ decision, ...(options?.resolution ? { resolution: options.resolution } : {}), source: options?.source ?? 'mcp' }),
984
- });
985
- if (res.status === 404) return null;
986
- if (!res.ok) throw new Error(`HTTP ${res.status}`);
987
- return (await res.json() as { modeRequest?: ResolveModeRequestResult }).modeRequest ?? null;
988
- }
989
-
990
402
  async ingestActivity(input: IngestActivityInput, options?: { source?: PmxAxSource }): Promise<IngestActivityResult> {
991
403
  return await this.requestJson<IngestActivityResult>('POST', '/api/canvas/ax/activity', {
992
404
  ...input,
@@ -994,181 +406,27 @@ class RemoteCanvasAccess implements CanvasAccess {
994
406
  });
995
407
  }
996
408
 
997
- async awaitApproval(id: string, options?: { timeoutMs?: number }): Promise<AwaitApprovalResult> {
998
- // Mirror PmxCanvas's `?? 30000` default so the remote transport blocks like the
999
- // local one (an omitted timeout must still long-poll). Explicit 0 = immediate read.
1000
- const ms = options?.timeoutMs ?? 30000;
1001
- const qs = ms > 0 ? `?waitMs=${ms}` : '';
1002
- const res = await fetch(`${this.remoteBaseUrl}/api/canvas/ax/approval/${encodeURIComponent(id)}${qs}`);
1003
- if (res.status === 404) return { approvalGate: null, pending: false };
1004
- if (!res.ok) throw new Error(`HTTP ${res.status}`);
1005
- const body = await res.json() as { approvalGate?: AwaitApprovalResult['approvalGate']; pending?: boolean };
1006
- return { approvalGate: body.approvalGate ?? null, pending: body.pending ?? false };
1007
- }
1008
-
1009
- async awaitElicitation(id: string, options?: { timeoutMs?: number }): Promise<AwaitElicitationResult> {
1010
- // Mirror PmxCanvas's `?? 30000` default so the remote transport blocks like the
1011
- // local one (an omitted timeout must still long-poll). Explicit 0 = immediate read.
1012
- const ms = options?.timeoutMs ?? 30000;
1013
- const qs = ms > 0 ? `?waitMs=${ms}` : '';
1014
- const res = await fetch(`${this.remoteBaseUrl}/api/canvas/ax/elicitation/${encodeURIComponent(id)}${qs}`);
1015
- if (res.status === 404) return { elicitation: null, pending: false };
1016
- if (!res.ok) throw new Error(`HTTP ${res.status}`);
1017
- const body = await res.json() as { elicitation?: AwaitElicitationResult['elicitation']; pending?: boolean };
1018
- return { elicitation: body.elicitation ?? null, pending: body.pending ?? false };
1019
- }
1020
-
1021
- async awaitMode(id: string, options?: { timeoutMs?: number }): Promise<AwaitModeResult> {
1022
- // Mirror PmxCanvas's `?? 30000` default so the remote transport blocks like the
1023
- // local one (an omitted timeout must still long-poll). Explicit 0 = immediate read.
1024
- const ms = options?.timeoutMs ?? 30000;
1025
- const qs = ms > 0 ? `?waitMs=${ms}` : '';
1026
- const res = await fetch(`${this.remoteBaseUrl}/api/canvas/ax/mode/${encodeURIComponent(id)}${qs}`);
1027
- if (res.status === 404) return { modeRequest: null, pending: false };
1028
- if (!res.ok) throw new Error(`HTTP ${res.status}`);
1029
- const body = await res.json() as { modeRequest?: AwaitModeResult['modeRequest']; pending?: boolean };
1030
- return { modeRequest: body.modeRequest ?? null, pending: body.pending ?? false };
1031
- }
1032
-
1033
- async getCommandRegistry(): Promise<GetCommandRegistryResult> {
1034
- const r = await this.requestJson<{ commands?: GetCommandRegistryResult }>('GET', '/api/canvas/ax/command');
1035
- return r.commands ?? [];
1036
- }
1037
-
1038
- async invokeCommand(name: string, args?: Record<string, unknown> | null, options?: { source?: PmxAxSource }): Promise<InvokeCommandResult> {
1039
- const r = await this.requestJson<{ event?: InvokeCommandResult }>('POST', '/api/canvas/ax/command', {
1040
- name,
1041
- ...(args ? { args } : {}),
1042
- source: options?.source ?? 'mcp',
1043
- });
1044
- return r.event ?? null;
1045
- }
1046
-
1047
409
  async getPolicy(): Promise<GetPolicyResult> {
1048
410
  const r = await this.requestJson<{ policy?: GetPolicyResult }>('GET', '/api/canvas/ax/policy');
1049
411
  if (!r.policy) throw new Error('Remote canvas did not return a policy.');
1050
412
  return r.policy;
1051
413
  }
1052
414
 
1053
- async setPolicy(patch: SetPolicyInput, options?: { source?: PmxAxSource }): Promise<SetPolicyResult> {
1054
- const r = await this.requestJson<{ policy?: SetPolicyResult }>('POST', '/api/canvas/ax/policy', {
1055
- ...patch,
1056
- source: options?.source ?? 'mcp',
1057
- });
1058
- if (!r.policy) throw new Error('Remote canvas did not return a policy.');
1059
- return r.policy;
1060
- }
1061
-
1062
- async updateWorkItem(id: string, patch: UpdateWorkItemPatch, options?: { source?: PmxAxSource }): Promise<UpdateWorkItemResult> {
1063
- const response = await fetch(`${this.remoteBaseUrl}/api/canvas/ax/work/${encodeURIComponent(id)}`, {
1064
- method: 'PATCH',
1065
- headers: { 'Content-Type': 'application/json' },
1066
- body: JSON.stringify({ ...patch, source: options?.source ?? 'mcp' }),
1067
- });
1068
- if (response.status === 404) return null;
1069
- if (!response.ok) throw new Error(`HTTP ${response.status}`);
1070
- return (await response.json() as { workItem?: AddWorkItemResult }).workItem ?? null;
1071
- }
1072
-
1073
415
  async listWorkItems(): Promise<ListWorkItemsResult> {
1074
416
  const response = await this.requestJson<{ workItems?: ListWorkItemsResult }>('GET', '/api/canvas/ax/work');
1075
417
  return response.workItems ?? [];
1076
418
  }
1077
419
 
1078
- async requestApproval(input: RequestApprovalInput, options?: { source?: PmxAxSource }): Promise<RequestApprovalResult> {
1079
- const response = await this.requestJson<{ approvalGate?: RequestApprovalResult }>('POST', '/api/canvas/ax/approval', {
1080
- ...input,
1081
- source: options?.source ?? 'mcp',
1082
- });
1083
- if (!response.approvalGate) throw new Error('Remote canvas did not return an approval gate.');
1084
- return response.approvalGate;
1085
- }
1086
-
1087
- async resolveApproval(id: string, decision: 'approved' | 'rejected', options?: { resolution?: string; source?: PmxAxSource }): Promise<ResolveApprovalResult> {
1088
- const response = await fetch(`${this.remoteBaseUrl}/api/canvas/ax/approval/${encodeURIComponent(id)}/resolve`, {
1089
- method: 'POST',
1090
- headers: { 'Content-Type': 'application/json' },
1091
- body: JSON.stringify({
1092
- decision,
1093
- ...(options?.resolution !== undefined ? { resolution: options.resolution } : {}),
1094
- source: options?.source ?? 'mcp',
1095
- }),
1096
- });
1097
- if (response.status === 404) return null;
1098
- if (!response.ok) throw new Error(`HTTP ${response.status}`);
1099
- return (await response.json() as { approvalGate?: RequestApprovalResult }).approvalGate ?? null;
1100
- }
1101
-
1102
420
  async listApprovalGates(): Promise<ListApprovalGatesResult> {
1103
421
  const response = await this.requestJson<{ approvalGates?: ListApprovalGatesResult }>('GET', '/api/canvas/ax/approval');
1104
422
  return response.approvalGates ?? [];
1105
423
  }
1106
424
 
1107
- async addEvidence(input: AddEvidenceInput, options?: { source?: PmxAxSource }): Promise<AddEvidenceResult> {
1108
- const response = await this.requestJson<{ evidence?: AddEvidenceResult }>('POST', '/api/canvas/ax/evidence', {
1109
- ...input,
1110
- source: options?.source ?? 'mcp',
1111
- });
1112
- if (!response.evidence) throw new Error('Remote canvas did not return an evidence item.');
1113
- return response.evidence;
1114
- }
1115
-
1116
- async addReviewAnnotation(input: AddReviewAnnotationInput, options?: { source?: PmxAxSource }): Promise<AddReviewAnnotationResult> {
1117
- const response = await fetch(`${this.remoteBaseUrl}/api/canvas/ax/review`, {
1118
- method: 'POST',
1119
- headers: { 'Content-Type': 'application/json' },
1120
- body: JSON.stringify({ ...input, source: options?.source ?? 'mcp' }),
1121
- });
1122
- // 400 = validation rejection (e.g. node-anchored review with an unknown
1123
- // nodeId); mirror the local path and return null rather than throwing.
1124
- if (response.status === 400) return null;
1125
- if (!response.ok) throw new Error(`HTTP ${response.status}`);
1126
- return (await response.json() as { reviewAnnotation?: AddReviewAnnotationResult }).reviewAnnotation ?? null;
1127
- }
1128
-
1129
- async updateReviewAnnotation(id: string, patch: UpdateReviewAnnotationPatch, options?: { source?: PmxAxSource }): Promise<UpdateReviewAnnotationResult> {
1130
- const response = await fetch(`${this.remoteBaseUrl}/api/canvas/ax/review/${encodeURIComponent(id)}`, {
1131
- method: 'PATCH',
1132
- headers: { 'Content-Type': 'application/json' },
1133
- body: JSON.stringify({ ...patch, source: options?.source ?? 'mcp' }),
1134
- });
1135
- if (response.status === 404) return null;
1136
- if (!response.ok) throw new Error(`HTTP ${response.status}`);
1137
- return (await response.json() as { reviewAnnotation?: AddReviewAnnotationResult }).reviewAnnotation ?? null;
1138
- }
1139
-
1140
425
  async listReviewAnnotations(): Promise<ListReviewAnnotationsResult> {
1141
426
  const response = await this.requestJson<{ reviewAnnotations?: ListReviewAnnotationsResult }>('GET', '/api/canvas/ax/review');
1142
427
  return response.reviewAnnotations ?? [];
1143
428
  }
1144
429
 
1145
- async getHostCapability(): Promise<GetHostCapabilityResult> {
1146
- const response = await this.requestJson<{ host?: GetHostCapabilityResult }>('GET', '/api/canvas/ax/host-capability');
1147
- return response.host ?? null;
1148
- }
1149
-
1150
- async reportHostCapability(input: unknown, options?: { source?: PmxAxSource }): Promise<ReportHostCapabilityResult> {
1151
- const body = input !== null && typeof input === 'object' && !Array.isArray(input) ? { ...input } : {};
1152
- const response = await this.requestJson<{ host?: ReportHostCapabilityResult }>('PUT', '/api/canvas/ax/host-capability', {
1153
- ...body,
1154
- source: options?.source ?? 'mcp',
1155
- });
1156
- if (!response.host) throw new Error('Remote canvas did not return host capability.');
1157
- return response.host;
1158
- }
1159
-
1160
- async setContextPins(nodeIds: string[], mode: 'set' | 'add' | 'remove' = 'set'): Promise<SetContextPinsResult> {
1161
- const existing = mode === 'set' ? [] : await this.getPinnedNodeIds();
1162
- const requested = new Set(nodeIds);
1163
- const next = mode === 'set'
1164
- ? nodeIds
1165
- : mode === 'add'
1166
- ? [...new Set([...existing, ...nodeIds])]
1167
- : existing.filter((id) => !requested.has(id));
1168
- const response = await this.requestJson<{ count?: number }>('POST', '/api/canvas/context-pins', { nodeIds: next });
1169
- return { count: response.count ?? next.length, nodeIds: next };
1170
- }
1171
-
1172
430
  async getPinnedNodeIds(): Promise<string[]> {
1173
431
  const response = await this.requestJson<{ nodeIds?: string[] }>('GET', '/api/canvas/pinned-context');
1174
432
  return Array.isArray(response.nodeIds) ? response.nodeIds : [];
@@ -1178,73 +436,15 @@ class RemoteCanvasAccess implements CanvasAccess {
1178
436
  return await this.requestJson<RunBatchResult>('POST', '/api/canvas/batch', { operations });
1179
437
  }
1180
438
 
1181
- async listSnapshots(options?: SnapshotListOptions): Promise<SnapshotList> {
1182
- const params = new URLSearchParams();
1183
- if (typeof options?.limit === 'number') params.set('limit', String(options.limit));
1184
- if (options?.query) params.set('q', options.query);
1185
- if (options?.before) params.set('before', options.before);
1186
- if (options?.after) params.set('after', options.after);
1187
- if (options?.all) params.set('all', 'true');
1188
- const query = params.size > 0 ? `?${params.toString()}` : '';
1189
- return await this.requestJson<SnapshotList>('GET', `/api/canvas/snapshots${query}`);
1190
- }
1191
-
1192
- async saveSnapshot(name: string): Promise<CanvasSnapshot | null> {
1193
- const response = await this.requestJson<SnapshotSaveResponse>('POST', '/api/canvas/snapshots', { name });
1194
- return response.snapshot ?? null;
1195
- }
1196
-
1197
- async restoreSnapshot(id: string): Promise<{ ok: boolean }> {
1198
- return await this.requestJson<{ ok: boolean }>('POST', `/api/canvas/snapshots/${encodeURIComponent(id)}`, {});
1199
- }
1200
-
1201
- async deleteSnapshot(id: string): Promise<DeleteSnapshotResult> {
1202
- return await this.requestJson<DeleteSnapshotResult>('DELETE', `/api/canvas/snapshots/${encodeURIComponent(id)}`);
1203
- }
1204
-
1205
- async gcSnapshots(options?: GcSnapshotsOptions): Promise<GcSnapshotsResult> {
1206
- return await this.requestJson<GcSnapshotsResult>('POST', '/api/canvas/snapshots/gc', options ?? {});
1207
- }
1208
-
1209
- async diffSnapshot(idOrName: string): Promise<DiffSnapshotResult> {
1210
- return await this.requestJson<DiffSnapshotResult>('GET', `/api/canvas/snapshots/${encodeURIComponent(idOrName)}/diff`);
1211
- }
1212
-
1213
439
  async getCodeGraph(): Promise<CodeGraphResult> {
1214
440
  const summary = await this.requestJson<CodeGraphResult['summary']>('GET', '/api/canvas/code-graph');
1215
441
  return { text: JSON.stringify(summary, null, 2), summary };
1216
442
  }
1217
443
 
1218
- async validate(): Promise<ValidationResult> {
1219
- return await this.requestJson<ValidationResult>('GET', '/api/canvas/validate');
1220
- }
1221
-
1222
444
  async getAutomationWebViewStatus(): Promise<AutomationWebViewStatus> {
1223
445
  return await this.requestJson<AutomationWebViewStatus>('GET', '/api/workbench/webview');
1224
446
  }
1225
447
 
1226
- async startAutomationWebView(options: AutomationWebViewOptions = {}): Promise<AutomationWebViewStatus> {
1227
- const response = await this.requestJson<WebViewEnvelope>('POST', '/api/workbench/webview/start', options);
1228
- if (!response.webview) throw new Error('WebView start response did not include status.');
1229
- return response.webview;
1230
- }
1231
-
1232
- async stopAutomationWebView(): Promise<boolean> {
1233
- const response = await this.requestJson<WebViewStopEnvelope>('DELETE', '/api/workbench/webview');
1234
- return response.stopped === true;
1235
- }
1236
-
1237
- async evaluateAutomationWebView(expression: string): Promise<AutomationEvaluateResult> {
1238
- const response = await this.requestJson<WebViewEvaluateEnvelope>('POST', '/api/workbench/webview/evaluate', { expression });
1239
- return response.value as AutomationEvaluateResult;
1240
- }
1241
-
1242
- async resizeAutomationWebView(width: number, height: number): Promise<AutomationWebViewStatus> {
1243
- const response = await this.requestJson<WebViewEnvelope>('POST', '/api/workbench/webview/resize', { width, height });
1244
- if (!response.webview) throw new Error('WebView resize response did not include status.');
1245
- return response.webview;
1246
- }
1247
-
1248
448
  async screenshotAutomationWebView(options: AutomationScreenshotOptions = {}): Promise<Uint8Array> {
1249
449
  const response = await fetch(`${this.remoteBaseUrl}/api/workbench/webview/screenshot`, {
1250
450
  method: 'POST',