blun-king-cli 9.1.609 → 9.1.611

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.609",
3
+ "version": "9.1.611",
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": {
@@ -17,6 +17,7 @@
17
17
  },
18
18
  "os": [
19
19
  "darwin",
20
+ "linux",
20
21
  "win32"
21
22
  ],
22
23
  "cpu": [
@@ -59412,6 +59412,9 @@ function isGermanGroupBroadcast(text) {
59412
59412
  }
59413
59413
  return false;
59414
59414
  }
59415
+ function hasAllMention(text) {
59416
+ return /(?:^|[^\p{L}\p{N}_@])@all(?=$|[^\p{L}\p{N}_])/iu.test(text);
59417
+ }
59415
59418
  /**
59416
59419
  * Bot-to-bot loop guard (Telegram's own safety requirement for Bot API 10.0
59417
59420
  * bot-to-bot mode): once King can receive other bots' messages, an unthrottled
@@ -59479,9 +59482,10 @@ function gate(ctx, botUsername) {
59479
59482
  if (groupAllowFrom.length > 0 && !groupAllowFrom.includes(senderId)) return { action: "drop" };
59480
59483
  const addressesAnother = matchesAny(ctx.text, access.ignorePatterns);
59481
59484
  const mentioned = !addressesAnother && isMentioned(ctx, botUsername, access.mentionPatterns);
59482
- if (policy.requireMention === true && !mentioned) return { action: "drop" };
59485
+ const broadcastMention = from.isBot !== true && hasAllMention(ctx.text);
59486
+ if (policy.requireMention && !mentioned && !broadcastMention) return { action: "drop" };
59483
59487
  const mentionBypass = from.isBot !== true && (policy.alwaysAllowFrom ?? []).includes(senderId);
59484
- const addressed = !addressesAnother && (mentionBypass || mentioned || isGermanGroupBroadcast(ctx.text));
59488
+ const addressed = broadcastMention || !addressesAnother && (mentionBypass || mentioned || isGermanGroupBroadcast(ctx.text));
59485
59489
  const inferAddressing = !addressesAnother && !addressed && from.isBot !== true;
59486
59490
  if (!addressed && from.isBot === true && !botLoopGuardAllows(senderId)) return { action: "drop" };
59487
59491
  return {
@@ -60193,7 +60197,7 @@ var BoundedChannelContextBuffer = class {
60193
60197
  function buildChannelTag(content, meta, addressed = true, inferAddressing = false) {
60194
60198
  const pairs = [["source", "telegram"]];
60195
60199
  for (const [key, value] of Object.entries(meta)) if (value !== void 0) pairs.push([key, String(value)]);
60196
- return `<channel ${pairs.map(([key, value]) => `${key}="${escapeAttr(value)}"`).join(" ")}>\n${content}\n</channel>\n${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.]"}`;
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. 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.]"}`;
60197
60201
  }
60198
60202
  /**
60199
60203
  * Sent once as the head of the first prompt of every bridged session —
@@ -60211,6 +60215,8 @@ function buildPreamble() {
60211
60215
  "useful contribution: a relevant finding, error, or improvement. Otherwise finish",
60212
60216
  "with no reply tool call and no assistant text. Do not narrate that decision, cite",
60213
60217
  "a silence rule, acknowledge inactivity, or report that you ended without output.",
60218
+ "If your host requires assistant text to finish a silent turn, output exactly",
60219
+ "[[BLUN_TELEGRAM_SILENT]] and nothing else. Never send this marker with reply.",
60214
60220
  "When you do have something useful to contribute, send it once without repeating it.",
60215
60221
  "",
60216
60222
  "Messages arrive wrapped as <channel source=\"telegram\" chat_id=\"...\" message_id=\"...\"",
@@ -67887,6 +67893,10 @@ function assertTelegramLanguageReleased(text, releaseToken, now) {
67887
67893
  function isGroupChat(chatId) {
67888
67894
  return chatId.startsWith("-");
67889
67895
  }
67896
+ const SILENT_TURN_MARKER = "[[BLUN_TELEGRAM_SILENT]]";
67897
+ function isSilentTurnMarker(text) {
67898
+ return text.trim() === SILENT_TURN_MARKER;
67899
+ }
67890
67900
  function normalize(s) {
67891
67901
  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();
67892
67902
  }
@@ -67896,6 +67906,7 @@ const EMPTY_CLAUSES = [
67896
67906
  /^(keine (gruppen ?)?antwort|gar keine antwort|keine reaktion|kein wort|no ?action|no (response|reply|answer|comment))( (notig|erforderlich|needed|required))?$/,
67897
67907
  /^(ich )?(halte mich raus|(bin|bleibe) (jetzt )?still|schweige|habe nichts zu melden)$/,
67898
67908
  /^(stille|still|silence|nichts (zu tun|zu melden|fur mich|fuer mich)|nicht meine lane)$/,
67909
+ /^(kein handlungsbedarf|es gibt keinen handlungsbedarf|hier besteht kein handlungsbedarf)( (fur|fuer) mich)?$/,
67899
67910
  /^(i (am |will be )?)?(staying (silent|out|quiet)|keeping (quiet|out)|holding back)$/,
67900
67911
  /^(i have )?nothing (to (add|do|report)|for me)$/,
67901
67912
  /^no need to (respond|reply)$/,
@@ -67909,11 +67920,16 @@ const EMPTY_CLAUSES = [
67909
67920
  /^(nach|gemaess) der leerlauf regel( \d+)? gibt es nichts zu melden$/
67910
67921
  ];
67911
67922
  const NOTIFICATION_CONTEXT = /^die(se)? nachricht ist eine (konfigurations )?information( [\p{L}\p{N} _-]{1,64} wird ab jetzt nur noch bei erwahnung benachrichtigt)?( an die gruppe gerichtet)?$/u;
67923
+ function isNarratedSilentTurn(text) {
67924
+ const words = normalize(text);
67925
+ return /^(kein neuer auftrag|kein handlungsbedarf|keine neue anweisung keine frage an mich nichts zu berichten)\b/u.test(words) && /\b(nichts weiter zu tun|stille|stille ohne jede ausgabe)$/u.test(words);
67926
+ }
67912
67927
  /**
67913
67928
  * True only when all clauses are acknowledgements or silence commentary.
67914
67929
  * One substantive or unrecognised clause preserves the entire message.
67915
67930
  */
