@blunking/codexlink 0.1.2 → 0.1.10

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.
package/README.md CHANGED
@@ -11,15 +11,16 @@
11
11
 
12
12
  CodexLink is the BLUN launcher for one visible CLI session with optional Telegram delivery.
13
13
 
14
- It keeps transport and queueing around the operator, without spinning up a hidden second session.
15
-
16
- Telegram delivery is serial by default:
17
-
18
- - inbound messages land in queue first
19
- - active work is not interrupted immediately
20
- - direct messages wait until the visible session is quiet
21
- - ambient group noise stays queued until it is relevant or manually drained
22
- - escalation-style messages can still jump the line
14
+ It keeps transport and queueing around the operator, without spinning up a hidden second session.
15
+
16
+ Telegram delivery is serial by default:
17
+
18
+ - inbound messages land in queue first
19
+ - active work is not interrupted immediately
20
+ - direct messages wait until the visible session is quiet
21
+ - ambient group noise stays queued until it is relevant or manually drained
22
+ - escalation-style messages can still jump the line
23
+ - stale pending replies time out automatically, so the queue cannot block forever
23
24
 
24
25
  ## Install
25
26
 
@@ -57,12 +58,11 @@ Telegram aktivieren:
57
58
  blun-codex telegram-plugin
58
59
  ```
59
60
 
60
- Wenn Telegram noch nicht eingerichtet ist, startet automatisch ein kurzer Setup-Flow und fragt:
61
-
62
- - Telegram Bot Token
63
- - erlaubte Chat ID(s)
64
-
65
- Die Werte werden automatisch lokal an die richtige Stelle geschrieben. Du musst keine `.env`-Datei suchen.
61
+ Wenn Telegram noch nicht eingerichtet ist, startet automatisch ein kurzer Setup-Flow und fragt:
62
+
63
+ - Telegram Bot Token
64
+
65
+ Danach oeffnest du Telegram und sendest eine Nachricht an den Bot. CodexLink erkennt Chat oder Gruppe automatisch und schreibt alles lokal an die richtige Stelle. Du musst keine Chat-ID suchen und keine `.env`-Datei bearbeiten.
66
66
 
67
67
  Pruefen:
68
68
 
@@ -108,7 +108,18 @@ Status:
108
108
  blun-codex telegram-status
109
109
  ```
110
110
 
111
- Doctor:
111
+ Wenn waehrend einer laufenden Arbeit Telegram-Nachrichten gepuffert werden, bleibt die sichtbare CLI-Eingabe unberuehrt. Pending-Nachrichten bleiben im Fenstertitel/Status sichtbar, bis die Antwort raus ist oder sie wirklich ablaufen. Den Queue-Stand kannst du jederzeit mit `blun-codex telegram-status` pruefen.
112
+
113
+ Der automatische Progress-Hinweis ist bewusst defensiv: standardmaessig sendet Telegram nur finale Antworten plus bei laengeren echten Arbeitslaeufen einen neutralen Status. Interne Commentary-Texte werden nicht als zweite fachliche Antwort gespiegelt. Wer das alte Verhalten will, kann `BLUN_TELEGRAM_PROGRESS_RELAY=commentary` setzen; mit `off` werden Progress-Hinweise ganz deaktiviert.
114
+
115
+ Wenn mehrere Agents denselben Gruppenchat nutzen, kann ein Agent andere Agent-Namen als Fremdroute markieren. Dann werden Owner-Nachrichten wie `Frida mach weiter` nicht in Ottos Session gezogen:
116
+
117
+ ```text
118
+ BLUN_TELEGRAM_MENTION_NAMES=otto
119
+ BLUN_TELEGRAM_OTHER_AGENT_NAMES=frida,angel,dieter,alfred
120
+ ```
121
+
122
+ Doctor:
112
123
 
