blun-king-cli 9.1.611 → 9.1.612

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.611",
3
+ "version": "9.1.612",
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": {
@@ -60197,7 +60197,7 @@ var BoundedChannelContextBuffer = class {
60197
60197
  function buildChannelTag(content, meta, addressed = true, inferAddressing = false) {
60198
60198
  const pairs = [["source", "telegram"]];
60199
60199
  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. 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.]"}`;
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
60201
  }
60202
60202
  /**
60203
60203
  * Sent once as the head of the first prompt of every bridged session —
@@ -60215,6 +60215,8 @@ function buildPreamble() {
60215
60215
  "useful contribution: a relevant finding, error, or improvement. Otherwise finish",
60216
60216
  "with no reply tool call and no assistant text. Do not narrate that decision, cite",
60217
60217
  "a silence rule, acknowledge inactivity, or report that you ended without output.",
60218
+ "If someone answers your status question with \"all work is done; new tasks will",
60219
+ "arrive later\", the exchange is closed. Do not restate or acknowledge that answer.",
60218
60220
  "If your host requires assistant text to finish a silent turn, output exactly",
60219
60221
  "[[BLUN_TELEGRAM_SILENT]] and nothing else. Never send this marker with reply.",
60220
60222
  "When you do have something useful to contribute, send it once without repeating it.",
package/worker-host.mjs CHANGED
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
- // BLUN_BUILD_INPUT_SHA256:0533ca113dc07cc119988be5e76555dec986b6881903f8e2b537f5adf96cc2c5
2
+ // BLUN_BUILD_INPUT_SHA256:f9f2527561c5e5ec1afc53eef27313667c4d323a444532439e73a66865340f69
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);
6
6
  const __dirname = __cjsShimDirname(__filename);
7
7
  import { createRequire } from "node:module";
8
- import crypto$1, { createHash, randomBytes, randomInt, randomUUID } from "node:crypto";
8
+ import crypto$1, { createHash, createHmac, randomBytes, randomInt, randomUUID } from "node:crypto";
9
9
  import * as fs$12 from "node:fs";
10
10
  import Kt, { appendFileSync, chmodSync, closeSync, constants, createReadStream, createWriteStream, existsSync, fstatSync, fsyncSync, linkSync, lstatSync, mkdirSync, openSync, opendirSync, promises, readFileSync, readSync, readdirSync, realpathSync, renameSync, statSync, unlinkSync, writeFileSync, writeSync } from "node:fs";
11
11
  import * as path$11 from "node:path";
@@ -1888,6 +1888,9 @@ var FetchTransport = class {
1888
1888
  const maxRetries = normalizeMaxRetries(requestOptions?.maxRetries);
1889
1889
  const requestBytes = exactBodyBytes(init.body);
1890
1890
  const requestBreakdown = pathname === "/chat/completions" && requestOptions?.onTransportAttempt !== void 0 && typeof init.body === "string" && requestBytes !== void 0 ? measureSerializedRequest(init.body, requestBytes) : void 0;
1891
+ if (pathname === "/chat/completions" && typeof init.body === "string") try {
1892
+ requestOptions?.onSerializedRequest?.(init.body);
1893
+ } catch {}
1891
1894
  let attempt = 0;
1892
1895
  while (true) {
1893
1896
  const headers = new Headers(init.headers);
@@ -1896,6 +1899,9 @@ var FetchTransport = class {
1896
1899
  const startedAt = performance.now();
1897
1900
  const startedAtMs = Date.now();
1898
1901
  try {
1902
+ try {
1903
+ requestOptions?.onRequestSent?.();
1904
+ } catch {}
1899
1905
  const response = await this.fetch(`${this.baseUrl}${pathname}`, {
1900
1906
  ...init,
1901
1907
  headers
@@ -2648,6 +2654,13 @@ function collectBlunVisionAttachments(messages) {
2648
2654
  }
2649
2655
  return attachments;
2650
2656
  }
2657
+ function estimateBlunRequestExtraBytes(tools, messages) {
2658
+ const attachments = collectBlunVisionAttachments(messages);
2659
+ return {
2660
+ toolsBytes: tools.length === 0 ? 0 : Buffer.byteLength(JSON.stringify({ tools: tools.map(convertTool) }), "utf8"),
2661
+ visionBytes: attachments.length === 0 ? 0 : Buffer.byteLength(JSON.stringify({ visionAttachments: attachments }), "utf8")
2662
+ };
2663
+ }
2651
2664
  function convertMessage(message) {
2652
2665
  let reasoningContent = "";
2653
2666
  const nonThinkParts = [];
@@ -2957,12 +2970,13 @@ var BlunChatProvider = class {
2957
2970
  if (this._stream) createParams["stream_options"] = { include_usage: true };
2958
2971
  try {
2959
2972
  const client = this._createClient(options?.auth);
2960
- const requestOptions = options?.signal !== void 0 || options?.transportMaxRetries !== void 0 || options?.onTransportAttempt !== void 0 ? {
2973
+ const requestOptions = options?.signal !== void 0 || options?.transportMaxRetries !== void 0 || options?.onRequestSent !== void 0 || options?.onSerializedRequest !== void 0 || options?.onTransportAttempt !== void 0 ? {
2961
2974
  ...options.signal !== void 0 ? { signal: options.signal } : {},
2962
2975
  ...options.transportMaxRetries !== void 0 ? { maxRetries: options.transportMaxRetries } : {},
2976
+ ...options.onRequestSent !== void 0 ? { onRequestSent: options.onRequestSent } : {},
2977
+ ...options.onSerializedRequest !== void 0 ? { onSerializedRequest: options.onSerializedRequest } : {},
2963
2978
  ...options.onTransportAttempt !== void 0 ? { onTransportAttempt: options.onTransportAttempt } : {}
2964
2979
  } : void 0;
2965
- options?.onRequestSent?.();
2966
2980
  let response;
2967
2981
  try {
2968
2982
  response = await client.chat.completions.create(createParams, requestOptions);
@@ -2971,7 +2985,6 @@ var BlunChatProvider = class {
2971
2985
  const cap = rejection instanceof APIContextOverflowError ? contextOverflowOutputCap(rejection, createParams["max_completion_tokens"]) : void 0;
2972
2986
  if (cap === void 0) throw rejection;
2973
2987
  options?.signal?.throwIfAborted();
2974
- options?.onRequestSent?.();
2975
2988
  response = await client.chat.completions.create({
2976
2989
  ...createParams,
2977
2990
  max_completion_tokens: cap
@@ -21757,10 +21770,15 @@ async function loadAgentsMdForRoots(kaos, brandHome, workDirs) {
21757
21770
  const discovered = [];
21758
21771
  const emptySources = /* @__PURE__ */ new Map();
