pmx-canvas 0.1.26 → 0.1.28

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 (64) hide show
  1. package/.github/extensions/pmx-canvas/extension.mjs +191 -0
  2. package/CHANGELOG.md +110 -0
  3. package/Readme.md +74 -27
  4. package/dist/canvas/index.js +82 -82
  5. package/dist/json-render/index.css +1 -1
  6. package/dist/json-render/index.js +944 -164
  7. package/dist/types/json-render/catalog.d.ts +195 -20
  8. package/dist/types/json-render/charts/components.d.ts +17 -0
  9. package/dist/types/json-render/charts/definitions.d.ts +13 -1
  10. package/dist/types/json-render/charts/tufte-components.d.ts +65 -0
  11. package/dist/types/json-render/charts/tufte-definitions.d.ts +164 -0
  12. package/dist/types/json-render/directives.d.ts +33 -0
  13. package/dist/types/json-render/renderer/index.d.ts +1 -0
  14. package/dist/types/json-render/server.d.ts +32 -1
  15. package/dist/types/mcp/canvas-access.d.ts +62 -0
  16. package/dist/types/server/ax-state.d.ts +170 -0
  17. package/dist/types/server/canvas-db.d.ts +17 -1
  18. package/dist/types/server/canvas-operations.d.ts +53 -0
  19. package/dist/types/server/canvas-schema.d.ts +5 -1
  20. package/dist/types/server/canvas-state.d.ts +95 -4
  21. package/dist/types/server/index.d.ts +120 -3
  22. package/dist/types/server/mutation-history.d.ts +1 -1
  23. package/docs/cli.md +42 -0
  24. package/docs/http-api.md +64 -0
  25. package/docs/mcp.md +23 -5
  26. package/docs/node-types.md +1 -1
  27. package/docs/screenshots/codex-app.png +0 -0
  28. package/docs/screenshots/github-copilot-app.png +0 -0
  29. package/docs/sdk.md +23 -5
  30. package/package.json +10 -7
  31. package/skills/control-session-orchestrator/SKILL.md +359 -0
  32. package/skills/control-session-orchestrator/evals/evals.json +75 -0
  33. package/skills/data-analysis/SKILL.md +6 -0
  34. package/skills/pmx-canvas/SKILL.md +50 -4
  35. package/skills/pmx-canvas/references/github-copilot-app-adapter.md +6 -0
  36. package/skills/tufte-viz/SKILL.md +157 -0
  37. package/skills/tufte-viz/references/analytical-design.md +217 -0
  38. package/skills/tufte-viz/references/tufte-principles.md +147 -0
  39. package/src/cli/agent.ts +302 -3
  40. package/src/cli/index.ts +2 -1
  41. package/src/client/nodes/ExtAppFrame.tsx +48 -1
  42. package/src/client/nodes/McpAppNode.tsx +6 -2
  43. package/src/json-render/catalog.ts +22 -1
  44. package/src/json-render/charts/components.tsx +127 -15
  45. package/src/json-render/charts/definitions.ts +19 -2
  46. package/src/json-render/charts/extra-components.tsx +5 -4
  47. package/src/json-render/charts/tufte-components.tsx +395 -0
  48. package/src/json-render/charts/tufte-definitions.ts +128 -0
  49. package/src/json-render/directives.ts +64 -0
  50. package/src/json-render/renderer/index.css +107 -1
  51. package/src/json-render/renderer/index.tsx +33 -0
  52. package/src/json-render/server.ts +275 -5
  53. package/src/mcp/canvas-access.ts +264 -1
  54. package/src/mcp/server.ts +498 -9
  55. package/src/server/ax-context.ts +8 -3
  56. package/src/server/ax-state.ts +447 -0
  57. package/src/server/canvas-db.ts +184 -1
  58. package/src/server/canvas-operations.ts +123 -2
  59. package/src/server/canvas-schema.ts +27 -3
  60. package/src/server/canvas-state.ts +349 -2
  61. package/src/server/index.ts +259 -7
  62. package/src/server/mutation-history.ts +6 -0
  63. package/src/server/server.ts +442 -5
  64. package/src/server/web-artifacts.ts +31 -5
@@ -19,6 +19,8 @@ type OpenMcpAppResult = Awaited<ReturnType<PmxCanvas['openMcpApp']>>;
19
19
  type AddDiagramInput = Parameters<PmxCanvas['addDiagram']>[0];