113
124
  ```powershell
114
125
  blun-codex telegram-doctor
@@ -160,39 +171,39 @@ Why this matters:
160
171
  - starting a second operator on `default` will replace the first `default` runtime
161
172
  - a private profile gives that operator a separate runtime slot, state directory, and Mnemo binding
162
173
 
163
- For internal/private profiles:
164
-
165
- - keep the profile local on the machine
166
- - give it its own `agent_name`
167
- - give it its own Telegram state directory
168
- - do not ship internal agent profiles in the public package
169
-
170
- Local private profiles are loaded from:
171
-
172
- ```text
173
- %USERPROFILE%\.codex\profiles\codexlink\<name>.json
174
- ```
175
-
176
- Example:
177
-
178
- ```powershell
179
- blun-codex --profile frida telegram-plugin
180
- ```
181
-
182
- looks for:
183
-
184
- ```text
185
- %USERPROFILE%\.codex\profiles\codexlink\frida.json
186
- ```
174
+ For internal/private profiles:
175
+
176
+ - keep the profile local on the machine
177
+ - give it its own `agent_name`
178
+ - give it its own Telegram state directory
179
+ - do not ship internal agent profiles in the public package
180
+
181
+ Local private profiles are loaded from:
182
+
183
+ ```text
184
+ %USERPROFILE%\.codex\profiles\codexlink\<name>.json
185
+ ```
186
+
187
+ Example:
188
+
189
+ ```powershell
190
+ blun-codex --profile frida telegram-plugin
191
+ ```
192
+
193
+ looks for:
194
+
195
+ ```text
196
+ %USERPROFILE%\.codex\profiles\codexlink\frida.json
197
+ ```
187
198
 
188
199
  ## What it does
189
200
 
190
201
  - starts one consistent local CLI runtime
191
202
  - writes a launch record into `.codex/runtimes/default/`
192
203
  - keeps Telegram queue state under `.codex/channels/telegram-default/`
193
- - attaches Telegram delivery to the same visible session
194
- - defers automatic Telegram delivery until the foreground session is idle
195
- - keeps poller, dispatcher, and reply relay separate from the foreground operator
204
+ - attaches Telegram delivery to the same visible session
205
+ - defers automatic Telegram delivery until the foreground session is idle
206
+ - keeps poller, dispatcher, and reply relay separate from the foreground operator
196
207
 
197
208
  ## What it does not do
198
209
 
@@ -229,12 +240,19 @@ The bundled plugin lives under `telegram-plugin/` and contains:
229
240
 
230
241
  ## First-run behavior
231
242
 
232
- `blun-codex telegram-plugin` now behaves like a guided setup for normal users:
233
-
234
- 1. check whether Telegram is already configured
235
- 2. ask for missing Bot Token or allowed Chat ID(s)
236
- 3. save everything automatically into the local Telegram state folder
237
- 4. continue into Telegram mode
243
+ `blun-codex telegram-plugin` now behaves like a guided setup for normal users:
244
+
245
+ 1. check whether Telegram is already configured
246
+ 2. ask only for a missing Bot Token
247
+ 3. wait for one Telegram message to the bot
248
+ 4. detect and store the chat/group ID automatically
249
+ 5. continue into Telegram mode
250
+
251
+ Allowed Chat ID(s) are no longer typed by hand. To pair a different chat or group later, run:
252
+
253
+ ```powershell
254
+ blun-codex telegram-setup
255
+ ```
238
256
 
239
257
  If something is missing later, `blun-codex telegram-doctor` tells you exactly what is missing and what to run next.
240
258
 
package/package.json CHANGED
@@ -1,38 +1,40 @@
1
- {
2
- "name": "@blunking/codexlink",
3
- "version": "0.1.2",
4
- "description": "BLUN CLI launcher with Telegram channel support for one visible session.",
5
- "license": "MIT",
6
- "private": false,
7
- "bin": {
8
- "blun-codex": "./bin/blun-codex.js",
9
- "codexlink": "./bin/blun-codex.js"
10
- },
11
- "files": [
12
- "bin/",
13
- "profiles/default.json",
14
- "telegram-plugin/",
15
- "README.md",
16
- "LICENSE",
17
- "blun-codex.cmd",
18
- "blun-codex.ps1",
19
- "start-codex-agent.ps1",
20
- "start-codex.cmd",
21
- "telegram-status.ps1",
22
- "telegram-doctor.ps1",
23
- "telegram-setup.ps1"
24
- ],
25
- "keywords": [
26
- "blun",
27
- "codexlink",
28
- "telegram",
29
- "runtime",
30
- "cli"
31
- ],
32
- "dependencies": {
33
- "@modelcontextprotocol/sdk": "^1.0.0"
34
- },
35
- "engines": {
36
- "node": ">=20"
37
- }
38
- }
1
+ {
2
+ "name": "@blunking/codexlink",
3
+ "version": "0.1.10",
4
+ "description": "BLUN CLI launcher with Telegram channel support for one visible session.",
5
+ "license": "MIT",
6
+ "private": false,
7
+ "bin": {
8
+ "blun-codex": "bin/blun-codex.js",
9
+ "codexlink": "bin/blun-codex.js"
10
+ },
11
+ "files": [
12
+ "bin/",
13
+ "profiles/default.json",
14
+ "telegram-plugin/",
15
+ "README.md",
16
+ "LICENSE",
17
+ "blun-codex.cmd",
18
+ "blun-codex.ps1",
19
+ "start-codex-agent.ps1",
20
+ "start-codex.cmd",
21
+ "telegram-title-embed.ps1",
22
+ "telegram-status.ps1",
23
+ "telegram-doctor.ps1",
24
+ "telegram-setup.ps1",
25
+ "telegram-title-watcher.ps1"
26
+ ],
27
+ "keywords": [
28
+ "blun",
29
+ "codexlink",
30
+ "telegram",
31
+ "runtime",
32
+ "cli"
33
+ ],
34
+ "dependencies": {
35
+ "@modelcontextprotocol/sdk": "^1.0.0"
36
+ },
37
+ "engines": {
38
+ "node": ">=20"
39
+ }
40
+ }
@@ -21,42 +21,42 @@ function Get-JsonFile {
21
21
  return Get-Content -Raw -Path $Path | ConvertFrom-Json
22
22
  }
23
23
 
24
- function Try-GetJsonFile {
25
- param([string]$Path)
26
- if (-not (Test-Path $Path)) {
27
- return $null
24
+ function Try-GetJsonFile {
25
+ param([string]$Path)
26
+ if (-not (Test-Path $Path)) {
27
+ return $null
28
28
  }
29
29
  try {
30
30
  return Get-Content -Raw -Path $Path | ConvertFrom-Json
31
31
  } catch {
32
32
  return $null
33
- }
34
- }
35
-
36
- function Get-ProfilePath {
37
- param(
38
- [string]$RuntimeRoot,
39
- [string]$ProfileName
40
- )
41
-
42
- $normalized = [string]$ProfileName
43
- if (-not $normalized) { $normalized = "" }
44
- $normalized = $normalized.ToLower()
45
- $candidates = @()
46
- if ($env:BLUN_CODEX_PROFILE_ROOT) {
47
- $candidates += (Join-Path $env:BLUN_CODEX_PROFILE_ROOT ($normalized + ".json"))
48
- }
49
- $candidates += (Join-Path $env:USERPROFILE (".codex\\profiles\\codexlink\\" + $normalized + ".json"))
50
- $candidates += (Join-Path $RuntimeRoot ("profiles\\" + $normalized + ".json"))
51
-
52
- foreach ($candidate in $candidates) {
53
- if ($candidate -and (Test-Path $candidate)) {
54
- return $candidate
55
- }
56
- }
57
-
58
- return $candidates[-1]
59
- }
33
+ }
34
+ }
35
+
36
+ function Get-ProfilePath {
37
+ param(
38
+ [string]$RuntimeRoot,
39
+ [string]$ProfileName
40
+ )
41
+
42
+ $normalized = [string]$ProfileName
43
+ if (-not $normalized) { $normalized = "" }
44
+ $normalized = $normalized.ToLower()
45
+ $candidates = @()
46
+ if ($env:BLUN_CODEX_PROFILE_ROOT) {
47
+ $candidates += (Join-Path $env:BLUN_CODEX_PROFILE_ROOT ($normalized + ".json"))
48
+ }
49
+ $candidates += (Join-Path $env:USERPROFILE (".codex\\profiles\\codexlink\\" + $normalized + ".json"))
50
+ $candidates += (Join-Path $RuntimeRoot ("profiles\\" + $normalized + ".json"))
51
+
52
+ foreach ($candidate in $candidates) {
53
+ if ($candidate -and (Test-Path $candidate)) {
54
+ return $candidate
55
+ }
56
+ }
57
+
58
+ return $candidates[-1]
59
+ }
60
60
 
61
61
  function Ensure-Dir {
62
62
  param([string]$Path)
@@ -113,7 +113,7 @@ function Get-FreeTcpPort {
113
113
  }
114
114
  }
115
115
 
116
- function Stop-ProcessTree {
116
+ function Stop-ProcessTree {
117
117
  param([int[]]$RootIds)
118
118
 
119
119
  $all = @(Get-CimInstance Win32_Process)
@@ -144,9 +144,9 @@ function Stop-ProcessTree {
144
144
  } catch {
145
145
  }
146
146
  }
147
- }
148
-
149
- function Wait-TcpPort {
147
+ }
148
+
149
+ function Wait-TcpPort {
150
150
  param(
151
151
  [string]$HostName,
152
152
  [int]$Port,
@@ -301,8 +301,8 @@ function Quote-PowerShellLiteral {
301
301
  }
302
302
 
303
303
  $runtimeRoot = Split-Path -Parent $MyInvocation.MyCommand.Path
304
- $profilePath = Get-ProfilePath -RuntimeRoot $runtimeRoot -ProfileName $Agent
305
- $profile = Get-JsonFile -Path $profilePath
304
+ $profilePath = Get-ProfilePath -RuntimeRoot $runtimeRoot -ProfileName $Agent
305
+ $profile = Get-JsonFile -Path $profilePath
306
306
 
307
307
  $resolvedWorkspace = if ($Workspace) { $Workspace } elseif ($profile.workspace) { $profile.workspace } else { (Get-Location).Path }
308
308
  $resolvedWorkspace = Resolve-ConfiguredPath -Value $resolvedWorkspace
@@ -527,13 +527,24 @@ if ($useRemoteAppServer) {
527
527
  if ($telegramAllowedChatId) {
528
528
  $stateEnv["BLUN_TELEGRAM_ALLOWED_CHAT_ID"] = $telegramAllowedChatId
529
529
  }
530
- $stateEnv["BLUN_TELEGRAM_PLUGIN_MODE"] = $TelegramMode
531
- $stateEnv["BLUN_TELEGRAM_APP_SERVER_WS_URL"] = $telegramAppServerWsUrl
532
- $stateEnv["BLUN_TELEGRAM_THREAD_ID"] = ""
533
- Write-DotEnvFile -Path $envFilePath -Values $stateEnv
534
- Write-DebugStage -Path $debugLogPath -Message ("ENV_WRITTEN ws_url=" + $telegramAppServerWsUrl + " env_file=" + $envFilePath)
535
-
536
- Set-EnvVar "BLUN_TELEGRAM_APP_SERVER_WS_URL" $telegramAppServerWsUrl
530
+ $stateEnv["BLUN_TELEGRAM_PLUGIN_MODE"] = $TelegramMode
531
+ $stateEnv["BLUN_TELEGRAM_APP_SERVER_WS_URL"] = $telegramAppServerWsUrl
532
+ $stateEnv["BLUN_TELEGRAM_THREAD_ID"] = ""
533
+ Write-DotEnvFile -Path $envFilePath -Values $stateEnv
534
+ Write-DebugStage -Path $debugLogPath -Message ("ENV_WRITTEN ws_url=" + $telegramAppServerWsUrl + " env_file=" + $envFilePath)
535
+ $telegramStateFile = Join-Path $telegramStateDir "state.json"
536
+ $telegramState = Try-GetJsonFile -Path $telegramStateFile
537
+ if ($null -ne $telegramState) {
538
+ if ($telegramState.PSObject.Properties.Name.Contains("currentThreadId")) {
539
+ $telegramState.currentThreadId = ""
540
+ } else {
541
+ $telegramState | Add-Member -NotePropertyName "currentThreadId" -NotePropertyValue ""
542
+ }
543
+ Write-TextFileWithRetry -Path $telegramStateFile -Content ($telegramState | ConvertTo-Json -Depth 10)
544
+ Write-DebugStage -Path $debugLogPath -Message "STATE_THREAD_CLEARED"
545
+ }
546
+
547
+ Set-EnvVar "BLUN_TELEGRAM_APP_SERVER_WS_URL" $telegramAppServerWsUrl
537
548
 
538
549
  $appServerPidFile = Join-Path $agentRuntimeDir "app-server.pid"
539
550
  $appServerInfoFile = Join-Path $agentRuntimeDir "app-server.json"
@@ -545,6 +556,7 @@ if ($useRemoteAppServer) {
545
556
  $oldPids = @()
546
557
  if ($previousRuntime.frontend_host_pid) { $oldPids += [int]$previousRuntime.frontend_host_pid }
547
558
  if ($previousRuntime.app_server_pid) { $oldPids += [int]$previousRuntime.app_server_pid }
559
+ if ($previousRuntime.queue_notifier_pid) { $oldPids += [int]$previousRuntime.queue_notifier_pid }
548
560
  if ($previousRuntime.poller_pid) { $oldPids += [int]$previousRuntime.poller_pid }
549
561
  if ($previousRuntime.dispatcher_pid) { $oldPids += [int]$previousRuntime.dispatcher_pid }
550
562
  if ($previousRuntime.responder_pid) { $oldPids += [int]$previousRuntime.responder_pid }
@@ -666,26 +678,57 @@ if ($PrintOnly) {
666
678
  if ($useRemoteAppServer) {
667
679
  $codexScript = (Get-Command codex).Source
668
680
  $currentRuntimeFile = Join-Path $agentRuntimeDir "current-remote-runtime.json"
669
- $windowTitle = "BLUN Codex Telegram [" + $profile.agent_name + "] " + $telegramAppServerWsUrl
670
- $resumeCommand = "$host.UI.RawUI.WindowTitle = " + (Quote-PowerShellLiteral $windowTitle) + "; & " + (Quote-PowerShellLiteral $codexScript) + " " + (($codexArgs | ForEach-Object { Quote-PowerShellLiteral $_ }) -join " ")
681
+ $windowTitle = "BLUN Codex Telegram [" + $profile.agent_name + "] " + $telegramAppServerWsUrl
682
+ $titleEmbedScript = Join-Path $runtimeRoot "telegram-title-embed.ps1"
683
+ $titleEmbedLog = Join-Path $agentRuntimeDir "title-embed.log"
684
+ $resumeCommand = ""
685
+ if (Test-Path $titleEmbedScript) {
686
+ $resumeCommand += "& " + (Quote-PowerShellLiteral $titleEmbedScript)
687
+ $resumeCommand += " -StateFile " + (Quote-PowerShellLiteral (Join-Path $telegramStateDir "state.json"))
688
+ $resumeCommand += " -BaseTitle " + (Quote-PowerShellLiteral $windowTitle)
689
+ $resumeCommand += " -LogFile " + (Quote-PowerShellLiteral $titleEmbedLog)
690
+ $resumeCommand += "; "
691
+ }
692
+ $resumeCommand += "$host.UI.RawUI.WindowTitle = " + (Quote-PowerShellLiteral $windowTitle) + "; & " + (Quote-PowerShellLiteral $codexScript) + " " + (($codexArgs | ForEach-Object { Quote-PowerShellLiteral $_ }) -join " ")
671
693
  Write-DebugStage -Path $debugLogPath -Message ("FRONTEND_SPAWN command=" + $resumeCommand)
672
694
  $frontendProcess = Start-Process -FilePath "powershell" -WorkingDirectory $resolvedWorkspace -ArgumentList @(
673
695
  "-NoExit",
674
696
  "-Command",
675
697
  $resumeCommand
676
698
  ) -PassThru
677
- $currentRuntime = [ordered]@{
678
- ws_url = $telegramAppServerWsUrl
679
- app_server_pid = $backendProcess.Id
680
- frontend_host_pid = $frontendProcess.Id
681
- profile = $profile.agent_name
699
+ $currentRuntime = [ordered]@{
700
+ ws_url = $telegramAppServerWsUrl
701
+ app_server_pid = $backendProcess.Id
702
+ frontend_host_pid = $frontendProcess.Id
703
+ profile = $profile.agent_name
682
704
  started_at = (Get-Date).ToUniversalTime().ToString("o")
683
- }
684
- Write-TextFileWithRetry -Path $currentRuntimeFile -Content ($currentRuntime | ConvertTo-Json -Depth 4)
685
- Write-DebugStage -Path $debugLogPath -Message "FRONTEND_SPAWNED"
686
- if ($envFilePath -and $bootstrapScript) {
687
- try {
688
- Write-DebugStage -Path $debugLogPath -Message "LOADED_THREAD_WAIT_START"
705
+ }
706
+ Write-TextFileWithRetry -Path $currentRuntimeFile -Content ($currentRuntime | ConvertTo-Json -Depth 4)
707
+ Write-DebugStage -Path $debugLogPath -Message "FRONTEND_SPAWNED"
708
+ $sidecarsStartedEarly = $false
709
+ if ($sidecarManager) {
710
+ try {
711
+ & node $sidecarManager | Out-Null
712
+ if ($LASTEXITCODE -ne 0) {
713
+ Write-DebugStage -Path $debugLogPath -Message "SIDECAR_MANAGER_EARLY_FAILED"
714
+ } else {
715
+ $sidecarsStartedEarly = $true
716
+ Write-DebugStage -Path $debugLogPath -Message "SIDECAR_MANAGER_EARLY_OK"
717
+ $pollerPid = Read-PidFileValue -Path (Join-Path $telegramStateDir "poller.pid")
718
+ $dispatcherPid = Read-PidFileValue -Path (Join-Path $telegramStateDir "dispatcher.pid")
719
+ $responderPid = Read-PidFileValue -Path (Join-Path $telegramStateDir "responder.pid")
720
+ if ($pollerPid -gt 0) { $currentRuntime["poller_pid"] = $pollerPid }
721
+ if ($dispatcherPid -gt 0) { $currentRuntime["dispatcher_pid"] = $dispatcherPid }
722
+ if ($responderPid -gt 0) { $currentRuntime["responder_pid"] = $responderPid }
723
+ Write-TextFileWithRetry -Path $currentRuntimeFile -Content ($currentRuntime | ConvertTo-Json -Depth 6)
724
+ }
725
+ } catch {
726
+ Write-DebugStage -Path $debugLogPath -Message ("SIDECAR_MANAGER_EARLY_ERROR " + $_.Exception.Message)
727
+ }
728
+ }
729
+ if ($envFilePath -and $bootstrapScript) {
730
+ try {
731
+ Write-DebugStage -Path $debugLogPath -Message "LOADED_THREAD_WAIT_START"
689
732
  $loadedIds = @()
690
733
  for ($attempt = 1; $attempt -le 40; $attempt++) {
691
734
  $loaded = Invoke-NodeJsonWithRetry -NodeArgs @($bootstrapScript, "list-loaded", "--ws-url", $telegramAppServerWsUrl) -Attempts 1 -DelayMs 0
@@ -697,17 +740,67 @@ if ($useRemoteAppServer) {
697
740
  if ($attempt -lt 40) {
698
741
  Start-Sleep -Milliseconds 500
699
742
  }
700
- }
701
- if ($loadedIds.Count -gt 0) {
702
- $activeThreadId = [string]$loadedIds[$loadedIds.Count - 1]
703
- Write-DebugStage -Path $debugLogPath -Message ("LOADED_THREAD_PICKED thread_id=" + $activeThreadId + " count=" + $loadedIds.Count)
704
- $stateEnv = Read-DotEnvFile -Path $envFilePath
705
- $stateEnv["BLUN_TELEGRAM_THREAD_ID"] = $activeThreadId
706
- Write-DotEnvFile -Path $envFilePath -Values $stateEnv
707
- Set-EnvVar "BLUN_TELEGRAM_THREAD_ID" $activeThreadId
708
- Write-DebugStage -Path $debugLogPath -Message "ENV_THREAD_WRITTEN"
709
-
710
- $currentRuntime["thread_id"] = $activeThreadId
743
+ }
744
+ if ($loadedIds.Count -gt 0) {
745
+ $activeThreadId = [string]$loadedIds[$loadedIds.Count - 1]
746
+ $bestThreadScore = [double]::NegativeInfinity
747
+ foreach ($candidate in $loadedIds) {
748
+ $candidateThreadId = [string]$candidate
749
+ if ([string]::IsNullOrWhiteSpace($candidateThreadId)) {
750
+ continue
751
+ }
752
+ $threadScore = 0.0
753
+ try {
754
+ $candidateInfo = Invoke-NodeJsonWithRetry -NodeArgs @($bootstrapScript, "read-thread", "--ws-url", $telegramAppServerWsUrl, "--thread-id", $candidateThreadId) -Attempts 1 -DelayMs 0
755
+ $thread = $candidateInfo.response.result.thread
756
+ if ($null -ne $thread -and $null -ne $thread.createdAt) {
757
+ $threadScore = [double]$thread.createdAt
758
+ if ($threadScore -gt 0 -and $threadScore -lt 1000000000000) {
759
+ $threadScore = $threadScore * 1000
760
+ }
761
+ }
762
+ $threadSource = ""
763
+ $threadStatusType = ""
764
+ if ($null -ne $thread -and $null -ne $thread.source) {
765
+ $threadSource = ([string]$thread.source).ToLowerInvariant()
766
+ }
767
+ if ($null -ne $thread -and $null -ne $thread.status -and $null -ne $thread.status.type) {
768
+ $threadStatusType = ([string]$thread.status.type).ToLowerInvariant()
769
+ }
770
+ if ($threadSource -eq "cli" -and $threadStatusType -eq "active") {
771
+ $threadScore += 1000000000000000
772
+ } elseif ($threadStatusType -eq "active") {
773
+ $threadScore += 900000000000000
774
+ } elseif ($threadSource -eq "cli") {
775
+ $threadScore += 800000000000000
776
+ }
777
+ } catch {
778
+ $threadScore = 0.0
779
+ }
780
+ if ($threadScore -ge $bestThreadScore) {
781
+ $bestThreadScore = $threadScore
782
+ $activeThreadId = $candidateThreadId
783
+ }
784
+ }
785
+ Write-DebugStage -Path $debugLogPath -Message ("LOADED_THREAD_PICKED thread_id=" + $activeThreadId + " count=" + $loadedIds.Count + " score=" + $bestThreadScore)
786
+ $stateEnv = Read-DotEnvFile -Path $envFilePath
787
+ $stateEnv["BLUN_TELEGRAM_THREAD_ID"] = $activeThreadId
788
+ Write-DotEnvFile -Path $envFilePath -Values $stateEnv
789
+ Set-EnvVar "BLUN_TELEGRAM_THREAD_ID" $activeThreadId
790
+ Write-DebugStage -Path $debugLogPath -Message "ENV_THREAD_WRITTEN"
791
+
792
+ $telegramState = Try-GetJsonFile -Path (Join-Path $telegramStateDir "state.json")
793
+ if ($null -ne $telegramState) {
794
+ if ($telegramState.PSObject.Properties.Name.Contains("currentThreadId")) {
795
+ $telegramState.currentThreadId = $activeThreadId
796
+ } else {
797
+ $telegramState | Add-Member -NotePropertyName "currentThreadId" -NotePropertyValue $activeThreadId
798
+ }
799
+ Write-TextFileWithRetry -Path (Join-Path $telegramStateDir "state.json") -Content ($telegramState | ConvertTo-Json -Depth 10)
800
+ Write-DebugStage -Path $debugLogPath -Message "STATE_THREAD_WRITTEN"
801
+ }
802
+
803
+ $currentRuntime["thread_id"] = $activeThreadId
711
804
  Write-TextFileWithRetry -Path $currentRuntimeFile -Content ($currentRuntime | ConvertTo-Json -Depth 6)
712
805
  Write-DebugStage -Path $debugLogPath -Message "CURRENT_RUNTIME_THREAD_WRITTEN"
713
806
 
@@ -749,9 +842,24 @@ if ($useRemoteAppServer) {
749
842
  if ($responderPid -gt 0) { $currentRuntime["responder_pid"] = $responderPid }
750
843
  Write-TextFileWithRetry -Path $currentRuntimeFile -Content ($currentRuntime | ConvertTo-Json -Depth 6)
751
844
  Write-DebugStage -Path $debugLogPath -Message "APP_SERVER_INFO_WRITTEN"
752
- } else {
753
- Write-DebugStage -Path $debugLogPath -Message "LOADED_THREAD_NONE"
754
- }
845
+ } else {
846
+ Write-DebugStage -Path $debugLogPath -Message "LOADED_THREAD_NONE"
847
+ if ($sidecarsStartedEarly) {
848
+ $remoteSessionInfo = [ordered]@{
849
+ ws_url = $telegramAppServerWsUrl
850
+ thread_id = ""
851
+ pid = $backendProcess.Id
852
+ started_at = (Get-Date).ToUniversalTime().ToString("o")
853
+ sidecars = [ordered]@{
854
+ poller = [ordered]@{ pid = Read-PidFileValue -Path (Join-Path $telegramStateDir "poller.pid") }
855
+ dispatcher = [ordered]@{ pid = Read-PidFileValue -Path (Join-Path $telegramStateDir "dispatcher.pid") }
856
+ responder = [ordered]@{ pid = Read-PidFileValue -Path (Join-Path $telegramStateDir "responder.pid") }
857
+ }
858
+ }
859
+ Write-TextFileWithRetry -Path $appServerInfoFile -Content ($remoteSessionInfo | ConvertTo-Json -Depth 6)
860
+ Write-DebugStage -Path $debugLogPath -Message "APP_SERVER_INFO_WRITTEN_UNBOUND"
861
+ }
862
+ }
755
863
  } catch {
756
864
  Write-DebugStage -Path $debugLogPath -Message ("LOADED_THREAD_ERROR " + $_.Exception.Message)
757
865
  }
@@ -195,32 +195,46 @@ $legacyEnv = Read-DotEnvFile -Path $legacyEnvPath
195
195
 
196
196
  $tokenValue = ""
197
197
  $tokenSource = ""
198
- if (Test-TelegramTokenFormat -Value $activeEnv["BLUN_TELEGRAM_BOT_TOKEN"]) {
199
- $tokenValue = [string]$activeEnv["BLUN_TELEGRAM_BOT_TOKEN"]
200
- $tokenSource = "state env"
201
- } elseif (Test-TelegramTokenFormat -Value $legacyEnv["BLUN_TELEGRAM_BOT_TOKEN"]) {
202
- $tokenValue = [string]$legacyEnv["BLUN_TELEGRAM_BOT_TOKEN"]
203
- $tokenSource = "legacy env fallback"
204
- }
198
+ if (Test-TelegramTokenFormat -Value $activeEnv["BLUN_TELEGRAM_BOT_TOKEN"]) {
199
+ $tokenValue = [string]$activeEnv["BLUN_TELEGRAM_BOT_TOKEN"]
200
+ $tokenSource = "state env"
201
+ } elseif (Test-TelegramTokenFormat -Value $activeEnv["TELEGRAM_BOT_TOKEN"]) {
202
+ $tokenValue = [string]$activeEnv["TELEGRAM_BOT_TOKEN"]
203
+ $tokenSource = "state env legacy key"
204
+ } elseif (Test-TelegramTokenFormat -Value $legacyEnv["BLUN_TELEGRAM_BOT_TOKEN"]) {
205
+ $tokenValue = [string]$legacyEnv["BLUN_TELEGRAM_BOT_TOKEN"]
206
+ $tokenSource = "legacy env fallback"
207
+ } elseif (Test-TelegramTokenFormat -Value $legacyEnv["TELEGRAM_BOT_TOKEN"]) {
208
+ $tokenValue = [string]$legacyEnv["TELEGRAM_BOT_TOKEN"]
209
+ $tokenSource = "legacy env fallback legacy key"
210
+ }
205
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." })
206
212
 
207
213
  $allowedChatIds = ""
208
214
  $allowedChatSource = ""
209
- if (Test-AllowedChatIdsFormat -Value $activeEnv["BLUN_TELEGRAM_ALLOWED_CHAT_ID"]) {
210
- $allowedChatIds = [string]$activeEnv["BLUN_TELEGRAM_ALLOWED_CHAT_ID"]
211
- $allowedChatSource = "state env"
212
- } elseif (Test-AllowedChatIdsFormat -Value $legacyEnv["BLUN_TELEGRAM_ALLOWED_CHAT_ID"]) {
213
- $allowedChatIds = [string]$legacyEnv["BLUN_TELEGRAM_ALLOWED_CHAT_ID"]
214
- $allowedChatSource = "legacy env fallback"
215
- }
216
- Add-Check -List $checks -Name "allowed_chat_ids" -Status $(if ($allowedChatIds) { "ok" } else { "fail" }) -Detail $(if ($allowedChatIds) { $allowedChatIds } else { "No valid BLUN_TELEGRAM_ALLOWED_CHAT_ID found." })
215
+ if (Test-AllowedChatIdsFormat -Value $activeEnv["BLUN_TELEGRAM_ALLOWED_CHAT_ID"]) {
216
+ $allowedChatIds = [string]$activeEnv["BLUN_TELEGRAM_ALLOWED_CHAT_ID"]
217
+ $allowedChatSource = "state env"
218
+ } elseif (Test-AllowedChatIdsFormat -Value $activeEnv["TELEGRAM_ALLOWED_CHAT_ID"]) {
219
+ $allowedChatIds = [string]$activeEnv["TELEGRAM_ALLOWED_CHAT_ID"]
220
+ $allowedChatSource = "state env legacy key"
221
+ } elseif (Test-AllowedChatIdsFormat -Value $legacyEnv["BLUN_TELEGRAM_ALLOWED_CHAT_ID"]) {
222
+ $allowedChatIds = [string]$legacyEnv["BLUN_TELEGRAM_ALLOWED_CHAT_ID"]
223
+ $allowedChatSource = "legacy env fallback"
224
+ } elseif (Test-AllowedChatIdsFormat -Value $legacyEnv["TELEGRAM_ALLOWED_CHAT_ID"]) {
225
+ $allowedChatIds = [string]$legacyEnv["TELEGRAM_ALLOWED_CHAT_ID"]
226
+ $allowedChatSource = "legacy env fallback legacy key"
227
+ }
228
+ 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." })
217
229
 
218
230
  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." })
219
- 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)
231
+ 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)
220
232
  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." })
221
- 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)
222
- 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)
223
- 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)
233
+ 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
+ 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
+ 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
+ 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
+ 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)
224
238
 
225
239
  if ($status.last_inbound) {
226
240
  $lastInboundSummary = [string]::Format(
@@ -248,7 +262,7 @@ if ($status.last_outbound) {
248
262
  Add-Check -List $checks -Name "last_outbound" -Status "warn" -Detail "No outbound Telegram message recorded yet."
249
263
  }
250
264
 
251
- 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 + " submitted=" + $status.submitted_depth + " pending_replies=" + $status.pending_reply_depth)
265
+ 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)
252
266
 
253
267
  $result = [ordered]@{
254
268
  profile = $status.profile
@@ -7,5 +7,6 @@ BLUN_TELEGRAM_THREAD_ID=
7
7
  BLUN_TELEGRAM_RESUME_TIMEOUT_MS=15000
8
8
  BLUN_TELEGRAM_IDLE_COOLDOWN_MS=15000
9
9
  BLUN_TELEGRAM_DISPATCH_MODE=deferred
10
+ BLUN_TELEGRAM_PROGRESS_RELAY=status
10
11
  BLUN_TELEGRAM_POLL_INTERVAL_MS=5000
11
12
  BLUN_TELEGRAM_INJECT_INTERVAL_MS=15000