blun-king-cli 9.1.615 → 9.1.617

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "9.1.615",
3
+ "version": "9.1.617",
4
4
  "description": "BLUN CLI - your own AI agent with a Telegram channel. Get it done. With BLUN.",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -6,6 +6,7 @@ import { homedir } from "node:os";
6
6
  import { spawn } from "node:child_process";
7
7
  import { Readable, Writable } from "node:stream";
8
8
  import { fileURLToPath } from "node:url";
9
+ import { StringDecoder } from "node:string_decoder";
9
10
  //#region \0rolldown/runtime.js
10
11
  var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
11
12
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
@@ -41794,10 +41795,10 @@ var require_internal = /* @__PURE__ */ __commonJSMin(((exports, module) => {
41794
41795
  }
41795
41796
  InternalCodec.prototype.encoder = InternalEncoder;
41796
41797
  InternalCodec.prototype.decoder = InternalDecoder;
41797
- var StringDecoder = __require("string_decoder").StringDecoder;
41798
- if (!StringDecoder.prototype.end) StringDecoder.prototype.end = function() {};
41798
+ var StringDecoder$1 = __require("string_decoder").StringDecoder;
41799
+ if (!StringDecoder$1.prototype.end) StringDecoder$1.prototype.end = function() {};
41799
41800
  function InternalDecoder(options, codec) {
41800
- this.decoder = new StringDecoder(codec.enc);
41801
+ this.decoder = new StringDecoder$1(codec.enc);
41801
41802
  }
41802
41803
  InternalDecoder.prototype.write = function(buf) {
41803
41804
  if (!Buffer.isBuffer(buf)) buf = Buffer.from(buf);
@@ -60026,7 +60027,7 @@ function toInboundCtx(ctx, text) {
60026
60027
  chatId: ctx.chat?.id,
60027
60028
  chatType: chatType === "private" || chatType === "group" || chatType === "supergroup" || chatType === "channel" ? chatType : void 0,
60028
60029
  messageId: ctx.message?.message_id,
60029
- messageThreadId: ctx.message?.message_thread_id,
60030
+ messageThreadId: ctx.message?.is_topic_message === true || ctx.chat?.is_forum === true ? ctx.message?.message_thread_id : void 0,
60030
60031
  text,
60031
60032
  entities: (ctx.message?.entities ?? ctx.message?.caption_entities ?? []).map((entity) => ({
60032
60033
  type: entity.type,
@@ -60197,7 +60198,7 @@ var BoundedChannelContextBuffer = class {
60197
60198
  function buildChannelTag(content, meta, addressed = true, inferAddressing = false) {
60198
60199
  const pairs = [["source", "telegram"]];
60199
60200
  for (const [key, value] of Object.entries(meta)) if (value !== void 0) pairs.push([key, String(value)]);
60200
- return `<channel ${pairs.map(([key, value]) => `${key}="${escapeAttr(value)}"`).join(" ")}>\n${content}\n</channel>\n${addressed && meta.chat_id.startsWith("-") ? `[GRUPPENANTWORT: Eine bloße Information oder eine Nachricht, die ausdrücklich keine Bestätigung verlangt, macht keine Antwort erforderlich. Beende den Turn dann ohne Ausgabe und ohne Bestätigung deiner Stille. Eine Antwort auf deine Statusfrage ohne neuen Auftrag braucht keine Zusammenfassung und keinen sichtbaren Text. Falls ein Abschlusstext nötig ist, gib nur [[BLUN_TELEGRAM_SILENT]] aus. Einen konkreten Auftrag an dich nimm kurz an und beginne die Arbeit; melde danach das Ergebnis oder einen echten Blocker. Eine Frage beantworte, wenn du etwas beitragen kannst. Sende eine nötige Antwort mit dem telegram reply-Tool an chat_id=${meta.chat_id}, ohne dieselbe Annahme zu wiederholen.]` : addressed ? `[REPLY-PFLICHT: Der Absender liest NUR Telegram. Sende deine Antwort mit dem telegram reply-Tool an chat_id=${meta.chat_id}. Sichtbarer Antworttext kann auch automatisch weitergeleitet werden; er ist kein privater Denkbereich.]` : inferAddressing ? `[SEMANTISCHE ADRESSIERUNG: Diese menschliche Gruppennachricht nennt dich nicht explizit. Entscheide anhand von Inhalt und Gesprächskontext, ob sie an dich gerichtet ist oder Arbeit von dir verlangt. Falls ja, antworte kurz mit dem telegram reply-Tool an chat_id=${meta.chat_id}. Falls nein, beende den Turn ohne Ausgabe; sende keine Bestätigung deiner Stille.]` : "[NUR MITLESEN — diese Nachricht ist NICHT an dich gerichtet (kein @King, keine Antwort an dich, sie spricht jemand anderen oder die Gruppe allgemein an). ANTWORTE NICHT: kein reply-Tool, keine Bestätigung, kein \"verstanden\", kein \"halte mich raus\" — beende den Turn einfach ohne Ausgabe. NUR wenn du ein echtes, konkretes Ergebnis oder einen echten Blocker beitragen kannst, antwortest du kurz. Im Zweifel: schweigen.]"}`;
60201
+ return `<channel ${pairs.map(([key, value]) => `${key}="${escapeAttr(value)}"`).join(" ")}>\n${content}\n</channel>\n${addressed && meta.chat_id.startsWith("-") ? `[GRUPPENANTWORT: Eine bloße Information oder eine Nachricht, die ausdrücklich keine Bestätigung verlangt, macht keine Antwort erforderlich. Beende den Turn dann ohne Ausgabe und ohne Bestätigung deiner Stille. Antwortet jemand auf deine eigene Statusfrage ohne neuen Auftrag, schweige. Fragt dich jemand nach dem Stand, antworte kurz und ehrlich. Falls ein Abschlusstext nötig ist, gib nur [[BLUN_TELEGRAM_SILENT]] aus. Einen konkreten Auftrag an dich nimm kurz an und beginne die Arbeit; melde danach das Ergebnis oder einen echten Blocker. Eine Frage beantworte, wenn du etwas beitragen kannst. Sende eine nötige Antwort mit dem telegram reply-Tool an chat_id=${meta.chat_id}, ohne dieselbe Annahme zu wiederholen.]` : addressed ? `[REPLY-PFLICHT: Der Absender liest NUR Telegram. Sende deine Antwort mit dem telegram reply-Tool an chat_id=${meta.chat_id}. Sichtbarer Antworttext kann auch automatisch weitergeleitet werden; er ist kein privater Denkbereich.]` : inferAddressing ? `[SEMANTISCHE ADRESSIERUNG: Diese menschliche Gruppennachricht nennt dich nicht explizit. Entscheide anhand von Inhalt und Gesprächskontext, ob sie an dich gerichtet ist oder Arbeit von dir verlangt. Falls ja, antworte kurz mit dem telegram reply-Tool an chat_id=${meta.chat_id}. Falls nein, beende den Turn ohne Ausgabe; sende keine Bestätigung deiner Stille.]` : "[NUR MITLESEN — diese Nachricht ist NICHT an dich gerichtet (kein @King, keine Antwort an dich, sie spricht jemand anderen oder die Gruppe allgemein an). ANTWORTE NICHT: kein reply-Tool, keine Bestätigung, kein \"verstanden\", kein \"halte mich raus\" — beende den Turn einfach ohne Ausgabe. NUR wenn du ein echtes, konkretes Ergebnis oder einen echten Blocker beitragen kannst, antwortest du kurz. Im Zweifel: schweigen.]"}`;
60201
60202
  }
60202
60203
  /**
60203
60204
  * Sent once as the head of the first prompt of every bridged session —
@@ -67697,9 +67698,12 @@ var RequestError = class RequestError extends Error {
67697
67698
  function isGroupChat(chatId) {
67698
67699
  return chatId.startsWith("-");
67699
67700
  }
67700
- const SILENT_TURN_MARKER = "[[BLUN_TELEGRAM_SILENT]]";
67701
- function isSilentTurnMarker(text) {
67702
- return text.trim() === SILENT_TURN_MARKER;
67701
+ function stripTrailingSilentTurnMarker(text) {
67702
+ const trimmed = text.trimEnd();
67703
+ if (!trimmed.endsWith("[[BLUN_TELEGRAM_SILENT]]")) return text;
67704
+ const prefix = trimmed.slice(0, -24);
67705
+ if (prefix.length > 0 && !/\s$/u.test(prefix)) return text;
67706
+ return prefix.trimEnd();
67703
67707
  }
67704
67708
  function normalize(s) {
67705
67709
  return s.toLowerCase().replace(/ä/g, "a").replace(/ö/g, "o").replace(/ü/g, "u").replace(/ß/g, "ss").replace(/[‐-―]/g, " ").replace(/[^\p{L}\p{N}\s]/gu, " ").replace(/\s+/g, " ").trim();
@@ -67709,6 +67713,7 @@ const EMPTY_CLAUSES = [
67709
67713
  /^(bin da|bin hier|hier bin ich|ich bin da|was brauchst du|was gibt es|was liegt an|wie kann ich helfen|bereit|ich warte|warte auf|melde mich|freut mich|here|standing by|ready|awaiting|what do you need)$/,
67710
67714
  /^(keine (gruppen ?)?antwort|gar keine antwort|keine reaktion|kein wort|no ?action|no (response|reply|answer|comment))( (notig|erforderlich|needed|required))?$/,
67711
67715
  /^(ich )?(halte mich raus|(bin|bleibe) (jetzt )?still|schweige|habe nichts zu melden)$/,
67716
+ /^ich best(?:a|ae|ä)tige nichts und beende still$/u,
67712
67717
  /^(stille|still|silence|nichts (zu tun|zu melden|fur mich|fuer mich)|nicht meine lane)$/,
67713
67718
  /^(kein handlungsbedarf|es gibt keinen handlungsbedarf|hier besteht kein handlungsbedarf)( (fur|fuer) mich)?$/,
67714
67719
  /^(i (am |will be )?)?(staying (silent|out|quiet)|keeping (quiet|out)|holding back)$/,
@@ -67737,6 +67742,40 @@ function isGroupNoiseReply(text) {
67737
67742
  return text.split(/[.!?;,\r\n]+|\s+[-\u2013\u2014]\s+/u).map(normalize).filter(Boolean).every((clause) => EMPTY_CLAUSES.some((pattern) => pattern.test(clause)) || NOTIFICATION_CONTEXT.test(clause));
67738
67743
  }
67739
67744
  //#endregion
67745
+ //#region src/outbox-reader.ts
67746
+ /** Read a byte suffix from one outbox descriptor, never the preceding history. */
67747
+ function readOutboxSuffix(start) {
67748
+ if (typeof start === "number" && (!Number.isSafeInteger(start) || start < 0)) return void 0;
67749
+ let fd;
67750
+ try {
67751
+ fd = openSync(outboxLog(), "r");
67752
+ const stat = fstatSync(fd);
67753
+ if (!stat.isFile()) return void 0;
67754
+ const end = stat.size;
67755
+ const offset = typeof start === "number" ? start : start(end);
67756
+ if (!Number.isSafeInteger(offset) || offset < 0) return void 0;
67757
+ if (offset >= end) return "";
67758
+ const buffer = Buffer.allocUnsafe(Math.min(64 * 1024, end - offset));
67759
+ const decoder = new StringDecoder("utf8");
67760
+ const chunks = [];
67761
+ let position = offset;
67762
+ while (position < end) {
67763
+ const count = readSync(fd, buffer, 0, Math.min(buffer.length, end - position), position);
67764
+ if (count === 0) break;
67765
+ position += count;
67766
+ chunks.push(decoder.write(buffer.subarray(0, count)));
67767
+ }
67768
+ chunks.push(decoder.end());
67769
+ return chunks.join("");
67770
+ } catch {
67771
+ return;
67772
+ } finally {
67773
+ if (fd !== void 0) try {
67774
+ closeSync(fd);
67775
+ } catch {}
67776
+ }
67777
+ }
67778
+ //#endregion
67740
67779
  //#region src/acp-session.ts
67741
67780
  /**
67742
67781
  * The King side of the bridge: spawns `<node> <main.mjs> acp`, drives it as
@@ -67830,8 +67869,8 @@ function outboxMarker() {
67830
67869
  /** True when the reply tool appended an outbox entry for this chat since `marker`. */
67831
67870
  function outboxGrewForChat(marker, chatId, threadId) {
67832
67871
  try {
67833
- if (statSync(outboxLog()).size <= marker) return false;
67834
- const fresh = readFileSync(outboxLog(), "utf8").slice(marker);
67872
+ const fresh = readOutboxSuffix(marker);
67873
+ if (fresh === void 0 || fresh.length === 0) return false;
67835
67874
  for (const line of fresh.split(/\r?\n/)) {
67836
67875
  if (line.trim().length === 0) continue;
67837
67876
  try {
@@ -67857,7 +67896,8 @@ function scrubSecrets(text) {
67857
67896
  * log it to outbox.jsonl as kind "reply-fallback". Returns true on success.
67858
67897
  */
67859
67898
  async function sendReplyFallback(chatId, rawText, threadId) {
67860
- if (rawText.trim().length === 0 || isSilentTurnMarker(rawText)) return true;
67899
+ const cleanText = stripTrailingSilentTurnMarker(rawText);
67900
+ if (cleanText.trim().length === 0) return true;
67861
67901
  let topic;
67862
67902
  try {
67863
67903
  topic = threadId === void 0 ? {} : { message_thread_id: telegramThreadId(threadId) };
@@ -67866,11 +67906,11 @@ async function sendReplyFallback(chatId, rawText, threadId) {
67866
67906
  }
67867
67907
  const token = botToken();
67868
67908
  if (token === void 0) return false;
67869
- if (isGroupChat(chatId) && isGroupNoiseReply(rawText)) {
67909
+ if (isGroupChat(chatId) && isGroupNoiseReply(cleanText)) {
67870
67910
  logLine(`reply-fallback suppressed (group meta-noise): ${rawText.slice(0, 60)}`);
67871
67911
  return true;
67872
67912
  }
67873
- const text = scrubSecrets(rawText);
67913
+ const text = scrubSecrets(cleanText);
67874
67914
  const body = text.length > TELEGRAM_TEXT_LIMIT ? `${text.slice(0, TELEGRAM_TEXT_LIMIT - 1)}…` : text;
67875
67915
  try {
67876
67916
  const payload = await (await fetch(`https://api.telegram.org/bot${token}/sendMessage`, {
@@ -1,9 +1,10 @@
1
1
  import { createRequire } from "node:module";
2
2
  import process$1 from "node:process";
3
- import { appendFileSync, chmodSync, mkdirSync, readFileSync, realpathSync, renameSync, statSync, writeFileSync } from "node:fs";
3
+ import { appendFileSync, chmodSync, closeSync, fstatSync, mkdirSync, openSync, readFileSync, readSync, realpathSync, renameSync, statSync, writeFileSync } from "node:fs";
4
4
  import { extname, join, sep } from "node:path";
5
5
  import { homedir } from "node:os";
6
6
  import { createHash, createHmac, timingSafeEqual } from "node:crypto";
7
+ import { StringDecoder } from "node:string_decoder";
7
8
  //#region \0rolldown/runtime.js
8
9
  var __create = Object.create;
9
10
  var __defProp = Object.defineProperty;
@@ -55633,10 +55634,10 @@ var require_internal = /* @__PURE__ */ __commonJSMin(((exports, module) => {
55633
55634
  }
55634
55635
  InternalCodec.prototype.encoder = InternalEncoder;
55635
55636
  InternalCodec.prototype.decoder = InternalDecoder;
55636
- var StringDecoder = __require("string_decoder").StringDecoder;
55637
- if (!StringDecoder.prototype.end) StringDecoder.prototype.end = function() {};
55637
+ var StringDecoder$1 = __require("string_decoder").StringDecoder;
55638
+ if (!StringDecoder$1.prototype.end) StringDecoder$1.prototype.end = function() {};
55638
55639
  function InternalDecoder(options, codec) {
55639
- this.decoder = new StringDecoder(codec.enc);
55640
+ this.decoder = new StringDecoder$1(codec.enc);
55640
55641
  }
55641
55642
  InternalDecoder.prototype.write = function(buf) {
55642
55643
  if (!Buffer.isBuffer(buf)) buf = Buffer.from(buf);
@@ -73469,6 +73470,13 @@ const SILENT_TURN_MARKER = "[[BLUN_TELEGRAM_SILENT]]";
73469
73470
  function isSilentTurnMarker(text) {
73470
73471
  return text.trim() === SILENT_TURN_MARKER;
73471
73472
  }
73473
+ function stripTrailingSilentTurnMarker(text) {
73474
+ const trimmed = text.trimEnd();
73475
+ if (!trimmed.endsWith("[[BLUN_TELEGRAM_SILENT]]")) return text;
73476
+ const prefix = trimmed.slice(0, -24);
73477
+ if (prefix.length > 0 && !/\s$/u.test(prefix)) return text;
73478
+ return prefix.trimEnd();
73479
+ }
73472
73480
  function normalize(s) {
73473
73481
  return s.toLowerCase().replace(/ä/g, "a").replace(/ö/g, "o").replace(/ü/g, "u").replace(/ß/g, "ss").replace(/[‐-―]/g, " ").replace(/[^\p{L}\p{N}\s]/gu, " ").replace(/\s+/g, " ").trim();
73474
73482
  }
@@ -73477,6 +73485,7 @@ const EMPTY_CLAUSES = [
73477
73485
  /^(bin da|bin hier|hier bin ich|ich bin da|was brauchst du|was gibt es|was liegt an|wie kann ich helfen|bereit|ich warte|warte auf|melde mich|freut mich|here|standing by|ready|awaiting|what do you need)$/,
73478
73486
  /^(keine (gruppen ?)?antwort|gar keine antwort|keine reaktion|kein wort|no ?action|no (response|reply|answer|comment))( (notig|erforderlich|needed|required))?$/,
73479
73487
  /^(ich )?(halte mich raus|(bin|bleibe) (jetzt )?still|schweige|habe nichts zu melden)$/,
73488
+ /^ich best(?:a|ae|ä)tige nichts und beende still$/u,
73480
73489
  /^(stille|still|silence|nichts (zu tun|zu melden|fur mich|fuer mich)|nicht meine lane)$/,
73481
73490
  /^(kein handlungsbedarf|es gibt keinen handlungsbedarf|hier besteht kein handlungsbedarf)( (fur|fuer) mich)?$/,
73482
73491
  /^(i (am |will be )?)?(staying (silent|out|quiet)|keeping (quiet|out)|holding back)$/,
@@ -73505,6 +73514,40 @@ function isGroupNoiseReply(text) {
73505
73514
  return text.split(/[.!?;,\r\n]+|\s+[-\u2013\u2014]\s+/u).map(normalize).filter(Boolean).every((clause) => EMPTY_CLAUSES.some((pattern) => pattern.test(clause)) || NOTIFICATION_CONTEXT.test(clause));
73506
73515
  }
73507
73516
  //#endregion
73517
+ //#region src/outbox-reader.ts
73518
+ /** Read a byte suffix from one outbox descriptor, never the preceding history. */
73519
+ function readOutboxSuffix(start) {
73520
+ if (typeof start === "number" && (!Number.isSafeInteger(start) || start < 0)) return void 0;
73521
+ let fd;
73522
+ try {
73523
+ fd = openSync(outboxLog(), "r");
73524
+ const stat = fstatSync(fd);
73525
+ if (!stat.isFile()) return void 0;
73526
+ const end = stat.size;
73527
+ const offset = typeof start === "number" ? start : start(end);
73528
+ if (!Number.isSafeInteger(offset) || offset < 0) return void 0;
73529
+ if (offset >= end) return "";
73530
+ const buffer = Buffer.allocUnsafe(Math.min(64 * 1024, end - offset));
73531
+ const decoder = new StringDecoder("utf8");
73532
+ const chunks = [];
73533
+ let position = offset;
73534
+ while (position < end) {
73535
+ const count = readSync(fd, buffer, 0, Math.min(buffer.length, end - position), position);
73536
+ if (count === 0) break;
73537
+ position += count;
73538
+ chunks.push(decoder.write(buffer.subarray(0, count)));
73539
+ }
73540
+ chunks.push(decoder.end());
73541
+ return chunks.join("");
73542
+ } catch {
73543
+ return;
73544
+ } finally {
73545
+ if (fd !== void 0) try {
73546
+ closeSync(fd);
73547
+ } catch {}
73548
+ }
73549
+ }
73550
+ //#endregion
73508
73551
  //#region src/inbound.ts
73509
73552
  function telegramThreadId(value) {
73510
73553
  if (value === void 0) return void 0;
@@ -73625,7 +73668,7 @@ async function runTool(api, token, name, args) {
73625
73668
  case "download_attachment": return runDownload(api, token, args["file_id"]);
73626
73669
  case "edit_message": {
73627
73670
  const chatId = resolveAllowedChatId(args["chat_id"]);
73628
- if (isSilentTurnMarker(args["text"])) throw new Error("silent marker cannot be used to edit a Telegram message");
73671
+ if (stripTrailingSilentTurnMarker(args["text"]) !== args["text"] || isSilentTurnMarker(args["text"])) throw new Error("silent marker cannot be used to edit a Telegram message");
73629
73672
  assertOptionalTelegramLanguageRelease(args["text"], args["release_token"]);
73630
73673
  const parseMode = args["format"] === "markdownv2" ? "MarkdownV2" : void 0;
73631
73674
  const edited = await api.editMessageText(chatId, Number(args["message_id"]), args["text"], parseMode !== void 0 ? { parse_mode: parseMode } : void 0);
@@ -73650,13 +73693,10 @@ async function runTool(api, token, name, args) {
73650
73693
  * nicht weiter retryt. Datei-Anhänge sind nie betroffen.
73651
73694
  */
73652
73695
  const DUPLICATE_WINDOW_MS = 3e5;
73653
- const OUTBOX_TAIL_BYTES = 32768;
73696
+ const OUTBOX_TAIL_CHARACTERS = 32768;
73654
73697
  function isRecentDuplicate(chatId, text, threadId, replyTo) {
73655
73698
  try {
73656
- const file = outboxLog();
73657
- const size = statSync(file).size;
73658
- const raw = readFileSync(file, "utf8");
73659
- const tail = size > OUTBOX_TAIL_BYTES ? raw.slice(raw.length - OUTBOX_TAIL_BYTES) : raw;
73699
+ const tail = readOutboxSuffix((size) => Math.max(0, size - 4 * OUTBOX_TAIL_CHARACTERS))?.slice(-OUTBOX_TAIL_CHARACTERS) ?? "";
73660
73700
  const wanted = text.trim();
73661
73701
  const cutoff = Date.now() - DUPLICATE_WINDOW_MS;
73662
73702
  for (const line of tail.split("\n")) {
@@ -73685,6 +73725,10 @@ async function runReply(api, args) {
73685
73725
  if (files.length > 0) throw new Error("silent marker cannot accompany files");
73686
73726
  return "suppressed: silent marker - not sent";
73687
73727
  }
73728
+ if (stripTrailingSilentTurnMarker(text) !== text) {
73729
+ if (files.length === 0 && isGroupChat(chatId) && isGroupNoiseReply(stripTrailingSilentTurnMarker(text))) return "suppressed: group meta/acknowledgement noise - not sent";
73730
+ throw new Error("silent marker cannot accompany Telegram reply text or files; resend the reply without it");
73731
+ }
73688
73732
  if (files.length === 0 && isGroupChat(chatId) && isGroupNoiseReply(text)) {
73689
73733
  appendJsonl(outboxLog(), {
73690
73734
  direction: "out",
@@ -13,6 +13,13 @@ const SILENT_TURN_MARKER = "[[BLUN_TELEGRAM_SILENT]]";
13
13
  function isSilentTurnMarker(text) {
14
14
  return text.trim() === SILENT_TURN_MARKER;
15
15
  }
16
+ function stripTrailingSilentTurnMarker(text) {
17
+ const trimmed = text.trimEnd();
18
+ if (!trimmed.endsWith("[[BLUN_TELEGRAM_SILENT]]")) return text;
19
+ const prefix = trimmed.slice(0, -24);
20
+ if (prefix.length > 0 && !/\s$/u.test(prefix)) return text;
21
+ return prefix.trimEnd();
22
+ }
16
23
  function normalize(s) {
17
24
  return s.toLowerCase().replace(/ä/g, "a").replace(/ö/g, "o").replace(/ü/g, "u").replace(/ß/g, "ss").replace(/[‐-―]/g, " ").replace(/[^\p{L}\p{N}\s]/gu, " ").replace(/\s+/g, " ").trim();
18
25
  }
@@ -21,6 +28,7 @@ const EMPTY_CLAUSES = [
21
28
  /^(bin da|bin hier|hier bin ich|ich bin da|was brauchst du|was gibt es|was liegt an|wie kann ich helfen|bereit|ich warte|warte auf|melde mich|freut mich|here|standing by|ready|awaiting|what do you need)$/,
22
29
  /^(keine (gruppen ?)?antwort|gar keine antwort|keine reaktion|kein wort|no ?action|no (response|reply|answer|comment))( (notig|erforderlich|needed|required))?$/,
23
30
  /^(ich )?(halte mich raus|(bin|bleibe) (jetzt )?still|schweige|habe nichts zu melden)$/,
31
+ /^ich best(?:a|ae|ä)tige nichts und beende still$/u,
24
32
  /^(stille|still|silence|nichts (zu tun|zu melden|fur mich|fuer mich)|nicht meine lane)$/,
25
33
  /^(kein handlungsbedarf|es gibt keinen handlungsbedarf|hier besteht kein handlungsbedarf)( (fur|fuer) mich)?$/,
26
34
  /^(i (am |will be )?)?(staying (silent|out|quiet)|keeping (quiet|out)|holding back)$/,
@@ -49,4 +57,4 @@ function isGroupNoiseReply(text) {
49
57
  return text.split(/[.!?;,\r\n]+|\s+[-\u2013\u2014]\s+/u).map(normalize).filter(Boolean).every((clause) => EMPTY_CLAUSES.some((pattern) => pattern.test(clause)) || NOTIFICATION_CONTEXT.test(clause));
50
58
  }
51
59
  //#endregion
52
- export { SILENT_TURN_MARKER, isGroupChat, isGroupNoiseReply, isSilentTurnMarker };
60
+ export { SILENT_TURN_MARKER, isGroupChat, isGroupNoiseReply, isSilentTurnMarker, stripTrailingSilentTurnMarker };