pmx-canvas 0.1.25 → 0.1.27

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 (63) hide show
  1. package/.github/extensions/pmx-canvas/extension.mjs +191 -0
  2. package/CHANGELOG.md +116 -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 +7 -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 +23 -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 +45 -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 +118 -2
  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 +19 -1
  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 +63 -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 +280 -2
  40. package/src/cli/index.ts +2 -1
  41. package/src/client/nodes/ExtAppFrame.tsx +23 -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 +97 -10
  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 +383 -0
  48. package/src/json-render/charts/tufte-definitions.ts +128 -0
  49. package/src/json-render/directives.ts +29 -0
  50. package/src/json-render/renderer/index.css +101 -0
  51. package/src/json-render/renderer/index.tsx +33 -0
  52. package/src/json-render/server.ts +257 -5
  53. package/src/mcp/canvas-access.ts +261 -0
  54. package/src/mcp/server.ts +500 -7
  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 +107 -0
  59. package/src/server/canvas-schema.ts +26 -3
  60. package/src/server/canvas-state.ts +349 -2
  61. package/src/server/index.ts +250 -2
  62. package/src/server/mutation-history.ts +6 -0
  63. package/src/server/server.ts +428 -2
@@ -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>;
@@ -194,6 +235,10 @@ class LocalCanvasAccess implements CanvasAccess {
194
235
  return this.canvas.addJsonRenderNode(input);
195
236
  }
196
237
 
238
+ async streamJsonRenderNode(input: StreamJsonRenderNodeInput): Promise<StreamJsonRenderNodeResult> {
239
+ return this.canvas.streamJsonRenderNode(input);
240
+ }
241
+
197
242
  async addHtmlNode(input: AddHtmlNodeInput): Promise<string> {
198
243
  return this.canvas.addHtmlNode(input);
199
244
  }
@@ -266,6 +311,69 @@ class LocalCanvasAccess implements CanvasAccess {
266
311
  return this.canvas.setAxFocus(nodeIds, { source: options?.source ?? 'mcp' });
267
312
  }
268
313
 
314
+ async recordAxEvent(input: RecordAxEventInput, options?: { source?: PmxAxSource }): Promise<RecordAxEventResult> {
315
+ return this.canvas.recordAxEvent(input, { source: options?.source ?? 'mcp' });
316
+ }
317
+
318
+ async sendSteering(message: string, options?: { source?: PmxAxSource }): Promise<SendSteeringResult> {
319
+ return this.canvas.sendSteering(message, { source: options?.source ?? 'mcp' });
320
+ }
321
+
322
+ async getAxTimeline(query?: GetAxTimelineQuery): Promise<GetAxTimelineResult> {
323
+ return this.canvas.getAxTimeline(query);
324
+ }
325
+
326
+ async addWorkItem(input: AddWorkItemInput, options?: { source?: PmxAxSource }): Promise<AddWorkItemResult> {
327
+ return this.canvas.addWorkItem(input, { source: options?.source ?? 'mcp' });
328
+ }
329
+
330
+ async updateWorkItem(id: string, patch: UpdateWorkItemPatch, options?: { source?: PmxAxSource }): Promise<UpdateWorkItemResult> {
331
+ return this.canvas.updateWorkItem(id, patch, { source: options?.source ?? 'mcp' });
332
+ }
333
+
334
+ async listWorkItems(): Promise<ListWorkItemsResult> {
335
+ return this.canvas.listWorkItems();
336
+ }
337
+
338
+ async requestApproval(input: RequestApprovalInput, options?: { source?: PmxAxSource }): Promise<RequestApprovalResult> {
339
+ return this.canvas.requestApproval(input, { source: options?.source ?? 'mcp' });
340
+ }
341
+
342
+ async resolveApproval(id: string, decision: 'approved' | 'rejected', options?: { resolution?: string; source?: PmxAxSource }): Promise<ResolveApprovalResult> {
343
+ return this.canvas.resolveApproval(id, decision, {
344
+ ...(options?.resolution !== undefined ? { resolution: options.resolution } : {}),
345
+ source: options?.source ?? 'mcp',
346
+ });
347
+ }
348
+
349
+ async listApprovalGates(): Promise<ListApprovalGatesResult> {
350
+ return this.canvas.listApprovalGates();
351
+ }
352
+
353
+ async addEvidence(input: AddEvidenceInput, options?: { source?: PmxAxSource }): Promise<AddEvidenceResult> {
354
+ return this.canvas.addEvidence(input, { source: options?.source ?? 'mcp' });
355
+ }
356
+
357
+ async addReviewAnnotation(input: AddReviewAnnotationInput, options?: { source?: PmxAxSource }): Promise<AddReviewAnnotationResult> {
358
+ return this.canvas.addReviewAnnotation(input, { source: options?.source ?? 'mcp' });
359
+ }
360
+
361
+ async updateReviewAnnotation(id: string, patch: UpdateReviewAnnotationPatch, options?: { source?: PmxAxSource }): Promise<UpdateReviewAnnotationResult> {
362
+ return this.canvas.updateReviewAnnotation(id, patch, { source: options?.source ?? 'mcp' });
363
+ }
364
+
365
+ async listReviewAnnotations(): Promise<ListReviewAnnotationsResult> {
366
+ return this.canvas.listReviewAnnotations();
367
+ }
368
+
369
+ async getHostCapability(): Promise<GetHostCapabilityResult> {
370
+ return this.canvas.getHostCapability();
371
+ }
372
+
373
+ async reportHostCapability(input: unknown, options?: { source?: PmxAxSource }): Promise<ReportHostCapabilityResult> {
374
+ return this.canvas.reportHostCapability(input, { source: options?.source ?? 'mcp' });
375
+ }
376
+
269
377
  async clear(): Promise<void> {
270
378
  this.canvas.clear();
271
379
  }