21759
21772
  const seen = /* @__PURE__ */ new Set();
21773
+ const notLoadedCandidates = /* @__PURE__ */ new Set();
21760
21774
  const collect = async (path) => {
21761
21775
  const file = await readAgentFile(kaos, path);
21762
- if (file === void 0) return false;
21776
+ if (file === void 0) {
21777
+ notLoadedCandidates.add(kaos.normpath(path));
21778
+ return false;
21779
+ }
21763
21780
  const key = kaos.normpath(file.path);
21781
+ notLoadedCandidates.delete(key);
21764
21782
  if (file.content.length === 0) {
21765
21783
  emptySources.set(key, InstructionText.file(file.path, "", file.sourceTextBytes));
21766
21784
  return false;
@@ -21796,7 +21814,10 @@ async function loadAgentsMdForRoots(kaos, brandHome, workDirs) {
21796
21814
  return {
21797
21815
  content,
21798
21816
  warning: totalBytes > AGENTS_MD_RECOMMENDED_MAX_BYTES ? `AGENTS.md total ${formatKB(totalBytes)} KB exceeds the recommended ${formatKB(AGENTS_MD_RECOMMENDED_MAX_BYTES)} KB. Large instruction files increase cost and may impact performance; consider trimming.` : void 0,
21799
- sources: rendered.measure()
21817
+ sources: {
21818
+ ...rendered.measure(),
21819
+ ...notLoadedCandidates.size === 0 ? {} : { notLoadedCandidates: [...notLoadedCandidates] }
21820
+ }
21800
21821
  };
21801
21822
  }
21802
21823
  async function loadAdditionalDirsInfo(kaos, additionalDirs) {
@@ -28778,17 +28799,20 @@ function createSystemPromptRenderer(merged) {
28778
28799
  const counts = renderPromptContributions(merged.systemPromptTemplate, vars, rendered, true) ?? renderPromptContributions(merged.systemPromptTemplate, vars, rendered);
28779
28800
  const sources = context.agentsMdSources;
28780
28801
  const copies = counts?.instructionCopies ?? 0;
28781
- const sourceMatches = sources !== void 0 && sources.version === 1 && Number.isSafeInteger(sources.generatedBytes) && sources.generatedBytes >= 0 && sources.files.every((file) => typeof file.path === "string" && Number.isSafeInteger(file.bytes) && file.bytes >= 0) && sources.totalBytes === Buffer.byteLength(vars["BLUN_AGENTS_MD"] ?? "", "utf8") && sources.generatedBytes + sources.files.reduce((sum, file) => sum + file.bytes, 0) === sources.totalBytes;
28802
+ const sourceMatches = sources !== void 0 && sources.version === 1 && Number.isSafeInteger(sources.generatedBytes) && sources.generatedBytes >= 0 && sources.files.every((file) => typeof file.path === "string" && Number.isSafeInteger(file.bytes) && file.bytes >= 0) && (sources.notLoadedCandidates?.every((path) => typeof path === "string") ?? true) && sources.totalBytes === Buffer.byteLength(vars["BLUN_AGENTS_MD"] ?? "", "utf8") && sources.generatedBytes + sources.files.reduce((sum, file) => sum + file.bytes, 0) === sources.totalBytes;
28803
+ const notLoadedCandidates = sourceMatches && sources.notLoadedCandidates !== void 0 ? { notLoadedCandidates: [...sources.notLoadedCandidates] } : {};
28782
28804
  const instructionSources = copies === 0 ? {
28783
28805
  version: 1,
28784
28806
  totalBytes: 0,
28785
28807
  generatedBytes: 0,
28786
- files: sourceMatches ? sources.files.map((file) => copyInstructionFileBytes(file, 0)) : []
28808
+ files: sourceMatches ? sources.files.map((file) => copyInstructionFileBytes(file, 0)) : [],
28809
+ ...notLoadedCandidates
28787
28810
  } : !sourceMatches ? void 0 : {
28788
28811
  version: 1,
28789
28812
  totalBytes: sources.totalBytes * copies,
28790
28813
  generatedBytes: sources.generatedBytes * copies,
28791
- files: sources.files.map((file) => copyInstructionFileBytes(file, copies))
28814
+ files: sources.files.map((file) => copyInstructionFileBytes(file, copies)),
28815
+ ...notLoadedCandidates
28792
28816
  };
28793
28817
  const skillListingSources = counts === void 0 ? void 0 : scaleSkillListingSources(skillSources, vars["BLUN_SKILLS"] ?? "", counts.skillListingBytes);
28794
28818
  context.onContributions(counts === void 0 ? void 0 : {
@@ -230235,16 +230259,19 @@ var FullCompaction = class {
230235
230259
  const projected = context.additionalMessages === void 0 ? messages : [...messages, ...context.additionalMessages];
230236
230260
  const historyBudget = this.requestHistoryByteBudget(projected);
230237
230261
  const historyThresholdReached = historyBudget !== void 0 && historyBudget.usedHistoryBytes >= historyBudget.thresholdHistoryBytes;
230262
+ const requestBudget = this.requestBodyByteBudget(projected, context);
230263
+ const requestThresholdReached = requestBudget !== void 0 && requestBudget.usedRequestBytesEstimate >= requestBudget.thresholdRequestBytes;
230238
230264
  if (this.sessionCompactionFailures >= MAX_SESSION_COMPACTION_FAILURES) reason = "session_stopped";
230239
230265
  else if (this.isCompacting) reason = "already_compacting";
230240
- else if (thresholdTokens === void 0 && historyBudget === void 0) reason = "no_budget";
230241
- else if (!historyThresholdReached && (thresholdTokens === void 0 || !Number.isFinite(usedTokens) || usedTokens < thresholdTokens)) reason = "below_threshold";
230266
+ else if (thresholdTokens === void 0 && historyBudget === void 0 && requestBudget === void 0) reason = "no_budget";
230267
+ else if (!historyThresholdReached && !requestThresholdReached && (thresholdTokens === void 0 || !Number.isFinite(usedTokens) || usedTokens < thresholdTokens)) reason = "below_threshold";
230242
230268
  else if (!this.hasShrinkableHistory(projected)) reason = "nothing_to_compact";
230243
230269
  return {
230244
230270
  eligible: reason === "eligible",
230245
230271
  usedTokens,
230246
230272
  thresholdTokens,
230247
230273
  ...historyBudget,
230274
+ ...requestBudget,
230248
230275
  reason
230249
230276
  };
230250
230277
  }
@@ -230266,6 +230293,35 @@ var FullCompaction = class {
230266
230293
  thresholdHistoryBytes: limit
230267
230294
  };
230268
230295
  }
230296
+ requestBodyByteBudget(messages, context) {
230297
+ const limit = this.agent.blunConfig?.loopControl?.maxRequestHistoryBytes;
230298
+ if (!this.agent.config.hasProvider || this.strategy.compactionBudgetTokens === void 0 || limit === void 0 || !Number.isSafeInteger(limit) || limit <= 0) return void 0;
230299
+ const model = this.agent.activeResponderModel ?? this.agent.config.modelAlias;
230300
+ if (model === void 0 || this.agent.config.runtimeModel(model).provider.name !== "blun") return void 0;
230301
+ const systemPrompt = context.systemPrompt ?? this.agent.effectiveSystemPrompt;
230302
+ const tools = (context.tools ?? this.agent.tools.loopTools).map((tool) => ({
230303
+ name: tool.name,
230304
+ description: tool.description,
230305
+ parameters: tool.parameters
230306
+ }));
230307
+ let extraBytes;
230308
+ try {
230309
+ extraBytes = estimateBlunRequestExtraBytes(tools, messages);
230310
+ } catch {
230311
+ return;
230312
+ }
230313
+ if (Buffer.byteLength(JSON.stringify({
230314
+ systemPrompt,
230315
+ messages: []
230316
+ }), "utf8") + extraBytes.toolsBytes >= BLUN_COMPACTION_REQUEST_BUDGET_BYTES) return void 0;
230317
+ return {
230318
+ usedRequestBytesEstimate: Buffer.byteLength(JSON.stringify({
230319
+ systemPrompt,
230320
+ messages
230321
+ }), "utf8") + extraBytes.toolsBytes + extraBytes.visionBytes,
230322
+ thresholdRequestBytes: BLUN_COMPACTION_REQUEST_BUDGET_BYTES
230323
+ };
230324
+ }
230269
230325
  requestProactiveCompaction(context = {}) {
230270
230326
  const status = this.proactiveCompactionStatus(context);
230271
230327
  if (status.eligible && !this.proactiveCompactionRequested) {
@@ -230274,7 +230330,9 @@ var FullCompaction = class {
230274
230330
  used_tokens: status.usedTokens,
230275
230331
  threshold_tokens: status.thresholdTokens,
230276
230332
  used_history_bytes: status.usedHistoryBytes,
230277
- threshold_history_bytes: status.thresholdHistoryBytes
230333
+ threshold_history_bytes: status.thresholdHistoryBytes,
230334
+ used_request_bytes_estimate: status.usedRequestBytesEstimate,
230335
+ threshold_request_bytes: status.thresholdRequestBytes
230278
230336
  });
230279
230337
  }
230280
230338
  return status;
@@ -230403,7 +230461,9 @@ var FullCompaction = class {
230403
230461
  const requestTokens = this.estimateCurrentRequestTokens(requestContext);
230404
230462
  const budget = this.getCompactionBudgetTokens();
230405
230463
  const historyBudget = this.requestHistoryByteBudget(projectedMessages);
230406
- const proactive = (historyBudget !== void 0 && historyBudget.usedHistoryBytes >= historyBudget.thresholdHistoryBytes || !this.isCompacting && budget !== void 0 && Number.isFinite(requestTokens) && requestTokens >= Math.max(1, Math.floor(budget * .9))) && this.hasShrinkableHistory(projectedMessages);
230464
+ const historyThresholdReached = historyBudget !== void 0 && historyBudget.usedHistoryBytes >= historyBudget.thresholdHistoryBytes;
230465
+ const requestBudget = this.requestBodyByteBudget(projectedMessages, requestContext);
230466
+ const proactive = (requestBudget !== void 0 && requestBudget.usedRequestBytesEstimate >= requestBudget.thresholdRequestBytes || historyThresholdReached || !this.isCompacting && budget !== void 0 && Number.isFinite(requestTokens) && requestTokens >= Math.max(1, Math.floor(budget * .9))) && this.hasShrinkableHistory(projectedMessages);
230407
230467
  if (this.checkAutoCompaction(true, requestTokens, projectedMessages, proactive) && (proactive || this.strategy.shouldBlock(requestTokens))) await this.block(signal);
230408
230468
  signal.throwIfAborted();
230409
230469
  const compacted = this.agent.context.history !== historyBefore;
@@ -238976,6 +239036,32 @@ var import_turn_tool_performance_policy = (/* @__PURE__ */ __commonJSMin(((expor
238976
239036
  "suchen"
238977
239037
  ]);
238978
239038
  const TOOL_SEARCH_RELATED_TERM_GROUPS = Object.freeze([
239039
+ Object.freeze([
239040
+ "persist",
239041
+ "save",
239042
+ "speichere",
239043
+ "speichern",
239044
+ "store"
239045
+ ]),
239046
+ Object.freeze([
239047
+ "erinnerung",
239048
+ "erinnerungen",
239049
+ "fact",
239050
+ "facts",
239051
+ "fakt",
239052
+ "fakten",
239053
+ "memory",
239054
+ "memories",
239055
+ "merk",
239056
+ "merken",
239057
+ "remember"
239058
+ ]),
239059
+ Object.freeze([
239060
+ "note",
239061
+ "notes",
239062
+ "notiz",
239063
+ "notizen"
239064
+ ]),
238979
239065
  Object.freeze([
238980
239066
  "checklist",
238981
239067
  "list",
@@ -239234,7 +239320,7 @@ var import_turn_tool_performance_policy = (/* @__PURE__ */ __commonJSMin(((expor
239234
239320
  if (document.nameTokens.has(token)) exactNameMatches += 1;
239235
239321
  else if (relatedSearchTerms(token).some((term) => document.nameTokens.has(term))) relatedNameMatches += 1;
239236
239322
  }
239237
- return matchedTokens >= 2 || exactNameMatches >= 1 && score >= 10 || relatedNameMatches >= 1 && score >= 5 ? {
239323
+ return matchedTokens >= 2 || exactNameMatches >= 1 && score >= 10 || queryTokens.length === 1 && relatedNameMatches >= 1 && score >= 5 ? {
239238
239324
  name: document.name,
239239
239325
  score,
239240
239326
  matchedTokens,
@@ -239255,11 +239341,11 @@ var import_turn_tool_performance_policy = (/* @__PURE__ */ __commonJSMin(((expor
239255
239341
  function searchRelatedDeferredTools(tools, query, options = {}) {
239256
239342
  const normalized = normalizeDeferredToolQuery(query).toLowerCase();
239257
239343
  if (!normalized || normalized.startsWith("select:")) return [];
239258
- const tokens = normalized.split(/\s+/).filter(Boolean);
239344
+ const tokens = normalized.match(/\+?[\p{L}\p{N}_-]+/gu) || [];
239259
239345
  const requestedNameTokens = tokens.filter((token) => token.startsWith("+")).map((token) => token.slice(1)).filter(Boolean);
239260
239346
  const relaxRequiredNameTokens = options.relaxRequiredNameTokens === true && requestedNameTokens.length > 0;
239261
239347
  const requiredNameTokens = relaxRequiredNameTokens ? [] : requestedNameTokens;
239262
- const searchTokens = [...tokens.filter((token) => !token.startsWith("+")), ...relaxRequiredNameTokens ? requestedNameTokens : []].filter((token) => !TOOL_SEARCH_INTENT_WORDS.has(token));
239348
+ const searchTokens = [...tokens.filter((token) => !token.startsWith("+")), ...relaxRequiredNameTokens ? requestedNameTokens : []].filter((token) => !TOOL_SEARCH_INTENT_WORDS.has(token) && !TOOL_RANK_STOP_WORDS.has(token));
239263
239349
  if (searchTokens.length < 2 && !TOOL_SEARCH_SINGLE_TERM_RELATED_FALLBACKS.has(searchTokens[0])) return [];
239264
239350
  const minimumMatchedTokens = searchTokens.length === 1 ? 1 : Math.max(2, Math.ceil(searchTokens.length * .75));
239265
239351
  const ranked = tools.map((tool) => {
@@ -267590,6 +267676,7 @@ var TurnFlow = class {
267590
267676
  const appendedMessage = this.agent.context.history.at(-1);
267591
267677
  const turnAnchor = appendedMessage !== previousLastMessage && appendedMessage?.role === "user" && appendedMessage.origin === origin ? appendedMessage : void 0;
267592
267678
  const startedAt = Date.now();
267679
+ const preRequestHookStartedAt = performance.now();
267593
267680
  let ended;
267594
267681
  let blockedByUserPromptHook = false;
267595
267682
  let completedStopReason;
@@ -267615,6 +267702,13 @@ var TurnFlow = class {
267615
267702
  this.agent.log.warn("Turn-start observation failed");
267616
267703
  }
267617
267704
  signal.throwIfAborted();
267705
+ if (this.agent.type === "main" && process.env["BLUN_DIAG_PRE_REQUEST_PHASES"] === "1") try {
267706
+ this.agent.log.info("turn pre-request phase", {
267707
+ turnId,
267708
+ phase: "turn-hooks",
267709
+ durationMs: Math.max(0, Math.round(performance.now() - preRequestHookStartedAt))
267710
+ });
267711
+ } catch {}
267618
267712
  const stopReason = await this.runStepLoop(turnId, signal, input, origin, turnAnchor, (response) => {
267619
267713
  completedResponse = response;
267620
267714
  });
@@ -267784,9 +267878,26 @@ var TurnFlow = class {
267784
267878
  let stopHookContinuationUsed = false;
267785
267879
  let goalOutcomeMessageContinuationUsed = false;
267786
267880
  const deduper = new ToolCallDeduplicator({ telemetry: this.agent.telemetry });
267881
+ const diagnosePreRequest = this.agent.type === "main" && process.env["BLUN_DIAG_PRE_REQUEST_PHASES"] === "1";
267882
+ const recordPreRequestPhase = (phase, startedAt) => {
267883
+ if (!diagnosePreRequest) return;
267884
+ try {
267885
+ this.agent.log.info("turn pre-request phase", {
267886
+ turnId,
267887
+ phase,
267888
+ durationMs: Math.max(0, Math.round(performance.now() - startedAt))
267889
+ });
267890
+ } catch {}
267891
+ };
267892
+ let phaseStartedAt = performance.now();
267787
267893
  await this.agent.mcp?.waitForInitialLoad(signal);
267894
+ recordPreRequestPhase("mcp-initial-load", phaseStartedAt);
267895
+ phaseStartedAt = performance.now();
267788
267896
  const personalMemoryRecall = await this.agent.injection.injectPersonalMemoryForTurn(turnId, input, origin, signal);
267897
+ recordPreRequestPhase("personal-memory-recall", phaseStartedAt);
267898
+ phaseStartedAt = performance.now();
267789
267899
  await this.agent.injection.injectGoal();
267900
+ recordPreRequestPhase("goal-injection", phaseStartedAt);
267790
267901
  this.setActiveSteerAcceptance(turnId, true);
267791
267902
  const turnNeedsThinking = blunTurnNeedsTools(blunThinkingIntentInput(input), origin);
267792
267903
  let toolInput = input;
@@ -267797,12 +267908,14 @@ var TurnFlow = class {
267797
267908
  const raw = [...this.agent.context.history];
267798
267909
  const history = personalMemoryRecall === void 0 ? raw : [...raw, personalMemoryRecall];
267799
267910
  const selected = taskToolsNeeded ? history : greetingHistory(history, greetingAnchor);
267911
+ const projectionStartedAt = performance.now();
267800
267912
  const projected = await this.historicalToolProjection.project(selected, turnAnchor, tools, {
267801
267913
  turnId: String(turnId),
267802
267914
  stepNumber: this.currentStep,
267803
267915
  signal,
267804
267916
  llm: turnLLM
267805
267917
  });
267918
+ recordPreRequestPhase("historical-tool-projection", projectionStartedAt);
267806
267919
  const current = this.agent.context.history;
267807
267920
  if (current.length !== raw.length || current.some((message, index) => message !== raw[index])) {
267808
267921
  const latest = personalMemoryRecall === void 0 ? current : [...current, personalMemoryRecall];
@@ -267813,7 +267926,7 @@ var TurnFlow = class {
267813
267926
  partialContext = selected.length < history.length || projected.changed;
267814
267927
  return projected.history;
267815
267928
  };
267816
- const turnLLM = this.agent.llmForTurn(turnNeedsThinking ? void 0 : "off");
267929
+ const turnLLM = this.agent.llmForTurn(turnNeedsThinking ? void 0 : "off", turnId);
267817
267930
  const turnTools = this.agent.tools.captureTurnTools();
267818
267931
  while (true) {
267819
267932
  signal.throwIfAborted();
@@ -267823,6 +267936,7 @@ var TurnFlow = class {
267823
267936
  try {
267824
267937
  const selectedTools = [];
267825
267938
  this.refreshSelectedTools = (snapshot) => {
267939
+ const selectionStartedAt = performance.now();
267826
267940
  if (snapshot.messages.length > 0) toolInput = snapshot.messages.flatMap((message) => message.content);
267827
267941
  const eligibleTools = blunToolsForOrigin(this.agent.injection.filterPersonalMemoryToolsForTurn(turnId, input, origin, turnTools()), origin);
267828
267942
  const intent = blunThinkingIntentInput(toolInput);
@@ -267835,6 +267949,7 @@ var TurnFlow = class {
267835
267949
  if (tool !== void 0 && !selectedTools.some((candidate) => candidate.name === name) && estimateTokensForTools([...selectedTools, tool]) <= (0, import_turn_tool_performance_policy.toolSchemaBudgetTokens)(this.agent.config.modelCapabilities?.max_context_tokens)) selectedTools.push(tool);
267836
267950
  }
267837
267951
  } else selectedTools.splice(0, selectedTools.length, ...eligibleTools.filter((tool) => BLUN_LANGUAGE_REVIEW_TOOL_NAMES.has(tool.name) || BLUN_TELEGRAM_OUTBOUND_TOOL_RE.test(tool.name)));
267952
+ recordPreRequestPhase("tool-selection", selectionStartedAt);
267838
267953
  return selectedTools;
267839
267954
  };
267840
267955
  return (await runTurn({
@@ -267847,14 +267962,24 @@ var TurnFlow = class {
267847
267962
  }, completion);
267848
267963
  },
267849
267964
  llm: turnLLM,
267850
- buildMessages: async () => this.agent.context.project(await turnHistory(selectedTools), { dropOrphanResults: true }),
267851
- buildMessagesStrict: async () => this.agent.context.project(await turnHistory(selectedTools), {
267852
- synthesizeMissing: true,
267853
- dropOrphanResults: true,
267854
- dedupeDuplicateToolCalls: true,
267855
- dropLeadingNonUser: true,
267856
- mergeConsecutiveAssistants: true
267857
- }),
267965
+ buildMessages: async () => {
267966
+ const startedAt = performance.now();
267967
+ const messages = this.agent.context.project(await turnHistory(selectedTools), { dropOrphanResults: true });
267968
+ recordPreRequestPhase("message-build", startedAt);
267969
+ return messages;
267970
+ },
267971
+ buildMessagesStrict: async () => {
267972
+ const startedAt = performance.now();
267973
+ const messages = this.agent.context.project(await turnHistory(selectedTools), {
267974
+ synthesizeMissing: true,
267975
+ dropOrphanResults: true,
267976
+ dedupeDuplicateToolCalls: true,
267977
+ dropLeadingNonUser: true,
267978
+ mergeConsecutiveAssistants: true
267979
+ });
267980
+ recordPreRequestPhase("message-build-strict", startedAt);
267981
+ return messages;
267982
+ },
267858
267983
  dispatchEvent: this.buildDispatchEvent(turnId, signal, onFinalResponse, () => partialContext),
267859
267984
  tools: selectedTools,
267860
267985
  log: this.agent.log,
@@ -267871,8 +267996,10 @@ var TurnFlow = class {
267871
267996
  },
267872
267997
  hooks: {
267873
267998
  beforeStep: async ({ signal: stepSignal, llm }) => {
267999
+ const injectionStartedAt = performance.now();
267874
268000
  this.agent.microCompaction.detect();
267875
268001
  await this.agent.injection.inject();
268002
+ recordPreRequestPhase("step-injection", injectionStartedAt);
267876
268003
  stepSignal.throwIfAborted();
267877
268004
  deduper.beginStep();
267878
268005
  },
@@ -271538,8 +271665,13 @@ var LlmRequestLogger = class {
271538
271665
  toolsEstimatedTokens = 0;
271539
271666
  requestSequence = 0;
271540
271667
  lastTurnRequest;
271541
- constructor(log) {
271668
+ serializedPrefixKey;
271669
+ requestSignatures = /* @__PURE__ */ new Map();
271670
+ prefixComparisons = /* @__PURE__ */ new Map();
271671
+ lastSerializedRequest;
271672
+ constructor(log, serializedPrefixDiagnostic = process.env["BLUN_DIAG_SERIALIZED_PREFIX"] === "1") {
271542
271673
  this.log = log;
271674
+ this.serializedPrefixKey = serializedPrefixDiagnostic ? randomBytes(32) : void 0;
271543
271675
  }
271544
271676
  logRequest(input) {
271545
271677
  const sequence = ++this.requestSequence;
@@ -271556,6 +271688,10 @@ var LlmRequestLogger = class {
271556
271688
  toolsHash: fingerprint(JSON.stringify(toolSignature(tools)))
271557
271689
  };
271558
271690
  const signature = JSON.stringify(config);
271691
+ if (this.serializedPrefixKey !== void 0) {
271692
+ this.requestSignatures.set(sequence, signature);
271693
+ if (this.requestSignatures.size > 32) this.requestSignatures.delete(this.requestSignatures.keys().next().value);
271694
+ }
271559
271695
  if (signature !== this.lastConfigLogSignature) {
271560
271696
  this.lastConfigLogSignature = signature;
271561
271697
  this.configRevision++;
@@ -271585,6 +271721,20 @@ var LlmRequestLogger = class {
271585
271721
  }
271586
271722
  };
271587
271723
  if (partialMessageCount > 0) requestFields.partialMessageCount = partialMessageCount;
271724
+ const source = input.systemPromptContributions;
271725
+ if (source?.version === 1 && [
271726
+ source.totalBytes,
271727
+ source.instructionBytes,
271728
+ source.skillListingBytes,
271729
+ source.runtimeMemoryBytes,
271730
+ source.otherBytes
271731
+ ].every((value) => Number.isSafeInteger(value) && value >= 0) && source.instructionBytes + source.skillListingBytes + source.runtimeMemoryBytes + source.otherBytes === source.totalBytes && source.totalBytes === Buffer.byteLength(systemPrompt, "utf8")) requestFields.systemPromptSourceBytes = {
271732
+ totalBytes: source.totalBytes,
271733
+ instructionBytes: source.instructionBytes,
271734
+ skillListingBytes: source.skillListingBytes,
271735
+ runtimeMemoryBytes: source.runtimeMemoryBytes,
271736
+ otherBytes: source.otherBytes
271737
+ };
271588
271738
  this.log.info("llm request", requestFields);
271589
271739
  if (fields?.turnStep !== void 0 && /^\d+\.\d+$/.test(fields.turnStep)) {
271590
271740
  const historyTextOrigins = summarizeRequestTextOrigins(messages);
@@ -271610,10 +271760,51 @@ var LlmRequestLogger = class {
271610
271760
  getLastTurnRequest() {
271611
271761
  return this.lastTurnRequest === void 0 ? void 0 : structuredClone(this.lastTurnRequest);
271612
271762
  }
271763
+ get serializedPrefixDiagnosticEnabled() {
271764
+ return this.serializedPrefixKey !== void 0;
271765
+ }
271766
+ observeSerializedRequest(body, fields, sequence) {
271767
+ const key = this.serializedPrefixKey;
271768
+ if (key === void 0 || !/^\d+\.\d+$/.test(fields?.turnStep ?? "")) return;
271769
+ const signature = this.requestSignatures.get(sequence);
271770
+ if (signature === void 0) return;
271771
+ try {
271772
+ const bytes = Buffer.from(body, "utf8");
271773
+ const granularityBytes = 1024;
271774
+ const chunks = [];
271775
+ for (let start = 0; start + granularityBytes <= bytes.length; start += granularityBytes) chunks.push(createHmac("sha256", key).update(bytes.subarray(start, start + granularityBytes)).digest("hex"));
271776
+ const previous = this.lastSerializedRequest;
271777
+ if (previous !== void 0 && previous.signature === signature) {
271778
+ let common = 0;
271779
+ while (common < previous.chunks.length && common < chunks.length && previous.chunks[common] === chunks[common]) common++;
271780
+ this.prefixComparisons.set(sequence, {
271781
+ version: 1,
271782
+ granularityBytes,
271783
+ previousBytes: previous.bytes,
271784
+ currentBytes: bytes.length,
271785
+ commonPrefixBytesLowerBound: common * granularityBytes,
271786
+ previousSequence: previous.sequence,
271787
+ currentSequence: sequence
271788
+ });
271789
+ if (this.prefixComparisons.size > 32) this.prefixComparisons.delete(this.prefixComparisons.keys().next().value);
271790
+ }
271791
+ this.lastSerializedRequest = {
271792
+ sequence,
271793
+ signature,
271794
+ bytes: bytes.length,
271795
+ chunks
271796
+ };
271797
+ } catch {
271798
+ this.lastSerializedRequest = void 0;
271799
+ }
271800
+ }
271613
271801
  logTransportAttempt(stats, fields, sequence) {
271802
+ const serializedPrefix = sequence === void 0 ? void 0 : this.prefixComparisons.get(sequence);
271614
271803
  this.log.info("llm transport attempt", {
271615
271804
  ...fields,
271616
- ...stats
271805
+ ...stats,
271806
+ ...this.serializedPrefixKey === void 0 || sequence === void 0 ? {} : { requestSequence: sequence },
271807
+ ...serializedPrefix === void 0 ? {} : { serializedPrefix }
271617
271808
  });
271618
271809
  if (sequence === void 0 || this.lastTurnRequest?.sequence !== sequence) return;
271619
271810
  const breakdown = stats.requestBreakdown;
@@ -271696,7 +271887,8 @@ function copyContributions(value) {
271696
271887
  version: source.version,
271697
271888
  totalBytes: source.totalBytes,
271698
271889
  generatedBytes: source.generatedBytes,
271699
- files: source.files.map((file) => copyInstructionFileBytes(file))
271890
+ files: source.files.map((file) => copyInstructionFileBytes(file)),
271891
+ ...source.notLoadedCandidates === void 0 ? {} : { notLoadedCandidates: [...source.notLoadedCandidates] }
271700
271892
  } }
271701
271893
  };
271702
271894
  }
@@ -271792,6 +271984,7 @@ var Agent$1 = class {
271792
271984
  errorMemory;
271793
271985
  replayBuilder;
271794
271986
  additionalDirs;
271987
+ systemPromptStartedAt = /* @__PURE__ */ new Date();
271795
271988
  activeProfile;
271796
271989
  brandHome;
271797
271990
  _activeResponderModel;
@@ -271879,6 +272072,7 @@ var Agent$1 = class {
271879
272072
  const retryScope = providerRetryScope(modelAlias, provider);
271880
272073
  await this.providerRetryWait.wait(retryScope, generateOptions?.signal);
271881
272074
  const callerTransportAttempt = generateOptions?.onTransportAttempt;
272075
+ const callerSerializedRequest = generateOptions?.onSerializedRequest;
271882
272076
  const ownedGenerateOptions = {
271883
272077
  ...generateOptions,
271884
272078
  transportMaxRetries: 0
@@ -271901,6 +272095,12 @@ var Agent$1 = class {
271901
272095
  callerTransportAttempt?.(stats);
271902
272096
  this.llmRequestLogger.logTransportAttempt(stats, requestLogFields, requestSequence);
271903
272097
  },
272098
+ ...callerSerializedRequest !== void 0 || this.llmRequestLogger.serializedPrefixDiagnosticEnabled ? { onSerializedRequest: (body) => {
272099
+ try {
272100
+ callerSerializedRequest?.(body);
272101
+ } catch {}
272102
+ this.llmRequestLogger.observeSerializedRequest(body, requestLogFields, requestSequence);
272103
+ } } : {},
271904
272104
  onRequestStart: () => {
271905
272105
  requestOptions?.onRequestStart?.();
271906
272106
  requestStarted = true;
@@ -271926,7 +272126,7 @@ var Agent$1 = class {
271926
272126
  get llm() {
271927
272127
  return this.llmForTurn();
271928
272128
  }
271929
- llmForTurn(thinkingEffort) {
272129
+ llmForTurn(thinkingEffort, diagnosticTurnId) {
271930
272130
  const selectedAlias = this.config.model;
271931
272131
  const modelProvider = this.modelProvider;
271932
272132
  if (modelProvider === void 0) throw new BlunError(ErrorCodes.MODEL_NOT_CONFIGURED, "Provider not set");
@@ -271941,6 +272141,7 @@ var Agent$1 = class {
271941
272141
  messages: await rebuildMessages()
271942
272142
  };
271943
272143
  let systemPrompt = this.effectiveSystemPrompt;
272144
+ const preflightStartedAt = performance.now();
271944
272145
  const compacted = await this.fullCompaction.beforeStep(params.signal, {
271945
272146
  messages: params.messages,
271946
272147
  additionalMessages: steers.messages,
@@ -271949,6 +272150,13 @@ var Agent$1 = class {
271949
272150
  rebuildSystemPrompt: () => systemPrompt = this.effectiveSystemPrompt,
271950
272151
  tools: params.tools
271951
272152
  });
272153
+ if (this.type === "main" && diagnosticTurnId !== void 0 && process.env["BLUN_DIAG_PRE_REQUEST_PHASES"] === "1") try {
272154
+ this.log.info("turn pre-request phase", {
272155
+ turnId: diagnosticTurnId,
272156
+ phase: "compaction-preflight",
272157
+ durationMs: Math.max(0, Math.round(performance.now() - preflightStartedAt))
272158
+ });
272159
+ } catch {}
271952
272160
  const tools = params.tools.some((tool) => tool.name === "CompactConversation") && !this.fullCompaction.proactiveCompactionStatus({
271953
272161
  messages: compacted ? await rebuildMessages() : params.messages,
271954
272162
  additionalMessages: steers.messages,
@@ -272053,6 +272261,7 @@ var Agent$1 = class {
272053
272261
  let contributions;
272054
272262
  const systemPrompt = profile.systemPrompt({
272055
272263
  osEnv: this.kaos.osEnv,
272264
+ now: this.systemPromptStartedAt,
272056
272265
  cwd: this.config.cwd,
272057
272266
  skills: this.skills?.registry,
272058
272267
  cwdListing: context?.cwdListing,