@blunking/codexlink 0.1.12 → 0.1.14

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,15 @@ public static class BlunEmbeddedQueueTitleWatcher
131
131
 
132
132
  try
133
133
  {
134
- Console.WriteLine("");
135
- Console.WriteLine("[Telegram] " + normalized);
134
+ var enabled = Environment.GetEnvironmentVariable("BLUN_TELEGRAM_CONSOLE_UI_NOTICES");
135
+ if (string.Equals(enabled, "1", StringComparison.OrdinalIgnoreCase)
136
+ || string.Equals(enabled, "true", StringComparison.OrdinalIgnoreCase)
137
+ || string.Equals(enabled, "yes", StringComparison.OrdinalIgnoreCase)
138
+ || string.Equals(enabled, "on", StringComparison.OrdinalIgnoreCase))
139
+ {
140
+ Console.WriteLine("");
141
+ Console.WriteLine("[Telegram] " + normalized);
142
+ }
136
143
  }
137
144
  catch
138
145
  {
@@ -154,13 +161,13 @@ public static class BlunEmbeddedQueueTitleWatcher
154
161
  {
155
162
  return;
156
163
  }
157
-
158
- var raw = File.ReadAllText(_stateFile);
159
- if (string.IsNullOrWhiteSpace(raw))
160
- {
161
- return;
162
- }
163
-
164
+
165
+ var raw = File.ReadAllText(_stateFile);
166
+ if (string.IsNullOrWhiteSpace(raw))
167
+ {
168
+ return;
169
+ }
170
+
164
171
  var serializer = new JavaScriptSerializer();
165
172
  var root = serializer.DeserializeObject(raw) as Dictionary<string, object>;
166
173
  if (root == null || !root.ContainsKey("queue"))
@@ -210,20 +217,20 @@ public static class BlunEmbeddedQueueTitleWatcher
210
217
  var entry = item as Dictionary<string, object>;
211
218
  if (entry == null)
212
219
  {
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
- }
220
+ continue;
221
+ }
222
+
223
+ var status = GetString(entry, "status");
224
+ if (!string.Equals(status, "queued", StringComparison.OrdinalIgnoreCase))
225
+ {
226
+ continue;
227
+ }
228
+
229
+ var relevance = GetString(entry, "relevance");
230
+ if (string.Equals(relevance, "ambient", StringComparison.OrdinalIgnoreCase) && IsOlderThan(entry, _ambientTtlMs))
231
+ {
232
+ continue;
233
+ }
227
234
 
228
235
  total += 1;
229
236
  CountRelevance(entry, ref direct, ref ambient, ref escalation);
@@ -236,8 +243,8 @@ public static class BlunEmbeddedQueueTitleWatcher
236
243
 
237
244
  if (total == 0)
238
245
  {
239
- return;
240
- }
246
+ return;
247
+ }
241
248
 
242
249
  var parts = new List<string> { "Q:" + total.ToString() };
243
250
  if (pending > 0) parts.Add("P:" + pending.ToString());
@@ -252,15 +259,15 @@ public static class BlunEmbeddedQueueTitleWatcher
252
259
  if (direct > 0) noticeParts.Add("direct " + direct.ToString());
253
260
  if (ambient > 0) noticeParts.Add("group " + ambient.ToString());
254
261
  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
-
262
+ notice = string.Join(" | ", noticeParts.ToArray());
263
+ if (string.IsNullOrWhiteSpace(preview))
264
+ {
265
+ return;
266
+ }
267
+ title = title + " | " + preview;
268
+ notice = notice + " | " + preview;
269
+ }
270
+
264
271
  private static object[] AsObjects(object value)
265
272
  {
266
273
  var arr = value as object[];
@@ -273,16 +280,16 @@ public static class BlunEmbeddedQueueTitleWatcher
273
280
  {
274
281
  return list.ToArray();
275
282
  }
276
- return new object[0];
277
- }
278
-
283
+ return new object[0];
284
+ }
285
+
279
286
  private static string GetString(Dictionary<string, object> entry, string key)
280
287
  {
281
- if (!entry.ContainsKey(key) || entry[key] == null)
282
- {
283
- return "";
284
- }
285
- return Convert.ToString(entry[key]) ?? "";
288
+ if (!entry.ContainsKey(key) || entry[key] == null)
289
+ {
290
+ return "";
291
+ }
292
+ return Convert.ToString(entry[key]) ?? "";
286
293
  }
287
294
 
288
295
  private static void CountRelevance(Dictionary<string, object> entry, ref int direct, ref int ambient, ref int escalation)
@@ -337,7 +344,7 @@ public static class BlunEmbeddedQueueTitleWatcher
337
344
  }
338
345
  return state + text;
339
346
  }
340
-
347
+
341
348
  private static string Normalize(string value, int maxLength)
342
349
  {
343
350
  if (string.IsNullOrWhiteSpace(value))
@@ -350,11 +357,11 @@ public static class BlunEmbeddedQueueTitleWatcher
350
357
  {
351
358
  compact = compact.Replace(" ", " ");
352
359
  }
353
-
354
- if (compact.Length <= maxLength)
355
- {
356
- return compact;
357
- }
360
+
361
+ if (compact.Length <= maxLength)
362
+ {
363
+ return compact;
364
+ }
358
365
 
359
366
  return compact.Substring(0, Math.Max(0, maxLength - 3)).TrimEnd() + "...";
360
367
  }
@@ -384,21 +391,21 @@ public static class BlunEmbeddedQueueTitleWatcher
384
391
  .Replace("ü", "ü")
385
392
  .Replace("ß", "ß");
386
393
  }
387
-
394
+
388
395
  private static bool IsOlderThan(Dictionary<string, object> entry, long ttlMs)
