oc-tweaks 0.5.2 → 0.7.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.
Files changed (3) hide show
  1. package/README.md +10 -0
  2. package/dist/index.js +212 -29
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -89,6 +89,11 @@ Customize the appearance of the WPF notification window.
89
89
  | `shadow` | `true` | Enable or disable the drop shadow effect. |
90
90
  | `idleColor` | `"#4ADE80"` | Accent color for idle (success) notifications. |
91
91
  | `errorColor` | `"#EF4444"` | Accent color for error notifications. |
92
+ | `fadeOut` | `true` | Animate opacity from `initialOpacity` to `finalOpacity` over `duration`. |
93
+ | `initialOpacity` | `0.85` | Window opacity at start (0–1). |
94
+ | `finalOpacity` | `0.05` | Window opacity at end before auto-close (0–1). |
95
+ | `clickThrough` | `true` | Mouse clicks pass through the window (WS_EX_TRANSPARENT). Hover over the ✕ zone to dismiss. |
96
+ | `hoverDismissMs` | `400` | Milliseconds the cursor must dwell on the ✕ zone to dismiss the notification. |
92
97
 
93
98
  #### `notify.toolCall` (Windows WPF)
94
99
 
@@ -305,6 +310,11 @@ bunx oc-tweaks init
305
310
  | `shadow` | `true` | 启用或禁用下拉阴影效果。 |
306
311
  | `idleColor` | `"#4ADE80"` | 空闲 (成功) 通知的强调色。 |
307
312
  | `errorColor` | `"#EF4444"` | 错误通知的强调色。 |
313
+ | `fadeOut` | `true` | 在 `duration` 时间内从 `initialOpacity` 渐隐到 `finalOpacity`。 |
314
+ | `initialOpacity` | `0.85` | 窗口初始不透明度 (0–1)。 |
315
+ | `finalOpacity` | `0.05` | 自动关闭前的最终不透明度 (0–1)。 |
316
+ | `clickThrough` | `true` | 鼠标点击穿透窗口 (WS_EX_TRANSPARENT)。将鼠标悬停在 ✕ 区域即可关闭。 |
317
+ | `hoverDismissMs` | `400` | 光标在 ✕ 区域停留多少毫秒后关闭通知。 |
308
318
 
309
319
  #### `notify.toolCall` (Windows WPF)
310
320
 
package/dist/index.js CHANGED
@@ -289,12 +289,50 @@ Only use \`run_in_background=false\` when ALL of these conditions are met:
289
289
  3. The user is explicitly waiting for that specific result
290
290
 
291
291
  When in doubt → background. Use \`background_output()\` to collect results later.
292
+
293
+ ## Description Transparency Rule
294
+
295
+ Every \`task()\` call MUST include an agent label in the \`description\` field so the user can see which mode you used.
296
+
297
+ Required formats:
298
+ - \`[category:xxx]\` when using \`category\`
299
+ - \`[subagent:xxx]\` when using \`subagent_type\`
300
+
301
+ Examples:
302
+ - \`description="[category:quick] Fix typo in config"\`
303
+ - \`description="[subagent:explore] Inspect auth flow"\`
304
+
305
+ Do NOT omit the tag.
292
306
  `;
293
307
  var VIOLATION_WARNING = `\uD83D\uDCA1 [Reminder] Consider using background mode for better responsiveness.
294
308
  You used foreground mode (run_in_background=false). Check the three conditions in the system prompt.
295
309
  If not all three are met, consider run_in_background=true + background_output() for next time.`;