@@ -460,6 +568,29 @@ class RemoteCanvasAccess implements CanvasAccess {
460
568
  return { id, url: response.url, spec: response.spec };
461
569
  }
462
570
 
571
+ async streamJsonRenderNode(input: StreamJsonRenderNodeInput): Promise<StreamJsonRenderNodeResult> {
572
+ const response = await this.requestJson<{
573
+ id?: string;
574
+ url?: string;
575
+ applied?: number;
576
+ skipped?: number;
577
+ specVersion?: number;
578
+ elementCount?: number;
579
+ streamStatus?: 'open' | 'closed';
580
+ }>('POST', '/api/canvas/json-render/stream', input);
581
+ const id = typeof response.id === 'string' ? response.id : undefined;
582
+ if (!id) throw new Error('json-render stream response did not include a node id.');
583
+ return {
584
+ id,
585
+ url: response.url ?? '',
586
+ applied: response.applied ?? 0,
587
+ skipped: response.skipped ?? 0,
588
+ specVersion: response.specVersion ?? 0,
589
+ elementCount: response.elementCount ?? 0,
590
+ streamStatus: response.streamStatus ?? 'open',
591
+ };
592
+ }
593
+
463
594
  async addHtmlNode(input: AddHtmlNodeInput): Promise<string> {
464
595
  const {
465
596
  summary,
@@ -625,6 +756,136 @@ class RemoteCanvasAccess implements CanvasAccess {
625
756
  return response.focus;
626
757
  }
627
758
 
759
+ async recordAxEvent(input: RecordAxEventInput, options?: { source?: PmxAxSource }): Promise<RecordAxEventResult> {
760
+ const response = await this.requestJson<{ event?: RecordAxEventResult }>('POST', '/api/canvas/ax/event', {
761
+ ...input,
762
+ source: options?.source ?? 'mcp',
763
+ });
764
+ if (!response.event) throw new Error('Remote canvas did not return an AX event.');
765
+ return response.event;
766
+ }
767
+
768
+ async sendSteering(message: string, options?: { source?: PmxAxSource }): Promise<SendSteeringResult> {
769
+ const response = await this.requestJson<{ steering?: SendSteeringResult }>('POST', '/api/canvas/ax/steer', {
770
+ message,
771
+ source: options?.source ?? 'mcp',
772
+ });
773
+ if (!response.steering) throw new Error('Remote canvas did not return a steering message.');
774
+ return response.steering;
775
+ }
776
+
777
+ async getAxTimeline(query?: GetAxTimelineQuery): Promise<GetAxTimelineResult> {
778
+ const qs = query?.limit ? `?limit=${query.limit}` : '';
779
+ return await this.requestJson<GetAxTimelineResult>('GET', `/api/canvas/ax/timeline${qs}`);
780
+ }
781
+
782
+ async addWorkItem(input: AddWorkItemInput, options?: { source?: PmxAxSource }): Promise<AddWorkItemResult> {
783
+ const response = await this.requestJson<{ workItem?: AddWorkItemResult }>('POST', '/api/canvas/ax/work', {
784
+ ...input,
785
+ source: options?.source ?? 'mcp',
786
+ });
787
+ if (!response.workItem) throw new Error('Remote canvas did not return a work item.');
788
+ return response.workItem;
789
+ }
790
+
791
+ async updateWorkItem(id: string, patch: UpdateWorkItemPatch, options?: { source?: PmxAxSource }): Promise<UpdateWorkItemResult> {
792
+ const response = await fetch(`${this.remoteBaseUrl}/api/canvas/ax/work/${encodeURIComponent(id)}`, {
793
+ method: 'PATCH',
794
+ headers: { 'Content-Type': 'application/json' },
795
+ body: JSON.stringify({ ...patch, source: options?.source ?? 'mcp' }),
796
+ });
797
+ if (response.status === 404) return null;
798
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
799
+ return (await response.json() as { workItem?: AddWorkItemResult }).workItem ?? null;
800
+ }
801
+
802
+ async listWorkItems(): Promise<ListWorkItemsResult> {
803
+ const response = await this.requestJson<{ workItems?: ListWorkItemsResult }>('GET', '/api/canvas/ax/work');
804
+ return response.workItems ?? [];
805
+ }
806
+
807
+ async requestApproval(input: RequestApprovalInput, options?: { source?: PmxAxSource }): Promise<RequestApprovalResult> {
808
+ const response = await this.requestJson<{ approvalGate?: RequestApprovalResult }>('POST', '/api/canvas/ax/approval', {
809
+ ...input,
810
+ source: options?.source ?? 'mcp',
811
+ });
812
+ if (!response.approvalGate) throw new Error('Remote canvas did not return an approval gate.');
813
+ return response.approvalGate;
814
+ }
815
+
816
+ async resolveApproval(id: string, decision: 'approved' | 'rejected', options?: { resolution?: string; source?: PmxAxSource }): Promise<ResolveApprovalResult> {
817
+ const response = await fetch(`${this.remoteBaseUrl}/api/canvas/ax/approval/${encodeURIComponent(id)}/resolve`, {
818
+ method: 'POST',
819
+ headers: { 'Content-Type': 'application/json' },
820
+ body: JSON.stringify({
821
+ decision,
822
+ ...(options?.resolution !== undefined ? { resolution: options.resolution } : {}),
823
+ source: options?.source ?? 'mcp',
824
+ }),
825
+ });
826
+ if (response.status === 404) return null;
827
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
828
+ return (await response.json() as { approvalGate?: RequestApprovalResult }).approvalGate ?? null;
829
+ }
830
+
831
+ async listApprovalGates(): Promise<ListApprovalGatesResult> {
832
+ const response = await this.requestJson<{ approvalGates?: ListApprovalGatesResult }>('GET', '/api/canvas/ax/approval');
833
+ return response.approvalGates ?? [];
834
+ }
835
+
836
+ async addEvidence(input: AddEvidenceInput, options?: { source?: PmxAxSource }): Promise<AddEvidenceResult> {
837
+ const response = await this.requestJson<{ evidence?: AddEvidenceResult }>('POST', '/api/canvas/ax/evidence', {
838
+ ...input,
839
+ source: options?.source ?? 'mcp',
840
+ });
841
+ if (!response.evidence) throw new Error('Remote canvas did not return an evidence item.');
842
+ return response.evidence;
843
+ }
844
+
845
+ async addReviewAnnotation(input: AddReviewAnnotationInput, options?: { source?: PmxAxSource }): Promise<AddReviewAnnotationResult> {
846
+ const response = await fetch(`${this.remoteBaseUrl}/api/canvas/ax/review`, {
847
+ method: 'POST',
848
+ headers: { 'Content-Type': 'application/json' },
849
+ body: JSON.stringify({ ...input, source: options?.source ?? 'mcp' }),
850
+ });
851
+ // 400 = validation rejection (e.g. node-anchored review with an unknown
852
+ // nodeId); mirror the local path and return null rather than throwing.
853
+ if (response.status === 400) return null;
854
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
855
+ return (await response.json() as { reviewAnnotation?: AddReviewAnnotationResult }).reviewAnnotation ?? null;
856
+ }
857
+
858
+ async updateReviewAnnotation(id: string, patch: UpdateReviewAnnotationPatch, options?: { source?: PmxAxSource }): Promise<UpdateReviewAnnotationResult> {
859
+ const response = await fetch(`${this.remoteBaseUrl}/api/canvas/ax/review/${encodeURIComponent(id)}`, {
860
+ method: 'PATCH',
861
+ headers: { 'Content-Type': 'application/json' },
862
+ body: JSON.stringify({ ...patch, source: options?.source ?? 'mcp' }),
863
+ });
864
+ if (response.status === 404) return null;
865
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
866
+ return (await response.json() as { reviewAnnotation?: AddReviewAnnotationResult }).reviewAnnotation ?? null;
867
+ }
868
+
869
+ async listReviewAnnotations(): Promise<ListReviewAnnotationsResult> {
870
+ const response = await this.requestJson<{ reviewAnnotations?: ListReviewAnnotationsResult }>('GET', '/api/canvas/ax/review');
871
+ return response.reviewAnnotations ?? [];
872
+ }
873
+
874
+ async getHostCapability(): Promise<GetHostCapabilityResult> {
875
+ const response = await this.requestJson<{ host?: GetHostCapabilityResult }>('GET', '/api/canvas/ax/host-capability');
876
+ return response.host ?? null;
877
+ }
878
+
879
+ async reportHostCapability(input: unknown, options?: { source?: PmxAxSource }): Promise<ReportHostCapabilityResult> {
880
+ const body = input !== null && typeof input === 'object' && !Array.isArray(input) ? { ...input } : {};
881
+ const response = await this.requestJson<{ host?: ReportHostCapabilityResult }>('PUT', '/api/canvas/ax/host-capability', {
882
+ ...body,
883
+ source: options?.source ?? 'mcp',
884
+ });
885
+ if (!response.host) throw new Error('Remote canvas did not return host capability.');
886
+ return response.host;
887
+ }
888
+
628
889
  async setContextPins(nodeIds: string[], mode: 'set' | 'add' | 'remove' = 'set'): Promise<SetContextPinsResult> {
629
890
  const existing = mode === 'set' ? [] : await this.getPinnedNodeIds();
630
891
  const requested = new Set(nodeIds);