blun-king-cli 9.1.614 → 9.1.616
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/CHANGELOG.md +15 -0
- package/LIESMICH.txt +1 -1
- package/README.md +1 -1
- package/bin/managed-plugin-selection.cjs +15 -8
- package/bin/plugin-bootstrap.js +40 -17
- package/bin/private-paths.js +10 -2
- package/bin/profile-launch.cjs +84 -6
- package/blun.mjs +2602 -1338
- package/codebase-index/codebase_index.py +25 -17
- package/package.json +1 -1
- package/telegram-plugin/dist/bridge.mjs +54 -213
- package/telegram-plugin/dist/mcp-server.mjs +62 -14
- package/telegram-plugin/dist/noise.mjs +9 -1
- package/worker-host.mjs +1883 -695
|
@@ -7,7 +7,7 @@ Auflagen Dieter 43102:
|
|
|
7
7
|
(Run-3-Befund: Liste trieb RAM-Spitze auf 10,5 GB).
|
|
8
8
|
- Inkrementell: Content-Hash je Datei im Manifest, Delta statt Vollindex.
|
|
9
9
|
- Query mit Frische-Auskunft + Selbstauskunft in JEDER Antwort.
|
|
10
|
-
- Query-
|
|
10
|
+
- Lokales Query-Metadatenlog (query-log.jsonl), ohne Fragetext.
|
|
11
11
|
|
|
12
12
|
Aufruf:
|
|
13
13
|
python codebase_index.py build <repo>
|
|
@@ -351,13 +351,29 @@ def cmd_update(repo: str) -> None:
|
|
|
351
351
|
)
|
|
352
352
|
|
|
353
353
|
|
|
354
|
-
def freshness(repo: str, man: dict) -> str:
|
|
355
|
-
current = git_head(repo)
|
|
356
|
-
same = "== HEAD" if current == man["head"] else f"Index {man['head']} != HEAD {current} — 'update' faellig"
|
|
357
|
-
return f"Index-Stand: {man['head']} ({man['built_at']}), {man['chunks']} Chunks | {same}"
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
def
|
|
354
|
+
def freshness(repo: str, man: dict) -> str:
|
|
355
|
+
current = git_head(repo)
|
|
356
|
+
same = "== HEAD" if current == man["head"] else f"Index {man['head']} != HEAD {current} — 'update' faellig"
|
|
357
|
+
return f"Index-Stand: {man['head']} ({man['built_at']}), {man['chunks']} Chunks | {same}"
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def append_query_metadata(dirpath: str, question: str, hits: list[tuple[str, float]],
|
|
361
|
+
requested_top: int, latency_ms: float, man: dict) -> None:
|
|
362
|
+
record = {
|
|
363
|
+
"ts": datetime.now(timezone.utc).isoformat(),
|
|
364
|
+
"query_bytes": len(question.encode("utf-8")),
|
|
365
|
+
"requested_top": requested_top,
|
|
366
|
+
"result_count": len(hits),
|
|
367
|
+
"result_paths": list(dict.fromkeys(meta.rsplit("#", 1)[0] for meta, _ in hits)),
|
|
368
|
+
"query_ms": round(latency_ms, 1),
|
|
369
|
+
"index_head": man["head"],
|
|
370
|
+
"model": man["model"],
|
|
371
|
+
}
|
|
372
|
+
with open(os.path.join(dirpath, "query-log.jsonl"), "a", encoding="utf-8") as fh:
|
|
373
|
+
fh.write(json.dumps(record) + "\n")
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def cmd_query(repo: str, question: str, top: int, selector: str = "auto") -> int:
|
|
361
377
|
dirpath, man = resolve_existing_index(repo, selector)
|
|
362
378
|
activate_model(man["model"], man["dim"])
|
|
363
379
|
mat = np.load(os.path.join(dirpath, "vectors.npy"))
|
|
@@ -380,15 +396,7 @@ def cmd_query(repo: str, question: str, top: int, selector: str = "auto") -> int
|
|
|
380
396
|
print(f" {s:.4f} {m}")
|
|
381
397
|
print(f"query_ms={latency_ms:.1f}")
|
|
382
398
|
|
|
383
|
-
|
|
384
|
-
with open(log_path, "a", encoding="utf-8") as fh:
|
|
385
|
-
fh.write(json.dumps({
|
|
386
|
-
"ts": datetime.now(timezone.utc).isoformat(),
|
|
387
|
-
"query": question,
|
|
388
|
-
"top": hits,
|
|
389
|
-
"query_ms": round(latency_ms, 1),
|
|
390
|
-
"index_head": man["head"],
|
|
391
|
-
}) + "\n")
|
|
399
|
+
append_query_metadata(dirpath, question, hits, top, latency_ms, man)
|
|
392
400
|
return 0
|
|
393
401
|
|
|
394
402
|
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
import { appendFileSync, chmodSync, closeSync, existsSync, fstatSync, lstatSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { dirname, join, resolve } from "node:path";
|
|
4
|
-
import { createHash,
|
|
4
|
+
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
5
5
|
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.
|
|
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 —
|
|
@@ -67686,204 +67687,6 @@ var RequestError = class RequestError extends Error {
|
|
|
67686
67687
|
}
|
|
67687
67688
|
};
|
|
67688
67689
|
//#endregion
|
|
67689
|
-
//#region src/language-release.ts
|
|
67690
|
-
const EXACT_LANGUAGE_TAG = /^(?:[A-Za-z]{2,8}|x)(?:-[A-Za-z0-9]{1,8})*$/;
|
|
67691
|
-
const LETTER = /^\p{Letter}$/u;
|
|
67692
|
-
const LATIN = /^\p{Script=Latin}$/u;
|
|
67693
|
-
const CYRILLIC = /^\p{Script=Cyrillic}$/u;
|
|
67694
|
-
const GREEK = /^\p{Script=Greek}$/u;
|
|
67695
|
-
const ARABIC = /^\p{Script=Arabic}$/u;
|
|
67696
|
-
const HEBREW = /^\p{Script=Hebrew}$/u;
|
|
67697
|
-
const HAN = /^\p{Script=Han}$/u;
|
|
67698
|
-
const HIRAGANA = /^\p{Script=Hiragana}$/u;
|
|
67699
|
-
const KATAKANA = /^\p{Script=Katakana}$/u;
|
|
67700
|
-
const HANGUL = /^\p{Script=Hangul}$/u;
|
|
67701
|
-
const LATIN_LANGUAGES = new Set([
|
|
67702
|
-
"af",
|
|
67703
|
-
"bs",
|
|
67704
|
-
"ca",
|
|
67705
|
-
"cs",
|
|
67706
|
-
"cy",
|
|
67707
|
-
"da",
|
|
67708
|
-
"de",
|
|
67709
|
-
"en",
|
|
67710
|
-
"es",
|
|
67711
|
-
"et",
|
|
67712
|
-
"eu",
|
|
67713
|
-
"fi",
|
|
67714
|
-
"fr",
|
|
67715
|
-
"ga",
|
|
67716
|
-
"gd",
|
|
67717
|
-
"gl",
|
|
67718
|
-
"hr",
|
|
67719
|
-
"hu",
|
|
67720
|
-
"id",
|
|
67721
|
-
"is",
|
|
67722
|
-
"it",
|
|
67723
|
-
"la",
|
|
67724
|
-
"lb",
|
|
67725
|
-
"lt",
|
|
67726
|
-
"lv",
|
|
67727
|
-
"ms",
|
|
67728
|
-
"mt",
|
|
67729
|
-
"nl",
|
|
67730
|
-
"no",
|
|
67731
|
-
"pl",
|
|
67732
|
-
"pt",
|
|
67733
|
-
"ro",
|
|
67734
|
-
"sk",
|
|
67735
|
-
"sl",
|
|
67736
|
-
"sq",
|
|
67737
|
-
"sv",
|
|
67738
|
-
"sw",
|
|
67739
|
-
"tr",
|
|
67740
|
-
"vi"
|
|
67741
|
-
]);
|
|
67742
|
-
function canonicalText(text) {
|
|
67743
|
-
return text.replace(/^\uFEFF/, "").replace(/\r\n?/g, "\n").normalize("NFC");
|
|
67744
|
-
}
|
|
67745
|
-
function canonicalHash(text) {
|
|
67746
|
-
return createHash("sha256").update(canonicalText(text), "utf8").digest("hex");
|
|
67747
|
-
}
|
|
67748
|
-
function maskTechnicalText(text) {
|
|
67749
|
-
const mask = (value) => " ".repeat(value.length);
|
|
67750
|
-
return text.replace(/```[\s\S]*?```|~~~[\s\S]*?~~~/g, mask).replace(/`[^`\r\n]*`/g, mask);
|
|
67751
|
-
}
|
|
67752
|
-
function allowedScriptMatchers(locale) {
|
|
67753
|
-
if (!EXACT_LANGUAGE_TAG.test(locale) || ["auto", "all"].includes(locale.toLowerCase())) throw new Error("language release blocked: exact output locale required");
|
|
67754
|
-
const parts = locale.replaceAll("_", "-").split("-");
|
|
67755
|
-
const base = parts[0]?.toLowerCase() ?? "";
|
|
67756
|
-
const explicitScript = parts.find((part) => part.length === 4)?.toLowerCase();
|
|
67757
|
-
if (explicitScript !== void 0) {
|
|
67758
|
-
const explicit = {
|
|
67759
|
-
latn: [LATIN],
|
|
67760
|
-
cyrl: [CYRILLIC],
|
|
67761
|
-
grek: [GREEK],
|
|
67762
|
-
arab: [ARABIC],
|
|
67763
|
-
hebr: [HEBREW],
|
|
67764
|
-
hani: [HAN],
|
|
67765
|
-
jpan: [
|
|
67766
|
-
HAN,
|
|
67767
|
-
HIRAGANA,
|
|
67768
|
-
KATAKANA
|
|
67769
|
-
],
|
|
67770
|
-
hang: [HANGUL, HAN]
|
|
67771
|
-
}[explicitScript];
|
|
67772
|
-
if (explicit === void 0) throw new Error("language release blocked: unsupported output script");
|
|
67773
|
-
return explicit;
|
|
67774
|
-
}
|
|
67775
|
-
if (LATIN_LANGUAGES.has(base)) return [LATIN];
|
|
67776
|
-
if ([
|
|
67777
|
-
"ru",
|
|
67778
|
-
"uk",
|
|
67779
|
-
"bg",
|
|
67780
|
-
"mk"
|
|
67781
|
-
].includes(base)) return [CYRILLIC];
|
|
67782
|
-
if (base === "el") return [GREEK];
|
|
67783
|
-
if ([
|
|
67784
|
-
"ar",
|
|
67785
|
-
"fa",
|
|
67786
|
-
"ur"
|
|
67787
|
-
].includes(base)) return [ARABIC];
|
|
67788
|
-
if (base === "he") return [HEBREW];
|
|
67789
|
-
if (base === "zh") return [HAN];
|
|
67790
|
-
if (base === "ja") return [
|
|
67791
|
-
HAN,
|
|
67792
|
-
HIRAGANA,
|
|
67793
|
-
KATAKANA
|
|
67794
|
-
];
|
|
67795
|
-
if (base === "ko") return [HANGUL, HAN];
|
|
67796
|
-
throw new Error("language release blocked: output locale has no deterministic script policy");
|
|
67797
|
-
}
|
|
67798
|
-
function assertExpectedScript(text, locale) {
|
|
67799
|
-
const allowed = allowedScriptMatchers(locale);
|
|
67800
|
-
for (const character of maskTechnicalText(text)) {
|
|
67801
|
-
if (!LETTER.test(character)) continue;
|
|
67802
|
-
if (allowed.some((matcher) => matcher.test(character))) continue;
|
|
67803
|
-
const point = character.codePointAt(0)?.toString(16).toUpperCase().padStart(4, "0") ?? "UNKNOWN";
|
|
67804
|
-
throw new Error(`language release blocked: unexpected-script-contamination (U+${point})`);
|
|
67805
|
-
}
|
|
67806
|
-
}
|
|
67807
|
-
function parseAndVerifySignature(token, key) {
|
|
67808
|
-
if (key.length < 32) throw new Error("language release blocked: invalid verification key");
|
|
67809
|
-
if (token.length > 16384) throw new Error("language release blocked: release token too large");
|
|
67810
|
-
const parts = token.split(".");
|
|
67811
|
-
if (parts.length !== 3 || parts[0] !== "blg6" || parts[1] === "" || parts[2] === "") throw new Error("language release blocked: malformed release token");
|
|
67812
|
-
const encoded = parts[1];
|
|
67813
|
-
let supplied;
|
|
67814
|
-
let payload;
|
|
67815
|
-
try {
|
|
67816
|
-
supplied = Buffer.from(parts[2], "base64url");
|
|
67817
|
-
payload = JSON.parse(Buffer.from(encoded, "base64url").toString("utf8"));
|
|
67818
|
-
} catch {
|
|
67819
|
-
throw new Error("language release blocked: malformed release token");
|
|
67820
|
-
}
|
|
67821
|
-
const expected = createHmac("sha256", key).update(encoded).digest();
|
|
67822
|
-
if (supplied.length !== expected.length || !timingSafeEqual(supplied, expected)) throw new Error("language release blocked: invalid release token signature");
|
|
67823
|
-
if (payload === null || typeof payload !== "object" || Array.isArray(payload)) throw new Error("language release blocked: malformed release payload");
|
|
67824
|
-
return payload;
|
|
67825
|
-
}
|
|
67826
|
-
function assertEqual(actual, expected, field) {
|
|
67827
|
-
if (actual !== expected) throw new Error(`language release blocked: ${field} mismatch`);
|
|
67828
|
-
}
|
|
67829
|
-
function verifyTelegramLanguageRelease(text, releaseToken, options) {
|
|
67830
|
-
if (typeof releaseToken !== "string" || releaseToken.length === 0) throw new Error("language release blocked: release token required");
|
|
67831
|
-
if (options.expectedVersion.trim().length === 0) throw new Error("language release blocked: expected guard version required");
|
|
67832
|
-
assertExpectedScript(text, options.locale);
|
|
67833
|
-
const payload = parseAndVerifySignature(releaseToken, options.key);
|
|
67834
|
-
const now = Math.floor(options.now ?? Date.now() / 1e3);
|
|
67835
|
-
if (!Number.isSafeInteger(payload.iat) || !Number.isSafeInteger(payload.exp)) throw new Error("language release blocked: invalid receipt lifetime");
|
|
67836
|
-
const issuedAt = payload.iat;
|
|
67837
|
-
const expiresAt = payload.exp;
|
|
67838
|
-
if (expiresAt < now) throw new Error("language release blocked: release token expired");
|
|
67839
|
-
if (issuedAt > now + 60 || expiresAt < issuedAt || expiresAt - issuedAt > 604800) throw new Error("language release blocked: invalid receipt lifetime");
|
|
67840
|
-
if (typeof payload.nonce !== "string" || payload.nonce.length === 0) throw new Error("language release blocked: invalid receipt nonce");
|
|
67841
|
-
assertEqual(payload.source_sha256, canonicalHash(""), "source");
|
|
67842
|
-
assertEqual(payload.target_sha256, canonicalHash(text), "target");
|
|
67843
|
-
if (payload.language !== options.locale) throw new Error(`language release blocked: language mismatch (expected ${options.locale})`);
|
|
67844
|
-
assertEqual(payload.purpose, "response", "purpose");
|
|
67845
|
-
assertEqual(payload.content_type, "prose", "content type");
|
|
67846
|
-
assertEqual(payload.short_text_reviewed, false, "short text review");
|
|
67847
|
-
assertEqual(payload.v, options.expectedVersion, "version");
|
|
67848
|
-
}
|
|
67849
|
-
function isLanguageReleaseRequired() {
|
|
67850
|
-
return process.env["BLUN_TELEGRAM_LANGUAGE_GATE"]?.trim().toLowerCase() === "required";
|
|
67851
|
-
}
|
|
67852
|
-
function requiredSetting(name) {
|
|
67853
|
-
const value = process.env[name]?.trim();
|
|
67854
|
-
if (value === void 0 || value.length === 0) throw new Error(`language release blocked: host setting ${name} required`);
|
|
67855
|
-
return value;
|
|
67856
|
-
}
|
|
67857
|
-
function telegramLanguageReleaseLocale() {
|
|
67858
|
-
if (!isLanguageReleaseRequired()) return void 0;
|
|
67859
|
-
const locale = requiredSetting("BLUN_TELEGRAM_OUTPUT_LOCALE");
|
|
67860
|
-
allowedScriptMatchers(locale);
|
|
67861
|
-
return locale;
|
|
67862
|
-
}
|
|
67863
|
-
function defaultKeyFile() {
|
|
67864
|
-
const configuredHome = process.env["BLUN_HOME"]?.trim();
|
|
67865
|
-
return join(configuredHome !== void 0 && configuredHome.length > 0 ? configuredHome : join(homedir(), ".blun"), "secrets", "blun-language-guard-signing.key");
|
|
67866
|
-
}
|
|
67867
|
-
function assertTelegramLanguageReleased(text, releaseToken, now) {
|
|
67868
|
-
if (!isLanguageReleaseRequired()) return;
|
|
67869
|
-
if (typeof releaseToken !== "string" || releaseToken.length === 0) throw new Error("language release blocked: release token required");
|
|
67870
|
-
const locale = telegramLanguageReleaseLocale();
|
|
67871
|
-
if (locale === void 0) throw new Error("language release blocked: exact output locale required");
|
|
67872
|
-
const keyFile = process.env["BLUN_LANGUAGE_GUARD_KEY_FILE"]?.trim() || defaultKeyFile();
|
|
67873
|
-
let key;
|
|
67874
|
-
try {
|
|
67875
|
-
key = readFileSync(keyFile);
|
|
67876
|
-
} catch {
|
|
67877
|
-
throw new Error("language release blocked: verification key unavailable");
|
|
67878
|
-
}
|
|
67879
|
-
verifyTelegramLanguageRelease(text, releaseToken, {
|
|
67880
|
-
locale,
|
|
67881
|
-
expectedVersion: requiredSetting("BLUN_LANGUAGE_GUARD_VERSION"),
|
|
67882
|
-
key,
|
|
67883
|
-
now
|
|
67884
|
-
});
|
|
67885
|
-
}
|
|
67886
|
-
//#endregion
|
|
67887
67690
|
//#region src/noise.ts
|
|
67888
67691
|
/**
|
|
67889
67692
|
* Conservative outbound guard for group acknowledgements and narrated silence.
|
|
@@ -67895,9 +67698,12 @@ function assertTelegramLanguageReleased(text, releaseToken, now) {
|
|
|
67895
67698
|
function isGroupChat(chatId) {
|
|
67896
67699
|
return chatId.startsWith("-");
|
|
67897
67700
|
}
|
|
67898
|
-
|
|
67899
|
-
|
|
67900
|
-
|
|
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();
|
|
67901
67707
|
}
|
|
67902
67708
|
function normalize(s) {
|
|
67903
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();
|
|
@@ -67907,6 +67713,7 @@ const EMPTY_CLAUSES = [
|
|
|
67907
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)$/,
|
|
67908
67714
|
/^(keine (gruppen ?)?antwort|gar keine antwort|keine reaktion|kein wort|no ?action|no (response|reply|answer|comment))( (notig|erforderlich|needed|required))?$/,
|
|
67909
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,
|
|
67910
67717
|
/^(stille|still|silence|nichts (zu tun|zu melden|fur mich|fuer mich)|nicht meine lane)$/,
|
|
67911
67718
|
/^(kein handlungsbedarf|es gibt keinen handlungsbedarf|hier besteht kein handlungsbedarf)( (fur|fuer) mich)?$/,
|
|
67912
67719
|
/^(i (am |will be )?)?(staying (silent|out|quiet)|keeping (quiet|out)|holding back)$/,
|
|
@@ -67935,6 +67742,40 @@ function isGroupNoiseReply(text) {
|
|
|
67935
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));
|
|
67936
67743
|
}
|
|
67937
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
|
|
67938
67779
|
//#region src/acp-session.ts
|
|
67939
67780
|
/**
|
|
67940
67781
|
* The King side of the bridge: spawns `<node> <main.mjs> acp`, drives it as
|
|
@@ -68028,8 +67869,8 @@ function outboxMarker() {
|
|
|
68028
67869
|
/** True when the reply tool appended an outbox entry for this chat since `marker`. */
|
|
68029
67870
|
function outboxGrewForChat(marker, chatId, threadId) {
|
|
68030
67871
|
try {
|
|
68031
|
-
|
|
68032
|
-
|
|
67872
|
+
const fresh = readOutboxSuffix(marker);
|
|
67873
|
+
if (fresh === void 0 || fresh.length === 0) return false;
|
|
68033
67874
|
for (const line of fresh.split(/\r?\n/)) {
|
|
68034
67875
|
if (line.trim().length === 0) continue;
|
|
68035
67876
|
try {
|
|
@@ -68055,7 +67896,8 @@ function scrubSecrets(text) {
|
|
|
68055
67896
|
* log it to outbox.jsonl as kind "reply-fallback". Returns true on success.
|
|
68056
67897
|
*/
|
|
68057
67898
|
async function sendReplyFallback(chatId, rawText, threadId) {
|
|
68058
|
-
|
|
67899
|
+
const cleanText = stripTrailingSilentTurnMarker(rawText);
|
|
67900
|
+
if (cleanText.trim().length === 0) return true;
|
|
68059
67901
|
let topic;
|
|
68060
67902
|
try {
|
|
68061
67903
|
topic = threadId === void 0 ? {} : { message_thread_id: telegramThreadId(threadId) };
|
|
@@ -68064,14 +67906,13 @@ async function sendReplyFallback(chatId, rawText, threadId) {
|
|
|
68064
67906
|
}
|
|
68065
67907
|
const token = botToken();
|
|
68066
67908
|
if (token === void 0) return false;
|
|
68067
|
-
if (isGroupChat(chatId) && isGroupNoiseReply(
|
|
67909
|
+
if (isGroupChat(chatId) && isGroupNoiseReply(cleanText)) {
|
|
68068
67910
|
logLine(`reply-fallback suppressed (group meta-noise): ${rawText.slice(0, 60)}`);
|
|
68069
67911
|
return true;
|
|
68070
67912
|
}
|
|
68071
|
-
const text = scrubSecrets(
|
|
67913
|
+
const text = scrubSecrets(cleanText);
|
|
68072
67914
|
const body = text.length > TELEGRAM_TEXT_LIMIT ? `${text.slice(0, TELEGRAM_TEXT_LIMIT - 1)}…` : text;
|
|
68073
67915
|
try {
|
|
68074
|
-
assertTelegramLanguageReleased(body, void 0);
|
|
68075
67916
|
const payload = await (await fetch(`https://api.telegram.org/bot${token}/sendMessage`, {
|
|
68076
67917
|
method: "POST",
|
|
68077
67918
|
headers: { "Content-Type": "application/json" },
|
|
@@ -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);
|
|
@@ -73449,6 +73450,10 @@ function assertTelegramLanguageReleased(text, releaseToken, now) {
|
|
|
73449
73450
|
now
|
|
73450
73451
|
});
|
|
73451
73452
|
}
|
|
73453
|
+
function assertOptionalTelegramLanguageRelease(text, releaseToken, now) {
|
|
73454
|
+
if (releaseToken === void 0) return;
|
|
73455
|
+
assertTelegramLanguageReleased(text, releaseToken, now);
|
|
73456
|
+
}
|
|
73452
73457
|
//#endregion
|
|
73453
73458
|
//#region src/noise.ts
|
|
73454
73459
|
/**
|
|
@@ -73465,6 +73470,13 @@ const SILENT_TURN_MARKER = "[[BLUN_TELEGRAM_SILENT]]";
|
|
|
73465
73470
|
function isSilentTurnMarker(text) {
|
|
73466
73471
|
return text.trim() === SILENT_TURN_MARKER;
|
|
73467
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
|
+
}
|
|
73468
73480
|
function normalize(s) {
|
|
73469
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();
|
|
73470
73482
|
}
|
|
@@ -73473,6 +73485,7 @@ const EMPTY_CLAUSES = [
|
|
|
73473
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)$/,
|
|
73474
73486
|
/^(keine (gruppen ?)?antwort|gar keine antwort|keine reaktion|kein wort|no ?action|no (response|reply|answer|comment))( (notig|erforderlich|needed|required))?$/,
|
|
73475
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,
|
|
73476
73489
|
/^(stille|still|silence|nichts (zu tun|zu melden|fur mich|fuer mich)|nicht meine lane)$/,
|
|
73477
73490
|
/^(kein handlungsbedarf|es gibt keinen handlungsbedarf|hier besteht kein handlungsbedarf)( (fur|fuer) mich)?$/,
|
|
73478
73491
|
/^(i (am |will be )?)?(staying (silent|out|quiet)|keeping (quiet|out)|holding back)$/,
|
|
@@ -73501,6 +73514,40 @@ function isGroupNoiseReply(text) {
|
|
|
73501
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));
|
|
73502
73515
|
}
|
|
73503
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
|
|
73504
73551
|
//#region src/inbound.ts
|
|
73505
73552
|
function telegramThreadId(value) {
|
|
73506
73553
|
if (value === void 0) return void 0;
|
|
@@ -73528,7 +73575,7 @@ const TOOL_DEFINITIONS = [
|
|
|
73528
73575
|
text: { type: "string" },
|
|
73529
73576
|
release_token: {
|
|
73530
73577
|
type: "string",
|
|
73531
|
-
description: "Language Guard receipt for
|
|
73578
|
+
description: "Optional Language Guard receipt for released text. When provided, it must match this exact text."
|
|
73532
73579
|
},
|
|
73533
73580
|
reply_to: {
|
|
73534
73581
|
type: "string",
|
|
@@ -73592,7 +73639,7 @@ const TOOL_DEFINITIONS = [
|
|
|
73592
73639
|
text: { type: "string" },
|
|
73593
73640
|
release_token: {
|
|
73594
73641
|
type: "string",
|
|
73595
|
-
description: "Language Guard receipt for
|
|
73642
|
+
description: "Optional Language Guard receipt for released text. When provided, it must match this exact text."
|
|
73596
73643
|
},
|
|
73597
73644
|
format: {
|
|
73598
73645
|
type: "string",
|
|
@@ -73621,8 +73668,8 @@ async function runTool(api, token, name, args) {
|
|
|
73621
73668
|
case "download_attachment": return runDownload(api, token, args["file_id"]);
|
|
73622
73669
|
case "edit_message": {
|
|
73623
73670
|
const chatId = resolveAllowedChatId(args["chat_id"]);
|
|
73624
|
-
if (isSilentTurnMarker(args["text"])) throw new Error("silent marker cannot be used to edit a Telegram message");
|
|
73625
|
-
|
|
73671
|
+
if (stripTrailingSilentTurnMarker(args["text"]) !== args["text"] || isSilentTurnMarker(args["text"])) throw new Error("silent marker cannot be used to edit a Telegram message");
|
|
73672
|
+
assertOptionalTelegramLanguageRelease(args["text"], args["release_token"]);
|
|
73626
73673
|
const parseMode = args["format"] === "markdownv2" ? "MarkdownV2" : void 0;
|
|
73627
73674
|
const edited = await api.editMessageText(chatId, Number(args["message_id"]), args["text"], parseMode !== void 0 ? { parse_mode: parseMode } : void 0);
|
|
73628
73675
|
const id = typeof edited === "object" ? edited.message_id : args["message_id"];
|
|
@@ -73646,13 +73693,10 @@ async function runTool(api, token, name, args) {
|
|
|
73646
73693
|
* nicht weiter retryt. Datei-Anhänge sind nie betroffen.
|
|
73647
73694
|
*/
|
|
73648
73695
|
const DUPLICATE_WINDOW_MS = 3e5;
|
|
73649
|
-
const
|
|
73696
|
+
const OUTBOX_TAIL_CHARACTERS = 32768;
|
|
73650
73697
|
function isRecentDuplicate(chatId, text, threadId, replyTo) {
|
|
73651
73698
|
try {
|
|
73652
|
-
const
|
|
73653
|
-
const size = statSync(file).size;
|
|
73654
|
-
const raw = readFileSync(file, "utf8");
|
|
73655
|
-
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) ?? "";
|
|
73656
73700
|
const wanted = text.trim();
|
|
73657
73701
|
const cutoff = Date.now() - DUPLICATE_WINDOW_MS;
|
|
73658
73702
|
for (const line of tail.split("\n")) {
|
|
@@ -73681,6 +73725,10 @@ async function runReply(api, args) {
|
|
|
73681
73725
|
if (files.length > 0) throw new Error("silent marker cannot accompany files");
|
|
73682
73726
|
return "suppressed: silent marker - not sent";
|
|
73683
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
|
+
}
|
|
73684
73732
|
if (files.length === 0 && isGroupChat(chatId) && isGroupNoiseReply(text)) {
|
|
73685
73733
|
appendJsonl(outboxLog(), {
|
|
73686
73734
|
direction: "out",
|
|
@@ -73701,7 +73749,7 @@ async function runReply(api, args) {
|
|
|
73701
73749
|
});
|
|
73702
73750
|
return "already delivered: an identical message was sent to this chat moments ago — do NOT send it again";
|
|
73703
73751
|
}
|
|
73704
|
-
|
|
73752
|
+
assertOptionalTelegramLanguageRelease(text, args["release_token"]);
|
|
73705
73753
|
for (const file of files) {
|
|
73706
73754
|
assertSendable(file);
|
|
73707
73755
|
const info = statSync(file);
|
|
@@ -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 };
|