389
396
  {
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;
397
+ if (!entry.ContainsKey("ts") || entry["ts"] == null)
398
+ {
399
+ return true;
400
+ }
401
+ try
402
+ {
403
+ var parsed = DateTimeOffset.Parse(Convert.ToString(entry["ts"]) ?? "");
404
+ return (DateTimeOffset.UtcNow - parsed.ToUniversalTime()).TotalMilliseconds >= ttlMs;
405
+ }
406
+ catch
407
+ {
408
+ return true;
402
409
  }
403
410
  }
404
411
 
@@ -419,24 +426,24 @@ public static class BlunEmbeddedQueueTitleWatcher
419
426
  }
420
427
  "@
421
428
  }
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
-
429
+
430
+ $ambientTtlMs = 600000
431
+ try {
432
+ $stateDir = Split-Path -Parent $StateFile
433
+ $envPath = Join-Path $stateDir ".env"
434
+ if (Test-Path $envPath) {
435
+ foreach ($line in (Get-Content -Path $envPath)) {
436
+ if (-not $line) { continue }
437
+ if ($line.Trim().StartsWith("#")) { continue }
438
+ $parts = $line -split "=", 2
439
+ if ($parts.Count -ne 2) { continue }
440
+ if ($parts[0].Trim() -eq "BLUN_TELEGRAM_AMBIENT_QUEUE_TTL_MS") {
441
+ $ambientTtlMs = [int64]$parts[1].Trim()
442
+ }
443
+ }
444
+ }
445
+ } catch {
446
+ $ambientTtlMs = 600000
447
+ }
448
+
442
449
  [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,12 @@ function Write-ConsoleUiNotice {
273
273
  [string]$Kind,
274
274
  [string]$Notice
275
275
  )
276
+ if ($env:BLUN_TELEGRAM_CONSOLE_UI_NOTICES -ne "1" -and
277
+ $env:BLUN_TELEGRAM_CONSOLE_UI_NOTICES -ine "true" -and
278
+ $env:BLUN_TELEGRAM_CONSOLE_UI_NOTICES -ine "yes" -and
279
+ $env:BLUN_TELEGRAM_CONSOLE_UI_NOTICES -ine "on") {
280
+ return $false
281
+ }
276
282
  try {
277
283
  [void][CodexLink.NativeMethods]::FreeConsole()
278
284
  $targetPid = Get-EffectiveAttachPid
@@ -318,17 +324,17 @@ function Get-UiNoticeSnapshot {
318
324
  text = $text
319
325
  }
320
326
  }
321
-
327
+
322
328
  function Get-QueueNotice {
323
329
  param(
324
330
  [object]$State,
325
331
  [int]$IdleCooldownMs = 15000
326
332
  )
327
-
328
- if ($null -eq $State -or $null -eq $State.queue) {
329
- return ""
330
- }
331
-
333
+
334
+ if ($null -eq $State -or $null -eq $State.queue) {
335
+ return ""
336
+ }
337
+
332
338
  $queued = @($State.queue | Where-Object { $_.status -eq "queued" })
333
339
  $pendingReplies = @(Get-OpenPendingReplies -State $State)
334
340
  $totalWaiting = $queued.Count + $pendingReplies.Count
@@ -365,7 +371,7 @@ function Get-QueueNotice {
365
371
  $parts += $preview
366
372
  }
367
373
  }
368
-
374
+
369
375
  return ($parts -join " | ")
370
376
  }
371
377
 
@@ -396,48 +402,48 @@ $lastNotice = ""
396
402
  $lastUiKind = ""
397
403
  $lastUiNotice = ""
398
404
  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
-
405
+
406
+ while ($true) {
407
+ if (-not (Test-PidAlive -ProcId $FrontendPid)) {
408
+ Write-WatcherLog "EXIT frontend_dead"
409
+ break
410
+ }
411
+
412
+ $runtime = Try-ReadJson -Path $RuntimeFile
413
+ if ($null -eq $runtime) {
414
+ Write-WatcherLog "WAIT runtime_missing"
415
+ Start-Sleep -Milliseconds 300
416
+ continue
417
+ }
418
+
419
+ $runtimeOwnerPid = 0
420
+ try { $runtimeOwnerPid = [int]$runtime.frontend_host_pid } catch { $runtimeOwnerPid = 0 }
421
+ if ($runtimeOwnerPid -ne $FrontendPid) {
422
+ Write-WatcherLog ("EXIT owner_changed owner=" + $runtimeOwnerPid)
423
+ break
424
+ }
425
+
420
426
  $state = Try-ReadJson -Path $StateFile
421
427
  if ($null -eq $state) {
422
428
  Write-WatcherLog "WAIT state_missing"
423
- Start-Sleep -Milliseconds 700
424
- continue
425
- }
426
-
429
+ Start-Sleep -Milliseconds 700
430
+ continue
431
+ }
432
+
427
433
  $title = Get-QueueTitle -State $state -FallbackTitle $BaseTitle -IdleCooldownMs $idleCooldownMs
428
434
  $notice = Get-QueueNotice -State $state -IdleCooldownMs $idleCooldownMs
429
435
  $ui = Get-UiNoticeSnapshot -State $state
430
436
  if ($title -ne $lastTitle) {
431
437
  $updated = Update-ConsoleTitle -Title $title
432
438
  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"
439
+ $lastTitle = $title
440
+ }
441
+ if ($notice -ne $lastNotice) {
442
+ if ($notice) {
443
+ $noticeUpdated = Write-ConsoleNotice -Notice $notice
444
+ Write-WatcherLog ("NOTICE updated=" + $noticeUpdated + " text=" + $notice)
445
+ } else {
446
+ Write-WatcherLog "NOTICE clear"
441
447
  }
442
448
  $lastNotice = $notice
443
449
  }