@blunking/codexlink 0.1.12 → 0.1.15

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.
@@ -7,15 +7,15 @@ param(
7
7
 
8
8
  [string]$LogFile = ""
9
9
  )
10
-
11
- $ErrorActionPreference = "SilentlyContinue"
12
-
13
- if (-not ("BlunEmbeddedQueueTitleWatcher" -as [type])) {
14
- Add-Type -ReferencedAssemblies "System.Web.Extensions" -TypeDefinition @"
15
- using System;
16
- using System.Collections;
17
- using System.Collections.Generic;
18
- using System.IO;
10
+
11
+ $ErrorActionPreference = "SilentlyContinue"
12
+
13
+ if (-not ("BlunEmbeddedQueueTitleWatcher" -as [type])) {
14
+ Add-Type -ReferencedAssemblies "System.Web.Extensions" -TypeDefinition @"
15
+ using System;
16
+ using System.Collections;
17
+ using System.Collections.Generic;
18
+ using System.IO;
19
19
  using System.Threading;
20
20
  using System.Web.Script.Serialization;
21
21
 
@@ -48,14 +48,14 @@ public static class BlunEmbeddedQueueTitleWatcher
48
48
  TrySetTitle(_baseTitle);
49
49
  if (_timer != null)
50
50
  {
51
- _timer.Dispose();
52
- }
53
- _timer = new Timer(_ => Tick(), null, 0, 900);
54
- }
55
- }
56
-
57
- private static void Tick()
58
- {
51
+ _timer.Dispose();
52
+ }
53
+ _timer = new Timer(_ => Tick(), null, 0, 900);
54
+ }
55
+ }
56
+
57
+ private static void Tick()
58
+ {
59
59
  try
60
60
  {
61
61
  string title;
@@ -91,15 +91,15 @@ public static class BlunEmbeddedQueueTitleWatcher
91
91
  WriteLog("TITLE_ERROR");
92
92
  }
93
93
  }
