@ericsanchezok/synergy-util 2.4.4

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.
@@ -0,0 +1,841 @@
1
+ export const SYNERGY_CAPABILITY_CATEGORIES = [
2
+ "tools",
3
+ "files",
4
+ "network",
5
+ "data",
6
+ "ui",
7
+ "runtime",
8
+ "hooks",
9
+ "session",
10
+ "browser",
11
+ "identity",
12
+ "communication",
13
+ "platform",
14
+ ];
15
+ export function publicToolNames(manifest) {
16
+ return (manifest.contributes?.tools ?? [])
17
+ .filter((tool) => tool.exposure?.mode !== "internal")
18
+ .map((tool) => tool.name);
19
+ }
20
+ export function hasPublicTools(manifest) {
21
+ return publicToolNames(manifest).length > 0;
22
+ }
23
+ export const SYNERGY_CAPABILITY_DETAILS = {
24
+ shell_read: {
25
+ category: "runtime",
26
+ severity: "low",
27
+ title: "Run read-only shell commands",
28
+ description: "Can run shell commands classified as read-only.",
29
+ },
30
+ shell: {
31
+ category: "runtime",
32
+ severity: "medium",
33
+ title: "Run shell commands",
34
+ description: "Can execute non-destructive shell commands in the current workspace.",
35
+ },
36
+ shell_remote_write: {
37
+ category: "runtime",
38
+ severity: "medium",
39
+ title: "Run remote-write shell commands",
40
+ description: "Can modify existing remote state outside ordinary branch/PR publication workflows.",
41
+ },
42
+ shell_remote_publish: {
43
+ category: "runtime",
44
+ severity: "medium",
45
+ title: "Publish development changes",
46
+ description: "Can push explicit non-protected branches or create pull requests without destructive remote updates.",
47
+ },
48
+ shell_destructive: {
49
+ category: "runtime",
50
+ severity: "high",
51
+ title: "Run destructive shell commands",
52
+ description: "Can run commands that may delete, overwrite, or rewrite important local state.",
53
+ nonBypassable: true,
54
+ },
55
+ shell_hardline: {
56
+ category: "runtime",
57
+ severity: "high",
58
+ title: "Run forbidden shell commands",
59
+ description: "Matches shell commands that Synergy treats as a hard safety boundary.",
60
+ nonBypassable: true,
61
+ },
62
+ file_write: {
63
+ category: "files",
64
+ severity: "medium",
65
+ title: "Write workspace files",
66
+ description: "Can create, modify, or delete files in your workspace.",
67
+ },
68
+ file_read: {
69
+ category: "files",
70
+ severity: "low",
71
+ title: "Read workspace files",
72
+ description: "Can read files and directories in your workspace.",
73
+ },
74
+ file_external_read: {
75
+ category: "files",
76
+ severity: "low",
77
+ title: "Read external files",
78
+ description: "Can read files outside the active workspace.",
79
+ },
80
+ file_external_write: {
81
+ category: "files",
82
+ severity: "high",
83
+ title: "Write external files",
84
+ description: "Can create, modify, or delete files outside the active workspace.",
85
+ nonBypassable: true,
86
+ },
87
+ network_read: {
88
+ category: "network",
89
+ severity: "low",
90
+ title: "Read from network",
91
+ description: "Can fetch or search public network resources without mutating remote state.",
92
+ },
93
+ network_request: {
94
+ category: "network",
95
+ severity: "medium",
96
+ title: "Access network",
97
+ description: "Can make outbound network requests.",
98
+ },
99
+ mcp_spawn: {
100
+ category: "runtime",
101
+ severity: "medium",
102
+ title: "Spawn MCP servers",
103
+ description: "Can start and manage MCP server processes.",
104
+ nonBypassable: true,
105
+ },
106
+ mcp_invoke: {
107
+ category: "tools",
108
+ severity: "medium",
109
+ title: "Invoke MCP tools",
110
+ description: "Can call tools exposed by MCP servers.",
111
+ nonBypassable: true,
112
+ },
113
+ session_data: {
114
+ category: "data",
115
+ severity: "low",
116
+ title: "Read session data",
117
+ description: "Can access session metadata and message history.",
118
+ },
119
+ workspace_data: {
120
+ category: "data",
121
+ severity: "low",
122
+ title: "Read workspace metadata",
123
+ description: "Can access workspace metadata and directory information.",
124
+ },
125
+ "config:write": {
126
+ category: "data",
127
+ severity: "medium",
128
+ title: "Write configuration",
129
+ description: "Can modify global Synergy configuration values.",
130
+ },
131
+ "config:read": {
132
+ category: "data",
133
+ severity: "low",
134
+ title: "Read configuration",
135
+ description: "Can read Synergy configuration values.",
136
+ },
137
+ secrets: {
138
+ category: "data",
139
+ severity: "high",
140
+ title: "Access stored credentials",
141
+ description: "Can read stored API keys, tokens, and other credentials.",
142
+ nonBypassable: true,
143
+ },
144
+ task: {
145
+ category: "tools",
146
+ severity: "medium",
147
+ title: "Delegate tasks to subagents",
148
+ description: "Can launch approved Synergy subagents from plugin tools.",
149
+ },
150
+ prompt_transform: {
151
+ category: "hooks",
152
+ severity: "high",
153
+ title: "Transform prompts",
154
+ description: "Can modify the system prompt and message context sent to the LLM.",
155
+ },
156
+ compaction_transform: {
157
+ category: "hooks",
158
+ severity: "high",
159
+ title: "Transform compaction",
160
+ description: "Can modify session compaction inputs and outputs.",
161
+ },
162
+ tool_execution_hook: {
163
+ category: "hooks",
164
+ severity: "medium",
165
+ title: "Intercept tool execution",
166
+ description: "Can rewrite tool arguments or outputs within its declared hook scope.",
167
+ },
168
+ permission_hook: {
169
+ category: "hooks",
170
+ severity: "high",
171
+ title: "Override permission decisions",
172
+ description: "Can allow or deny permission requests within its declared hook scope.",
173
+ },
174
+ event_hook: {
175
+ category: "hooks",
176
+ severity: "medium",
177
+ title: "Subscribe to Synergy events",
178
+ description: "Can receive approved Synergy runtime events.",
179
+ },
180
+ session_state: {
181
+ category: "session",
182
+ severity: "low",
183
+ title: "Update session state",
184
+ description: "Can update local session coordination state.",
185
+ },
186
+ browser_interact: {
187
+ category: "browser",
188
+ severity: "medium",
189
+ title: "Interact with browser",
190
+ description: "Can click, type, scroll, or otherwise interact with the browser workspace.",
191
+ },
192
+ browser_inspect: {
193
+ category: "browser",
194
+ severity: "low",
195
+ title: "Inspect browser",
196
+ description: "Can inspect browser page state, screenshots, console, or network metadata.",
197
+ },
198
+ browser_eval_readonly: {
199
+ category: "browser",
200
+ severity: "medium",
201
+ title: "Evaluate read-only browser code",
202
+ description: "Can run read-only JavaScript inspection in the browser workspace.",
203
+ },
204
+ browser_eval_trusted: {
205
+ category: "browser",
206
+ severity: "high",
207
+ title: "Evaluate trusted browser code",
208
+ description: "Can run privileged JavaScript in the browser workspace.",
209
+ nonBypassable: true,
210
+ },
211
+ browser_clipboard: {
212
+ category: "browser",
213
+ severity: "medium",
214
+ title: "Use browser clipboard",
215
+ description: "Can read from or write to browser clipboard state.",
216
+ },
217
+ browser_download: {
218
+ category: "browser",
219
+ severity: "medium",
220
+ title: "Download browser files",
221
+ description: "Can download files through the browser workspace.",
222
+ },
223
+ browser_viewport: {
224
+ category: "browser",
225
+ severity: "low",
226
+ title: "Resize browser viewport",
227
+ description: "Can change the browser workspace viewport.",
228
+ },
229
+ identity_act: {
230
+ category: "identity",
231
+ severity: "high",
232
+ title: "Act as an identity",
233
+ description: "Can perform actions under a user or agent identity.",
234
+ nonBypassable: true,
235
+ },
236
+ communication_email: {
237
+ category: "communication",
238
+ severity: "high",
239
+ title: "Use email",
240
+ description: "Can read or send email through configured accounts.",
241
+ nonBypassable: true,
242
+ },
243
+ channel_outbound: {
244
+ category: "communication",
245
+ severity: "high",
246
+ title: "Send outbound channel messages",
247
+ description: "Can send messages through configured external channels.",
248
+ nonBypassable: true,
249
+ },
250
+ platform_control: {
251
+ category: "platform",
252
+ severity: "high",
253
+ title: "Control platform integrations",
254
+ description: "Can modify or control platform-level integrations.",
255
+ nonBypassable: true,
256
+ },
257
+ protected_op: {
258
+ category: "platform",
259
+ severity: "high",
260
+ title: "Protected operation",
261
+ description: "Touches a protected Synergy safety boundary.",
262
+ nonBypassable: true,
263
+ },
264
+ };
265
+ export const SYNERGY_PROFILE_CAPABILITIES = [
266
+ "file_read",
267
+ "file_write",
268
+ "shell_read",
269
+ "shell",
270
+ "shell_remote_write",
271
+ "shell_remote_publish",
272
+ "shell_destructive",
273
+ "shell_hardline",
274
+ "file_external_read",
275
+ "file_external_write",
276
+ "network_read",
277
+ "network_request",
278
+ "mcp_invoke",
279
+ "mcp_spawn",
280
+ "session_data",
281
+ "workspace_data",
282
+ "config:read",
283
+ "config:write",
284
+ "secrets",
285
+ "task",
286
+ "prompt_transform",
287
+ "compaction_transform",
288
+ "tool_execution_hook",
289
+ "permission_hook",
290
+ "event_hook",
291
+ "identity_act",
292
+ "communication_email",
293
+ "channel_outbound",
294
+ "platform_control",
295
+ "protected_op",
296
+ "session_state",
297
+ "browser_interact",
298
+ "browser_inspect",
299
+ "browser_eval_readonly",
300
+ "browser_eval_trusted",
301
+ "browser_clipboard",
302
+ "browser_download",
303
+ "browser_viewport",
304
+ ];
305
+ export const SYNERGY_PERMISSION_CAPABILITY = {
306
+ read: "file_read",
307
+ view_file: "file_read",
308
+ scan_files: "file_read",
309
+ parse_code: "file_read",
310
+ grep: "file_read",
311
+ file_search: "file_read",
312
+ glob: "file_read",
313
+ list: "file_read",
314
+ edit: "file_write",
315
+ write: "file_write",
316
+ revise_file: "file_write",
317
+ save_file: "file_write",
318
+ bash: "shell",
319
+ external_directory: "file_external_read",
320
+ webfetch: "network_read",
321
+ websearch: "network_read",
322
+ arxiv_search: "network_read",
323
+ arxiv_download: "network_read",
324
+ download: "network_read",
325
+ network_request: "network_request",
326
+ scan_document: "file_read",
327
+ look_at: "file_read",
328
+ attach: "file_read",
329
+ ast_grep: "file_read",
330
+ lsp: "file_read",
331
+ dagread: "file_read",
332
+ todoread: "file_read",
333
+ task_list: "file_read",
334
+ task_output: "file_read",
335
+ question: "file_read",
336
+ skill: "file_read",
337
+ render: "file_read",
338
+ diagram: "file_read",
339
+ agenda_list: "file_read",
340
+ agenda_logs: "file_read",
341
+ session_list: "file_read",
342
+ session_search: "file_read",
343
+ session_read: "file_read",
344
+ note_list: "file_read",
345
+ note_search: "file_read",
346
+ note_read: "file_read",
347
+ memory_search: "file_read",
348
+ memory_get: "file_read",
349
+ worktree_list: "file_read",
350
+ dagwrite: "session_state",
351
+ dagpatch: "session_state",
352
+ todowrite: "session_state",
353
+ task_cancel: "session_state",
354
+ batch: "session_state",
355
+ doom_loop: "session_state",
356
+ worktree_enter: "file_write",
357
+ worktree_leave: "file_write",
358
+ session_data: "session_data",
359
+ workspace_data: "workspace_data",
360
+ "config:read": "config:read",
361
+ "config:write": "config:write",
362
+ secrets: "secrets",
363
+ email_read: "communication_email",
364
+ email_send: "communication_email",
365
+ communication_email: "communication_email",
366
+ session_send: "channel_outbound",
367
+ channel_outbound: "channel_outbound",
368
+ identity_act: "identity_act",
369
+ platform_control: "platform_control",
370
+ };
371
+ export function permissionCapability(permission) {
372
+ return SYNERGY_PERMISSION_CAPABILITY[permission] ?? permission;
373
+ }
374
+ export function capabilityNonBypassable(capability) {
375
+ return SYNERGY_CAPABILITY_DETAILS[capability]?.nonBypassable === true;
376
+ }
377
+ export function capabilityRisk(capability, manifest) {
378
+ if (capability === "network_request" && manifest) {
379
+ return (manifest.permissions?.network?.connectDomains ?? []).length > 0 ? "medium" : "high";
380
+ }
381
+ const details = SYNERGY_CAPABILITY_DETAILS[capability];
382
+ if (details)
383
+ return details.severity;
384
+ return "high";
385
+ }
386
+ function maxRisk(current, next) {
387
+ if (current === "high" || next === "high")
388
+ return "high";
389
+ if (current === "medium" || next === "medium")
390
+ return "medium";
391
+ return "low";
392
+ }
393
+ function sortKeys(value) {
394
+ if (Array.isArray(value))
395
+ return value.map(sortKeys);
396
+ if (value !== null && typeof value === "object") {
397
+ const entries = Object.entries(value).sort(([a], [b]) => a.localeCompare(b));
398
+ const result = {};
399
+ for (const [key, item] of entries) {
400
+ result[key] = sortKeys(item);
401
+ }
402
+ return result;
403
+ }
404
+ return value;
405
+ }
406
+ function buildCapabilitySet(permissions, toolOverrides) {
407
+ const caps = new Set();
408
+ const pt = permissions?.tools;
409
+ const pd = permissions?.data;
410
+ const tc = toolOverrides;
411
+ const fs = tc?.filesystem ?? pt?.filesystem ?? "none";
412
+ if (fs === "read")
413
+ caps.add("file_read");
414
+ if (fs === "write") {
415
+ caps.add("file_read");
416
+ caps.add("file_write");
417
+ }
418
+ if (tc?.shell ?? pt?.shell ?? false)
419
+ caps.add("shell");
420
+ if (tc?.network ?? pt?.network ?? false)
421
+ caps.add("network_request");
422
+ if (pt?.mcp === "invoke")
423
+ caps.add("mcp_invoke");
424
+ if (pt?.mcp === "spawn") {
425
+ caps.add("mcp_invoke");
426
+ caps.add("mcp_spawn");
427
+ }
428
+ if (pt?.task)
429
+ caps.add("task");
430
+ const sess = tc?.session ?? pd?.session ?? "none";
431
+ if (sess === "read")
432
+ caps.add("session_data");
433
+ const ws = tc?.workspace ?? pd?.workspace ?? "none";
434
+ if (ws === "read")
435
+ caps.add("workspace_data");
436
+ const cfg = tc?.config ?? pd?.config ?? "none";
437
+ if (cfg === "global") {
438
+ caps.add("config:read");
439
+ caps.add("config:write");
440
+ }
441
+ if (cfg === "plugin")
442
+ caps.add("config:read");
443
+ if (pd?.secrets === "own")
444
+ caps.add("secrets");
445
+ const hooks = permissions?.hooks;
446
+ if (hooks?.promptTransform)
447
+ caps.add("prompt_transform");
448
+ if (hooks?.compactionTransform)
449
+ caps.add("compaction_transform");
450
+ if (hooks?.toolExecute && hooks.toolExecute !== "none")
451
+ caps.add("tool_execution_hook");
452
+ if (hooks?.permissionAsk && hooks.permissionAsk !== "none")
453
+ caps.add("permission_hook");
454
+ if (hooks?.events === "all" || (hooks?.events === "selected" && (hooks.eventNames?.length ?? 0) > 0)) {
455
+ caps.add("event_hook");
456
+ }
457
+ return [...caps].sort();
458
+ }
459
+ export function baseCapabilities(manifest) {
460
+ return buildCapabilitySet(manifest.permissions);
461
+ }
462
+ export function toolCapabilities(manifest, tool) {
463
+ return buildCapabilitySet(manifest.permissions, tool.capabilities);
464
+ }
465
+ export function publicToolCapabilities(manifest) {
466
+ const caps = new Set();
467
+ for (const tool of manifest.contributes?.tools ?? []) {
468
+ if (tool.exposure?.mode === "internal")
469
+ continue;
470
+ for (const capability of toolCapabilities(manifest, tool))
471
+ caps.add(capability);
472
+ }
473
+ return [...caps].sort();
474
+ }
475
+ export function computeRisk(capabilities, manifest) {
476
+ if (capabilities.length === 0)
477
+ return "low";
478
+ let risk = "low";
479
+ for (const cap of capabilities) {
480
+ risk = maxRisk(risk, capabilityRisk(cap, manifest));
481
+ }
482
+ return risk;
483
+ }
484
+ export function capabilitiesForRiskScope(manifest, scope) {
485
+ return scope === "agent" ? publicToolCapabilities(manifest) : baseCapabilities(manifest);
486
+ }
487
+ export function pluginRisk(manifest, input) {
488
+ return computeRisk(capabilitiesForRiskScope(manifest, input.scope), manifest);
489
+ }
490
+ export function toolRisk(manifest, tool) {
491
+ return computeRisk(toolCapabilities(manifest, tool), manifest);
492
+ }
493
+ function networkPermissionItem(manifest) {
494
+ const domains = manifest.permissions?.network?.connectDomains ?? [];
495
+ const severity = capabilityRisk("network_request", manifest);
496
+ return {
497
+ key: "network_request",
498
+ category: "network",
499
+ severity,
500
+ title: "Access network",
501
+ description: domains.length > 0
502
+ ? `Can make network requests to: ${domains.join(", ")}.`
503
+ : "Can make outbound network requests to any domain.",
504
+ technical: domains.length > 0 ? `domains: ${domains.join(", ")}` : undefined,
505
+ };
506
+ }
507
+ function contributionPermissionItems(manifest) {
508
+ const ui = manifest.contributes?.ui;
509
+ const perms = manifest.permissions?.ui;
510
+ const items = [];
511
+ if (ui?.toolRenderers || perms?.toolRenderers) {
512
+ items.push({
513
+ key: "ui.toolRenderers",
514
+ category: "ui",
515
+ severity: "low",
516
+ title: "Custom tool renderers",
517
+ description: "Overrides how tool outputs appear in the chat UI.",
518
+ });
519
+ }
520
+ if (ui?.partRenderers || perms?.partRenderers) {
521
+ items.push({
522
+ key: "ui.partRenderers",
523
+ category: "ui",
524
+ severity: "low",
525
+ title: "Custom part renderers",
526
+ description: "Overrides how message parts appear in the chat UI.",
527
+ });
528
+ }
529
+ if (ui?.workbenchPanels || perms?.workbenchPanels) {
530
+ items.push({
531
+ key: "ui.workbenchPanels",
532
+ category: "ui",
533
+ severity: "low",
534
+ title: "Workbench panels",
535
+ description: "Adds custom panels to the side workspace or BottomSpace.",
536
+ });
537
+ }
538
+ if (ui?.appPanels || perms?.appPanels) {
539
+ items.push({
540
+ key: "ui.appPanels",
541
+ category: "ui",
542
+ severity: "low",
543
+ title: "App panels",
544
+ description: "Adds top-level sidebar panels to the app.",
545
+ });
546
+ }
547
+ if (ui?.settings || perms?.settings) {
548
+ items.push({
549
+ key: "ui.settings",
550
+ category: "ui",
551
+ severity: "low",
552
+ title: "Settings page",
553
+ description: "Adds a custom settings page or form.",
554
+ });
555
+ }
556
+ if (ui?.themes || perms?.themes) {
557
+ items.push({
558
+ key: "ui.themes",
559
+ category: "ui",
560
+ severity: "low",
561
+ title: "Custom themes",
562
+ description: "Adds custom color themes to the UI.",
563
+ });
564
+ }
565
+ if (ui?.icons || perms?.icons) {
566
+ items.push({
567
+ key: "ui.icons",
568
+ category: "ui",
569
+ severity: "low",
570
+ title: "Custom icons",
571
+ description: "Adds custom icon sets to the UI.",
572
+ });
573
+ }
574
+ if (ui?.messageSlots || perms?.messageSlots) {
575
+ items.push({
576
+ key: "ui.messageSlots",
577
+ category: "ui",
578
+ severity: "low",
579
+ title: "Message slots",
580
+ description: "Adds custom components around reasoning and tool timeline sections.",
581
+ });
582
+ }
583
+ if (ui?.appRoutes || perms?.appRoutes) {
584
+ items.push({
585
+ key: "ui.appRoutes",
586
+ category: "ui",
587
+ severity: "low",
588
+ title: "App routes",
589
+ description: "Adds custom plugin pages to the app.",
590
+ });
591
+ }
592
+ if (ui?.commands || perms?.commands) {
593
+ items.push({
594
+ key: "ui.commands",
595
+ category: "ui",
596
+ severity: "low",
597
+ title: "Custom commands",
598
+ description: "Adds custom UI commands to the command palette.",
599
+ });
600
+ }
601
+ return items;
602
+ }
603
+ function dataPermissionItems(manifest) {
604
+ const data = manifest.permissions?.data;
605
+ const items = [];
606
+ if (data?.session === "read") {
607
+ items.push({
608
+ key: "data.session",
609
+ category: "data",
610
+ severity: "medium",
611
+ title: "Read session data",
612
+ description: "Can access session history and metadata across all sessions.",
613
+ });
614
+ }
615
+ else if (data?.session === "metadata") {
616
+ items.push({
617
+ key: "data.session",
618
+ category: "data",
619
+ severity: "low",
620
+ title: "Read session metadata",
621
+ description: "Can access session metadata but not message content.",
622
+ });
623
+ }
624
+ if (data?.workspace === "read") {
625
+ items.push({
626
+ key: "data.workspace",
627
+ category: "data",
628
+ severity: "low",
629
+ title: "Read workspace metadata",
630
+ description: "Can access workspace metadata and directory information.",
631
+ });
632
+ }
633
+ if (data?.secrets === "own") {
634
+ items.push({
635
+ key: "data.secrets",
636
+ category: "data",
637
+ severity: "high",
638
+ title: "Own credential store",
639
+ description: "Can read and write its own stored credentials.",
640
+ });
641
+ }
642
+ return items;
643
+ }
644
+ function hookPermissionItems(manifest) {
645
+ const hooks = manifest.permissions?.hooks;
646
+ const items = [];
647
+ if (hooks?.promptTransform) {
648
+ items.push({
649
+ key: "hooks.promptTransform",
650
+ category: "hooks",
651
+ severity: "high",
652
+ title: "Transform prompts",
653
+ description: "Can modify the system prompt and message context sent to the LLM.",
654
+ });
655
+ }
656
+ if (hooks?.toolExecute === "all") {
657
+ items.push({
658
+ key: "hooks.toolExecute",
659
+ category: "hooks",
660
+ severity: "medium",
661
+ title: "Intercept all tool execution",
662
+ description: "Can rewrite arguments and outputs for all tool calls, including from other plugins.",
663
+ });
664
+ }
665
+ else if (hooks?.toolExecute === "declared") {
666
+ items.push({
667
+ key: "hooks.toolExecute",
668
+ category: "hooks",
669
+ severity: "low",
670
+ title: "Intercept declared tool execution",
671
+ description: "Can rewrite arguments and outputs for tools declared in its manifest.",
672
+ });
673
+ }
674
+ else if (hooks?.toolExecute === "own") {
675
+ items.push({
676
+ key: "hooks.toolExecute",
677
+ category: "hooks",
678
+ severity: "low",
679
+ title: "Intercept own tool execution",
680
+ description: "Can rewrite arguments and outputs for its own tool calls.",
681
+ });
682
+ }
683
+ if (hooks?.permissionAsk === "all") {
684
+ items.push({
685
+ key: "hooks.permissionAsk",
686
+ category: "hooks",
687
+ severity: "high",
688
+ title: "Override all permission decisions",
689
+ description: "Can allow or deny any permission request, including from other plugins.",
690
+ });
691
+ }
692
+ else if (hooks?.permissionAsk === "own") {
693
+ items.push({
694
+ key: "hooks.permissionAsk",
695
+ category: "hooks",
696
+ severity: "medium",
697
+ title: "Override own permission decisions",
698
+ description: "Can allow or deny its own permission requests.",
699
+ });
700
+ }
701
+ return items;
702
+ }
703
+ export function permissionItems(manifest, capabilities) {
704
+ const items = [];
705
+ const seen = new Set();
706
+ const add = (item) => {
707
+ if (seen.has(item.key))
708
+ return;
709
+ seen.add(item.key);
710
+ items.push(item);
711
+ };
712
+ for (const capability of capabilities) {
713
+ if (capability === "network_request") {
714
+ add(networkPermissionItem(manifest));
715
+ continue;
716
+ }
717
+ const details = SYNERGY_CAPABILITY_DETAILS[capability];
718
+ if (details)
719
+ add({ key: capability, ...details });
720
+ }
721
+ for (const item of contributionPermissionItems(manifest))
722
+ add(item);
723
+ for (const item of dataPermissionItems(manifest))
724
+ add(item);
725
+ for (const item of hookPermissionItems(manifest))
726
+ add(item);
727
+ return items;
728
+ }
729
+ export function registryPermissionSummary(manifest, capabilities) {
730
+ return permissionItems(manifest, capabilities).map((item) => ({
731
+ key: item.key,
732
+ description: item.description,
733
+ risk: item.severity,
734
+ }));
735
+ }
736
+ export function permissionCategoryForKey(key) {
737
+ const details = SYNERGY_CAPABILITY_DETAILS[key];
738
+ if (details)
739
+ return details.category;
740
+ if (key.startsWith("ui."))
741
+ return "ui";
742
+ if (key.startsWith("hooks."))
743
+ return "hooks";
744
+ if (key.startsWith("data."))
745
+ return "data";
746
+ if (key.startsWith("runtime."))
747
+ return "runtime";
748
+ if (key.startsWith("session."))
749
+ return "session";
750
+ if (key.startsWith("browser."))
751
+ return "browser";
752
+ return "tools";
753
+ }
754
+ export function permissionsHashPayload(manifest, capabilities) {
755
+ return {
756
+ capabilities: [...capabilities].sort(),
757
+ permissions: manifest.permissions ?? {},
758
+ contributes: permissionsRelevantContributes(manifest),
759
+ lifecycle: manifest.lifecycle ?? {},
760
+ };
761
+ }
762
+ function permissionsRelevantContributes(manifest) {
763
+ const contributes = manifest.contributes;
764
+ if (!contributes)
765
+ return undefined;
766
+ return {
767
+ ...(contributes.permissions ? { permissions: contributes.permissions } : {}),
768
+ ...(contributes.tools
769
+ ? {
770
+ tools: contributes.tools.map((tool) => ({
771
+ id: tool.id,
772
+ name: tool.name,
773
+ exposure: tool.exposure,
774
+ capabilities: tool.capabilities,
775
+ })),
776
+ }
777
+ : {}),
778
+ ...(contributes.agents
779
+ ? {
780
+ agents: contributes.agents.map((agent) => ({
781
+ name: agent.name,
782
+ mode: agent.mode,
783
+ hidden: agent.hidden,
784
+ model: agent.model,
785
+ modelRole: agent.modelRole,
786
+ permission: agent.permission,
787
+ })),
788
+ }
789
+ : {}),
790
+ ...(contributes.mcp ? { mcp: contributes.mcp } : {}),
791
+ ...(contributes.ui
792
+ ? {
793
+ ui: {
794
+ toolRenderers: Boolean(contributes.ui.toolRenderers?.length),
795
+ partRenderers: Boolean(contributes.ui.partRenderers?.length),
796
+ workbenchPanels: Boolean(contributes.ui.workbenchPanels?.length),
797
+ appPanels: Boolean(contributes.ui.appPanels?.length),
798
+ settings: Boolean(contributes.ui.settings?.length),
799
+ messageSlots: Boolean(contributes.ui.messageSlots?.length),
800
+ themes: Boolean(contributes.ui.themes?.length),
801
+ icons: Boolean(contributes.ui.icons?.length),
802
+ appRoutes: Boolean(contributes.ui.appRoutes?.length),
803
+ commands: Boolean(contributes.ui.commands?.length),
804
+ },
805
+ }
806
+ : {}),
807
+ };
808
+ }
809
+ export function manifestHashPayload(manifest) {
810
+ return manifest;
811
+ }
812
+ export function stablePluginJson(value) {
813
+ return JSON.stringify(sortKeys(value));
814
+ }
815
+ export const SYNERGY_BRIDGE_METHOD_POLICY = {
816
+ "config.get": { type: "capability", capability: "config:read" },
817
+ "config.set": { type: "capability", capability: "config:write" },
818
+ "config.replace": { type: "capability", capability: "config:write" },
819
+ "secret.get": { type: "capability", capability: "secrets" },
820
+ "secret.set": { type: "capability", capability: "secrets" },
821
+ "secret.delete": { type: "capability", capability: "secrets" },
822
+ "cache.get": { type: "unprivileged" },
823
+ "cache.set": { type: "unprivileged" },
824
+ "cache.delete": { type: "unprivileged" },
825
+ "file.read": { type: "capability", capability: "file_read" },
826
+ "file.write": { type: "capability", capability: "file_write" },
827
+ "network.fetch": { type: "capability", capability: "network_request" },
828
+ "shell.run": { type: "capability", capability: "shell" },
829
+ "session.getMetadata": { type: "capability", capability: "session_data" },
830
+ "workspace.getMetadata": { type: "capability", capability: "workspace_data" },
831
+ "task.run": { type: "capability", capability: "task" },
832
+ "tool.invoke": { type: "unprivileged" },
833
+ "permission.request": { type: "unprivileged" },
834
+ };
835
+ export function bridgeMethodPolicy(method) {
836
+ return SYNERGY_BRIDGE_METHOD_POLICY[method] ?? { type: "unknown" };
837
+ }
838
+ export function bridgeCapability(method) {
839
+ const policy = bridgeMethodPolicy(method);
840
+ return policy.type === "capability" ? policy.capability : undefined;
841
+ }