@blunking/codexlink 0.1.18 → 0.1.20

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.
@@ -1,138 +1,228 @@
1
- param(
2
- [string]$Profile = "default",
3
- [switch]$Json
4
- )
5
-
6
- $ErrorActionPreference = "Stop"
7
-
8
- function Read-DotEnvFile {
9
- param([string]$Path)
10
- $values = @{}
11
- if (-not (Test-Path $Path)) { return $values }
12
- foreach ($line in (Get-Content -Path $Path)) {
13
- if (-not $line) { continue }
14
- if ($line.Trim().StartsWith("#")) { continue }
15
- $parts = $line -split "=", 2
16
- if ($parts.Count -ne 2) { continue }
17
- $values[$parts[0].Trim()] = $parts[1]
18
- }
19
- return $values
20
- }
21
-
22
- function Add-Check {
23
- param(
24
- [System.Collections.Generic.List[object]]$List,
25
- [string]$Name,
26
- [string]$Status,
27
- [string]$Detail
28
- )
29
- $List.Add([pscustomobject]@{
30
- name = $Name
31
- status = $Status
32
- detail = $Detail
33
- }) | Out-Null
34
- }
35
-
36
- function Test-TelegramTokenFormat {
37
- param([string]$Value)
38
- if (-not $Value) { return $false }
39
- return $Value -match '^\d{6,}:[A-Za-z0-9_-]{20,}$'
40
- }
41
-
42
- function Test-AllowedChatIdsFormat {
43
- param([string]$Value)
44
- if (-not $Value) { return $false }
45
- $parts = @($Value -split "," | ForEach-Object { $_.Trim() } | Where-Object { $_ })
46
- if ($parts.Count -eq 0) { return $false }
47
- foreach ($part in $parts) {
48
- if ($part -notmatch '^-?\d+$') {
49
- return $false
50
- }
51
- }
52
- return $true
53
- }
54
-
55
- function Get-OverallStatus {
56
- param([System.Collections.Generic.List[object]]$Checks)
57
- if (@($Checks | Where-Object { $_.status -eq "fail" }).Count -gt 0) {
58
- return "fail"
59
- }
60
- if (@($Checks | Where-Object { $_.status -eq "warn" }).Count -gt 0) {
61
- return "warn"
62
- }
63
- return "ok"
64
- }
65
-
1
+ param(
2
+ [string]$Profile = "default",
3
+ [switch]$Json,
4
+ [switch]$Fix
5
+ )
6
+
7
+ $ErrorActionPreference = "Stop"
8
+
9
+ function Read-DotEnvFile {
10
+ param([string]$Path)
11
+ $values = @{}
12
+ if (-not (Test-Path $Path)) { return $values }
13
+ foreach ($line in (Get-Content -Path $Path)) {
14
+ if (-not $line) { continue }
15
+ if ($line.Trim().StartsWith("#")) { continue }
16
+ $parts = $line -split "=", 2
17
+ if ($parts.Count -ne 2) { continue }
18
+ $values[$parts[0].Trim()] = $parts[1]
19
+ }
20
+ return $values
21
+ }
22
+
23
+ function Add-Check {
24
+ param(
25
+ [System.Collections.Generic.List[object]]$List,
26
+ [string]$Name,
27
+ [string]$Status,
28
+ [string]$Detail
29
+ )
30
+ $List.Add([pscustomobject]@{
31
+ name = $Name
32
+ status = $Status
33
+ detail = $Detail
34
+ }) | Out-Null
35
+ }
36
+
37
+ function Test-TelegramTokenFormat {
38
+ param([string]$Value)
39
+ if (-not $Value) { return $false }
40
+ return $Value -match '^\d{6,}:[A-Za-z0-9_-]{20,}$'
41
+ }
42
+
43
+ function Test-AllowedChatIdsFormat {
44
+ param([string]$Value)
45
+ if (-not $Value) { return $false }
46
+ $parts = @($Value -split "," | ForEach-Object { $_.Trim() } | Where-Object { $_ })
47
+ if ($parts.Count -eq 0) { return $false }
48
+ foreach ($part in $parts) {
49
+ if ($part -notmatch '^-?\d+$') {
50
+ return $false
51
+ }
52
+ }
53
+ return $true
54
+ }
55
+
56
+ function Get-OverallStatus {
57
+ param([System.Collections.Generic.List[object]]$Checks)
58
+ if (@($Checks | Where-Object { $_.status -eq "fail" }).Count -gt 0) {
59
+ return "fail"
60
+ }
61
+ if (@($Checks | Where-Object { $_.status -eq "warn" }).Count -gt 0) {
62
+ return "warn"
63
+ }
64
+ return "ok"
65
+ }
66
+
66
67
  function Write-DoctorReport {
67
- param(
68
- [object]$Result,
69
- [string]$TokenSource,
70
- [string]$AllowedChatSource
71
- )
72
-
73
- $emoji = switch ($Result.overall) {
74
- "ok" { "[OK]" }
75
- "warn" { "[WARN]" }
76
- default { "[FAIL]" }
77
- }
78
-
79
- Write-Host ""
80
- Write-Host "CodexLink Telegram Doctor $emoji" -ForegroundColor Cyan
81
- Write-Host "Profil: $($Result.profile)"
82
- Write-Host "State-Ordner: $($Result.state_dir)"
83
- Write-Host ""
84
-
85
- foreach ($check in $Result.checks) {
86
- $prefix = switch ($check.status) {
87
- "ok" { "[OK]" }
88
- "warn" { "[WARN]" }
89
- default { "[FAIL]" }
90
- }
91
- $color = switch ($check.status) {
92
- "ok" { "Green" }
93
- "warn" { "Yellow" }
94
- default { "Red" }
95
- }
96
- Write-Host "$prefix $($check.name): $($check.detail)" -ForegroundColor $color
97
- }
98
-
99
- Write-Host ""
100
- if ($TokenSource) {
101
- Write-Host "Bot-Token gefunden aus: $TokenSource" -ForegroundColor DarkGray
102
- }
103
- if ($AllowedChatSource) {
104
- Write-Host "Erlaubte Chat-ID(s) gefunden aus: $AllowedChatSource" -ForegroundColor DarkGray
105
- }
106
-
107
- $failed = @($Result.checks | Where-Object { $_.status -eq "fail" })
108
- if ($failed.Count -gt 0) {
109
- Write-Host ""
110
- Write-Host "Was jetzt fehlt:" -ForegroundColor Yellow
111
- foreach ($item in $failed) {
112
- switch ($item.name) {
113
- "bot_token" { Write-Host " - Telegram Bot Token fehlt. Starte: blun-codex --profile $($Result.profile) telegram-setup" }
68
+ param(
69
+ [object]$Result,
70
+ [string]$TokenSource,
71
+ [string]$AllowedChatSource
72
+ )
73
+
74
+ $emoji = switch ($Result.overall) {
75
+ "ok" { "[OK]" }
76
+ "warn" { "[WARN]" }
77
+ default { "[FAIL]" }
78
+ }
79
+
80
+ Write-Host ""
81
+ Write-Host "CodexLink Telegram Doctor $emoji" -ForegroundColor Cyan
82
+ Write-Host "Profil: $($Result.profile)"
83
+ Write-Host "State-Ordner: $($Result.state_dir)"
84
+ Write-Host ""
85
+
86
+ foreach ($check in $Result.checks) {
87
+ $prefix = switch ($check.status) {
88
+ "ok" { "[OK]" }
89
+ "warn" { "[WARN]" }
90
+ default { "[FAIL]" }
91
+ }
92
+ $color = switch ($check.status) {
93
+ "ok" { "Green" }
94
+ "warn" { "Yellow" }
95
+ default { "Red" }
96
+ }
97
+ Write-Host "$prefix $($check.name): $($check.detail)" -ForegroundColor $color
98
+ }
99
+
100
+ Write-Host ""
101
+ if ($TokenSource) {
102
+ Write-Host "Bot-Token gefunden aus: $TokenSource" -ForegroundColor DarkGray
103
+ }
104
+ if ($AllowedChatSource) {
105
+ Write-Host "Erlaubte Chat-ID(s) gefunden aus: $AllowedChatSource" -ForegroundColor DarkGray
106
+ }
107
+
108
+ $failed = @($Result.checks | Where-Object { $_.status -eq "fail" })
109
+ if ($failed.Count -gt 0) {
110
+ Write-Host ""
111
+ Write-Host "Was jetzt fehlt:" -ForegroundColor Yellow
112
+ foreach ($item in $failed) {
113
+ switch ($item.name) {
114
+ "bot_token" { Write-Host " - Telegram Bot Token fehlt. Starte: blun-codex --profile $($Result.profile) telegram-setup" }
114
115
  "allowed_chat_ids" { Write-Host " - Chat-ID ist optional. Zum automatischen Koppeln: blun-codex --profile $($Result.profile) telegram-setup" }
115
- "state_dir" { Write-Host " - Der lokale Telegram-State-Ordner fehlt noch. Ein Setup-Lauf legt ihn automatisch an." }
116
- "profile_file" { Write-Host " - Das angegebene Profil existiert nicht." }
117
- "node" { Write-Host " - Node.js fehlt in PATH." }
118
- "codex" { Write-Host " - Der lokale codex-Befehl fehlt in PATH." }
119
- "telegram_plugin_root" { Write-Host " - Der Telegram-Plugin-Ordner konnte nicht gefunden werden." }
120
- default { Write-Host " - $($item.detail)" }
121
- }
122
- }
123
- }
124
-
125
- if ($Result.overall -eq "ok") {
126
- Write-Host ""
127
- Write-Host "Telegram ist sauber eingerichtet." -ForegroundColor Green
128
- Write-Host "Starten: blun-codex --profile $($Result.profile) telegram-plugin"
129
- } elseif ($Result.overall -eq "warn") {
130
- Write-Host ""
131
- Write-Host "Die Grundkonfiguration steht, aber es gibt noch Laufzeit-Hinweise." -ForegroundColor Yellow
132
- Write-Host "Das ist oft normal, wenn Telegram noch nicht aktiv gestartet wurde oder noch keine Nachricht durchlief."
116
+ "state_dir" { Write-Host " - Der lokale Telegram-State-Ordner fehlt noch. Ein Setup-Lauf legt ihn automatisch an." }
117
+ "profile_file" { Write-Host " - Das angegebene Profil existiert nicht." }
118
+ "node" { Write-Host " - Node.js fehlt in PATH." }
119
+ "codex" { Write-Host " - Der lokale codex-Befehl fehlt in PATH." }
120
+ "telegram_plugin_root" { Write-Host " - Der Telegram-Plugin-Ordner konnte nicht gefunden werden." }
121
+ default { Write-Host " - $($item.detail)" }
122
+ }
123
+ }
124
+ }
125
+
126
+ if ($Result.overall -eq "ok") {
127
+ Write-Host ""
128
+ Write-Host "Telegram ist sauber eingerichtet." -ForegroundColor Green
129
+ Write-Host "Starten: blun-codex --profile $($Result.profile) telegram-plugin"
130
+ } elseif ($Result.overall -eq "warn") {
131
+ Write-Host ""
132
+ Write-Host "Die Grundkonfiguration steht, aber es gibt noch Laufzeit-Hinweise." -ForegroundColor Yellow
133
+ Write-Host "Das ist oft normal, wenn Telegram noch nicht aktiv gestartet wurde oder noch keine Nachricht durchlief."
134
+ }
135
+ }
136
+
137
+ function Write-DotEnvFile {
138
+ param(
139
+ [string]$Path,
140
+ [hashtable]$Values
141
+ )
142
+ $dir = Split-Path -Parent $Path
143
+ if ($dir -and -not (Test-Path $dir)) {
144
+ New-Item -ItemType Directory -Path $dir -Force | Out-Null
145
+ }
146
+ $lines = New-Object 'System.Collections.Generic.List[string]'
147
+ foreach ($key in ($Values.Keys | Sort-Object)) {
148
+ if ([string]::IsNullOrWhiteSpace([string]$key)) { continue }
149
+ $value = [string]$Values[$key]
150
+ $lines.Add($key + "=" + $value) | Out-Null
151
+ }
152
+ Set-Content -Path $Path -Value $lines -Encoding UTF8
153
+ }
154
+
155
+ function Stop-PidQuiet {
156
+ param([object]$PidValue)
157
+ $pidText = [string]$PidValue
158
+ if (-not $pidText) { return $false }
159
+ $pidInt = 0
160
+ if (-not [int]::TryParse($pidText, [ref]$pidInt)) { return $false }
161
+ if ($pidInt -le 0) { return $false }
162
+ try {
163
+ Stop-Process -Id $pidInt -Force -ErrorAction Stop
164
+ return $true
165
+ } catch {
166
+ return $false
133
167
  }
134
168
  }
135
169
 
170
+ function Invoke-RuntimeFix {
171
+ param(
172
+ [object]$Status,
173
+ [string]$RuntimeRoot
174
+ )
175
+ $actions = New-Object 'System.Collections.Generic.List[string]'
176
+ $runtime = $Status.current_runtime
177
+ if ($runtime) {
178
+ $pids = @(
179
+ $runtime.frontend_host_pid,
180
+ $runtime.app_server_pid,
181
+ $runtime.queue_notifier_pid,
182
+ $runtime.poller_pid,
183
+ $runtime.dispatcher_pid,
184
+ $runtime.responder_pid
185
+ ) | Where-Object { $_ } | Select-Object -Unique
186
+ foreach ($pidValue in $pids) {
187
+ if (Stop-PidQuiet -PidValue $pidValue) {
188
+ $actions.Add("stopped_pid=" + [string]$pidValue) | Out-Null
189
+ }
190
+ }
191
+ }
192
+
193
+ $runtimeFile = Join-Path $env:USERPROFILE (".codex\runtimes\" + [string]$Status.profile + "\current-remote-runtime.json")
194
+ if (Test-Path $runtimeFile) {
195
+ Remove-Item -LiteralPath $runtimeFile -Force -ErrorAction SilentlyContinue
196
+ $actions.Add("removed_runtime_file") | Out-Null
197
+ }
198
+
199
+ $envFile = Join-Path ([string]$Status.state_dir) ".env"
200
+ $envValues = Read-DotEnvFile -Path $envFile
201
+ if ($envValues.ContainsKey("BLUN_TELEGRAM_THREAD_ID")) {
202
+ $envValues["BLUN_TELEGRAM_THREAD_ID"] = ""
203
+ Write-DotEnvFile -Path $envFile -Values $envValues
204
+ $actions.Add("cleared_env_thread") | Out-Null
205
+ }
206
+
207
+ $stateFile = Join-Path ([string]$Status.state_dir) "state.json"
208
+ if (Test-Path $stateFile) {
209
+ try {
210
+ $state = Get-Content -Raw -Path $stateFile | ConvertFrom-Json
211
+ if ($state.PSObject.Properties.Name.Contains("currentThreadId")) {
212
+ $state.currentThreadId = ""
213
+ } else {
214
+ $state | Add-Member -NotePropertyName "currentThreadId" -NotePropertyValue ""
215
+ }
216
+ $state | ConvertTo-Json -Depth 10 | Set-Content -Path $stateFile -Encoding UTF8
217
+ $actions.Add("cleared_state_thread") | Out-Null
218
+ } catch {
219
+ $actions.Add("state_thread_clear_failed") | Out-Null
220
+ }
221
+ }
222
+
223
+ return @($actions)
224
+ }
225
+
136
226
  function Get-ProfilePath {
137
227
  param(
138
228
  [string]$RuntimeRoot,
@@ -161,40 +251,40 @@ function Get-ProfilePath {
161
251
  $runtimeRoot = Split-Path -Parent $MyInvocation.MyCommand.Path
162
252
  $profilePath = Get-ProfilePath -RuntimeRoot $runtimeRoot -ProfileName $Profile
163
253
  $checks = New-Object 'System.Collections.Generic.List[object]'
164
-
165
- if (Test-Path $profilePath) {
166
- Add-Check -List $checks -Name "profile_file" -Status "ok" -Detail $profilePath
167
- } else {
168
- Add-Check -List $checks -Name "profile_file" -Status "fail" -Detail ("Missing profile: " + $profilePath)
169
- }
170
-
171
- $statusRaw = & powershell -ExecutionPolicy Bypass -File (Join-Path $runtimeRoot "telegram-status.ps1") -Profile $Profile
172
- $status = $statusRaw | ConvertFrom-Json
173
-
174
- $nodeCommand = Get-Command node -ErrorAction SilentlyContinue
175
- $codexCommand = Get-Command codex -ErrorAction SilentlyContinue
176
- Add-Check -List $checks -Name "node" -Status $(if ($nodeCommand) { "ok" } else { "fail" }) -Detail $(if ($nodeCommand) { $nodeCommand.Source } else { "node not found in PATH" })
177
- Add-Check -List $checks -Name "codex" -Status $(if ($codexCommand) { "ok" } else { "fail" }) -Detail $(if ($codexCommand) { $codexCommand.Source } else { "codex not found in PATH" })
178
-
179
- if ($status.plugin_root) {
180
- Add-Check -List $checks -Name "telegram_plugin_root" -Status "ok" -Detail $status.plugin_root
181
- } else {
182
- Add-Check -List $checks -Name "telegram_plugin_root" -Status "fail" -Detail "Telegram plugin root could not be resolved."
183
- }
184
-
185
- if ($status.state_dir -and (Test-Path $status.state_dir)) {
186
- Add-Check -List $checks -Name "state_dir" -Status "ok" -Detail $status.state_dir
187
- } else {
188
- Add-Check -List $checks -Name "state_dir" -Status "fail" -Detail ("Missing state dir: " + $status.state_dir)
189
- }
190
-
191
- $activeEnvPath = Join-Path $status.state_dir ".env"
192
- $activeEnv = Read-DotEnvFile -Path $activeEnvPath
193
- $legacyEnvPath = Join-Path $env:USERPROFILE ".codex\channels\codexlink-telegram\.env"
194
- $legacyEnv = Read-DotEnvFile -Path $legacyEnvPath
195
-
196
- $tokenValue = ""
197
- $tokenSource = ""
254
+
255
+ if (Test-Path $profilePath) {
256
+ Add-Check -List $checks -Name "profile_file" -Status "ok" -Detail $profilePath
257
+ } else {
258
+ Add-Check -List $checks -Name "profile_file" -Status "fail" -Detail ("Missing profile: " + $profilePath)
259
+ }
260
+
261
+ $statusRaw = & powershell -ExecutionPolicy Bypass -File (Join-Path $runtimeRoot "telegram-status.ps1") -Profile $Profile
262
+ $status = $statusRaw | ConvertFrom-Json
263
+
264
+ $nodeCommand = Get-Command node -ErrorAction SilentlyContinue
265
+ $codexCommand = Get-Command codex -ErrorAction SilentlyContinue
266
+ Add-Check -List $checks -Name "node" -Status $(if ($nodeCommand) { "ok" } else { "fail" }) -Detail $(if ($nodeCommand) { $nodeCommand.Source } else { "node not found in PATH" })
267
+ Add-Check -List $checks -Name "codex" -Status $(if ($codexCommand) { "ok" } else { "fail" }) -Detail $(if ($codexCommand) { $codexCommand.Source } else { "codex not found in PATH" })
268
+
269
+ if ($status.plugin_root) {
270
+ Add-Check -List $checks -Name "telegram_plugin_root" -Status "ok" -Detail $status.plugin_root
271
+ } else {
272
+ Add-Check -List $checks -Name "telegram_plugin_root" -Status "fail" -Detail "Telegram plugin root could not be resolved."
273
+ }
274
+
275
+ if ($status.state_dir -and (Test-Path $status.state_dir)) {
276
+ Add-Check -List $checks -Name "state_dir" -Status "ok" -Detail $status.state_dir
277
+ } else {
278
+ Add-Check -List $checks -Name "state_dir" -Status "fail" -Detail ("Missing state dir: " + $status.state_dir)
279
+ }
280
+
281
+ $activeEnvPath = Join-Path $status.state_dir ".env"
282
+ $activeEnv = Read-DotEnvFile -Path $activeEnvPath
283
+ $legacyEnvPath = Join-Path $env:USERPROFILE ".codex\channels\codexlink-telegram\.env"
284
+ $legacyEnv = Read-DotEnvFile -Path $legacyEnvPath
285
+
286
+ $tokenValue = ""
287
+ $tokenSource = ""
198
288
  if (Test-TelegramTokenFormat -Value $activeEnv["BLUN_TELEGRAM_BOT_TOKEN"]) {
199
289
  $tokenValue = [string]$activeEnv["BLUN_TELEGRAM_BOT_TOKEN"]
200
290
  $tokenSource = "state env"
@@ -208,10 +298,10 @@ if (Test-TelegramTokenFormat -Value $activeEnv["BLUN_TELEGRAM_BOT_TOKEN"]) {
208
298
  $tokenValue = [string]$legacyEnv["TELEGRAM_BOT_TOKEN"]
209
299
  $tokenSource = "legacy env fallback legacy key"
210
300
  }
211
- Add-Check -List $checks -Name "bot_token" -Status $(if ($tokenValue) { "ok" } else { "fail" }) -Detail $(if ($tokenValue) { $tokenSource } else { "No valid BLUN_TELEGRAM_BOT_TOKEN found." })
212
-
213
- $allowedChatIds = ""
214
- $allowedChatSource = ""
301
+ Add-Check -List $checks -Name "bot_token" -Status $(if ($tokenValue) { "ok" } else { "fail" }) -Detail $(if ($tokenValue) { $tokenSource } else { "No valid BLUN_TELEGRAM_BOT_TOKEN found." })
302
+
303
+ $allowedChatIds = ""
304
+ $allowedChatSource = ""
215
305
  if (Test-AllowedChatIdsFormat -Value $activeEnv["BLUN_TELEGRAM_ALLOWED_CHAT_ID"]) {
216
306
  $allowedChatIds = [string]$activeEnv["BLUN_TELEGRAM_ALLOWED_CHAT_ID"]
217
307
  $allowedChatSource = "state env"
@@ -226,57 +316,84 @@ if (Test-AllowedChatIdsFormat -Value $activeEnv["BLUN_TELEGRAM_ALLOWED_CHAT_ID"]
226
316
  $allowedChatSource = "legacy env fallback legacy key"
227
317
  }
228
318
  Add-Check -List $checks -Name "allowed_chat_ids" -Status $(if ($allowedChatIds) { "ok" } else { "warn" }) -Detail $(if ($allowedChatIds) { $allowedChatIds } else { "No allowlist set. Telegram currently accepts any chat the bot can see." })
229
-
319
+
230
320
  Add-Check -List $checks -Name "app_server_ws" -Status $(if ($status.active_ws) { "ok" } else { "warn" }) -Detail $(if ($status.active_ws) { $status.active_ws } else { "No active websocket recorded." })
231
321
  Add-Check -List $checks -Name "dispatch_mode" -Status $(if ($status.dispatch_mode -eq "deferred") { "ok" } else { "warn" }) -Detail ("mode=" + [string]$status.dispatch_mode + " cooldown_ms=" + [string]$status.idle_cooldown_ms + " pending_reply_timeout_ms=" + [string]$status.pending_reply_timeout_ms)
232
322
  Add-Check -List $checks -Name "bound_thread" -Status $(if ($status.active_thread_id) { "ok" } else { "warn" }) -Detail $(if ($status.active_thread_id) { $status.active_thread_id } else { "No active thread bound yet." })
323
+ $loadedThreads = @($status.loaded_threads | Where-Object { $_ })
324
+ $threadVisibilityStatus = "ok"
325
+ $threadVisibilityDetail = "loaded=" + [string]$loadedThreads.Count
326
+ if ($loadedThreads.Count -gt 1) {
327
+ $threadVisibilityStatus = "warn"
328
+ $threadVisibilityDetail = "multiple loaded threads: " + (($loadedThreads | ForEach-Object { [string]$_ }) -join ",") + ". Run telegram-doctor --fix, then restart telegram-plugin."
329
+ } elseif ($status.active_thread_id -and $loadedThreads.Count -eq 1 -and ([string]$loadedThreads[0]) -ne ([string]$status.active_thread_id)) {
330
+ $threadVisibilityStatus = "warn"
331
+ $threadVisibilityDetail = "bound thread differs from loaded visible thread. Run telegram-doctor --fix, then restart telegram-plugin."
332
+ }
333
+ Add-Check -List $checks -Name "thread_visibility" -Status $threadVisibilityStatus -Detail $threadVisibilityDetail
233
334
  Add-Check -List $checks -Name "frontend_owner" -Status $(if ($status.frontend_owner_alive) { "ok" } else { "warn" }) -Detail ("pid=" + [string]$status.frontend_owner_pid + " alive=" + [string]$status.frontend_owner_alive)
234
335
  Add-Check -List $checks -Name "queue_notifier" -Status $(if (($null -eq $status.queue_notifier_pid) -or ($status.queue_notifier_alive)) { "ok" } else { "warn" }) -Detail ("pid=" + [string]$status.queue_notifier_pid + " alive=" + [string]$status.queue_notifier_alive)
235
336
  Add-Check -List $checks -Name "poller" -Status $(if ($status.poller_alive) { "ok" } else { "warn" }) -Detail ("pid=" + [string]$status.poller_pid + " alive=" + [string]$status.poller_alive)
236
337
  Add-Check -List $checks -Name "dispatcher" -Status $(if ($status.dispatcher_alive) { "ok" } else { "warn" }) -Detail ("pid=" + [string]$status.dispatcher_pid + " alive=" + [string]$status.dispatcher_alive)
237
338
  Add-Check -List $checks -Name "responder" -Status $(if ($status.responder_alive) { "ok" } else { "warn" }) -Detail ("pid=" + [string]$status.responder_pid + " alive=" + [string]$status.responder_alive)
238
-
239
- if ($status.last_inbound) {
240
- $lastInboundSummary = [string]::Format(
241
- "chat={0} message={1} type={2} thread={3}",
242
- $status.last_inbound.chatId,
243
- $status.last_inbound.messageId,
244
- $status.last_inbound.chatType,
245
- $(if ($status.last_inbound.telegramThreadId) { $status.last_inbound.telegramThreadId } else { "-" })
246
- )
247
- Add-Check -List $checks -Name "last_inbound" -Status "ok" -Detail $lastInboundSummary
248
- } else {
249
- Add-Check -List $checks -Name "last_inbound" -Status "warn" -Detail "No inbound Telegram message recorded yet."
250
- }
251
-
252
- if ($status.last_outbound) {
253
- $lastOutboundSummary = [string]::Format(
254
- "chat={0} message={1} reply_to={2} thread={3}",
255
- $status.last_outbound.chatId,
256
- $status.last_outbound.messageId,
257
- $(if ($status.last_outbound.replyToMessageId) { $status.last_outbound.replyToMessageId } else { "-" }),
258
- $(if ($status.last_outbound.telegramThreadId) { $status.last_outbound.telegramThreadId } else { "-" })
259
- )
260
- Add-Check -List $checks -Name "last_outbound" -Status "ok" -Detail $lastOutboundSummary
261
- } else {
262
- Add-Check -List $checks -Name "last_outbound" -Status "warn" -Detail "No outbound Telegram message recorded yet."
263
- }
264
-
339
+
340
+ if ($status.last_inbound) {
341
+ $lastInboundSummary = [string]::Format(
342
+ "chat={0} message={1} type={2} thread={3}",
343
+ $status.last_inbound.chatId,
344
+ $status.last_inbound.messageId,
345
+ $status.last_inbound.chatType,
346
+ $(if ($status.last_inbound.telegramThreadId) { $status.last_inbound.telegramThreadId } else { "-" })
347
+ )
348
+ Add-Check -List $checks -Name "last_inbound" -Status "ok" -Detail $lastInboundSummary
349
+ } else {
350
+ Add-Check -List $checks -Name "last_inbound" -Status "warn" -Detail "No inbound Telegram message recorded yet."
351
+ }
352
+
353
+ if ($status.last_outbound) {
354
+ $lastOutboundSummary = [string]::Format(
355
+ "chat={0} message={1} reply_to={2} thread={3}",
356
+ $status.last_outbound.chatId,
357
+ $status.last_outbound.messageId,
358
+ $(if ($status.last_outbound.replyToMessageId) { $status.last_outbound.replyToMessageId } else { "-" }),
359
+ $(if ($status.last_outbound.telegramThreadId) { $status.last_outbound.telegramThreadId } else { "-" })
360
+ )
361
+ Add-Check -List $checks -Name "last_outbound" -Status "ok" -Detail $lastOutboundSummary
362
+ } else {
363
+ Add-Check -List $checks -Name "last_outbound" -Status "warn" -Detail "No outbound Telegram message recorded yet."
364
+ }
365
+
265
366
  Add-Check -List $checks -Name "queue" -Status $(if (([int]$status.queue_depth -eq 0) -and ([int]$status.pending_reply_depth -eq 0)) { "ok" } else { "warn" }) -Detail ("queued=" + $status.queue_depth + " ambient=" + $status.ambient_queue_depth + " parked=" + $status.parked_queue_depth + " submitted=" + $status.submitted_depth + " pending_replies=" + $status.pending_reply_depth + " expired_pending_replies=" + $status.expired_pending_reply_depth)
266
-
267
- $result = [ordered]@{
268
- profile = $status.profile
269
- overall = Get-OverallStatus -Checks $checks
270
- runtime_root = $runtimeRoot
271
- state_dir = $status.state_dir
272
- plugin_root = $status.plugin_root
273
- checks = $checks
274
- status = $status
275
- }
276
-
277
- if ($Json) {
278
- $result | ConvertTo-Json -Depth 8
279
- exit 0
280
- }
281
-
282
- Write-DoctorReport -Result $result -TokenSource $tokenSource -AllowedChatSource $allowedChatSource
367
+
368
+ $result = [ordered]@{
369
+ profile = $status.profile
370
+ overall = Get-OverallStatus -Checks $checks
371
+ runtime_root = $runtimeRoot
372
+ state_dir = $status.state_dir
373
+ plugin_root = $status.plugin_root
374
+ checks = $checks
375
+ status = $status
376
+ }
377
+
378
+ if ($Fix) {
379
+ $fixActions = Invoke-RuntimeFix -Status $status -RuntimeRoot $runtimeRoot
380
+ $result["fix_actions"] = $fixActions
381
+ if ($Json) {
382
+ $result | ConvertTo-Json -Depth 8
383
+ exit 0
384
+ }
385
+ Write-DoctorReport -Result $result -TokenSource $tokenSource -AllowedChatSource $allowedChatSource
386
+ Write-Host ""
387
+ Write-Host "Fix angewendet. Starte danach neu: blun-codex --profile $($result.profile) telegram-plugin" -ForegroundColor Yellow
388
+ if ($fixActions.Count -gt 0) {
389
+ Write-Host ("Aktionen: " + ($fixActions -join ", ")) -ForegroundColor DarkGray
390
+ }
391
+ exit 0
392
+ }
393
+
394
+ if ($Json) {
395
+ $result | ConvertTo-Json -Depth 8
396
+ exit 0
397
+ }
398
+
399
+ Write-DoctorReport -Result $result -TokenSource $tokenSource -AllowedChatSource $allowedChatSource
@@ -79,6 +79,10 @@ function containsToken(text, token) {
79
79
  return new RegExp(`(^|[^a-z0-9_])${escaped}([^a-z0-9_]|$)`, "i").test(String(text || ""));
80
80
  }
81
81
 
82
+ function shouldAckOnlyAddressPing() {
83
+ return String(process.env.BLUN_TELEGRAM_PING_ACK_ONLY || "").trim() === "1";
84
+ }
85
+
82
86
  function looksLikeEscalation(text) {
83
87
  const value = foldTriggerText(text);
84
88
  if (!value) {
@@ -445,6 +449,10 @@ function isAgentAddressed(config, text) {
445
449
  if (workDirective) {
446
450
  return true;
447
451
  }
452
+
453
+ if (containsToken(normalized, name)) {
454
+ return true;
455
+ }
448
456
  }
449
457
 
450
458
  return false;
@@ -1238,12 +1246,14 @@ function normalizeTelegramThreadId(value) {
1238
1246
  return String(value || "").trim();
1239
1247
  }
1240
1248
 
1241
- function isAllowedChat(config, chatId) {
1249
+ function isAllowedChat(config, inbound) {
1242
1250
  const allowed = Array.isArray(config.allowedChatIds) ? config.allowedChatIds : [];
1243
1251
  if (allowed.length === 0) {
1244
1252
  return true;
1245
1253
  }
1246
- return allowed.includes(String(chatId || "").trim());
1254
+ const chatId = String(inbound?.chatId || inbound || "").trim();
1255
+ const userId = String(inbound?.userId || "").trim();
1256
+ return allowed.includes(chatId) || (userId && allowed.includes(userId));
1247
1257
  }
1248
1258
 
1249
1259
  function splitTelegramText(text, maxLength = 3500) {
@@ -2125,9 +2135,9 @@ export async function pollOnce() {
2125
2135
  continue;
2126
2136
  }
2127
2137
  const inbound = normalizeInbound(update.message);
2128
- if (!isAllowedChat(config, inbound.chatId)) {
2138
+ if (!isAllowedChat(config, inbound)) {
2129
2139
  ignored += 1;
2130
- appendLog(config.paths.activityFile, `IGNORED chat=${inbound.chatId} message=${inbound.messageId}`);
2140
+ appendLog(config.paths.activityFile, `IGNORED chat=${inbound.chatId} user=${inbound.userId || "-"} message=${inbound.messageId}`);
2131
2141
  continue;
2132
2142
  }
2133
2143
  if (String(inbound.chatType || "") === "private" && looksLikeMnemoIdleLoopBrief(inbound.text)) {
@@ -2268,6 +2278,11 @@ function isRealtimeAppServerEntry(entry) {
2268
2278
  return chatType === "private" || relevance === "direct" || relevance === "lane";
2269
2279
  }
2270
2280
 
2281
+ function isGroupChatEntry(entry) {
2282
+ const chatType = String(entry?.chatType || "").trim().toLowerCase();
2283
+ return chatType === "group" || chatType === "supergroup";
2284
+ }
2285
+
2271
2286
  export async function injectNext(threadId, options = {}) {
2272
2287
  const config = loadConfig();
2273
2288
  const state = loadState(config);
@@ -2311,7 +2326,7 @@ export async function injectNext(threadId, options = {}) {
2311
2326
  };
2312
2327
  }
2313
2328
 
2314
- if (isAddressOnlyPing(config, next.text) && !looksLikeBotSender(next)) {
2329
+ if (shouldAckOnlyAddressPing() && isAddressOnlyPing(config, next.text) && !looksLikeBotSender(next) && !isGroupChatEntry(next)) {
2315
2330
  next.status = "delivered";
2316
2331
  next.deliveredAt = nowIso();
2317
2332
  next.threadId = null;