310
+ function getMissingTransparencyTags(args) {
311
+ const description = args?.description ?? "";
312
+ const missingTags = [];
313
+ if (args?.category) {
314
+ const tag = `[category:${args.category}]`;
315
+ if (!description.includes(tag))
316
+ missingTags.push(tag);
317
+ }
318
+ if (args?.subagent_type) {
319
+ const tag = `[subagent:${args.subagent_type}]`;
320
+ if (!description.includes(tag))
321
+ missingTags.push(tag);
322
+ }
323
+ return missingTags;
324
+ }
325
+ function buildTransparencyWarning(missingTags) {
326
+ const plural = missingTags.length > 1 ? "s" : "";
327
+ return `⚠️ [Transparency] Your task description is missing the required transparency tag${plural}: ${missingTags.join(", ")}
328
+ Add the tag directly to \`description\` so the user can see which agent mode you used.
329
+ Required formats:
330
+ - \`[category:xxx]\`
331
+ - \`[subagent:xxx]\``;
332
+ }
296
333
  var backgroundSubagentPlugin = async () => {
297
334
  const foregroundCalls = new Set;
335
+ const transparencyViolations = new Map;
298
336
  return {
299
337
  "experimental.chat.system.transform": safeHook("background-subagent:system.transform", async (_input, output) => {
300
338
  const config = await loadOcTweaksConfig();
@@ -308,17 +346,32 @@ var backgroundSubagentPlugin = async () => {
308
346
  return;
309
347
  if (input.tool !== "task")
310
348
  return;
349
+ const missingTags = getMissingTransparencyTags(output.args);
350
+ if (missingTags.length > 0) {
351
+ transparencyViolations.set(input.callID, missingTags);
352
+ }
311
353
  if (!output.args?.run_in_background) {
312
354
  foregroundCalls.add(input.callID);
313
355
  }
314
356
  }),
315
357
  "tool.execute.after": safeHook("background-subagent:tool.execute.after", async (input, output) => {
316
- if (!foregroundCalls.has(input.callID))
358
+ const warnings = [];
359
+ if (foregroundCalls.has(input.callID)) {
360
+ foregroundCalls.delete(input.callID);
361
+ warnings.push(VIOLATION_WARNING);
362
+ }
363
+ const missingTags = transparencyViolations.get(input.callID);
364
+ if (missingTags) {
365
+ transparencyViolations.delete(input.callID);
366
+ warnings.push(buildTransparencyWarning(missingTags));
367
+ }
368
+ if (warnings.length === 0)
317
369
  return;
318
- foregroundCalls.delete(input.callID);
319
370
  output.output += `
320
371
 
321
- ${VIOLATION_WARNING}`;
372
+ ${warnings.join(`
373
+
374
+ `)}`;
322
375
  })
323
376
  };
324
377
  };
@@ -662,6 +715,11 @@ async function runWpfNotification($, shellCommand, title, message, tag, style, p
662
715
  const shadow = style?.shadow !== false;
663
716
  const idleColor = style?.idleColor ?? "#4ADE80";
664
717
  const errorColor = style?.errorColor ?? "#EF4444";
718
+ const fadeOut = style?.fadeOut !== false;
719
+ const initialOpacity = style?.initialOpacity ?? 0.85;
720
+ const finalOpacity = style?.finalOpacity ?? 0.05;
721
+ const clickThrough = style?.clickThrough !== false;
722
+ const hoverDismissMs = style?.hoverDismissMs ?? 400;
665
723
  const contentMaxWidth = Math.max(160, width - 120);
666
724
  const accentColor = renderOptions?.accentColor ?? (tag === "Error" ? errorColor : idleColor);
667
725
  const icon = renderOptions?.icon ?? (tag === "Error" ? "❌" : "✅");
@@ -677,8 +735,19 @@ async function runWpfNotification($, shellCommand, title, message, tag, style, p
677
735
  const messageLimit = typeof renderOptions?.maxMessageLength === "number" && renderOptions.maxMessageLength > 0 ? renderOptions.maxMessageLength : 400;
678
736
  const normalizedMessage = renderOptions?.preserveMessageFormatting ? message : cleanMarkdown(message);
679
737
  const textB64 = escapeForPowerShell(truncateText(normalizedMessage, messageLimit));
680
- const shadowXaml = shadow ? '<Border.Effect><DropShadowEffect BlurRadius="20" ShadowDepth="2" Opacity="0.7" Color="Black"/></Border.Effect>' : "";
681
- const dismissHintXaml = showDismissHint ? ` <TextBlock Text="Click to dismiss" Foreground="#555555" FontSize="9" Margin="0,4,0,0"/>` : "";
738
+ const shadowXaml = shadow ? '<Border.Effect><DropShadowEffect BlurRadius="16" ShadowDepth="0" Opacity="0.4" Color="Black"/></Border.Effect>' : "";
739
+ const closeBtnXaml = clickThrough ? [
740
+ ` <Border Name="CloseBg" CornerRadius="4" Width="22" Height="22"`,
741
+ ` HorizontalAlignment="Right" VerticalAlignment="Top"`,
742
+ ` Margin="0,6,6,0" Background="Transparent">`,
743
+ ` <TextBlock Name="CloseBtn" Text="&#x2715;" FontSize="13"`,
744
+ ` Foreground="#2A2A3A"`,
745
+ ` HorizontalAlignment="Center" VerticalAlignment="Center"`,
746
+ ` Margin="0,-1,0,0"/>`,
747
+ ` </Border>`
748
+ ].join(`
749
+ `) : "";
750
+ const dismissHintXaml = showDismissHint && !clickThrough ? ` <TextBlock Text="Click to dismiss" Foreground="#555555" FontSize="9" Margin="0,4,0,0"/>` : "";
682
751
  const xaml = [
683
752
  `<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"`,
684
753
  ` xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"`,
@@ -693,7 +762,7 @@ async function runWpfNotification($, shellCommand, title, message, tag, style, p
693
762
  ` ${shadowXaml}`,
694
763
  ` <Grid>`,
695
764
  ` <Border CornerRadius="${borderRadius},0,0,${borderRadius}" Width="${colorBarWidth}" HorizontalAlignment="Left" Name="ColorBar"/>`,
696
- ` <StackPanel Orientation="Horizontal" Margin="22,12,16,12" VerticalAlignment="Center">`,
765
+ ` <StackPanel Orientation="Horizontal" Margin="22,12,${clickThrough ? 50 : 16},12" VerticalAlignment="Center">`,
697
766
  ` <TextBlock Name="IconText" FontSize="${iconFontSize}" VerticalAlignment="Center" Margin="0,0,15,0" Foreground="White"/>`,
698
767
  ` <StackPanel VerticalAlignment="Center" MaxWidth="${contentMaxWidth}">`,
699
768
  ` <TextBlock Name="TitleText" FontSize="${titleFontSize}" FontWeight="SemiBold" TextWrapping="Wrap"/>`,
@@ -701,6 +770,7 @@ async function runWpfNotification($, shellCommand, title, message, tag, style, p
701
770
  dismissHintXaml,
702
771
  ` </StackPanel>`,
703
772
  ` </StackPanel>`,
773
+ closeBtnXaml,
704
774
  ` </Grid>`,
705
775
  ` </Border>`,
706
776
  `</Window>`
@@ -711,13 +781,55 @@ async function runWpfNotification($, shellCommand, title, message, tag, style, p
711
781
  `$window.Left = ${resolvedPosition.leftExpr}`,
712
782
  `$window.Top = ${resolvedPosition.topExpr}`
713
783
  ] : [];
714
- const psScript = [
715
- "Add-Type -AssemblyName PresentationFramework",
716
- "Add-Type -AssemblyName PresentationCore",
717
- "Add-Type -AssemblyName WindowsBase",
718
- "Add-Type -AssemblyName System.Windows.Forms",
719
- "$targetScreen = [System.Windows.Forms.Screen]::FromPoint([System.Windows.Forms.Cursor]::Position).WorkingArea",
784
+ const win32TypeDef = clickThrough ? [
785
+ "Add-Type -TypeDefinition @'",
786
+ "using System;",
787
+ "using System.Runtime.InteropServices;",
788
+ "",
789
+ "[StructLayout(LayoutKind.Sequential)]",
790
+ "public struct RECT { public int Left, Top, Right, Bottom; }",
791
+ "",
792
+ "[StructLayout(LayoutKind.Sequential)]",
793
+ "public struct POINT { public int X, Y; }",
794
+ "",
795
+ "public static class VDesktop {",
796
+ ' [DllImport("user32.dll", SetLastError = true)]',
797
+ " public static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);",
798
+ "",
799
+ ' [DllImport("user32.dll", SetLastError = true)]',
800
+ " public static extern int GetWindowLong(IntPtr hWnd, int nIndex);",
801
+ "",
802
+ ' [DllImport("user32.dll")]',
803
+ " public static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);",
804
+ "",
805
+ ' [DllImport("user32.dll")]',
806
+ " public static extern bool GetCursorPos(out POINT lpPoint);",
807
+ "",
808
+ " public const int GWL_EXSTYLE = -20;",
809
+ " public const int WS_EX_TOOLWINDOW = 0x00000080;",
810
+ " public const int WS_EX_NOACTIVATE = 0x08000000;",
811
+ " public const int WS_EX_APPWINDOW = 0x00040000;",
812
+ " public const int WS_EX_TRANSPARENT = 0x00000020;",
813
+ "",
814
+ " public static void MakeGlobalWindow(IntPtr hwnd, bool transparent) {",
815
+ " int style = GetWindowLong(hwnd, GWL_EXSTYLE);",
816
+ " style = style | WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE;",
817
+ " style = style & ~WS_EX_APPWINDOW;",
818
+ " if (transparent) { style = style | WS_EX_TRANSPARENT; }",
819
+ " else { style = style & ~WS_EX_TRANSPARENT; }",
820
+ " SetWindowLong(hwnd, GWL_EXSTYLE, style);",
821
+ " }",
720
822
  "",
823
+ " public static bool IsCursorInTopRight(IntPtr hwnd, int zoneW, int zoneH) {",
824
+ " RECT r; POINT p;",
825
+ " if (!GetWindowRect(hwnd, out r)) return false;",
826
+ " if (!GetCursorPos(out p)) return false;",
827
+ " return (p.X >= r.Right - zoneW && p.X <= r.Right &&",
828
+ " p.Y >= r.Top && p.Y <= r.Top + zoneH);",
829
+ " }",
830
+ "}",
831
+ "'@ -ErrorAction SilentlyContinue"
832
+ ] : [
721
833
  "Add-Type -TypeDefinition @'",
722
834
  "using System;",
723
835
  "using System.Runtime.InteropServices;",
@@ -734,14 +846,95 @@ async function runWpfNotification($, shellCommand, title, message, tag, style, p
734
846
  " public const int WS_EX_NOACTIVATE = 0x08000000;",
735
847
  " public const int WS_EX_APPWINDOW = 0x00040000;",
736
848
  "",
737
- " public static void MakeGlobalWindow(IntPtr hwnd) {",
849
+ " public static void MakeGlobalWindow(IntPtr hwnd, bool transparent) {",
738
850
  " int style = GetWindowLong(hwnd, GWL_EXSTYLE);",
739
851
  " style = style | WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE;",
740
852
  " style = style & ~WS_EX_APPWINDOW;",
741
853
  " SetWindowLong(hwnd, GWL_EXSTYLE, style);",
742
854
  " }",
743
855
  "}",
744
- "'@ -ErrorAction SilentlyContinue",
856
+ "'@ -ErrorAction SilentlyContinue"
857
+ ];
858
+ const hoverThreshold = Math.max(1, Math.round(hoverDismissMs / 100));
859
+ const hoverDismissLines = clickThrough ? [
860
+ "",
861
+ "$script:hoverTicks = 0",
862
+ `$hoverThreshold = ${hoverThreshold}`,
863
+ "$brushConv = [System.Windows.Media.BrushConverter]::new()",
864
+ "$closeBtnEl = $window.FindName('CloseBtn')",
865
+ "$closeBgEl = $window.FindName('CloseBg')",
866
+ "",
867
+ "$hitTimer = New-Object System.Windows.Threading.DispatcherTimer",
868
+ "$hitTimer.Interval = [TimeSpan]::FromMilliseconds(100)",
869
+ "$hitTimer.Add_Tick({",
870
+ " if ($script:hwnd -eq [IntPtr]::Zero) { return }",
871
+ " $inZone = [VDesktop]::IsCursorInTopRight($script:hwnd, 80, 50)",
872
+ " if ($inZone) {",
873
+ " $script:hoverTicks++",
874
+ " $closeBtnEl.Foreground = $brushConv.ConvertFromString('#FFFFFF')",
875
+ " $closeBgEl.Background = $brushConv.ConvertFromString('#CC3344')",
876
+ " if ($script:hoverTicks -ge $hoverThreshold) {",
877
+ " $hitTimer.Stop()",
878
+ " $window.Close()",
879
+ " }",
880
+ " } else {",
881
+ " $script:hoverTicks = 0",
882
+ " $closeBtnEl.Foreground = $brushConv.ConvertFromString('#2A2A3A')",
883
+ " $closeBgEl.Background = $brushConv.ConvertFromString('Transparent')",
884
+ " }",
885
+ "})"
886
+ ] : [];
887
+ const loadedLines = clickThrough ? [
888
+ "$window.Add_Loaded({",
889
+ " $script:hwnd = (New-Object System.Windows.Interop.WindowInteropHelper($window)).Handle",
890
+ " [VDesktop]::MakeGlobalWindow($script:hwnd, $true)",
891
+ ...fadeOut ? [
892
+ ` $fadeAnim = New-Object System.Windows.Media.Animation.DoubleAnimation`,
893
+ ` $fadeAnim.From = ${initialOpacity}`,
894
+ ` $fadeAnim.To = ${finalOpacity}`,
895
+ ` $fadeAnim.Duration = [System.Windows.Duration]::new([TimeSpan]::FromMilliseconds($duration))`,
896
+ ` $fadeAnim.Add_Completed({ $window.Close() })`,
897
+ ` $window.BeginAnimation([System.Windows.UIElement]::OpacityProperty, $fadeAnim)`
898
+ ] : [],
899
+ " $hitTimer.Start()",
900
+ "})",
901
+ "",
902
+ "$window.Add_Closed({ $hitTimer.Stop() })"
903
+ ] : [
904
+ "$window.Add_Loaded({",
905
+ " $script:hwnd = (New-Object System.Windows.Interop.WindowInteropHelper($window)).Handle",
906
+ " [VDesktop]::MakeGlobalWindow($script:hwnd, $false)",
907
+ ...fadeOut ? [
908
+ ` $fadeAnim = New-Object System.Windows.Media.Animation.DoubleAnimation`,
909
+ ` $fadeAnim.From = ${initialOpacity}`,
910
+ ` $fadeAnim.To = ${finalOpacity}`,
911
+ ` $fadeAnim.Duration = [System.Windows.Duration]::new([TimeSpan]::FromMilliseconds($duration))`,
912
+ ` $fadeAnim.Add_Completed({ $window.Close() })`,
913
+ ` $window.BeginAnimation([System.Windows.UIElement]::OpacityProperty, $fadeAnim)`
914
+ ] : [],
915
+ "})"
916
+ ];
917
+ const timerLines = !fadeOut ? [
918
+ "",
919
+ "if ($duration -gt 0) {",
920
+ " $timer = New-Object System.Windows.Threading.DispatcherTimer",
921
+ " $timer.Interval = [TimeSpan]::FromMilliseconds($duration)",
922
+ " $timer.Add_Tick({",
923
+ " $window.Close()",
924
+ " $timer.Stop()",
925
+ " })",
926
+ " $timer.Start()",
927
+ "}"
928
+ ] : [];
929
+ const clickDismissLine = !clickThrough ? "$window.Add_MouseLeftButtonDown({ $window.Close() })" : "";
930
+ const psScript = [
931
+ "Add-Type -AssemblyName PresentationFramework",
932
+ "Add-Type -AssemblyName PresentationCore",
933
+ "Add-Type -AssemblyName WindowsBase",
934
+ "Add-Type -AssemblyName System.Windows.Forms",
935
+ "$targetScreen = [System.Windows.Forms.Screen]::FromPoint([System.Windows.Forms.Cursor]::Position).WorkingArea",
936
+ "",
937
+ ...win32TypeDef,
745
938
  "",
746
939
  `$title = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String('${titleB64}'))`,
747
940
  `$text = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String('${textB64}'))`,
@@ -756,6 +949,7 @@ async function runWpfNotification($, shellCommand, title, message, tag, style, p
756
949
  "$reader = New-Object System.Xml.XmlNodeReader $xaml",
757
950
  "$window = [Windows.Markup.XamlReader]::Load($reader)",
758
951
  ...manualPositionLines,
952
+ ...fadeOut ? [`$window.Opacity = ${initialOpacity}`] : [],
759
953
  "",
760
954
  "$colorBar = $window.FindName('ColorBar')",
761
955
  "$iconText = $window.FindName('IconText')",
@@ -768,22 +962,11 @@ async function runWpfNotification($, shellCommand, title, message, tag, style, p
768
962
  "$titleText.Foreground = [System.Windows.Media.BrushConverter]::new().ConvertFromString($accentColor)",
769
963
  "$contentText.Text = $text",
770
964
  "",
771
- "$window.Add_MouseLeftButtonDown({ $window.Close() })",
965
+ clickDismissLine,
966
+ ...hoverDismissLines,
772
967
  "",
773
- "$window.Add_Loaded({",
774
- " $hwnd = (New-Object System.Windows.Interop.WindowInteropHelper($window)).Handle",
775
- " [VDesktop]::MakeGlobalWindow($hwnd)",
776
- "})",
777
- "",
778
- "if ($duration -gt 0) {",
779
- " $timer = New-Object System.Windows.Threading.DispatcherTimer",
780
- " $timer.Interval = [TimeSpan]::FromMilliseconds($duration)",
781
- " $timer.Add_Tick({",
782
- " $window.Close()",
783
- " $timer.Stop()",
784
- " })",
785
- " $timer.Start()",
786
- "}",
968
+ ...loadedLines,
969
+ ...timerLines,
787
970
  "",
788
971
  "$window.ShowActivated = $false",
789
972
  "$window.Show()",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oc-tweaks",
3
- "version": "0.5.2",
3
+ "version": "0.7.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./dist/index.js"