67916
67931
  function isGroupNoiseReply(text) {
67932
+ if (isNarratedSilentTurn(text)) return true;
67917
67933
  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));
67918
67934
  }
67919
67935
  //#endregion
@@ -68037,6 +68053,7 @@ function scrubSecrets(text) {
68037
68053
  * log it to outbox.jsonl as kind "reply-fallback". Returns true on success.
68038
68054
  */
68039
68055
  async function sendReplyFallback(chatId, rawText, threadId) {
68056
+ if (rawText.trim().length === 0 || isSilentTurnMarker(rawText)) return true;
68040
68057
  let topic;
68041
68058
  try {
68042
68059
  topic = threadId === void 0 ? {} : { message_thread_id: telegramThreadId(threadId) };
@@ -73461,6 +73461,10 @@ function assertTelegramLanguageReleased(text, releaseToken, now) {
73461
73461
  function isGroupChat(chatId) {
73462
73462
  return chatId.startsWith("-");
73463
73463
  }
73464
+ const SILENT_TURN_MARKER = "[[BLUN_TELEGRAM_SILENT]]";
73465
+ function isSilentTurnMarker(text) {
73466
+ return text.trim() === SILENT_TURN_MARKER;
73467
+ }
73464
73468
  function normalize(s) {
73465
73469
  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();
73466
73470
  }
@@ -73470,6 +73474,7 @@ const EMPTY_CLAUSES = [
73470
73474
  /^(keine (gruppen ?)?antwort|gar keine antwort|keine reaktion|kein wort|no ?action|no (response|reply|answer|comment))( (notig|erforderlich|needed|required))?$/,
73471
73475
  /^(ich )?(halte mich raus|(bin|bleibe) (jetzt )?still|schweige|habe nichts zu melden)$/,
73472
73476
  /^(stille|still|silence|nichts (zu tun|zu melden|fur mich|fuer mich)|nicht meine lane)$/,
73477
+ /^(kein handlungsbedarf|es gibt keinen handlungsbedarf|hier besteht kein handlungsbedarf)( (fur|fuer) mich)?$/,
73473
73478
  /^(i (am |will be )?)?(staying (silent|out|quiet)|keeping (quiet|out)|holding back)$/,
73474
73479
  /^(i have )?nothing (to (add|do|report)|for me)$/,
73475
73480
  /^no need to (respond|reply)$/,
@@ -73483,11 +73488,16 @@ const EMPTY_CLAUSES = [
73483
73488
  /^(nach|gemaess) der leerlauf regel( \d+)? gibt es nichts zu melden$/
73484
73489
  ];
73485
73490
  const NOTIFICATION_CONTEXT = /^die(se)? nachricht ist eine (konfigurations )?information( [\p{L}\p{N} _-]{1,64} wird ab jetzt nur noch bei erwahnung benachrichtigt)?( an die gruppe gerichtet)?$/u;
73491
+ function isNarratedSilentTurn(text) {
73492
+ const words = normalize(text);
73493
+ return /^(kein neuer auftrag|kein handlungsbedarf|keine neue anweisung keine frage an mich nichts zu berichten)\b/u.test(words) && /\b(nichts weiter zu tun|stille|stille ohne jede ausgabe)$/u.test(words);
73494
+ }
73486
73495
  /**
73487
73496
  * True only when all clauses are acknowledgements or silence commentary.
73488
73497
  * One substantive or unrecognised clause preserves the entire message.
73489
73498
  */
73490
73499
  function isGroupNoiseReply(text) {
73500
+ if (isNarratedSilentTurn(text)) return true;
73491
73501
  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));
73492
73502
  }
73493
73503
  //#endregion
@@ -73611,6 +73621,7 @@ async function runTool(api, token, name, args) {
73611
73621
  case "download_attachment": return runDownload(api, token, args["file_id"]);
73612
73622
  case "edit_message": {
73613
73623
  const chatId = resolveAllowedChatId(args["chat_id"]);
73624
+ if (isSilentTurnMarker(args["text"])) throw new Error("silent marker cannot be used to edit a Telegram message");
73614
73625
  assertTelegramLanguageReleased(args["text"], args["release_token"]);
73615
73626
  const parseMode = args["format"] === "markdownv2" ? "MarkdownV2" : void 0;
73616
73627
  const edited = await api.editMessageText(chatId, Number(args["message_id"]), args["text"], parseMode !== void 0 ? { parse_mode: parseMode } : void 0);
@@ -73666,6 +73677,10 @@ async function runReply(api, args) {
73666
73677
  const replyTo = args["reply_to"] !== null && args["reply_to"] !== void 0 ? Number(args["reply_to"]) : void 0;
73667
73678
  const files = args["files"] ?? [];
73668
73679
  const parseMode = args["format"] === "markdownv2" ? "MarkdownV2" : void 0;
73680
+ if (isSilentTurnMarker(text)) {
73681
+ if (files.length > 0) throw new Error("silent marker cannot accompany files");
73682
+ return "suppressed: silent marker - not sent";
73683
+ }
73669
73684
  if (files.length === 0 && isGroupChat(chatId) && isGroupNoiseReply(text)) {
73670
73685
  appendJsonl(outboxLog(), {
73671
73686
  direction: "out",
@@ -9,6 +9,10 @@
9
9
  function isGroupChat(chatId) {
10
10
  return chatId.startsWith("-");
11
11
  }
12
+ const SILENT_TURN_MARKER = "[[BLUN_TELEGRAM_SILENT]]";
13
+ function isSilentTurnMarker(text) {
14
+ return text.trim() === SILENT_TURN_MARKER;
15
+ }
12
16
  function normalize(s) {
13
17
  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();
14
18
  }
@@ -18,6 +22,7 @@ const EMPTY_CLAUSES = [
18
22
  /^(keine (gruppen ?)?antwort|gar keine antwort|keine reaktion|kein wort|no ?action|no (response|reply|answer|comment))( (notig|erforderlich|needed|required))?$/,
19
23
  /^(ich )?(halte mich raus|(bin|bleibe) (jetzt )?still|schweige|habe nichts zu melden)$/,
20
24
  /^(stille|still|silence|nichts (zu tun|zu melden|fur mich|fuer mich)|nicht meine lane)$/,
25
+ /^(kein handlungsbedarf|es gibt keinen handlungsbedarf|hier besteht kein handlungsbedarf)( (fur|fuer) mich)?$/,
21
26
  /^(i (am |will be )?)?(staying (silent|out|quiet)|keeping (quiet|out)|holding back)$/,
22
27
  /^(i have )?nothing (to (add|do|report)|for me)$/,
23
28
  /^no need to (respond|reply)$/,
@@ -31,12 +36,17 @@ const EMPTY_CLAUSES = [
31
36
  /^(nach|gemaess) der leerlauf regel( \d+)? gibt es nichts zu melden$/
32
37
  ];
33
38
  const NOTIFICATION_CONTEXT = /^die(se)? nachricht ist eine (konfigurations )?information( [\p{L}\p{N} _-]{1,64} wird ab jetzt nur noch bei erwahnung benachrichtigt)?( an die gruppe gerichtet)?$/u;
39
+ function isNarratedSilentTurn(text) {
40
+ const words = normalize(text);
41
+ return /^(kein neuer auftrag|kein handlungsbedarf|keine neue anweisung keine frage an mich nichts zu berichten)\b/u.test(words) && /\b(nichts weiter zu tun|stille|stille ohne jede ausgabe)$/u.test(words);
42
+ }
34
43
  /**
35
44
  * True only when all clauses are acknowledgements or silence commentary.
36
45
  * One substantive or unrecognised clause preserves the entire message.
37
46
  */
38
47
  function isGroupNoiseReply(text) {
48
+ if (isNarratedSilentTurn(text)) return true;
39
49
  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));
40
50
  }
41
51
  //#endregion
42
- export { isGroupChat, isGroupNoiseReply };
52
+ export { SILENT_TURN_MARKER, isGroupChat, isGroupNoiseReply, isSilentTurnMarker };
package/worker-host.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- // BLUN_BUILD_INPUT_SHA256:5d42292f090cacd4506d9194f177756c6d8acc509f938ecc413c752522d368c2
2
+ // BLUN_BUILD_INPUT_SHA256:0533ca113dc07cc119988be5e76555dec986b6881903f8e2b537f5adf96cc2c5
3
3
  import { fileURLToPath as __cjsShimFileURLToPath } from 'node:url';
4
4
  import { dirname as __cjsShimDirname } from 'node:path';
5
5
  const __filename = __cjsShimFileURLToPath(import.meta.url);
@@ -230115,6 +230115,7 @@ function buildCompactionSummaryText(summary) {
230115
230115
  const MAX_SESSION_COMPACTION_FAILURES = 2;
230116
230116
  const DEFAULT_COMPACTION_MAX_COMPLETION_TOKENS = 128 * 1024;
230117
230117
  const COMPACTION_SUMMARY_RESERVE_RATIO = .1;
230118
+ const BLUN_COMPACTION_REQUEST_BUDGET_BYTES = 75e4;
230118
230119
  const MAX_HIERARCHICAL_COMPACTION_PASSES = 64;
230119
230120
  const HIERARCHICAL_COMPACTION_PREFIX = "This is a complete summary of an earlier chronological segment. Preserve it as source material when merging it with the following conversation:";
230120
230121
  var CompactionTruncatedError = class extends Error {
@@ -230604,6 +230605,7 @@ var FullCompaction = class {
230604
230605
  let charsReceived = 0;
230605
230606
  let lastProgressEmitAt = 0;
230606
230607
  let initialCompactionRequestTokens;
230608
+ let initialCompactionRequestBytes;
230607
230609
  const compactionTools = [];
230608
230610
  const buildRequestMessages = (history, requestInstruction) => {
230609
230611
  return stripMediaForCompaction(downgradeUnsupportedMedia([...this.agent.context.project(history, {
@@ -230611,6 +230613,7 @@ var FullCompaction = class {
230611
230613
  dropOrphanResults: true
230612
230614
  }), createUserMessage(requestInstruction)], capability));
230613
230615
  };
230616
+ const estimateRequestBytes = (messages) => Buffer.byteLength(JSON.stringify(messages), "utf8") + Buffer.byteLength(this.agent.config.systemPrompt, "utf8");
230614
230617
  while (true) {
230615
230618
  signal.throwIfAborted();
230616
230619
  if (hierarchicalPassCount >= MAX_HIERARCHICAL_COMPACTION_PASSES) throw new BlunError(ErrorCodes.COMPACTION_FAILED, `Compaction stopped after ${String(MAX_HIERARCHICAL_COMPACTION_PASSES)} staged requests without reaching a final summary. The conversation history was not changed.`, { details: {
@@ -230620,30 +230623,39 @@ var FullCompaction = class {
230620
230623
  const compactionRequestLimit = this.getEffectiveMaxContextTokens(model);
230621
230624
  const safeCompactionRequestLimit = compactionRequestLimit > 0 ? Math.max(1, Math.floor(compactionRequestLimit * (1 - COMPACTION_SUMMARY_RESERVE_RATIO))) : compactionRequestLimit;
230622
230625
  let messages = buildRequestMessages(historyForModel, instruction);
230626
+ const requestByteBudget = provider.name === "blun" ? BLUN_COMPACTION_REQUEST_BUDGET_BYTES : void 0;
230623
230627
  let estimatedCompactionRequestTokens = this.estimateRequestTokens(messages, this.agent.effectiveSystemPrompt, compactionTools);
230628
+ let estimatedCompactionRequestBytes = requestByteBudget === void 0 ? void 0 : estimateRequestBytes(messages);
230624
230629
  initialCompactionRequestTokens ??= estimatedCompactionRequestTokens;
230630
+ initialCompactionRequestBytes ??= estimatedCompactionRequestBytes;
230625
230631
  let hierarchicalChunkEnd;
230626
- if (safeCompactionRequestLimit > 0 && estimatedCompactionRequestTokens >= safeCompactionRequestLimit) {
230632
+ const tokenBudgetExceeded = safeCompactionRequestLimit > 0 && estimatedCompactionRequestTokens >= safeCompactionRequestLimit;
230633
+ const byteBudgetExceeded = requestByteBudget !== void 0 && estimatedCompactionRequestBytes !== void 0 && estimatedCompactionRequestBytes >= requestByteBudget;
230634
+ if (tokenBudgetExceeded || byteBudgetExceeded) {
230627
230635
  if (historyForModel.length > 1) {
230628
230636
  const chunkInstruction = `${instruction}\n\nThis request contains only the oldest chronological segment. Summarize every fact needed by a later merge pass. Do not assume later messages are visible.`;
230629
- const chunk = selectHierarchicalCompactionChunk(historyForModel, safeCompactionRequestLimit, safeCompactionRequestLimit, (candidate) => {
230637
+ const chunk = selectHierarchicalCompactionChunk(historyForModel, safeCompactionRequestLimit, safeCompactionRequestLimit, requestByteBudget, (candidate) => {
230630
230638
  const candidateMessages = buildRequestMessages(candidate, chunkInstruction);
230631
230639
  return {
230632
230640
  messages: candidateMessages,
230633
- estimatedTokens: this.estimateRequestTokens(candidateMessages, this.agent.effectiveSystemPrompt, compactionTools)
230641
+ estimatedTokens: this.estimateRequestTokens(candidateMessages, this.agent.effectiveSystemPrompt, compactionTools),
230642
+ estimatedBytes: requestByteBudget === void 0 ? void 0 : estimateRequestBytes(candidateMessages)
230634
230643
  };
230635
230644
  });
230636
230645
  if (chunk !== void 0) {
230637
230646
  hierarchicalChunkEnd = chunk.end;
230638
230647
  messages = chunk.messages;
230639
230648
  estimatedCompactionRequestTokens = chunk.estimatedTokens;
230649
+ estimatedCompactionRequestBytes = chunk.estimatedBytes;
230640
230650
  }
230641
230651
  }
230642
- if (hierarchicalChunkEnd === void 0) throw new BlunError(ErrorCodes.CONTEXT_OVERFLOW, `Compaction stopped before upload: the conversation (${String(tokensBefore)} tokens) could not be divided into a request below the active model window (${String(compactionRequestLimit)} tokens).`, { details: {
230652
+ if (hierarchicalChunkEnd === void 0) throw new BlunError(ErrorCodes.CONTEXT_OVERFLOW, byteBudgetExceeded ? "Compaction stopped before upload: the conversation could not be divided into a request below the active byte limit." : `Compaction stopped before upload: the conversation (${String(tokensBefore)} tokens) could not be divided into a request below the active model window (${String(compactionRequestLimit)} tokens).`, { details: {
230643
230653
  contextBrakeBlocked: true,
230644
230654
  estimatedRequestTokens: estimatedCompactionRequestTokens,
230645
230655
  maxContextTokens: compactionRequestLimit,
230646
230656
  requestBudgetTokens: safeCompactionRequestLimit,
230657
+ estimatedRequestBytes: estimatedCompactionRequestBytes,
230658
+ requestBudgetBytes: requestByteBudget,
230647
230659
  summaryReserveTokens: compactionRequestLimit - safeCompactionRequestLimit,
230648
230660
  contextUnchanged: true
230649
230661
  } });
@@ -230652,7 +230664,7 @@ var FullCompaction = class {
230652
230664
  attemptCount += 1;
230653
230665
  charsReceived = 0;
230654
230666
  const stage = hierarchicalPassCount + 1;
230655
- const estimatedStageCount = estimateCompactionStageCount(initialCompactionRequestTokens, safeCompactionRequestLimit, stage);
230667
+ const estimatedStageCount = estimateCompactionStageCount(initialCompactionRequestTokens, safeCompactionRequestLimit, stage, initialCompactionRequestBytes, requestByteBudget);
230656
230668
  const estimatedProgressPercent = estimateCompactionProgressPercent(stage, estimatedStageCount);
230657
230669
  const windowUsagePercent = estimateCompactionWindowUsagePercent(estimatedCompactionRequestTokens, compactionRequestLimit);
230658
230670
  this.agent.log.info("compaction stage request", {
@@ -230661,6 +230673,8 @@ var FullCompaction = class {
230661
230673
  estimatedStageCount,
230662
230674
  activeContextTokens: this.estimateProjectedRequestTokens(),
230663
230675
  estimatedInputTokens: estimatedCompactionRequestTokens,
230676
+ estimatedInputBytes: estimatedCompactionRequestBytes,
230677
+ requestBudgetBytes: requestByteBudget,
230664
230678
  windowUsagePercent,
230665
230679
  safeInputLimitTokens: safeCompactionRequestLimit,
230666
230680
  maxContextTokens: compactionRequestLimit
@@ -230732,13 +230746,17 @@ var FullCompaction = class {
230732
230746
  if (hierarchicalChunkEnd !== void 0) {
230733
230747
  hierarchicalPassCount += 1;
230734
230748
  const previousTokens = estimateTokensForMessages(historyForModel);
230749
+ const previousBytes = requestByteBudget === void 0 ? void 0 : Buffer.byteLength(JSON.stringify(historyForModel), "utf8");
230735
230750
  const nextHistory = [createUserMessage(`${HIERARCHICAL_COMPACTION_PREFIX}\n${extractedSummary}`), ...historyForModel.slice(hierarchicalChunkEnd)];
230736
230751
  const nextTokens = estimateTokensForMessages(nextHistory);
230737
- if (nextTokens >= previousTokens) throw new BlunError(ErrorCodes.COMPACTION_FAILED, "A staged compaction response did not reduce the pending history. The conversation history was not changed.", { details: {
230752
+ const nextBytes = requestByteBudget === void 0 ? void 0 : Buffer.byteLength(JSON.stringify(nextHistory), "utf8");
230753
+ if (nextTokens >= previousTokens || previousBytes !== void 0 && nextBytes !== void 0 && nextBytes >= previousBytes) throw new BlunError(ErrorCodes.COMPACTION_FAILED, "A staged compaction response did not reduce the pending history. The conversation history was not changed.", { details: {
230738
230754
  contextUnchanged: true,
230739
230755
  hierarchicalPassCount,
230740
230756
  previousTokens,
230741
- nextTokens
230757
+ nextTokens,
230758
+ previousBytes,
230759
+ nextBytes
230742
230760
  } });
230743
230761
  historyForModel = nextHistory;
230744
230762
  retryCount = 0;
@@ -230934,9 +230952,10 @@ function isModelFallbackStatus(error) {
230934
230952
  function compactionTimingKey(provider) {
230935
230953
  return `${provider.name}\u0000${provider.modelName}`;
230936
230954
  }
230937
- function estimateCompactionStageCount(initialInputTokens, safeRequestLimitTokens, currentStage) {
230938
- if (safeRequestLimitTokens <= 0) return currentStage;
230939
- return Math.max(currentStage, Math.ceil(initialInputTokens / safeRequestLimitTokens));
230955
+ function estimateCompactionStageCount(initialInputTokens, safeRequestLimitTokens, currentStage, initialInputBytes, safeRequestLimitBytes) {
230956
+ const tokenStages = safeRequestLimitTokens > 0 ? Math.ceil(initialInputTokens / safeRequestLimitTokens) : 0;
230957
+ const byteStages = initialInputBytes !== void 0 && safeRequestLimitBytes !== void 0 && safeRequestLimitBytes > 0 ? Math.ceil(initialInputBytes / safeRequestLimitBytes) : 0;
230958
+ return Math.max(currentStage, tokenStages, byteStages);
230940
230959
  }
230941
230960
  function estimateCompactionProgressPercent(stage, estimatedStageCount) {
230942
230961
  if (estimatedStageCount <= 0) return 0;
@@ -230952,7 +230971,7 @@ const COMPACTION_OVERFLOW_SHRINK_RATIOS = [
230952
230971
  .5,
230953
230972
  .35
230954
230973
  ];
230955
- function selectHierarchicalCompactionChunk(history, targetRequestTokens, hardRequestLimit, build) {
230974
+ function selectHierarchicalCompactionChunk(history, targetRequestTokens, hardRequestLimit, requestByteBudget, build) {
230956
230975
  const safeEnds = [];
230957
230976
  for (let end = 1; end < history.length; end++) if (history[end]?.role !== "tool") safeEnds.push(end);
230958
230977
  if (safeEnds.length === 0) return void 0;
@@ -230964,7 +230983,7 @@ function selectHierarchicalCompactionChunk(history, targetRequestTokens, hardReq
230964
230983
  const middle = Math.floor((low + high) / 2);
230965
230984
  const end = safeEnds[middle];
230966
230985
  const request = build(history.slice(0, end));
230967
- if (request.estimatedTokens < limit) {
230986
+ if (request.estimatedTokens < limit && (requestByteBudget === void 0 || request.estimatedBytes !== void 0 && request.estimatedBytes < requestByteBudget)) {
230968
230987
  best = {
230969
230988
  end,
230970
230989
  ...request
@@ -236771,8 +236790,14 @@ var PersonalMemoryRecallInjector = class {
236771
236790
  * to read, write, list, or change any personal-memory state.
236772
236791
  */
236773
236792
  filterToolsForTurn(turnId, input, origin, tools) {
236774
- if (this.toolsAllowedForTurn(turnId, input, origin)) return tools.filter((tool) => !isPersonalMemoryRecallToolName(tool.name) && (!isPersonalMemoryToolName(tool.name) || this.isTrustedHostTool(tool.name)));
236775
- return tools.filter((tool) => !isPersonalMemoryToolName(tool.name));
236793
+ const brokerAllowed = this.toolsAllowedForTurn(turnId, input, origin);
236794
+ const localSearchAllowed = origin.kind === "user" && this.agent.type === "main" && !isTelegramGroupInput(input) && this.agent.localWorkHistorySearchEligibility?.() === true;
236795
+ return tools.filter((tool) => {
236796
+ if (!isPersonalMemoryToolName(tool.name)) return true;
236797
+ if (isPersonalMemoryRecallToolName(tool.name)) return false;
236798
+ if (tool.name === "mcp__personal-memory__memory_search" && localSearchAllowed) return true;
236799
+ return brokerAllowed && this.isTrustedHostTool(tool.name);
236800
+ });
236776
236801
  }
236777
236802
  isTrustedHostTool(name) {
236778
236803
  const isHostTool = this.agent.isPersonalMemoryHostTool;
@@ -239295,9 +239320,12 @@ var import_turn_tool_performance_policy = (/* @__PURE__ */ __commonJSMin(((expor
239295
239320
  const name = String(tool?.name || "").trim();
239296
239321
  if (name && !available.has(name)) available.set(name, tool);
239297
239322
  }
239323
+ const usedNames = new Set([...selectedTools, ...available.values()].map((tool) => String(tool?.name ?? "")));
239324
+ let loaderName = DEFERRED_TOOL_LOADER_NAME;
239325
+ for (let suffix = 1; usedNames.has(loaderName); suffix += 1) loaderName = `${DEFERRED_TOOL_LOADER_NAME}_${suffix}`;
239298
239326
  const deferredCount = available.size;
239299
239327
  const loader = {
239300
- name: DEFERRED_TOOL_LOADER_NAME,
239328
+ name: loaderName,
239301
239329
  description: [
239302
239330
  `Search ${deferredCount} deferred ${deferredCount === 1 ? "tool schema" : "tool schemas"} without loading all definitions.`,
239303
239331
  "Use select:leaf_name for one known tool, plain keywords to search, or +word to require that word in the tool name.",
@@ -239317,7 +239345,7 @@ var import_turn_tool_performance_policy = (/* @__PURE__ */ __commonJSMin(((expor
239317
239345
  const query = normalizeDeferredToolQuery(args?.query);
239318
239346
  return {
239319
239347
  description: query ? `Searching tool schemas for ${query}` : "Searching tool schemas",
239320
- approvalRule: DEFERRED_TOOL_LOADER_NAME,
239348
+ approvalRule: loaderName,
239321
239349
  execute: async () => {
239322
239350
  let matches = searchDeferredTools([...available.values()], query);
239323
239351
  let relatedFallback = false;
@@ -239330,7 +239358,7 @@ var import_turn_tool_performance_policy = (/* @__PURE__ */ __commonJSMin(((expor
239330
239358
  relatedFallback = matches.length > 0;
239331
239359
  }
239332
239360
  if (matches.length === 0) {
239333
- const alreadyLoaded = query.toLowerCase().startsWith("select:") ? searchDeferredTools(selectedTools.filter((tool) => tool?.name !== DEFERRED_TOOL_LOADER_NAME), query) : [];
239361
+ const alreadyLoaded = query.toLowerCase().startsWith("select:") ? searchDeferredTools(selectedTools.filter((tool) => tool?.name !== loaderName), query) : [];
239334
239362
  if (alreadyLoaded.length === 1) return {
239335
239363
  isError: false,
239336
239364
  output: `Tool schema already loaded: ${alreadyLoaded[0].name}. Invoke it now.`
@@ -239340,7 +239368,7 @@ var import_turn_tool_performance_policy = (/* @__PURE__ */ __commonJSMin(((expor
239340
239368
  output: [
239341
239369
  `No eligible deferred tool is available for: ${query || "[empty query]"}.`,
239342
239370
  "Use a loaded alternative or report this integration as unavailable.",
239343
- "ToolSearch remains available for a different tool needed later."
239371
+ `${loaderName} remains available for a different tool needed later.`
239344
239372
  ].join(" ")
239345
239373
  };
239346
239374
  }
@@ -250032,6 +250060,9 @@ function logStepTiming(log, turnId, step, response) {
250032
250060
  streamDurationMs: timing.streamDurationMs,
250033
250061
  ...timing.serverDecodeMs !== void 0 ? { serverDecodeMs: timing.serverDecodeMs } : {},
250034
250062
  ...timing.clientConsumeMs !== void 0 ? { clientConsumeMs: timing.clientConsumeMs } : {},
250063
+ inputOtherTokens: response.usage.inputOther,
250064
+ cacheReadTokens: response.usage.inputCacheRead,
250065
+ cacheCreationTokens: response.usage.inputCacheCreation,
250035
250066
  outputTokens: response.usage.output
250036
250067
  });
250037
250068
  }
@@ -266492,7 +266523,7 @@ var HistoricalToolProjection = class {
266492
266523
  for (const { index, message, outputPath, text, reference, shellOutput } of candidates) try {
266493
266524
  context.signal.throwIfAborted();
266494
266525
  if (!await this.canRead(read, outputPath, context)) continue;
266495
- const save = this.pending.then(() => this.persist(outputPath, text, context.signal));
266526
+ const save = this.pending.then(() => this.persist(outputPath, text, context.signal, true));
266496
266527
  this.pending = save.catch(() => void 0);
266497
266528
  await save;
266498
266529
  const stat = await this.agent.kaos.stat(outputPath);
@@ -266584,7 +266615,7 @@ var HistoricalToolProjection = class {
266584
266615
  revision: revision(after)
266585
266616
  });
266586
266617
  }
266587
- async persist(path, text, signal) {
266618
+ async persist(path, text, signal, finalVerificationFollows = false) {
266588
266619
  signal.throwIfAborted();
266589
266620
  await checkedAncestors(dirname(dirname(dirname(path))));
266590
266621
  signal.throwIfAborted();
@@ -266593,7 +266624,7 @@ var HistoricalToolProjection = class {
266593
266624
  await this.ensureDirectory(dirname(path));
266594
266625
  signal.throwIfAborted();
266595
266626
  if (this.files.has(path)) {
266596
- await this.verifyFile(path, text);
266627
+ if (!finalVerificationFollows) await this.verifyFile(path, text);
266597
266628
  return;
266598
266629
  }
266599
266630
  try {
@@ -267062,7 +267093,7 @@ function blunToolsForOrigin(tools, origin) {
267062
267093
  * the ordinary permission pipeline.
267063
267094
  */
267064
267095
  function blunSelectTurnTools(tools, maxContextTokens, loadedToolNames = /* @__PURE__ */ new Set(), requiredToolNames = /* @__PURE__ */ new Set(), selected = []) {
267065
- if (estimateTokensForTools(tools) <= (0, import_turn_tool_performance_policy.toolSchemaBudgetTokens)(maxContextTokens) || tools.some((tool) => tool.name === "ToolSearch")) {
267096
+ if (estimateTokensForTools(tools) <= (0, import_turn_tool_performance_policy.toolSchemaBudgetTokens)(maxContextTokens) || tools.some(import_turn_tool_performance_policy.isDeferredToolLoader)) {
267066
267097
  selected.splice(0, selected.length, ...tools);
267067
267098
  return selected;
267068
267099
  }
@@ -271831,6 +271862,7 @@ var Agent$1 = class {
271831
271862
  }
271832
271863
  personalMemoryRecallEligibility = () => this.type === "main" && this.personalMemoryHostCapabilityEnabled;
271833
271864
  isPersonalMemoryHostTool = (name) => this.personalMemoryHostCapabilityEnabled && this.tools.hasUserTool(name);
271865
+ localWorkHistorySearchEligibility = () => this.type === "main" && this.tools.hasUserTool("mcp__personal-memory__memory_search");
271834
271866
  setPersonalMemoryHostCapability(enabled) {
271835
271867
  this.personalMemoryHostCapabilityEnabled = enabled === true;
271836
271868
  }
@@ -347625,6 +347657,7 @@ var BlunAuthTransitionError = class extends Error {
347625
347657
  this.warnings = warnings;
347626
347658
  }
347627
347659
  };
347660
+ const LEGACY_PROFILE_MODEL = "blun/king";
347628
347661
  var BlunAuthFacade = class {
347629
347662
  options;
347630
347663
  toolkit;
@@ -347651,6 +347684,57 @@ var BlunAuthFacade = class {
347651
347684
  }
347652
347685
  });
347653
347686
  }
347687
+ async restoreMissingProfileModel() {
347688
+ const needsRepair = (config) => config.defaultModel === LEGACY_PROFILE_MODEL && config.models?.[LEGACY_PROFILE_MODEL] === void 0 && config.providers["managed:blun"] === void 0;
347689
+ if (!needsRepair(readConfigFileForUpdate(this.options.configPath))) return false;
347690
+ const runtime = this.resolveRuntimeManagedAuth(BLUN_PROVIDER_NAME$1);
347691
+ if (!(await this.toolkit.status("managed:blun", runtime.oauthRef)).providers.some((provider) => provider.hasToken)) return false;
347692
+ let accessToken;
347693
+ try {
347694
+ accessToken = await this.toolkit.ensureFresh(BLUN_PROVIDER_NAME$1, { oauthRef: runtime.oauthRef });
347695
+ } catch (error) {
347696
+ if (error instanceof OAuthError$1) return false;
347697
+ throw error;
347698
+ }
347699
+ let models;
347700
+ try {
347701
+ models = requireManagedBlunKingCatalog(await fetchManagedBlunCodeModels({
347702
+ accessToken,
347703
+ baseUrl: runtime.baseUrl,
347704
+ headers: this.options.identity === void 0 ? void 0 : createBlunDefaultHeaders({
347705
+ homeDir: this.options.homeDir,
347706
+ ...this.options.identity
347707
+ })
347708
+ }));
347709
+ } catch (error) {
347710
+ if (error instanceof ManagedBlunCodeModelsAuthError) return false;
347711
+ throw error;
347712
+ }
347713
+ const repaired = await mutateConfigFile(this.options.configPath, (config) => {
347714
+ if (!needsRepair(config)) return {
347715
+ config,
347716
+ result: false
347717
+ };
347718
+ const existingServices = config.services;
347719
+ applyManagedBlunCodeConfig(config, {
347720
+ models,
347721
+ baseUrl: runtime.baseUrl,
347722
+ oauthKey: runtime.oauthRef.key,
347723
+ oauthHost: runtime.oauthRef.oauthHost,
347724
+ preserveDefaultModel: true
347725
+ });
347726
+ config.services = {
347727
+ ...config.services,
347728
+ ...existingServices
347729
+ };
347730
+ return {
347731
+ config,
347732
+ result: true
347733
+ };
347734
+ });
347735
+ if (repaired) this.options.onConfigUpdated?.(readConfigFile(this.options.configPath));
347736
+ return repaired;
347737
+ }
347654
347738
  async status(providerName) {
347655
347739
  const auth = this.resolveManagedAuth(providerName);
347656
347740
  const apiKey = nonEmpty(auth.apiKey);
@@ -348683,6 +348767,15 @@ function errorMessage(error) {
348683
348767
  }
348684
348768
  //#endregion
348685
348769
  //#region ../../packages/node-sdk/src/sdk-rpc-client.ts
348770
+ function authHomeForProfile(homeDir) {
348771
+ const sharedHome = process.env["BLUN_SHARED_HOME"]?.trim();
348772
+ const profile = process.env["BLUN_PROFILE"]?.trim();
348773
+ if (!sharedHome || !profile || !/^[a-z0-9][a-z0-9_-]{0,31}$/u.test(profile)) return homeDir;
348774
+ const expected = resolve(join(sharedHome, "profile", profile));
348775
+ const actual = resolve(homeDir);
348776
+ if ((process.platform === "win32" ? expected.toLowerCase() : expected) !== (process.platform === "win32" ? actual.toLowerCase() : actual)) return homeDir;
348777
+ return resolve(sharedHome);
348778
+ }
348686
348779
  var SDKRpcClient = class extends SDKRpcClientBase {
348687
348780
  homeDir;
348688
348781
  configPath;
@@ -348691,6 +348784,7 @@ var SDKRpcClient = class extends SDKRpcClientBase {
348691
348784
  auth;
348692
348785
  core;
348693
348786
  ready;
348787
+ usesDefaultConfigPath;
348694
348788
  constructor(options = {}) {
348695
348789
  super();
348696
348790
  this.identity = options.identity === void 0 ? void 0 : assertBlunHostIdentity(options.identity);
@@ -348699,9 +348793,10 @@ var SDKRpcClient = class extends SDKRpcClientBase {
348699
348793
  homeDir: this.homeDir,
348700
348794
  configPath: options.configPath
348701
348795
  });
348796
+ this.usesDefaultConfigPath = options.configPath === void 0;
348702
348797
  this.telemetry = options.telemetry ?? noopTelemetryClient;
348703
348798
  this.auth = new BlunAuthFacade({
348704
- homeDir: this.homeDir,
348799
+ homeDir: authHomeForProfile(this.homeDir),
348705
348800
  configPath: this.configPath,
348706
348801
  identity: this.identity,
348707
348802
  onRefresh: options.onOAuthRefresh
@@ -348721,6 +348816,7 @@ var SDKRpcClient = class extends SDKRpcClientBase {
348721
348816
  }
348722
348817
  async ensureConfigFile() {
348723
348818
  await ensureConfigFile(this.configPath);
348819
+ if (this.usesDefaultConfigPath && authHomeForProfile(this.homeDir) !== this.homeDir) await this.auth.restoreMissingProfileModel();
348724
348820
  }
348725
348821
  async close() {
348726
348822
  try {