@ericsanchezok/synergy-util 2.4.4 → 3.0.0

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.
@@ -12,14 +12,6 @@ export const SYNERGY_CAPABILITY_CATEGORIES = [
12
12
  "communication",
13
13
  "platform",
14
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
15
  export const SYNERGY_CAPABILITY_DETAILS = {
24
16
  shell_read: {
25
17
  category: "runtime",
@@ -33,17 +25,30 @@ export const SYNERGY_CAPABILITY_DETAILS = {
33
25
  title: "Run shell commands",
34
26
  description: "Can execute non-destructive shell commands in the current workspace.",
35
27
  },
28
+ shell_branch_mutation: {
29
+ category: "runtime",
30
+ severity: "medium",
31
+ title: "Change active branch in workspace",
32
+ description: "Can run git checkout / git switch to change the active branch, which is safe in worktrees but can corrupt concurrent sessions on the main checkout.",
33
+ },
36
34
  shell_remote_write: {
37
35
  category: "runtime",
38
36
  severity: "medium",
39
37
  title: "Run remote-write shell commands",
40
38
  description: "Can modify existing remote state outside ordinary branch/PR publication workflows.",
41
39
  },
40
+ shell_remote_execute: {
41
+ category: "runtime",
42
+ severity: "high",
43
+ title: "Execute commands on remote host",
44
+ description: "Can run bash/process commands on a remote Synergy Link host.",
45
+ nonBypassable: true,
46
+ },
42
47
  shell_remote_publish: {
43
48
  category: "runtime",
44
49
  severity: "medium",
45
50
  title: "Publish development changes",
46
- description: "Can push explicit non-protected branches or create pull requests without destructive remote updates.",
51
+ description: "Can push explicit non-protected branches, create pull requests, comment on PRs/issues, and create issues without destructive remote updates.",
47
52
  },
48
53
  shell_destructive: {
49
54
  category: "runtime",
@@ -143,9 +148,21 @@ export const SYNERGY_CAPABILITY_DETAILS = {
143
148
  },
144
149
  task: {
145
150
  category: "tools",
146
- severity: "medium",
151
+ severity: "low",
147
152
  title: "Delegate tasks to subagents",
148
- description: "Can launch approved Synergy subagents from plugin tools.",
153
+ description: "Runtime control-profile permission to launch an approved Synergy subagent.",
154
+ },
155
+ "blueprint.delegate": {
156
+ category: "runtime",
157
+ severity: "high",
158
+ title: "Delegate Blueprint runs",
159
+ description: "Can create and control BlueprintLoop executions in an existing Session.",
160
+ },
161
+ "lightloop.delegate": {
162
+ category: "runtime",
163
+ severity: "high",
164
+ title: "Enable Light Loop",
165
+ description: "Can enable the Light Loop workflow in an existing Session.",
149
166
  },
150
167
  prompt_transform: {
151
168
  category: "hooks",
@@ -177,6 +194,12 @@ export const SYNERGY_CAPABILITY_DETAILS = {
177
194
  title: "Subscribe to Synergy events",
178
195
  description: "Can receive approved Synergy runtime events.",
179
196
  },
197
+ config_hook: {
198
+ category: "hooks",
199
+ severity: "medium",
200
+ title: "Observe runtime config",
201
+ description: "Can receive redacted runtime configuration snapshots when Synergy starts or reloads config.",
202
+ },
180
203
  session_state: {
181
204
  category: "session",
182
205
  severity: "low",
@@ -214,17 +237,30 @@ export const SYNERGY_CAPABILITY_DETAILS = {
214
237
  title: "Use browser clipboard",
215
238
  description: "Can read from or write to browser clipboard state.",
216
239
  },
240
+ browser_upload: {
241
+ category: "browser",
242
+ severity: "high",
243
+ title: "Upload files to browser",
244
+ description: "Can transfer approved workspace file content into the current browser page.",
245
+ nonBypassable: true,
246
+ },
247
+ browser_coordinate: {
248
+ category: "browser",
249
+ severity: "medium",
250
+ title: "Use browser coordinates",
251
+ description: "Can interact with a browser page using visual coordinates instead of semantic locators.",
252
+ },
217
253
  browser_download: {
218
254
  category: "browser",
219
255
  severity: "medium",
220
256
  title: "Download browser files",
221
257
  description: "Can download files through the browser workspace.",
222
258
  },
223
- browser_viewport: {
259
+ browser_emulation: {
224
260
  category: "browser",
225
261
  severity: "low",
226
- title: "Resize browser viewport",
227
- description: "Can change the browser workspace viewport.",
262
+ title: "Emulate a browser environment",
263
+ description: "Can change browser viewport, device, locale, media, CPU, and network emulation.",
228
264
  },
229
265
  identity_act: {
230
266
  category: "identity",
@@ -267,8 +303,10 @@ export const SYNERGY_PROFILE_CAPABILITIES = [
267
303
  "file_write",
268
304
  "shell_read",
269
305
  "shell",
270
- "shell_remote_write",
306
+ "shell_branch_mutation",
307
+ "shell_remote_execute",
271
308
  "shell_remote_publish",
309
+ "shell_remote_write",
272
310
  "shell_destructive",
273
311
  "shell_hardline",
274
312
  "file_external_read",
@@ -288,6 +326,7 @@ export const SYNERGY_PROFILE_CAPABILITIES = [
288
326
  "tool_execution_hook",
289
327
  "permission_hook",
290
328
  "event_hook",
329
+ "config_hook",
291
330
  "identity_act",
292
331
  "communication_email",
293
332
  "channel_outbound",
@@ -299,8 +338,10 @@ export const SYNERGY_PROFILE_CAPABILITIES = [
299
338
  "browser_eval_readonly",
300
339
  "browser_eval_trusted",
301
340
  "browser_clipboard",
341
+ "browser_upload",
342
+ "browser_coordinate",
302
343
  "browser_download",
303
- "browser_viewport",
344
+ "browser_emulation",
304
345
  ];
305
346
  export const SYNERGY_PERMISSION_CAPABILITY = {
306
347
  read: "file_read",
@@ -325,6 +366,7 @@ export const SYNERGY_PERMISSION_CAPABILITY = {
325
366
  network_request: "network_request",
326
367
  scan_document: "file_read",
327
368
  look_at: "file_read",
369
+ view_image: "file_read",
328
370
  attach: "file_read",
329
371
  ast_grep: "file_read",
330
372
  lsp: "file_read",
@@ -363,7 +405,7 @@ export const SYNERGY_PERMISSION_CAPABILITY = {
363
405
  email_read: "communication_email",
364
406
  email_send: "communication_email",
365
407
  communication_email: "communication_email",
366
- session_send: "channel_outbound",
408
+ session_send: "identity_act",
367
409
  channel_outbound: "channel_outbound",
368
410
  identity_act: "identity_act",
369
411
  platform_control: "platform_control",
@@ -374,364 +416,8 @@ export function permissionCapability(permission) {
374
416
  export function capabilityNonBypassable(capability) {
375
417
  return SYNERGY_CAPABILITY_DETAILS[capability]?.nonBypassable === true;
376
418
  }
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
- }));
419
+ export function capabilityRisk(capability) {
420
+ return SYNERGY_CAPABILITY_DETAILS[capability]?.severity ?? "high";
735
421
  }
736
422
  export function permissionCategoryForKey(key) {
737
423
  const details = SYNERGY_CAPABILITY_DETAILS[key];
@@ -751,91 +437,3 @@ export function permissionCategoryForKey(key) {
751
437
  return "browser";
752
438
  return "tools";
753
439
  }
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
- }