94
-
95
- private static void TryWriteNotice(string notice)
96
- {
97
- var normalized = notice ?? "";
98
- if (string.Equals(normalized, _lastNotice, StringComparison.Ordinal))
99
- {
100
- return;
101
- }
102
-
94
+
95
+ private static void TryWriteNotice(string notice)
96
+ {
97
+ var normalized = notice ?? "";
98
+ if (string.Equals(normalized, _lastNotice, StringComparison.Ordinal))
99
+ {
100
+ return;
101
+ }
102
+
103
103
  try
104
104
  {
105
105
  // Do not write background queue notices into the interactive Codex
@@ -131,8 +131,21 @@ public static class BlunEmbeddedQueueTitleWatcher
131
131
 
132
132
  try
133
133
  {
134
- Console.WriteLine("");
135
- Console.WriteLine("[Telegram] " + normalized);
134
+ var mode = Environment.GetEnvironmentVariable("BLUN_TELEGRAM_CONSOLE_UI_NOTICES") ?? "";
135
+ var disabled = string.Equals(mode, "0", StringComparison.OrdinalIgnoreCase)
136
+ || string.Equals(mode, "false", StringComparison.OrdinalIgnoreCase)
137
+ || string.Equals(mode, "no", StringComparison.OrdinalIgnoreCase)
138
+ || string.Equals(mode, "off", StringComparison.OrdinalIgnoreCase);
139
+ var allowAll = string.Equals(mode, "all", StringComparison.OrdinalIgnoreCase)
140
+ || string.Equals(mode, "1", StringComparison.OrdinalIgnoreCase)
141
+ || string.Equals(mode, "true", StringComparison.OrdinalIgnoreCase)
142
+ || string.Equals(mode, "yes", StringComparison.OrdinalIgnoreCase)
143
+ || string.Equals(mode, "on", StringComparison.OrdinalIgnoreCase);
144
+ if (!disabled && (allowAll || string.Equals(normalizedKind, "inbound", StringComparison.OrdinalIgnoreCase)))
145
+ {
146
+ Console.WriteLine("");
147
+ Console.WriteLine("[Telegram] " + normalized);
148
+ }
136
149
  }
137
150
  catch
138
151
  {
@@ -154,13 +167,13 @@ public static class BlunEmbeddedQueueTitleWatcher
154
167
  {
155
168
  return;
156
169
  }
157
-
158
- var raw = File.ReadAllText(_stateFile);
159
- if (string.IsNullOrWhiteSpace(raw))
160
- {
161
- return;
162
- }
163
-
170
+
171
+ var raw = File.ReadAllText(_stateFile);
172
+ if (string.IsNullOrWhiteSpace(raw))
173
+ {
174
+ return;
175
+ }
176
+
164
177
  var serializer = new JavaScriptSerializer();
165
178
  var root = serializer.DeserializeObject(raw) as Dictionary<string, object>;
166
179
  if (root == null || !root.ContainsKey("queue"))
@@ -210,20 +223,20 @@ public static class BlunEmbeddedQueueTitleWatcher
210
223
  var entry = item as Dictionary<string, object>;
211
224
  if (entry == null)
212
225
  {
213
- continue;
214
- }
215
-
216
- var status = GetString(entry, "status");
217
- if (!string.Equals(status, "queued", StringComparison.OrdinalIgnoreCase))
218
- {
219
- continue;
220
- }
221
-
222
- var relevance = GetString(entry, "relevance");
223
- if (string.Equals(relevance, "ambient", StringComparison.OrdinalIgnoreCase) && IsOlderThan(entry, _ambientTtlMs))
224
- {
225
- continue;
226
- }
226
+ continue;
227
+ }
228
+
229
+ var status = GetString(entry, "status");
230
+ if (!string.Equals(status, "queued", StringComparison.OrdinalIgnoreCase))
231
+ {
232
+ continue;
233
+ }
234
+
235
+ var relevance = GetString(entry, "relevance");
236
+ if (string.Equals(relevance, "ambient", StringComparison.OrdinalIgnoreCase) && IsOlderThan(entry, _ambientTtlMs))
237
+ {
238
+ continue;
239
+ }
227
240
 
228
241
  total += 1;
229
242
  CountRelevance(entry, ref direct, ref ambient, ref escalation);
@@ -236,8 +249,8 @@ public static class BlunEmbeddedQueueTitleWatcher
236
249
 
237
250
  if (total == 0)
238
251
  {
239
- return;
240
- }
252
+ return;
253
+ }
241
254
 
242
255
  var parts = new List<string> { "Q:" + total.ToString() };
243
256
  if (pending > 0) parts.Add("P:" + pending.ToString());
@@ -252,15 +265,15 @@ public static class BlunEmbeddedQueueTitleWatcher
252
265
  if (direct > 0) noticeParts.Add("direct " + direct.ToString());
253
266
  if (ambient > 0) noticeParts.Add("group " + ambient.ToString());
254
267
  if (escalation > 0) noticeParts.Add("escalation " + escalation.ToString());
255
- notice = string.Join(" | ", noticeParts.ToArray());
256
- if (string.IsNullOrWhiteSpace(preview))
257
- {
258
- return;
259
- }
260
- title = title + " | " + preview;
261
- notice = notice + " | " + preview;
262
- }
263
-
268
+ notice = string.Join(" | ", noticeParts.ToArray());
269
+ if (string.IsNullOrWhiteSpace(preview))
270
+ {
271
+ return;
272
+ }
273
+ title = title + " | " + preview;
274
+ notice = notice + " | " + preview;
275
+ }
276
+
264
277
  private static object[] AsObjects(object value)
265
278
  {
266
279
  var arr = value as object[];
@@ -273,16 +286,16 @@ public static class BlunEmbeddedQueueTitleWatcher
273
286
  {
274
287
  return list.ToArray();
275
288
  }
276
- return new object[0];
277
- }
278
-
289
+ return new object[0];
290
+ }
291
+
279
292
  private static string GetString(Dictionary<string, object> entry, string key)
280
293
  {
281
- if (!entry.ContainsKey(key) || entry[key] == null)
282
- {
283
- return "";
284
- }
285
- return Convert.ToString(entry[key]) ?? "";
294
+ if (!entry.ContainsKey(key) || entry[key] == null)
295
+ {
296
+ return "";
297
+ }
298
+ return Convert.ToString(entry[key]) ?? "";
286
299
  }
287
300
 
288
301
  private static void CountRelevance(Dictionary<string, object> entry, ref int direct, ref int ambient, ref int escalation)
@@ -337,7 +350,7 @@ public static class BlunEmbeddedQueueTitleWatcher
337
350
  }
338
351
  return state + text;
339
352
  }
340
-
353
+
341
354
  private static string Normalize(string value, int maxLength)
