@blunking/codexlink 0.1.10 → 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.
package/README.md CHANGED
@@ -105,13 +105,15 @@ blun-codex --telegram plugin
105
105
  Status:
106
106
 
107
107
  ```powershell
108
- blun-codex telegram-status
109
- ```
110
-
108
+ blun-codex telegram-status
109
+ ```
110
+
111
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
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
+ 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
+
115
117
  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
118
 
117
119
  ```text
@@ -244,7 +246,7 @@ The bundled plugin lives under `telegram-plugin/` and contains:
244
246
 
245
247
  1. check whether Telegram is already configured
246
248
  2. ask only for a missing Bot Token
247
- 3. wait for one Telegram message to the bot
249
+ 3. wait for one Telegram message to the bot when no chat is paired yet
248
250
  4. detect and store the chat/group ID automatically
249
251
  5. continue into Telegram mode
250
252
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blunking/codexlink",
3
- "version": "0.1.10",
3
+ "version": "0.1.14",
4
4
  "description": "BLUN CLI launcher with Telegram channel support for one visible session.",
5
5
  "license": "MIT",
6
6
  "private": false,
@@ -111,7 +111,7 @@ function Write-DoctorReport {
111
111
  foreach ($item in $failed) {
112
112
  switch ($item.name) {
113
113
  "bot_token" { Write-Host " - Telegram Bot Token fehlt. Starte: blun-codex --profile $($Result.profile) telegram-setup" }
114
- "allowed_chat_ids" { Write-Host " - Erlaubte Chat-ID(s) fehlen. Starte: blun-codex --profile $($Result.profile) telegram-setup" }
114
+ "allowed_chat_ids" { Write-Host " - Chat-ID ist optional. Zum automatischen Koppeln: blun-codex --profile $($Result.profile) telegram-setup" }
115
115
  "state_dir" { Write-Host " - Der lokale Telegram-State-Ordner fehlt noch. Ein Setup-Lauf legt ihn automatisch an." }
116
116
  "profile_file" { Write-Host " - Das angegebene Profil existiert nicht." }
117
117
  "node" { Write-Host " - Node.js fehlt in PATH." }
@@ -2,7 +2,7 @@
2
2
  import { injectNext } from "./lib/bridge.js";
3
3
  import { isCurrentSidecarPid } from "./lib/singleton.js";
4
4
 
5
- const intervalMs = Number.parseInt(process.env.BLUN_TELEGRAM_INJECT_INTERVAL_MS || "1500", 10) || 1500;
5
+ const intervalMs = Number.parseInt(process.env.BLUN_TELEGRAM_INJECT_INTERVAL_MS || "700", 10) || 700;
6
6
  let stopping = false;
7
7
 
8
8
  function sleep(ms) {
@@ -389,7 +389,10 @@ function looksLikeStatusBroadcast(text) {
389
389
  if (/^status(?:\s|$|[~:.-])/u.test(normalized)) {
390
390
  return true;
391
391
  }
392
- return /^[a-z][a-z0-9_-]{1,24}\s+\d{1,2}:\d{2}\b/u.test(normalized);
392
+ if (/^status\s+~?\s*\d{1,2}\s+\d{2}\b/u.test(normalized)) {
393
+ return true;
394
+ }
395
+ return /^[a-z][a-z0-9_-]{1,24}\s+~?\s*\d{1,2}\s+\d{2}\b/u.test(normalized);
393
396
  }
394
397
 
395
398
  function isAgentAddressed(config, text) {
@@ -415,7 +418,7 @@ function isAgentAddressed(config, text) {
415
418
  return true;
416
419
  }
417
420
 
418
- const briefDirective = new RegExp(`\\bbrief\\b[\\s\\S]{0,60}\\b@?${mention}\\b|\\b@?${mention}\\b[\\s\\S]{0,60}\\bbrief\\b`, "u").test(normalized);
421
+ const briefDirective = new RegExp(`\\bbrief\\b(?:\\s+#?\\d+)?\\s+(?:fuer|fur|for|an|to)\\s+@?${mention}\\b|\\b@?${mention}\\b\\s*[-:,]?\\s*brief\\b`, "u").test(normalized);
419
422
  if (briefDirective) {
420
423
  return true;
421
424
  }
@@ -452,7 +455,7 @@ function isOtherAgentAddressed(config, text) {
452
455
  return true;
453
456
  }
454
457
 
455
- const briefDirective = new RegExp(`\\bbrief\\b[\\s\\S]{0,60}\\b@?${mention}\\b|\\b@?${mention}\\b[\\s\\S]{0,60}\\bbrief\\b`, "u").test(normalized);
458
+ const briefDirective = new RegExp(`\\bbrief\\b(?:\\s+#?\\d+)?\\s+(?:fuer|fur|for|an|to)\\s+@?${mention}\\b|\\b@?${mention}\\b\\s*[-:,]?\\s*brief\\b`, "u").test(normalized);
456
459
  if (briefDirective) {
457
460
  return true;
458
461
  }
@@ -467,31 +470,27 @@ function isOtherAgentAddressed(config, text) {
467
470
  }
468
471
 
469
472
  function classifyInboundRelevance(config, inbound) {
470
- if (looksLikeEscalation(inbound.text)) {
471
- return "escalation";
472
- }
473
-
474
- if (String(inbound.chatType || "") === "private") {
475
- return "direct";
476
- }
477
-
478
473
  const text = String(inbound.text || "");
479
- if (!looksLikeStatusBroadcast(text) && isAgentAddressed(config, text)) {
474
+ const isStatusBroadcast = looksLikeStatusBroadcast(text);
475
+
476
+ if (!isStatusBroadcast && isAgentAddressed(config, text)) {
480
477
  return "direct";
481
478
  }
482
479
 
483
- if (!looksLikeStatusBroadcast(text) && isOtherAgentAddressed(config, text)) {
480
+ if (!isStatusBroadcast && isOtherAgentAddressed(config, text)) {
484
481
  return "ambient";
485
482
  }
486
483
 
487
- const allowedUserIds = Array.isArray(config.allowedChatIds) ? config.allowedChatIds : [];
488
- if (!inbound.senderIsBot && allowedUserIds.includes(String(inbound.userId || ""))) {
484
+ if (String(inbound.chatType || "") === "private") {
489
485
  return "direct";
490
486
  }
491
487
 
492
- const agentName = String(config.agentName || "").trim();
493
- if (agentName && agentName.toLowerCase() !== "default" && !looksLikeStatusBroadcast(text) && containsToken(text, agentName)) {
494
- return "direct";
488
+ if (inbound.senderIsBot || isStatusBroadcast) {
489
+ return "ambient";
490
+ }
491
+
492
+ if (looksLikeEscalation(text)) {
493
+ return "escalation";
495
494
  }
496
495
 
497
496
  const lane = String(config.lane || "").trim();
@@ -759,6 +758,33 @@ function parkExpiredAmbientQueueEntriesInPlace(config, queue) {
759
758
  return parked;
760
759
  }
761
760
 
761
+ function reclassifyQueuedEntriesInPlace(config, queue) {
762
+ const entries = Array.isArray(queue) ? queue : [];
763
+ let changed = 0;
764
+ let parked = 0;
765
+ for (const entry of entries) {
766
+ if (!entry || entry.status !== "queued") {
767
+ continue;
768
+ }
769
+ const previous = String(entry.relevance || "").trim().toLowerCase();
770
+ const next = classifyInboundRelevance(config, entry);
771
+ if (next === previous) {
772
+ continue;
773
+ }
774
+ const reclassifiedAt = nowIso();
775
+ entry.relevance = next;
776
+ entry.reclassifiedAt = reclassifiedAt;
777
+ if (next === "ambient" && String(entry.chatType || "").trim().toLowerCase() !== "private") {
778
+ entry.status = "parked";
779
+ entry.parkedAt = entry.parkedAt || reclassifiedAt;
780
+ entry.responsePreview = entry.responsePreview || "[reclassified ambient]";
781
+ parked += 1;
782
+ }
783
+ changed += 1;
784
+ }
785
+ return { changed, parked };
786
+ }
787
+
762
788
  function mergeQueueEntry(current, incoming) {
763
789
  if (!current && !incoming) {
764
790
  return null;
@@ -2068,11 +2094,15 @@ function isFastDispatchEntry(entry) {
2068
2094
  export async function injectNext(threadId, options = {}) {
2069
2095
  const config = loadConfig();
2070
2096
  const state = loadState(config);
2097
+ const reclassified = reclassifyQueuedEntriesInPlace(config, state.queue || []);
2071
2098
  const parkedAmbient = parkExpiredAmbientQueueEntriesInPlace(config, state.queue || []);
2072
2099
  const runtimeOwner = getRuntimeOwner(config);
2073
2100
  state.pendingReplies = reconcilePendingRepliesInPlace(state.pendingReplies || []);
2074
2101
  const expiredPendingReplies = closeExpiredPendingRepliesInPlace(config, state.pendingReplies || []);
2075
- if (expiredPendingReplies > 0 || parkedAmbient > 0) {
2102
+ if (expiredPendingReplies > 0 || parkedAmbient > 0 || reclassified.changed > 0) {
2103
+ if (reclassified.changed > 0) {
2104
+ appendLog(config.paths.activityFile, `QUEUE_RECLASSIFIED changed=${reclassified.changed} parked=${reclassified.parked}`);
2105
+ }
2076
2106
  if (parkedAmbient > 0) {
2077
2107
  appendLog(config.paths.activityFile, `AMBIENT_PARKED count=${parkedAmbient}`);
2078
2108
  }
@@ -19,9 +19,10 @@ async function telegramRequest(config, method, body) {
19
19
  }
20
20
 
21
21
  export async function getUpdates(config, offset) {
22
+ const timeout = Math.max(Number.parseInt(process.env.BLUN_TELEGRAM_GETUPDATES_TIMEOUT || "0", 10) || 0, 0);
22
23
  return telegramRequest(config, "getUpdates", {
23
24
  offset,
24
- timeout: 1,
25
+ timeout,
25
26
  allowed_updates: ["message"]
26
27
  });
27
28
  }
@@ -2,7 +2,7 @@
2
2
  import { pollOnce } from "./lib/bridge.js";
3
3
  import { isCurrentSidecarPid } from "./lib/singleton.js";
4
4
 
5
- const intervalMs = Number.parseInt(process.env.BLUN_TELEGRAM_POLL_INTERVAL_MS || "1500", 10) || 1500;
5
+ const intervalMs = Number.parseInt(process.env.BLUN_TELEGRAM_POLL_INTERVAL_MS || "700", 10) || 700;
6
6
  let stopping = false;
7
7
 
8
8
  function sleep(ms) {
@@ -349,7 +349,8 @@ if ($needsToken) {
349
349
  if ($needsChatIds -and -not $Json) {
350
350
  try {
351
351
  $botInfo = Get-TelegramBotInfo -Token $currentToken
352
- $shouldPair = $tokenWasPrompted -or (-not $EnsureConfigured)
352
+ $pairingDone = [string]$envValues["BLUN_TELEGRAM_PAIRING_DONE"]
353
+ $shouldPair = $tokenWasPrompted -or (-not $EnsureConfigured) -or ($pairingDone -ne "1")
353
354
  if ($shouldPair) {
354
355
  $pairedChat = Wait-TelegramPairingChat -Token $currentToken -BotInfo $botInfo -TimeoutSeconds 90
355
356
  if ($pairedChat -and $pairedChat.chat_id) {
@@ -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
  {
@@ -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