@blunking/codexlink 0.1.17 → 0.1.19

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
@@ -112,6 +112,8 @@ Wenn waehrend einer laufenden Arbeit Telegram-Nachrichten gepuffert werden, blei
112
112
 
113
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
114
 
115
+ CodexLink injiziert Nachrichten standardmaessig ueber den App-Server in den aktiven Thread. Der alte Windows-Tastaturmodus, der Text sichtbar ins Eingabefeld schreibt, ist absichtlich deaktiviert, weil einzelne Terminals Enter nicht zuverlaessig absenden. Wer ihn fuer Debugging trotzdem erzwingen will, muss explizit `BLUN_TELEGRAM_VISIBLE_CONSOLE_INJECT=force` setzen.
116
+
115
117
  Wichtig bei mehreren Profilen: ein Telegram-Bot-Token darf nicht gleichzeitig von alten oder fremden Pollern abgefragt werden. Sonst meldet Telegram `Conflict: terminated by other getUpdates request`, und Nachrichten koennen verspaetet oder gar nicht in der sichtbaren CLI landen. Aktuelle Versionen pollen non-blocking und schneller; wenn trotzdem Conflicts auftauchen, alle alten `blun-codex telegram-plugin` Fenster fuer denselben Bot schliessen und genau eine aktuelle Session starten.
116
118
 
117
119
  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:
@@ -122,16 +124,23 @@ BLUN_TELEGRAM_OTHER_AGENT_NAMES=frida,angel,dieter,alfred
122
124
  ```
123
125
 
124
126
  Doctor:
125
-
126
- ```powershell
127
- blun-codex telegram-doctor
128
- ```
129
-
130
- JSON doctor output:
131
-
132
- ```powershell
133
- blun-codex telegram-doctor --json
134
- ```
127
+
128
+ ```powershell
129
+ blun-codex telegram-doctor
130
+ ```
131
+
132
+ Runtime automatisch bereinigen, wenn mehrere Threads geladen sind oder eine alte Bindung klemmt:
133
+
134
+ ```powershell
135
+ blun-codex telegram-doctor --fix
136
+ blun-codex telegram-plugin
137
+ ```
138
+
139
+ JSON doctor output:
140
+
141
+ ```powershell
142
+ blun-codex telegram-doctor --json
143
+ ```
135
144
 
136
145
  Dry run:
137
146
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blunking/codexlink",
3
- "version": "0.1.17",
3
+ "version": "0.1.19",
4
4
  "description": "BLUN CLI launcher with Telegram channel support for one visible session.",
5
5
  "license": "MIT",
6
6
  "private": false,
@@ -1,11 +1,12 @@
1
- param(
2
- [string]$Profile = "default",
3
- [switch]$Json
4
- )
1
+ param(
2
+ [string]$Profile = "default",
3
+ [switch]$Json,
4
+ [switch]$Fix
5
+ )
5
6
 
6
7
  $ErrorActionPreference = "Stop"
7
8
 
8
- function Read-DotEnvFile {
9
+ function Read-DotEnvFile {
9
10
  param([string]$Path)
10
11
  $values = @{}
11
12
  if (-not (Test-Path $Path)) { return $values }
@@ -133,6 +134,95 @@ function Write-DoctorReport {
133
134
  }
134
135
  }
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
167
+ }
168
+ }
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,
@@ -230,6 +320,17 @@ Add-Check -List $checks -Name "allowed_chat_ids" -Status $(if ($allowedChatIds)
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)
@@ -264,15 +365,31 @@ if ($status.last_outbound) {
264
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
367
 
267
- $result = [ordered]@{
368
+ $result = [ordered]@{
268
369
  profile = $status.profile
269
370
  overall = Get-OverallStatus -Checks $checks
270
371
  runtime_root = $runtimeRoot
271
372
  state_dir = $status.state_dir
272
373
  plugin_root = $status.plugin_root
273
374
  checks = $checks
274
- status = $status
275
- }
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
+ }
276
393
 
277
394
  if ($Json) {
278
395
  $result | ConvertTo-Json -Depth 8
@@ -1238,12 +1238,14 @@ function normalizeTelegramThreadId(value) {
1238
1238
  return String(value || "").trim();
1239
1239
  }
1240
1240
 
1241
- function isAllowedChat(config, chatId) {
1241
+ function isAllowedChat(config, inbound) {
1242
1242
  const allowed = Array.isArray(config.allowedChatIds) ? config.allowedChatIds : [];
1243
1243
  if (allowed.length === 0) {
1244
1244
  return true;
1245
1245
  }
1246
- return allowed.includes(String(chatId || "").trim());
1246
+ const chatId = String(inbound?.chatId || inbound || "").trim();
1247
+ const userId = String(inbound?.userId || "").trim();
1248
+ return allowed.includes(chatId) || (userId && allowed.includes(userId));
1247
1249
  }
1248
1250
 
1249
1251
  function splitTelegramText(text, maxLength = 3500) {
@@ -2125,9 +2127,9 @@ export async function pollOnce() {
2125
2127
  continue;
2126
2128
  }
2127
2129
  const inbound = normalizeInbound(update.message);
2128
- if (!isAllowedChat(config, inbound.chatId)) {
2130
+ if (!isAllowedChat(config, inbound)) {
2129
2131
  ignored += 1;
2130
- appendLog(config.paths.activityFile, `IGNORED chat=${inbound.chatId} message=${inbound.messageId}`);
2132
+ appendLog(config.paths.activityFile, `IGNORED chat=${inbound.chatId} user=${inbound.userId || "-"} message=${inbound.messageId}`);
2131
2133
  continue;
2132
2134
  }
2133
2135
  if (String(inbound.chatType || "") === "private" && looksLikeMnemoIdleLoopBrief(inbound.text)) {
@@ -272,7 +272,8 @@ function getVisibleConsoleSkipReason(config, message) {
272
272
  if (!config.appServerWsUrl) {
273
273
  return "no_app_server";
274
274
  }
275
- if (String(process.env.BLUN_TELEGRAM_VISIBLE_CONSOLE_INJECT || "0").trim() !== "1") {
275
+ const visibleConsoleMode = String(process.env.BLUN_TELEGRAM_VISIBLE_CONSOLE_INJECT || "0").trim().toLowerCase();
276
+ if (visibleConsoleMode !== "force") {
276
277
  return "env_disabled";
277
278
  }
278
279
  if (Array.isArray(message.attachments) && message.attachments.some((attachment) => attachment?.isImage && attachment?.localPath && !attachment.error)) {