342
355
  {
343
356
  if (string.IsNullOrWhiteSpace(value))
@@ -350,11 +363,11 @@ public static class BlunEmbeddedQueueTitleWatcher
350
363
  {
351
364
  compact = compact.Replace(" ", " ");
352
365
  }
353
-
354
- if (compact.Length <= maxLength)
355
- {
356
- return compact;
357
- }
366
+
367
+ if (compact.Length <= maxLength)
368
+ {
369
+ return compact;
370
+ }
358
371
 
359
372
  return compact.Substring(0, Math.Max(0, maxLength - 3)).TrimEnd() + "...";
360
373
  }
@@ -384,21 +397,21 @@ public static class BlunEmbeddedQueueTitleWatcher
384
397
  .Replace("ü", "ü")
385
398
  .Replace("ß", "ß");
386
399
  }
387
-
400
+
388
401
  private static bool IsOlderThan(Dictionary<string, object> entry, long ttlMs)
389
402
  {
390
- if (!entry.ContainsKey("ts") || entry["ts"] == null)
391
- {
392
- return true;
393
- }
394
- try
395
- {
396
- var parsed = DateTimeOffset.Parse(Convert.ToString(entry["ts"]) ?? "");
397
- return (DateTimeOffset.UtcNow - parsed.ToUniversalTime()).TotalMilliseconds >= ttlMs;
398
- }
399
- catch
400
- {
401
- return true;
403
+ if (!entry.ContainsKey("ts") || entry["ts"] == null)
404
+ {
405
+ return true;
406
+ }
407
+ try
408
+ {
409
+ var parsed = DateTimeOffset.Parse(Convert.ToString(entry["ts"]) ?? "");
410
+ return (DateTimeOffset.UtcNow - parsed.ToUniversalTime()).TotalMilliseconds >= ttlMs;
411
+ }
412
+ catch
413
+ {
414
+ return true;
402
415
  }
403
416
  }
404
417
 
@@ -419,24 +432,24 @@ public static class BlunEmbeddedQueueTitleWatcher
419
432
  }
420
433
  "@
421
434
  }
422
-
423
- $ambientTtlMs = 600000
424
- try {
425
- $stateDir = Split-Path -Parent $StateFile
426
- $envPath = Join-Path $stateDir ".env"
427
- if (Test-Path $envPath) {
428
- foreach ($line in (Get-Content -Path $envPath)) {
429
- if (-not $line) { continue }
430
- if ($line.Trim().StartsWith("#")) { continue }
431
- $parts = $line -split "=", 2
432
- if ($parts.Count -ne 2) { continue }
433
- if ($parts[0].Trim() -eq "BLUN_TELEGRAM_AMBIENT_QUEUE_TTL_MS") {
434
- $ambientTtlMs = [int64]$parts[1].Trim()
435
- }
436
- }
437
- }
438
- } catch {
439
- $ambientTtlMs = 600000
440
- }
441
-
435
+
436
+ $ambientTtlMs = 600000
437
+ try {
438
+ $stateDir = Split-Path -Parent $StateFile
439
+ $envPath = Join-Path $stateDir ".env"
440
+ if (Test-Path $envPath) {
441
+ foreach ($line in (Get-Content -Path $envPath)) {
442
+ if (-not $line) { continue }
443
+ if ($line.Trim().StartsWith("#")) { continue }
444
+ $parts = $line -split "=", 2
445
+ if ($parts.Count -ne 2) { continue }
446
+ if ($parts[0].Trim() -eq "BLUN_TELEGRAM_AMBIENT_QUEUE_TTL_MS") {
447
+ $ambientTtlMs = [int64]$parts[1].Trim()
448
+ }
449
+ }
450
+ }
451
+ } catch {
452
+ $ambientTtlMs = 600000
453
+ }
454
+
442
455
  [BlunEmbeddedQueueTitleWatcher]::Start($StateFile, $BaseTitle, $ambientTtlMs, $LogFile)