20
20
  type AddJsonRenderNodeInput = Parameters<PmxCanvas['addJsonRenderNode']>[0];
21
21
  type AddJsonRenderNodeResult = ReturnType<PmxCanvas['addJsonRenderNode']>;
22
+ type StreamJsonRenderNodeInput = Parameters<PmxCanvas['streamJsonRenderNode']>[0];
23
+ type StreamJsonRenderNodeResult = ReturnType<PmxCanvas['streamJsonRenderNode']>;
22
24
  type AddHtmlNodeInput = Parameters<PmxCanvas['addHtmlNode']>[0];
23
25
  type AddHtmlPrimitiveInput = Parameters<PmxCanvas['addHtmlPrimitive']>[0];
24
26
  type AddHtmlPrimitiveResult = ReturnType<PmxCanvas['addHtmlPrimitive']>;
@@ -35,6 +37,29 @@ type FitViewResult = ReturnType<PmxCanvas['fitView']>;
35
37
  type AxStateResult = ReturnType<PmxCanvas['getAxState']>;
36
38
  type AxContextResult = ReturnType<PmxCanvas['getAxContext']>;
37
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
+ type GetAxTimelineQuery = Parameters<PmxCanvas['getAxTimeline']>[0];
44
+ type GetAxTimelineResult = ReturnType<PmxCanvas['getAxTimeline']>;
45
+ type AddWorkItemInput = Parameters<PmxCanvas['addWorkItem']>[0];
46
+ type AddWorkItemResult = ReturnType<PmxCanvas['addWorkItem']>;
47
+ type UpdateWorkItemPatch = Parameters<PmxCanvas['updateWorkItem']>[1];
48
+ type UpdateWorkItemResult = ReturnType<PmxCanvas['updateWorkItem']>;
49
+ type ListWorkItemsResult = ReturnType<PmxCanvas['listWorkItems']>;
50
+ type RequestApprovalInput = Parameters<PmxCanvas['requestApproval']>[0];
51
+ type RequestApprovalResult = ReturnType<PmxCanvas['requestApproval']>;
52
+ type ResolveApprovalResult = ReturnType<PmxCanvas['resolveApproval']>;
53
+ type ListApprovalGatesResult = ReturnType<PmxCanvas['listApprovalGates']>;
54
+ type AddEvidenceInput = Parameters<PmxCanvas['addEvidence']>[0];
55
+ type AddEvidenceResult = ReturnType<PmxCanvas['addEvidence']>;
56
+ type AddReviewAnnotationInput = Parameters<PmxCanvas['addReviewAnnotation']>[0];
57
+ type AddReviewAnnotationResult = ReturnType<PmxCanvas['addReviewAnnotation']>;
58
+ type UpdateReviewAnnotationPatch = Parameters<PmxCanvas['updateReviewAnnotation']>[1];
59
+ type UpdateReviewAnnotationResult = ReturnType<PmxCanvas['updateReviewAnnotation']>;
60
+ type ListReviewAnnotationsResult = ReturnType<PmxCanvas['listReviewAnnotations']>;
61
+ type GetHostCapabilityResult = ReturnType<PmxCanvas['getHostCapability']>;
62
+ type ReportHostCapabilityResult = ReturnType<PmxCanvas['reportHostCapability']>;
38
63
  type SearchResult = ReturnType<PmxCanvas['search']>;
39
64
  type UndoRedoResult = Awaited<ReturnType<PmxCanvas['undo']>>;
40
65
  type HistoryResult = ReturnType<PmxCanvas['getHistory']>;