@@ -6,18 +6,18 @@ param(
6
6
 
7
7
  [Parameter(Mandatory = $true)]
8
8
  [string]$RuntimeFile,
9
-
10
- [Parameter(Mandatory = $true)]
11
- [string]$StateFile,
12
-
13
- [Parameter(Mandatory = $true)]
14
- [string]$BaseTitle,
15
-
16
- [string]$LogFile = ""
17
- )
18
-
19
- $ErrorActionPreference = "SilentlyContinue"
20
-
9
+
10
+ [Parameter(Mandatory = $true)]
11
+ [string]$StateFile,
12
+
13
+ [Parameter(Mandatory = $true)]
14
+ [string]$BaseTitle,
15
+
16
+ [string]$LogFile = ""
17
+ )
18
+
19
+ $ErrorActionPreference = "SilentlyContinue"
20
+
21
21
  if (-not ("CodexLink.NativeMethods" -as [type])) {
22
22
  Add-Type -TypeDefinition @"
23
23
  using System;
@@ -45,28 +45,28 @@ namespace CodexLink
45
45
  }
46
46
  "@
47
47
  }
48
-
49
- function Try-ReadJson {
50
- param([string]$Path)
51
- if (-not (Test-Path $Path)) { return $null }
52
- try {
53
- $raw = Get-Content -Raw -Path $Path
54
- if ($null -eq $raw) { return $null }
55
- return ($raw -replace "^\uFEFF", "") | ConvertFrom-Json
56
- } catch {
57
- return $null
58
- }
59
- }
60
-
61
- function Write-WatcherLog {
62
- param([string]$Message)
63
- if (-not $LogFile) { return }
64
- try {
65
- Add-Content -Path $LogFile -Value (((Get-Date).ToUniversalTime().ToString("o")) + " " + $Message) -Encoding UTF8
66
- } catch {
67
- }
68
- }
69
-
48
+
49
+ function Try-ReadJson {
50
+ param([string]$Path)
51
+ if (-not (Test-Path $Path)) { return $null }
52
+ try {
53
+ $raw = Get-Content -Raw -Path $Path
54
+ if ($null -eq $raw) { return $null }
55
+ return ($raw -replace "^\uFEFF", "") | ConvertFrom-Json
56
+ } catch {
57
+ return $null
58
+ }
59
+ }
60
+
61
+ function Write-WatcherLog {
62
+ param([string]$Message)
63
+ if (-not $LogFile) { return }
64
+ try {
65
+ Add-Content -Path $LogFile -Value (((Get-Date).ToUniversalTime().ToString("o")) + " " + $Message) -Encoding UTF8
66
+ } catch {
67
+ }
68
+ }
69
+
70
70
  function Test-PidAlive {
71
71
  param([int]$ProcId)
72
72
  if ($ProcId -le 0) { return $false }
@@ -103,14 +103,14 @@ function Get-EffectiveAttachPid {
103
103
 
104
104
  return 0
105
105
  }
106
-
106
+
107
107
  function Normalize-Preview {
108
108
  param([string]$Value, [int]$MaxLength = 44)
109
- $text = [string]$Value
110
- $text = $text -replace "\s+", " "
111
- $text = $text.Trim()
112
- if (-not $text) { return "" }
113
- if ($text.Length -le $MaxLength) { return $text }
109
+ $text = [string]$Value
110
+ $text = $text -replace "\s+", " "
111
+ $text = $text.Trim()
112
+ if (-not $text) { return "" }
113
+ if ($text.Length -le $MaxLength) { return $text }
114
114
  return ($text.Substring(0, [Math]::Max(0, $MaxLength - 3)).TrimEnd() + "...")
115
115
  }
116
116
 
@@ -178,18 +178,18 @@ function Format-QueuePreview {
178
178
  }
179
179
  return "${prefix}${text}"
180
180
  }