@@ -107,6 +132,7 @@ export interface CanvasAccess {
107
132
  openMcpApp(input: OpenMcpAppInput): Promise<OpenMcpAppResult>;
108
133
  addDiagram(input: AddDiagramInput): Promise<OpenMcpAppResult>;
109
134
  addJsonRenderNode(input: AddJsonRenderNodeInput): Promise<AddJsonRenderNodeResult>;
135
+ streamJsonRenderNode(input: StreamJsonRenderNodeInput): Promise<StreamJsonRenderNodeResult>;
110
136
  addHtmlNode(input: AddHtmlNodeInput): Promise<string>;
111
137
  addHtmlPrimitive(input: AddHtmlPrimitiveInput): Promise<AddHtmlPrimitiveResult>;
112
138
  addGraphNode(input: AddGraphNodeInput): Promise<AddGraphNodeResult>;
@@ -125,6 +151,21 @@ export interface CanvasAccess {
125
151
  getAxState(): Promise<AxStateResult>;
126
152
  getAxContext(): Promise<AxContextResult>;
127
153
  setAxFocus(nodeIds: string[], options?: { source?: PmxAxSource }): Promise<SetAxFocusResult>;
154
+ recordAxEvent(input: RecordAxEventInput, options?: { source?: PmxAxSource }): Promise<RecordAxEventResult>;
155
+ sendSteering(message: string, options?: { source?: PmxAxSource }): Promise<SendSteeringResult>;
156
+ getAxTimeline(query?: GetAxTimelineQuery): Promise<GetAxTimelineResult>;
157
+ addWorkItem(input: AddWorkItemInput, options?: { source?: PmxAxSource }): Promise<AddWorkItemResult>;
158
+ updateWorkItem(id: string, patch: UpdateWorkItemPatch, options?: { source?: PmxAxSource }): Promise<UpdateWorkItemResult>;
159
+ listWorkItems(): Promise<ListWorkItemsResult>;
160
+ requestApproval(input: RequestApprovalInput, options?: { source?: PmxAxSource }): Promise<RequestApprovalResult>;
161
+ resolveApproval(id: string, decision: 'approved' | 'rejected', options?: { resolution?: string; source?: PmxAxSource }): Promise<ResolveApprovalResult>;
162
+ listApprovalGates(): Promise<ListApprovalGatesResult>;
163
+ addEvidence(input: AddEvidenceInput, options?: { source?: PmxAxSource }): Promise<AddEvidenceResult>;
164
+ addReviewAnnotation(input: AddReviewAnnotationInput, options?: { source?: PmxAxSource }): Promise<AddReviewAnnotationResult>;
165
+ updateReviewAnnotation(id: string, patch: UpdateReviewAnnotationPatch, options?: { source?: PmxAxSource }): Promise<UpdateReviewAnnotationResult>;
166
+ listReviewAnnotations(): Promise<ListReviewAnnotationsResult>;
167
+ getHostCapability(): Promise<GetHostCapabilityResult>;
168
+ reportHostCapability(input: unknown, options?: { source?: PmxAxSource }): Promise<ReportHostCapabilityResult>;
128
169
  clear(): Promise<void>;
129
170
  search(query: string): Promise<SearchResult>;
130
171
  undo(): Promise<UndoRedoResult>;
@@ -171,7 +212,9 @@ class LocalCanvasAccess implements CanvasAccess {
171
212
  }
172
213
 
173
214
  async addNode(input: AddNodeInput): Promise<string> {
174
- return this.canvas.addNode(input);
215
+ // PmxCanvas.addNode returns the created node; the CanvasAccess contract
216
+ // (shared with the remote proxy + MCP) stays id-only.
217
+ return this.canvas.addNode(input).id;
175
218
  }
176
219
 
177
220
  async addWebpageNode(input: AddWebpageNodeInput): Promise<Awaited<ReturnType<PmxCanvas['addWebpageNode']>>> {
@@ -194,6 +237,10 @@ class LocalCanvasAccess implements CanvasAccess {
194
237
  return this.canvas.addJsonRenderNode(input);
195
238
  }
196
239
 
240
+ async streamJsonRenderNode(input: StreamJsonRenderNodeInput): Promise<StreamJsonRenderNodeResult> {
241
+ return this.canvas.streamJsonRenderNode(input);
242
+ }
243
+
197
244
  async addHtmlNode(input: AddHtmlNodeInput): Promise<string> {
198
245
  return this.canvas.addHtmlNode(input);
199
246
  }
@@ -266,6 +313,69 @@ class LocalCanvasAccess implements CanvasAccess {
266
313
  return this.canvas.setAxFocus(nodeIds, { source: options?.source ?? 'mcp' });
267
314
  }
268
315
 
316
+ async recordAxEvent(input: RecordAxEventInput, options?: { source?: PmxAxSource }): Promise<RecordAxEventResult> {
317
+ return this.canvas.recordAxEvent(input, { source: options?.source ?? 'mcp' });
318
+ }
319
+
320
+ async sendSteering(message: string, options?: { source?: PmxAxSource }): Promise<SendSteeringResult> {
321
+ return this.canvas.sendSteering(message, { source: options?.source ?? 'mcp' });
322
+ }
323
+
324
+ async getAxTimeline(query?: GetAxTimelineQuery): Promise<GetAxTimelineResult> {
325
+ return this.canvas.getAxTimeline(query);
326
+ }
327
+
328
+ async addWorkItem(input: AddWorkItemInput, options?: { source?: PmxAxSource }): Promise<AddWorkItemResult> {
329
+ return this.canvas.addWorkItem(input, { source: options?.source ?? 'mcp' });
330
+ }
331
+
332
+ async updateWorkItem(id: string, patch: UpdateWorkItemPatch, options?: { source?: PmxAxSource }): Promise<UpdateWorkItemResult> {
333
+ return this.canvas.updateWorkItem(id, patch, { source: options?.source ?? 'mcp' });
334
+ }
335
+
336
+ async listWorkItems(): Promise<ListWorkItemsResult> {
337
+ return this.canvas.listWorkItems();
338
+ }
339
+
340
+ async requestApproval(input: RequestApprovalInput, options?: { source?: PmxAxSource }): Promise<RequestApprovalResult> {
341
+ return this.canvas.requestApproval(input, { source: options?.source ?? 'mcp' });
342
+ }
343
+
344
+ async resolveApproval(id: string, decision: 'approved' | 'rejected', options?: { resolution?: string; source?: PmxAxSource }): Promise<ResolveApprovalResult> {
345
+ return this.canvas.resolveApproval(id, decision, {
346
+ ...(options?.resolution !== undefined ? { resolution: options.resolution } : {}),
347
+ source: options?.source ?? 'mcp',
348
+ });
349
+ }
350
+
351
+ async listApprovalGates(): Promise<ListApprovalGatesResult> {
352
+ return this.canvas.listApprovalGates();
353
+ }
354
+
355
+ async addEvidence(input: AddEvidenceInput, options?: { source?: PmxAxSource }): Promise<AddEvidenceResult> {
356
+ return this.canvas.addEvidence(input, { source: options?.source ?? 'mcp' });
357
+ }
358
+
359
+ async addReviewAnnotation(input: AddReviewAnnotationInput, options?: { source?: PmxAxSource }): Promise<AddReviewAnnotationResult> {
360
+ return this.canvas.addReviewAnnotation(input, { source: options?.source ?? 'mcp' });
361
+ }
362
+
363
+ async updateReviewAnnotation(id: string, patch: UpdateReviewAnnotationPatch, options?: { source?: PmxAxSource }): Promise<UpdateReviewAnnotationResult> {
364
+ return this.canvas.updateReviewAnnotation(id, patch, { source: options?.source ?? 'mcp' });
365
+ }
366
+
367
+ async listReviewAnnotations(): Promise<ListReviewAnnotationsResult> {
368
+ return this.canvas.listReviewAnnotations();
369
+ }
370
+
371
+ async getHostCapability(): Promise<GetHostCapabilityResult> {
372
+ return this.canvas.getHostCapability();
373
+ }
374
+
375
+ async reportHostCapability(input: unknown, options?: { source?: PmxAxSource }): Promise<ReportHostCapabilityResult> {
376
+ return this.canvas.reportHostCapability(input, { source: options?.source ?? 'mcp' });
377
+ }
378
+
269
379
  async clear(): Promise<void> {
270
380
  this.canvas.clear();
271
381
  }
@@ -460,6 +570,29 @@ class RemoteCanvasAccess implements CanvasAccess {
460
570
  return { id, url: response.url, spec: response.spec };
461
571
  }
462
572
 
573
+ async streamJsonRenderNode(input: StreamJsonRenderNodeInput): Promise<StreamJsonRenderNodeResult> {
574
+ const response = await this.requestJson<{
575
+ id?: string;
576
+ url?: string;
577
+ applied?: number;
578
+ skipped?: number;
579
+ specVersion?: number;
580
+ elementCount?: number;
581
+ streamStatus?: 'open' | 'closed';
582
+ }>('POST', '/api/canvas/json-render/stream', input);
583
+ const id = typeof response.id === 'string' ? response.id : undefined;
584
+ if (!id) throw new Error('json-render stream response did not include a node id.');
585
+ return {
586
+ id,
587
+ url: response.url ?? '',
588
+ applied: response.applied ?? 0,
589
+ skipped: response.skipped ?? 0,
590
+ specVersion: response.specVersion ?? 0,
591
+ elementCount: response.elementCount ?? 0,
592
+ streamStatus: response.streamStatus ?? 'open',
593
+ };
594
+ }
595
+
463
596
  async addHtmlNode(input: AddHtmlNodeInput): Promise<string> {
464
597
  const {
465
598
  summary,
@@ -625,6 +758,136 @@ class RemoteCanvasAccess implements CanvasAccess {
625
758
  return response.focus;
626
759
  }
627
760
 
761
+ async recordAxEvent(input: RecordAxEventInput, options?: { source?: PmxAxSource }): Promise<RecordAxEventResult> {
762
+ const response = await this.requestJson<{ event?: RecordAxEventResult }>('POST', '/api/canvas/ax/event', {
763
+ ...input,
764
+ source: options?.source ?? 'mcp',
765
+ });
766
+ if (!response.event) throw new Error('Remote canvas did not return an AX event.');
767
+ return response.event;
768
+ }
769
+
770
+ async sendSteering(message: string, options?: { source?: PmxAxSource }): Promise<SendSteeringResult> {
771
+ const response = await this.requestJson<{ steering?: SendSteeringResult }>('POST', '/api/canvas/ax/steer', {
772
+ message,
773
+ source: options?.source ?? 'mcp',
774
+ });
775
+ if (!response.steering) throw new Error('Remote canvas did not return a steering message.');
776
+ return response.steering;
777
+ }
778
+
779
+ async getAxTimeline(query?: GetAxTimelineQuery): Promise<GetAxTimelineResult> {
780
+ const qs = query?.limit ? `?limit=${query.limit}` : '';
781
+ return await this.requestJson<GetAxTimelineResult>('GET', `/api/canvas/ax/timeline${qs}`);
782
+ }
783
+
784
+ async addWorkItem(input: AddWorkItemInput, options?: { source?: PmxAxSource }): Promise<AddWorkItemResult> {
785
+ const response = await this.requestJson<{ workItem?: AddWorkItemResult }>('POST', '/api/canvas/ax/work', {
786
+ ...input,
787
+ source: options?.source ?? 'mcp',
788
+ });
789
+ if (!response.workItem) throw new Error('Remote canvas did not return a work item.');
790
+ return response.workItem;
791
+ }
792
+
793
+ async updateWorkItem(id: string, patch: UpdateWorkItemPatch, options?: { source?: PmxAxSource }): Promise<UpdateWorkItemResult> {
794
+ const response = await fetch(`${this.remoteBaseUrl}/api/canvas/ax/work/${encodeURIComponent(id)}`, {
795
+ method: 'PATCH',
796
+ headers: { 'Content-Type': 'application/json' },
797
+ body: JSON.stringify({ ...patch, source: options?.source ?? 'mcp' }),
798
+ });
799
+ if (response.status === 404) return null;
800
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
801
+ return (await response.json() as { workItem?: AddWorkItemResult }).workItem ?? null;
802
+ }
803
+
804
+ async listWorkItems(): Promise<ListWorkItemsResult> {
805
+ const response = await this.requestJson<{ workItems?: ListWorkItemsResult }>('GET', '/api/canvas/ax/work');
806
+ return response.workItems ?? [];
807
+ }
808
+
809
+ async requestApproval(input: RequestApprovalInput, options?: { source?: PmxAxSource }): Promise<RequestApprovalResult> {
810
+ const response = await this.requestJson<{ approvalGate?: RequestApprovalResult }>('POST', '/api/canvas/ax/approval', {
811
+ ...input,
812
+ source: options?.source ?? 'mcp',
813
+ });
814
+ if (!response.approvalGate) throw new Error('Remote canvas did not return an approval gate.');
815
+ return response.approvalGate;
816
+ }
817
+
818
+ async resolveApproval(id: string, decision: 'approved' | 'rejected', options?: { resolution?: string; source?: PmxAxSource }): Promise<ResolveApprovalResult> {
819
+ const response = await fetch(`${this.remoteBaseUrl}/api/canvas/ax/approval/${encodeURIComponent(id)}/resolve`, {
820
+ method: 'POST',
821
+ headers: { 'Content-Type': 'application/json' },
822
+ body: JSON.stringify({
823
+ decision,
824
+ ...(options?.resolution !== undefined ? { resolution: options.resolution } : {}),
825
+ source: options?.source ?? 'mcp',
826
+ }),
827
+ });
828
+ if (response.status === 404) return null;
829
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
830
+ return (await response.json() as { approvalGate?: RequestApprovalResult }).approvalGate ?? null;
831
+ }
832
+
833
+ async listApprovalGates(): Promise<ListApprovalGatesResult> {
834
+ const response = await this.requestJson<{ approvalGates?: ListApprovalGatesResult }>('GET', '/api/canvas/ax/approval');
835
+ return response.approvalGates ?? [];
836
+ }
837
+
838
+ async addEvidence(input: AddEvidenceInput, options?: { source?: PmxAxSource }): Promise<AddEvidenceResult> {
839
+ const response = await this.requestJson<{ evidence?: AddEvidenceResult }>('POST', '/api/canvas/ax/evidence', {
840
+ ...input,
841
+ source: options?.source ?? 'mcp',
842
+ });
843
+ if (!response.evidence) throw new Error('Remote canvas did not return an evidence item.');
844
+ return response.evidence;
845
+ }
846
+
847
+ async addReviewAnnotation(input: AddReviewAnnotationInput, options?: { source?: PmxAxSource }): Promise<AddReviewAnnotationResult> {
848
+ const response = await fetch(`${this.remoteBaseUrl}/api/canvas/ax/review`, {
849
+ method: 'POST',
850
+ headers: { 'Content-Type': 'application/json' },
851
+ body: JSON.stringify({ ...input, source: options?.source ?? 'mcp' }),
852
+ });
853
+ // 400 = validation rejection (e.g. node-anchored review with an unknown
854
+ // nodeId); mirror the local path and return null rather than throwing.
855
+ if (response.status === 400) return null;
856
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
857
+ return (await response.json() as { reviewAnnotation?: AddReviewAnnotationResult }).reviewAnnotation ?? null;
858
+ }
859
+
860
+ async updateReviewAnnotation(id: string, patch: UpdateReviewAnnotationPatch, options?: { source?: PmxAxSource }): Promise<UpdateReviewAnnotationResult> {
861
+ const response = await fetch(`${this.remoteBaseUrl}/api/canvas/ax/review/${encodeURIComponent(id)}`, {
862
+ method: 'PATCH',
863
+ headers: { 'Content-Type': 'application/json' },
864
+ body: JSON.stringify({ ...patch, source: options?.source ?? 'mcp' }),
865
+ });
866
+ if (response.status === 404) return null;
867
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
868
+ return (await response.json() as { reviewAnnotation?: AddReviewAnnotationResult }).reviewAnnotation ?? null;
869
+ }
870
+
871
+ async listReviewAnnotations(): Promise<ListReviewAnnotationsResult> {
872
+ const response = await this.requestJson<{ reviewAnnotations?: ListReviewAnnotationsResult }>('GET', '/api/canvas/ax/review');
873
+ return response.reviewAnnotations ?? [];
874
+ }
875
+
876
+ async getHostCapability(): Promise<GetHostCapabilityResult> {
877
+ const response = await this.requestJson<{ host?: GetHostCapabilityResult }>('GET', '/api/canvas/ax/host-capability');
878
+ return response.host ?? null;
879
+ }
880
+
881
+ async reportHostCapability(input: unknown, options?: { source?: PmxAxSource }): Promise<ReportHostCapabilityResult> {
882
+ const body = input !== null && typeof input === 'object' && !Array.isArray(input) ? { ...input } : {};
883
+ const response = await this.requestJson<{ host?: ReportHostCapabilityResult }>('PUT', '/api/canvas/ax/host-capability', {
884
+ ...body,
885
+ source: options?.source ?? 'mcp',
886
+ });
887
+ if (!response.host) throw new Error('Remote canvas did not return host capability.');
888
+ return response.host;
889
+ }
890
+
628
891
  async setContextPins(nodeIds: string[], mode: 'set' | 'add' | 'remove' = 'set'): Promise<SetContextPinsResult> {
629
892
  const existing = mode === 'set' ? [] : await this.getPinnedNodeIds();
630
893
  const requested = new Set(nodeIds);