181
-
181
+
182
182
  function Get-QueueTitle {
183
183
  param(
184
184
  [object]$State,
185
185
  [string]$FallbackTitle,
186
186
  [int]$IdleCooldownMs = 15000
187
187
  )
188
-
189
- if ($null -eq $State -or $null -eq $State.queue) {
190
- return $FallbackTitle
191
- }
192
-
188
+
189
+ if ($null -eq $State -or $null -eq $State.queue) {
190
+ return $FallbackTitle
191
+ }
192
+
193
193
  $queued = @($State.queue | Where-Object { $_.status -eq "queued" })
194
194
  $pendingReplies = @(Get-OpenPendingReplies -State $State)
195
195
  $totalWaiting = $queued.Count + $pendingReplies.Count
@@ -212,8 +212,8 @@ function Get-QueueTitle {
212
212
  if ($pendingCount -gt 0) { $parts += "P:$pendingCount" }
213
213
  if ($directCount -gt 0) { $parts += "D:$directCount" }
214
214
  if ($ambientCount -gt 0) { $parts += "G:$ambientCount" }
215
- if ($escalationCount -gt 0) { $parts += "E:$escalationCount" }
216
-
215
+ if ($escalationCount -gt 0) { $parts += "E:$escalationCount" }
216
+
217
217
  $summary = ($parts -join " ")
218
218
  $waitReason = Get-QueueWaitReason -State $State -IdleCooldownMs $IdleCooldownMs
219
219
  if ($preview) {
@@ -221,7 +221,7 @@ function Get-QueueTitle {
221
221
  }
222
222
  return "$FallbackTitle | $summary | $waitReason"
223
223
  }
224
-
224
+
225
225
  function Update-ConsoleTitle {
226
226
  param([string]$Title)
227
227
  try {
@@ -235,11 +235,11 @@ function Update-ConsoleTitle {
235
235
  $updated = [CodexLink.NativeMethods]::SetConsoleTitle($Title)
236
236
  [void][CodexLink.NativeMethods]::FreeConsole()
237
237
  return $updated
238
- } catch {
239
- return $false
240
- }
241
- }
242
-
238
+ } catch {
239
+ return $false
240
+ }
241
+ }
242
+
243
243
  function Write-ConsoleNotice {
244
244
  param([string]$Notice)
245
245
  if ($env:BLUN_TELEGRAM_CONSOLE_NOTICES -ne "1") {
@@ -253,15 +253,15 @@ function Write-ConsoleNotice {
253
253
  Write-WatcherLog ("WAIT attach_console_failed_notice target=" + $targetPid)
254
254
  return $false
255
255
  }
256
- $handle = [CodexLink.NativeMethods]::GetStdHandle(-11)
257
- if ($handle -eq [IntPtr]::Zero -or $handle -eq [IntPtr](-1)) {
258
- [void][CodexLink.NativeMethods]::FreeConsole()
259
- return $false
260
- }
261
- $line = "[CodexLink Queue] $Notice`r`n"
262
- [uint32]$written = 0
263
- $ok = [CodexLink.NativeMethods]::WriteConsole($handle, $line, [uint32]$line.Length, [ref]$written, [IntPtr]::Zero)
264
- [void][CodexLink.NativeMethods]::FreeConsole()
256
+ $handle = [CodexLink.NativeMethods]::GetStdHandle(-11)
257
+ if ($handle -eq [IntPtr]::Zero -or $handle -eq [IntPtr](-1)) {
258
+ [void][CodexLink.NativeMethods]::FreeConsole()
259
+ return $false
260
+ }
261
+ $line = "[CodexLink Queue] $Notice`r`n"
262
+ [uint32]$written = 0
263
+ $ok = [CodexLink.NativeMethods]::WriteConsole($handle, $line, [uint32]$line.Length, [ref]$written, [IntPtr]::Zero)
264
+ [void][CodexLink.NativeMethods]::FreeConsole()
265
265
  return $ok
266
266
  } catch {
267
267
  return $false
@@ -273,6 +273,13 @@ function Write-ConsoleUiNotice {
273
273
  [string]$Kind,
274
274
  [string]$Notice
275
275
  )
276
+ $mode = [string]$env:BLUN_TELEGRAM_CONSOLE_UI_NOTICES
277
+ $disabled = $mode -eq "0" -or $mode -ieq "false" -or $mode -ieq "no" -or $mode -ieq "off"
278
+ $allowAll = $mode -ieq "all" -or $mode -eq "1" -or $mode -ieq "true" -or $mode -ieq "yes" -or $mode -ieq "on"
279
+ $isInbound = [string]::Equals($Kind, "inbound", [System.StringComparison]::OrdinalIgnoreCase)
280
+ if ($disabled -or (-not $allowAll -and -not $isInbound)) {
281
+ return $false
282
+ }
276
283
  try {
277
284
  [void][CodexLink.NativeMethods]::FreeConsole()
278
285
  $targetPid = Get-EffectiveAttachPid
@@ -318,17 +325,17 @@ function Get-UiNoticeSnapshot {
318
325
  text = $text
319
326
  }
320
327
  }
321
-
328
+
322
329
  function Get-QueueNotice {
323
330
  param(
324
331
  [object]$State,
325
332
  [int]$IdleCooldownMs = 15000
326
333
  )
327
-
328
- if ($null -eq $State -or $null -eq $State.queue) {
329
- return ""
330
- }
331
-
334
+
335
+ if ($null -eq $State -or $null -eq $State.queue) {
336
+ return ""
337
+ }
338
+
332
339
  $queued = @($State.queue | Where-Object { $_.status -eq "queued" })
333
340
  $pendingReplies = @(Get-OpenPendingReplies -State $State)
334
341
  $totalWaiting = $queued.Count + $pendingReplies.Count
@@ -365,7 +372,7 @@ function Get-QueueNotice {
365
372
  $parts += $preview
366
373
  }
367
374
  }
368
-
375
+
369
376
  return ($parts -join " | ")
370
377
  }
371
378
 
@@ -396,48 +403,48 @@ $lastNotice = ""
396
403
  $lastUiKind = ""
397
404
  $lastUiNotice = ""
398
405
  Write-WatcherLog "START"
399
-
400
- while ($true) {
401
- if (-not (Test-PidAlive -ProcId $FrontendPid)) {
402
- Write-WatcherLog "EXIT frontend_dead"
403
- break
404
- }
405
-
406
- $runtime = Try-ReadJson -Path $RuntimeFile
407
- if ($null -eq $runtime) {
408
- Write-WatcherLog "WAIT runtime_missing"
409
- Start-Sleep -Milliseconds 300
410
- continue
411
- }
412
-
413
- $runtimeOwnerPid = 0
414
- try { $runtimeOwnerPid = [int]$runtime.frontend_host_pid } catch { $runtimeOwnerPid = 0 }
415
- if ($runtimeOwnerPid -ne $FrontendPid) {
416
- Write-WatcherLog ("EXIT owner_changed owner=" + $runtimeOwnerPid)
417
- break
418
- }
419
-
406
+
407
+ while ($true) {
408
+ if (-not (Test-PidAlive -ProcId $FrontendPid)) {
409
+ Write-WatcherLog "EXIT frontend_dead"
410
+ break
411
+ }
412
+
413
+ $runtime = Try-ReadJson -Path $RuntimeFile
414
+ if ($null -eq $runtime) {
415
+ Write-WatcherLog "WAIT runtime_missing"
416
+ Start-Sleep -Milliseconds 300
417
+ continue
418
+ }
419
+
420
+ $runtimeOwnerPid = 0
421
+ try { $runtimeOwnerPid = [int]$runtime.frontend_host_pid } catch { $runtimeOwnerPid = 0 }
422
+ if ($runtimeOwnerPid -ne $FrontendPid) {
423
+ Write-WatcherLog ("EXIT owner_changed owner=" + $runtimeOwnerPid)
424
+ break
425
+ }
426
+
420
427
  $state = Try-ReadJson -Path $StateFile
421
428
  if ($null -eq $state) {
422
429
  Write-WatcherLog "WAIT state_missing"
423
- Start-Sleep -Milliseconds 700
424
- continue
425
- }
426
-
430
+ Start-Sleep -Milliseconds 700
431
+ continue
432
+ }
433
+
427
434
  $title = Get-QueueTitle -State $state -FallbackTitle $BaseTitle -IdleCooldownMs $idleCooldownMs
428
435
  $notice = Get-QueueNotice -State $state -IdleCooldownMs $idleCooldownMs
429
436
  $ui = Get-UiNoticeSnapshot -State $state
430
437
  if ($title -ne $lastTitle) {
431
438
  $updated = Update-ConsoleTitle -Title $title
432
439
  Write-WatcherLog ("TITLE updated=" + $updated + " text=" + $title)
433
- $lastTitle = $title
434
- }
435
- if ($notice -ne $lastNotice) {
436
- if ($notice) {
437
- $noticeUpdated = Write-ConsoleNotice -Notice $notice
438
- Write-WatcherLog ("NOTICE updated=" + $noticeUpdated + " text=" + $notice)
439
- } else {
440
- Write-WatcherLog "NOTICE clear"
440
+ $lastTitle = $title
441
+ }
442
+ if ($notice -ne $lastNotice) {
443
+ if ($notice) {
444
+ $noticeUpdated = Write-ConsoleNotice -Notice $notice
445
+ Write-WatcherLog ("NOTICE updated=" + $noticeUpdated + " text=" + $notice)
446
+ } else {
447
+ Write-WatcherLog "NOTICE clear"
441
448
  }
442
449
  $lastNotice = $notice
443
450
  }