blun-king-cli 9.1.594 → 9.1.596

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.
Files changed (3) hide show
  1. package/blun.mjs +708 -31
  2. package/package.json +1 -1
  3. package/worker-host.mjs +672 -33
package/worker-host.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- // BLUN_BUILD_INPUT_SHA256:410be0cd699a00876404e3f2e9a6d091b2c2fa9c4bb1f483b7fbdd3480a39fb2
2
+ // BLUN_BUILD_INPUT_SHA256:2c4b3f451c41d328bb5eb43ffb226f0f3b0c458270217cecb6e1ffc63287e8bd
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);
@@ -233816,10 +233816,11 @@ var ContextMemory = class {
233816
233816
  const openStepIndex = openStep === void 0 ? -1 : this._history.indexOf(openStep);
233817
233817
  const coveredCount = openStepIndex === -1 ? this._history.length : openStepIndex + 1;
233818
233818
  const totalUsage = event.usage.inputCacheRead + event.usage.inputCacheCreation + event.usage.inputOther + event.usage.output;
233819
- if (totalUsage > 0) this._tokenCount = totalUsage;
233819
+ if (totalUsage > 0 && event.contextCoverage !== "partial") this._tokenCount = totalUsage;
233820
233820
  else {
233821
233821
  const previousCoveredCount = this.tokenCountCoveredMessageCount;
233822
233822
  this._tokenCount += estimateTokensForMessages(this._history.slice(previousCoveredCount, coveredCount));
233823
+ this._tokenCount = Math.max(this._tokenCount, totalUsage);
233823
233824
  }
233824
233825
  this.tokenCountCoveredMessageCount = coveredCount;
233825
233826
  }
@@ -237788,6 +237789,518 @@ var AutoModeAskUserQuestionDenyPermissionPolicy = class {
237788
237789
  };
237789
237790
  //#endregion
237790
237791
  //#region ../../packages/agent-core/src/agent/permission/policies/default-tool-approve.ts
237792
+ var import_turn_tool_performance_policy = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
237793
+ const TOOL_SCHEMA_BUDGET_RATIO = .25;
237794
+ const TOOL_SCHEMA_MAX_TOKENS = 64e3;
237795
+ const DEFERRED_TOOL_LOADER_NAME = "ToolSearch";
237796
+ const MAX_PERSISTENT_DEFERRED_TOOLS = 12;
237797
+ const MAX_TOOL_SEARCH_QUERY_CHARS = 1e3;
237798
+ const MAX_AUTO_RANKED_TOOLS = 6;
237799
+ const deferredToolLoaders = /* @__PURE__ */ new WeakSet();
237800
+ const TOOL_RANK_EMBEDDED_CONTENT_RE = /<(attached_documents?|attachment_content|document_content|file_content)\b[^>]*>[\s\S]*?<\/\1>/giu;
237801
+ const TOOL_RANK_STOP_WORDS = new Set([
237802
+ "aber",
237803
+ "also",
237804
+ "and",
237805
+ "aus",
237806
+ "bitte",
237807
+ "can",
237808
+ "das",
237809
+ "den",
237810
+ "der",
237811
+ "des",
237812
+ "die",
237813
+ "dies",
237814
+ "diese",
237815
+ "du",
237816
+ "ein",
237817
+ "eine",
237818
+ "einer",
237819
+ "eines",
237820
+ "for",
237821
+ "fuer",
237822
+ "für",
237823
+ "haben",
237824
+ "ich",
237825
+ "ist",
237826
+ "kann",
237827
+ "kannst",
237828
+ "mal",
237829
+ "me",
237830
+ "mein",
237831
+ "meine",
237832
+ "mir",
237833
+ "mit",
237834
+ "of",
237835
+ "please",
237836
+ "soll",
237837
+ "the",
237838
+ "und",
237839
+ "uns",
237840
+ "von",
237841
+ "was",
237842
+ "wir",
237843
+ "with",
237844
+ "you",
237845
+ "zeige",
237846
+ "zeig",
237847
+ "pruefe",
237848
+ "prüfe",
237849
+ "lies",
237850
+ "read",
237851
+ "show",
237852
+ "get"
237853
+ ]);
237854
+ const TOOL_SEARCH_INTENT_WORDS = new Set([
237855
+ "find",
237856
+ "fetch",
237857
+ "get",
237858
+ "holen",
237859
+ "list",
237860
+ "read",
237861
+ "search",
237862
+ "show",
237863
+ "anzeigen",
237864
+ "lesen",
237865
+ "suchen"
237866
+ ]);
237867
+ const TOOL_SEARCH_RELATED_TERM_GROUPS = Object.freeze([
237868
+ Object.freeze([
237869
+ "checklist",
237870
+ "list",
237871
+ "liste",
237872
+ "listen"
237873
+ ]),
237874
+ Object.freeze([
237875
+ "aufgabe",
237876
+ "aufgaben",
237877
+ "aufgabenliste",
237878
+ "todo",
237879
+ "todos"
237880
+ ]),
237881
+ Object.freeze([
237882
+ "current",
237883
+ "inbox",
237884
+ "latest",
237885
+ "message",
237886
+ "messages",
237887
+ "nachricht",
237888
+ "nachrichten",
237889
+ "queue",
237890
+ "queued",
237891
+ "recent",
237892
+ "letzte",
237893
+ "letzten",
237894
+ "letzter",
237895
+ "vergangen",
237896
+ "update",
237897
+ "updates"
237898
+ ]),
237899
+ Object.freeze([
237900
+ "chat",
237901
+ "conversation",
237902
+ "history",
237903
+ "log",
237904
+ "logs",
237905
+ "processed",
237906
+ "protokoll",
237907
+ "transcript",
237908
+ "verlauf"
237909
+ ]),
237910
+ Object.freeze([
237911
+ "fact",
237912
+ "facts",
237913
+ "fakt",
237914
+ "fakten",
237915
+ "memory",
237916
+ "memories",
237917
+ "merk",
237918
+ "merken",
237919
+ "note",
237920
+ "notes",
237921
+ "persist",
237922
+ "persistent",
237923
+ "profile",
237924
+ "remember",
237925
+ "save",
237926
+ "speichern",
237927
+ "store"
237928
+ ])
237929
+ ]);
237930
+ const TOOL_SEARCH_SINGLE_TERM_RELATED_FALLBACKS = new Set([
237931
+ "fact",
237932
+ "facts",
237933
+ "fakt",
237934
+ "fakten",
237935
+ "memory",
237936
+ "memories",
237937
+ "merk",
237938
+ "merken",
237939
+ "note",
237940
+ "notes",
237941
+ "persist",
237942
+ "persistent",
237943
+ "profile",
237944
+ "remember",
237945
+ "save",
237946
+ "speichern",
237947
+ "store",
237948
+ "transcript"
237949
+ ]);
237950
+ const CORE_TOOL_NAMES = Object.freeze([
237951
+ "Bash",
237952
+ "Read",
237953
+ "ReadBatch",
237954
+ "Edit",
237955
+ "Grep",
237956
+ "Write",
237957
+ "Glob",
237958
+ "TodoList",
237959
+ "mcp__plugin-telegram_telegram__reply"
237960
+ ]);
237961
+ const IMAGE_MEDIA_TERM_RE = /(?:^|[^\p{L}])(?:bild(?:er)?|foto(?:s)?|grafik(?:en)?|illustration(?:en)?|logo(?:s)?|image|images|photo|photos|picture|pictures|illustration|illustrations|visual|visuals)(?=$|[^\p{L}])/u;
237962
+ const VIDEO_MEDIA_TERM_RE = /(?:^|[^\p{L}])(?:video(?:s)?|clip(?:s)?|film(?:e)?|animation(?:en)?|movie|movies)(?=$|[^\p{L}])/u;
237963
+ const ANIMATE_ACTION_RE = /(?:^|[^\p{L}])(?:animier(?:e|en|t)?|animate|animated|animating)(?=$|[^\p{L}])/u;
237964
+ const CREATE_ACTION_RE = /(?:^|[^\p{L}])(?:erstell(?:e|en|t)?|generier(?:e|en|t)?|erzeug(?:e|en|t)?|produzier(?:e|en|t)?|zeichn(?:e|en|et)?|mal(?:e|en|t)?|render(?:e|n|t)?|mach(?:e|en|t)?|create|creates|creating|generate|generates|generating|make|makes|making|draw|draws|drawing|paint|paints|painting|render|renders|rendering|produce|produces|producing)(?=$|[^\p{L}])/u;
237965
+ const REQUEST_PREFIX_RE = /^(?:bitte\s+|please\s+)?(?:erstell|generier|erzeug|produzier|zeichn|mal|render|mach|animier|create|generate|make|draw|paint|render|produce|animate)/u;
237966
+ const REQUEST_PHRASE_RE = /(?:^|[^\p{L}])(?:bitte|kannst du|könntest du|ich möchte|ich will|ich brauche|please|can you|could you|i want|i need)(?=$|[^\p{L}])/u;
237967
+ function mediaGenerationToolNamesForText(value) {
237968
+ const text = String(value || "").normalize("NFKC").trim().toLowerCase();
237969
+ const selected = /* @__PURE__ */ new Set();
237970
+ if (!text) return selected;
237971
+ if (/(?:^|[^a-z])generateimage(?=$|[^a-z])/u.test(text)) selected.add("GenerateImage");
237972
+ if (/(?:^|[^a-z])generatevideo(?=$|[^a-z])/u.test(text)) selected.add("GenerateVideo");
237973
+ if (selected.size > 0) return selected;
237974
+ const animate = ANIMATE_ACTION_RE.test(text);
237975
+ const create = CREATE_ACTION_RE.test(text);
237976
+ if (!(REQUEST_PREFIX_RE.test(text) || REQUEST_PHRASE_RE.test(text)) || !create && !animate) return selected;
237977
+ if (animate && (IMAGE_MEDIA_TERM_RE.test(text) || VIDEO_MEDIA_TERM_RE.test(text))) {
237978
+ selected.add("GenerateVideo");
237979
+ return selected;
237980
+ }
237981
+ if (IMAGE_MEDIA_TERM_RE.test(text)) selected.add("GenerateImage");
237982
+ if (VIDEO_MEDIA_TERM_RE.test(text)) selected.add("GenerateVideo");
237983
+ return selected;
237984
+ }
237985
+ function mediaToolNamesForTurnText(value) {
237986
+ const text = String(value || "");
237987
+ const selected = /* @__PURE__ */ new Set();
237988
+ const channelBodies = [...text.matchAll(/(?:^|\r?\n)<channel\b[^>]*>\r?\n([\s\S]*?)\r?\n<\/channel>(?=\r?\n|$)/giu)].map((match) => match[1]);
237989
+ const intentTexts = channelBodies.length > 0 ? channelBodies : [text];
237990
+ for (const intentText of intentTexts) for (const name of mediaGenerationToolNamesForText(intentText)) selected.add(name);
237991
+ if (/\bimage_path\s*=\s*["'][^"']+["']/iu.test(text)) {
237992
+ selected.add("ReadMediaFile");
237993
+ selected.add("UnderstandImage");
237994
+ }
237995
+ if (/\battachment_file_id\s*=\s*["'][^"']+["']/iu.test(text)) {
237996
+ selected.add("mcp__plugin-telegram_telegram__download_attachment");
237997
+ selected.add("UnderstandImage");
237998
+ }
237999
+ return selected;
238000
+ }
238001
+ function toolSchemaBudgetTokens(maxContextTokens) {
238002
+ const context = Number(maxContextTokens);
238003
+ if (!Number.isFinite(context) || context <= 0) return TOOL_SCHEMA_MAX_TOKENS;
238004
+ return Math.min(TOOL_SCHEMA_MAX_TOKENS, Math.floor(context * TOOL_SCHEMA_BUDGET_RATIO));
238005
+ }
238006
+ function deferredToolNames(tools) {
238007
+ return [...new Set((Array.isArray(tools) ? tools : []).map((tool) => String(tool?.name || "").trim()).filter(Boolean))].sort((left, right) => left.localeCompare(right));
238008
+ }
238009
+ function deferredToolCatalog(tools) {
238010
+ const names = deferredToolNames(tools);
238011
+ const leaves = names.map((name) => name.split(/__|:/).at(-1) || name);
238012
+ const leafCounts = /* @__PURE__ */ new Map();
238013
+ for (const leaf of leaves) leafCounts.set(leaf, (leafCounts.get(leaf) || 0) + 1);
238014
+ return names.map((name, index) => leafCounts.get(leaves[index]) === 1 ? leaves[index] : name).sort((left, right) => left.localeCompare(right));
238015
+ }
238016
+ function normalizeDeferredToolQuery(value) {
238017
+ const query = String(value ?? "").trim();
238018
+ if (query.length <= MAX_TOOL_SEARCH_QUERY_CHARS) return query;
238019
+ const bounded = query.slice(0, MAX_TOOL_SEARCH_QUERY_CHARS);
238020
+ const wordBoundary = bounded.lastIndexOf(" ");
238021
+ return (wordBoundary >= MAX_TOOL_SEARCH_QUERY_CHARS * .8 ? bounded.slice(0, wordBoundary) : bounded).trimEnd();
238022
+ }
238023
+ function searchDeferredTools(tools, query) {
238024
+ const normalized = normalizeDeferredToolQuery(query).toLowerCase();
238025
+ if (!normalized) return [];
238026
+ if (normalized.startsWith("select:")) {
238027
+ const selector = normalized.slice(7).trim();
238028
+ if (!selector) return [];
238029
+ const exact = tools.find((tool) => String(tool?.name || "").toLowerCase() === selector);
238030
+ if (exact) return [exact];
238031
+ return tools.filter((tool) => {
238032
+ return String(tool?.name || "").toLowerCase().split(/__|:/).at(-1) === selector;
238033
+ });
238034
+ }
238035
+ const tokens = normalized.split(/\s+/).filter(Boolean);
238036
+ const requiredNameTokens = tokens.filter((token) => token.startsWith("+")).map((token) => token.slice(1)).filter(Boolean);
238037
+ const searchTokens = tokens.filter((token) => !token.startsWith("+"));
238038
+ const eligibleTools = tools.filter((tool) => {
238039
+ const name = String(tool?.name || "").toLowerCase();
238040
+ return requiredNameTokens.every((token) => name.includes(token));
238041
+ });
238042
+ const firstSelector = searchTokens[0];
238043
+ if (firstSelector) {
238044
+ const exactNameMatches = eligibleTools.filter((tool) => String(tool?.name || "").toLowerCase() === firstSelector);
238045
+ if (exactNameMatches.length === 1) return exactNameMatches;
238046
+ const exactLeafMatches = eligibleTools.filter((tool) => {
238047
+ return String(tool?.name || "").toLowerCase().split(/__|:/).at(-1) === firstSelector;
238048
+ });
238049
+ if (exactLeafMatches.length === 1) return exactLeafMatches;
238050
+ }
238051
+ return tools.map((tool) => {
238052
+ const name = String(tool?.name || "").toLowerCase();
238053
+ const description = String(tool?.description || "").toLowerCase();
238054
+ if (!requiredNameTokens.every((token) => name.includes(token))) return null;
238055
+ if (!searchTokens.every((token) => name.includes(token) || description.includes(token))) return null;
238056
+ return {
238057
+ tool,
238058
+ score: searchTokens.reduce((total, token) => total + (name.includes(token) ? 5 : 1), 0) + requiredNameTokens.length * 5
238059
+ };
238060
+ }).filter(Boolean).sort((left, right) => right.score - left.score || String(left.tool.name).localeCompare(String(right.tool.name))).slice(0, 5).map((entry) => entry.tool);
238061
+ }
238062
+ function relatedSearchTerms(token) {
238063
+ return TOOL_SEARCH_RELATED_TERM_GROUPS.find((terms) => terms.includes(token)) || [token];
238064
+ }
238065
+ function rankingIntentTexts(value) {
238066
+ const text = String(value || "");
238067
+ const channelBodies = [...text.matchAll(/(?:^|\r?\n)<channel\b[^>]*>\r?\n([\s\S]*?)\r?\n<\/channel>(?=\r?\n|$)/giu)].map((match) => match[1]);
238068
+ return (channelBodies.length > 0 ? channelBodies : [text]).map((intentText) => normalizeDeferredToolQuery(intentText.replace(TOOL_RANK_EMBEDDED_CONTENT_RE, " ")));
238069
+ }
238070
+ function rankingTokens(value) {
238071
+ return [...new Set(String(value || "").normalize("NFKC").toLowerCase().match(/[\p{L}\p{N}]{3,}/gu) || [])].filter((token) => !TOOL_RANK_STOP_WORDS.has(token));
238072
+ }
238073
+ function toolRankingDocument(tool) {
238074
+ const name = String(tool?.name || "");
238075
+ const leaf = name.split(/__|:/).at(-1) || name;
238076
+ const splitToolName = (value) => String(value || "").replace(/([a-z0-9])([A-Z])/g, "$1 $2").replaceAll("_", " ");
238077
+ const nameTokens = rankingTokens(`${splitToolName(name)} ${splitToolName(leaf)}`);
238078
+ const descriptionTokens = rankingTokens(tool?.description);
238079
+ const parameterText = [];
238080
+ const properties = tool?.parameters?.properties;
238081
+ if (properties && typeof properties === "object") for (const [parameterName, definition] of Object.entries(properties)) parameterText.push(parameterName, definition?.description || "");
238082
+ const exampleText = Array.isArray(tool?.examples) ? tool.examples.map((example) => example?.prompt || "").join(" ") : "";
238083
+ return {
238084
+ name,
238085
+ nameTokens: new Set(nameTokens),
238086
+ descriptionTokens: new Set(descriptionTokens),
238087
+ detailTokens: new Set(rankingTokens(`${parameterText.join(" ")} ${exampleText}`))
238088
+ };
238089
+ }
238090
+ function tokenMatchScore(document, token) {
238091
+ if (document.nameTokens.has(token)) return 10;
238092
+ if (document.descriptionTokens.has(token)) return 5;
238093
+ if (document.detailTokens.has(token)) return 4;
238094
+ const related = relatedSearchTerms(token).filter((term) => term !== token);
238095
+ if (related.some((term) => document.nameTokens.has(term))) return 5;
238096
+ if (related.some((term) => document.descriptionTokens.has(term))) return 3;
238097
+ if (related.some((term) => document.detailTokens.has(term))) return 2;
238098
+ return 0;
238099
+ }
238100
+ /**
238101
+ * Select a tiny, high-confidence subset of deferred schemas for the current
238102
+ * request. This mirrors AnythingLLM's tool-reranker boundary without adding an
238103
+ * embedding runtime: King reuses the descriptions it already owns and keeps
238104
+ * ToolSearch available whenever the lexical evidence is weak or ambiguous.
238105
+ */
238106
+ function rankedToolNamesForTurnText(tools, value, options = {}) {
238107
+ const maxTools = Math.max(0, Math.min(MAX_AUTO_RANKED_TOOLS, Number.isInteger(options.maxTools) ? options.maxTools : MAX_AUTO_RANKED_TOOLS));
238108
+ if (maxTools === 0) return /* @__PURE__ */ new Set();
238109
+ const queryTokens = [...new Set(rankingIntentTexts(value).flatMap(rankingTokens))];
238110
+ if (queryTokens.length === 0) return /* @__PURE__ */ new Set();
238111
+ const ranked = (Array.isArray(tools) ? tools : []).map((tool) => {
238112
+ const document = toolRankingDocument(tool);
238113
+ if (!document.name) return null;
238114
+ let score = 0;
238115
+ let matchedTokens = 0;
238116
+ let exactNameMatches = 0;
238117
+ let relatedNameMatches = 0;
238118
+ for (const token of queryTokens) {
238119
+ const tokenScore = tokenMatchScore(document, token);
238120
+ if (tokenScore === 0) continue;
238121
+ score += tokenScore;
238122
+ matchedTokens += 1;
238123
+ if (document.nameTokens.has(token)) exactNameMatches += 1;
238124
+ else if (relatedSearchTerms(token).some((term) => document.nameTokens.has(term))) relatedNameMatches += 1;
238125
+ }
238126
+ return matchedTokens >= 2 || exactNameMatches >= 1 && score >= 10 || relatedNameMatches >= 1 && score >= 5 ? {
238127
+ name: document.name,
238128
+ score,
238129
+ matchedTokens,
238130
+ exactNameMatches
238131
+ } : null;
238132
+ }).filter(Boolean).sort((left, right) => right.matchedTokens - left.matchedTokens || right.score - left.score || right.exactNameMatches - left.exactNameMatches || left.name.localeCompare(right.name));
238133
+ return new Set(ranked.slice(0, maxTools).map((entry) => entry.name));
238134
+ }
238135
+ function rankedSupportToolNamesForGoal(tools, goal, origin) {
238136
+ if (origin?.kind !== "system_trigger" || origin?.name !== "goal_continuation") return /* @__PURE__ */ new Set();
238137
+ if (goal?.status !== "active") return /* @__PURE__ */ new Set();
238138
+ const checkpoint = goal.actionCheckpoint;
238139
+ if (checkpoint?.phase === "wait" || checkpoint?.nextTrigger?.kind !== "immediate") return /* @__PURE__ */ new Set();
238140
+ const supportChoice = String(checkpoint?.problemFrame?.supportChoice ?? "").trim();
238141
+ if (!supportChoice.match(/^(?:tool|skill)\s*:\s*(.+)$/iu)?.[1]) return /* @__PURE__ */ new Set();
238142
+ return rankedToolNamesForTurnText(tools, supportChoice, { maxTools: 1 });
238143
+ }
238144
+ function searchRelatedDeferredTools(tools, query, options = {}) {
238145
+ const normalized = normalizeDeferredToolQuery(query).toLowerCase();
238146
+ if (!normalized || normalized.startsWith("select:")) return [];
238147
+ const tokens = normalized.split(/\s+/).filter(Boolean);
238148
+ const requestedNameTokens = tokens.filter((token) => token.startsWith("+")).map((token) => token.slice(1)).filter(Boolean);
238149
+ const relaxRequiredNameTokens = options.relaxRequiredNameTokens === true && requestedNameTokens.length > 0;
238150
+ const requiredNameTokens = relaxRequiredNameTokens ? [] : requestedNameTokens;
238151
+ const searchTokens = [...tokens.filter((token) => !token.startsWith("+")), ...relaxRequiredNameTokens ? requestedNameTokens : []].filter((token) => !TOOL_SEARCH_INTENT_WORDS.has(token));
238152
+ if (searchTokens.length < 2 && !TOOL_SEARCH_SINGLE_TERM_RELATED_FALLBACKS.has(searchTokens[0])) return [];
238153
+ const minimumMatchedTokens = searchTokens.length === 1 ? 1 : Math.max(2, Math.ceil(searchTokens.length * .75));
238154
+ const ranked = tools.map((tool) => {
238155
+ const name = String(tool?.name || "").toLowerCase();
238156
+ const description = String(tool?.description || "").toLowerCase();
238157
+ if (!requiredNameTokens.every((token) => name.includes(token))) return null;
238158
+ let matchedTokens = 0;
238159
+ let score = requiredNameTokens.length * 8;
238160
+ for (const token of searchTokens) {
238161
+ if (name.includes(token)) {
238162
+ matchedTokens += 1;
238163
+ score += 8;
238164
+ continue;
238165
+ }
238166
+ if (description.includes(token)) {
238167
+ matchedTokens += 1;
238168
+ score += 4;
238169
+ continue;
238170
+ }
238171
+ const related = relatedSearchTerms(token).filter((term) => term !== token);
238172
+ if (related.some((term) => name.includes(term))) {
238173
+ matchedTokens += 1;
238174
+ score += 3;
238175
+ } else if (related.some((term) => description.includes(term))) {
238176
+ matchedTokens += 1;
238177
+ score += 2;
238178
+ }
238179
+ }
238180
+ return matchedTokens >= minimumMatchedTokens ? {
238181
+ tool,
238182
+ score,
238183
+ matchedTokens
238184
+ } : null;
238185
+ }).filter(Boolean).sort((left, right) => right.matchedTokens - left.matchedTokens || right.score - left.score || String(left.tool.name).localeCompare(String(right.tool.name)));
238186
+ if (relaxRequiredNameTokens && ranked.length > 0) {
238187
+ const best = ranked[0];
238188
+ return ranked.filter((entry) => entry.matchedTokens === best.matchedTokens && entry.score === best.score).slice(0, 5).map((entry) => entry.tool);
238189
+ }
238190
+ return ranked.slice(0, 5).map((entry) => entry.tool);
238191
+ }
238192
+ function rememberLoadedTool(loadedToolNames, name) {
238193
+ loadedToolNames.delete(name);
238194
+ loadedToolNames.add(name);
238195
+ while (loadedToolNames.size > MAX_PERSISTENT_DEFERRED_TOOLS) loadedToolNames.delete(loadedToolNames.values().next().value);
238196
+ }
238197
+ function rememberDeferredToolAfterNotFound(loadedToolNames, toolName, result) {
238198
+ if (!(loadedToolNames instanceof Set) || result?.isError !== true) return false;
238199
+ const name = String(toolName || "").trim();
238200
+ if (!name || String(result?.output || "").trim() !== `Tool "${name}" not found`) return false;
238201
+ rememberLoadedTool(loadedToolNames, name);
238202
+ return true;
238203
+ }
238204
+ function createDeferredToolLoader(selectedTools, deferredTools, loadedToolNames = /* @__PURE__ */ new Set()) {
238205
+ if (!Array.isArray(selectedTools)) throw new TypeError("selectedTools must be an array");
238206
+ if (!(loadedToolNames instanceof Set)) throw new TypeError("loadedToolNames must be a Set");
238207
+ const available = /* @__PURE__ */ new Map();
238208
+ for (const tool of Array.isArray(deferredTools) ? deferredTools : []) {
238209
+ const name = String(tool?.name || "").trim();
238210
+ if (name && !available.has(name)) available.set(name, tool);
238211
+ }
238212
+ const deferredCount = available.size;
238213
+ const loader = {
238214
+ name: DEFERRED_TOOL_LOADER_NAME,
238215
+ description: [
238216
+ `Search ${deferredCount} deferred ${deferredCount === 1 ? "tool schema" : "tool schemas"} without loading all definitions.`,
238217
+ "Use select:leaf_name for one known tool, plain keywords to search, or +word to require that word in the tool name.",
238218
+ `Loaded tools are available in the next step; the ${MAX_PERSISTENT_DEFERRED_TOOLS} most recently selected schemas remain loaded.`
238219
+ ].join("\n"),
238220
+ parameters: {
238221
+ type: "object",
238222
+ properties: { query: {
238223
+ type: "string",
238224
+ maxLength: MAX_TOOL_SEARCH_QUERY_CHARS,
238225
+ description: "Exact selection or keyword query, for example select:download_attachment or +slack send."
238226
+ } },
238227
+ required: ["query"],
238228
+ additionalProperties: false
238229
+ },
238230
+ resolveExecution(args) {
238231
+ const query = normalizeDeferredToolQuery(args?.query);
238232
+ return {
238233
+ description: query ? `Searching tool schemas for ${query}` : "Searching tool schemas",
238234
+ approvalRule: DEFERRED_TOOL_LOADER_NAME,
238235
+ execute: async () => {
238236
+ let matches = searchDeferredTools([...available.values()], query);
238237
+ let relatedFallback = false;
238238
+ if (matches.length === 0) {
238239
+ matches = searchRelatedDeferredTools([...available.values()], query);
238240
+ relatedFallback = matches.length > 0;
238241
+ }
238242
+ if (matches.length === 0) {
238243
+ matches = searchRelatedDeferredTools([...available.values()], query, { relaxRequiredNameTokens: true });
238244
+ relatedFallback = matches.length > 0;
238245
+ }
238246
+ if (matches.length === 0) {
238247
+ const alreadyLoaded = query.toLowerCase().startsWith("select:") ? searchDeferredTools(selectedTools.filter((tool) => tool?.name !== DEFERRED_TOOL_LOADER_NAME), query) : [];
238248
+ if (alreadyLoaded.length === 1) return {
238249
+ isError: false,
238250
+ output: `Tool schema already loaded: ${alreadyLoaded[0].name}. Invoke it now.`
238251
+ };
238252
+ return {
238253
+ isError: false,
238254
+ output: [
238255
+ `No eligible deferred tool is available for: ${query || "[empty query]"}.`,
238256
+ "Use a loaded alternative or report this integration as unavailable.",
238257
+ "ToolSearch remains available for a different tool needed later."
238258
+ ].join(" ")
238259
+ };
238260
+ }
238261
+ if (query.toLowerCase().startsWith("select:") && matches.length !== 1) return {
238262
+ isError: true,
238263
+ output: `Tool selection is ambiguous: ${matches.map((tool) => tool.name).join(", ")}`
238264
+ };
238265
+ const loaded = [];
238266
+ for (const tool of matches) {
238267
+ const name = String(tool.name);
238268
+ if (!selectedTools.some((candidate) => candidate?.name === name)) selectedTools.push(tool);
238269
+ rememberLoadedTool(loadedToolNames, name);
238270
+ loaded.push(name);
238271
+ }
238272
+ return {
238273
+ isError: false,
238274
+ output: `${relatedFallback ? "Loaded related tool schemas" : "Loaded tool schemas"}: ${loaded.join(", ")}. Invoke them in the next step.`
238275
+ };
238276
+ }
238277
+ };
238278
+ }
238279
+ };
238280
+ deferredToolLoaders.add(loader);
238281
+ return loader;
238282
+ }
238283
+ module.exports = {
238284
+ isDeferredToolLoader: (tool) => deferredToolLoaders.has(tool),
238285
+ CORE_TOOL_NAMES,
238286
+ DEFERRED_TOOL_LOADER_NAME,
238287
+ MAX_PERSISTENT_DEFERRED_TOOLS,
238288
+ MAX_AUTO_RANKED_TOOLS,
238289
+ MAX_TOOL_SEARCH_QUERY_CHARS,
238290
+ TOOL_SCHEMA_BUDGET_RATIO,
238291
+ TOOL_SCHEMA_MAX_TOKENS,
238292
+ createDeferredToolLoader,
238293
+ deferredToolCatalog,
238294
+ deferredToolNames,
238295
+ mediaGenerationToolNamesForText,
238296
+ mediaToolNamesForTurnText,
238297
+ normalizeDeferredToolQuery,
238298
+ rankedSupportToolNamesForGoal,
238299
+ rankedToolNamesForTurnText,
238300
+ rememberDeferredToolAfterNotFound,
238301
+ toolSchemaBudgetTokens
238302
+ };
238303
+ })))();
237791
238304
  const DEFAULT_APPROVE_TOOLS = new Set([
237792
238305
  "Read",
237793
238306
  "Grep",
@@ -237810,7 +238323,7 @@ const DEFAULT_APPROVE_TOOLS = new Set([
237810
238323
  var DefaultToolApprovePermissionPolicy = class {
237811
238324
  name = "default-tool-approve";
237812
238325
  evaluate(context) {
237813
- if (!DEFAULT_APPROVE_TOOLS.has(context.toolCall.name)) return;
238326
+ if (!DEFAULT_APPROVE_TOOLS.has(context.toolCall.name) && !(0, import_turn_tool_performance_policy.isDeferredToolLoader)(context.tool)) return;
237814
238327
  return { kind: "approve" };
237815
238328
  }
237816
238329
  };
@@ -239495,6 +240008,7 @@ function isWithin$1(child, parent) {
239495
240008
  //#endregion
239496
240009
  //#region ../../packages/agent-core/src/skill/registry.ts
239497
240010
  const LISTING_DESC_MAX = 250;
240011
+ const MODEL_SKILL_LISTING_MAX_CHARS = 8e3;
239498
240012
  var SessionSkillRegistry = class {
239499
240013
  byName = /* @__PURE__ */ new Map();
239500
240014
  byPluginAndName = /* @__PURE__ */ new Map();
@@ -239574,10 +240088,58 @@ var SessionSkillRegistry = class {
239574
240088
  getModelSkillListing() {
239575
240089
  const lines = ["DISREGARD any earlier skill listings. Current available skills:"];
239576
240090
  const listing = renderGroupedSkills(this.listInvocableSkills().filter((skill) => skill.metadata.isSubSkill !== true), formatModelSkill);
240091
+ if (listing.length > MODEL_SKILL_LISTING_MAX_CHARS) {
240092
+ const count = this.listInvocableSkills().filter((skill) => skill.metadata.isSubSkill !== true).length;
240093
+ return [
240094
+ lines[0],
240095
+ `${count} skills are available on demand.`,
240096
+ "Before specialized work, search descriptions with Skill({skill: \"task keywords\", search: true}).",
240097
+ "Search returns names and metadata only. Invoke Skill({skill: \"exact-name\", args: \"...\"}) to load instructions.",
240098
+ "Use an empty search string to browse; follow nextOffset with offset to see more results.",
240099
+ "A search miss is not proof that no skill exists; try its exact name or browse the catalog."
240100
+ ].join("\n");
240101
+ }
239577
240102
  if (listing.length > 0) lines.push(listing);
239578
240103
  return lines.length === 1 ? "" : lines.join("\n");
239579
240104
  }
239580
240105
  };
240106
+ function compactLegacyModelSkillListing(prompt, registry) {
240107
+ const header = "DISREGARD any earlier skill listings. Current available skills:";
240108
+ if (!prompt.includes(header)) return prompt;
240109
+ const listing = renderGroupedSkills(registry.listInvocableSkills().filter((skill) => skill.metadata.isSubSkill !== true), formatModelSkill);
240110
+ if (listing.length <= MODEL_SKILL_LISTING_MAX_CHARS) return prompt;
240111
+ const legacy = `${header}\n${listing}`;
240112
+ const start = prompt.indexOf(legacy);
240113
+ if (start < 0) return prompt;
240114
+ const end = start + legacy.length;
240115
+ if (end < prompt.length && !prompt.startsWith("\n\n", end)) return prompt;
240116
+ return prompt.slice(0, start) + registry.getModelSkillListing() + prompt.slice(end);
240117
+ }
240118
+ function searchModelSkills(skills, query, offset = 0) {
240119
+ const normalize = (value) => value.normalize("NFKC").toLowerCase();
240120
+ const words = normalize(query.slice(0, 1e3)).match(/[\p{L}\p{N}_-]+/gu) ?? [];
240121
+ const matches = skills.filter((skill) => skill.metadata.disableModelInvocation !== true && skill.metadata.isSubSkill !== true && isInlineSkillType(skill.metadata.type)).map((skill) => {
240122
+ const name = normalize(skill.name);
240123
+ const metadata = normalize(`${skill.name} ${skill.description} ${skill.metadata.whenToUse ?? ""}`);
240124
+ return {
240125
+ skill,
240126
+ matches: words.every((word) => metadata.includes(word)),
240127
+ score: words.filter((word) => name.includes(word)).length
240128
+ };
240129
+ }).filter((item) => item.matches).sort((a, b) => b.score - a.score || a.skill.name.localeCompare(b.skill.name));
240130
+ const start = Number.isSafeInteger(offset) && offset >= 0 ? offset : 0;
240131
+ const page = matches.slice(start, start + 10);
240132
+ return JSON.stringify({
240133
+ total: matches.length,
240134
+ offset: start,
240135
+ nextOffset: start + page.length < matches.length ? start + page.length : null,
240136
+ matches: page.map(({ skill }) => ({
240137
+ name: skill.name,
240138
+ description: truncate(skill.description, LISTING_DESC_MAX),
240139
+ path: skill.path
240140
+ }))
240141
+ });
240142
+ }
239581
240143
  function pluginSkillKey(pluginId, skillName) {
239582
240144
  return `${pluginId}\0${normalizeSkillName(skillName)}`;
239583
240145
  }
@@ -256149,7 +256711,7 @@ function isQuestionResponse(result) {
256149
256711
  }
256150
256712
  //#endregion
256151
256713
  //#region ../../packages/agent-core/src/tools/builtin/collaboration/skill-tool.md?raw
256152
- var skill_tool_default = "Invoke a registered skill from the current skill listing. BLOCKING REQUIREMENT: when a skill from the listing matches the user's request, you MUST call this tool (not free-form text). Do not re-invoke a skill to repeat work already done: if a `<blun-skill-loaded>` block for it with the same `args` is already present in the conversation, follow those instructions directly instead of calling the tool again. Do call the tool again when you need the skill with different arguments — the loaded block was expanded with the earlier `args` and will not reflect new inputs.\n\nAUTO-SKILL-ROUTING — you MUST proactively use skills WITHOUT being asked:\n- Before responding to any user request, check if an available skill matches the task. If it does, call this tool FIRST — do not attempt the task without the skill.\n- Images/screenshots → use the image/vision skill (e.g. \"screenshot-lesen\") to read and understand the image content before responding.\n- Design/UI tasks → use the appropriate design skill (e.g. \"design-taste-frontend\", \"high-end-visual-design\", \"apple-design\", \"huashu-design\", \"emil-design-eng\", \"motion-design-taste\", \"stitch-design-taste\", \"minimalist-ui\", \"industrial-brutalist-ui\", \"redesign-existing-projects\").\n- FOR ANY UI/frontend/visual output you MUST first load a design skill — never produce raw/default-styled UI. Gradients-everywhere, random emoji-icons, generic AI-looking layouts = FORBIDDEN slop. Always use a design skill before writing UI code.\n- Video/media tasks → use \"ffmpeg\" or the relevant media skill.\n- Web scraping/reading → use \"web-lesen\".\n- PDF/document tasks → use the PDF or document skill.\n- When in doubt whether a skill applies, call it — using a skill when not needed is harmless; NOT using a skill when it would help is a failure.\n- Never tell the user \"I don't have the right tool\" if a matching skill exists in the listing — use it.\n- This is automatic: do not ask the user \"should I use a skill?\" — just use it.\n";
256714
+ var skill_tool_default = "Invoke a registered skill from the current skill listing. BLOCKING REQUIREMENT: when a skill from the listing matches the user's request, you MUST call this tool (not free-form text). Do not re-invoke a skill to repeat work already done: if a `<blun-skill-loaded>` block for it with the same `args` is already present in the conversation, follow those instructions directly instead of calling the tool again. Do call the tool again when you need the skill with different arguments — the loaded block was expanded with the earlier `args` and will not reflect new inputs.\n\nFor a deferred catalog, call `Skill({skill: \"task keywords\", search: true})` first. Search returns at most 10 eligible names and descriptions without loading skill instructions. An empty search browses the catalog; pass `nextOffset` as `offset` for the next page. Then invoke the chosen exact name with `search` omitted.\n\nAUTO-SKILL-ROUTING — you MUST proactively use skills WITHOUT being asked:\n- Before responding to any user request, check if an available skill matches the task. If it does, call this tool FIRST — do not attempt the task without the skill.\n- Images/screenshots → use the image/vision skill (e.g. \"screenshot-lesen\") to read and understand the image content before responding.\n- Design/UI tasks → use the appropriate design skill (e.g. \"design-taste-frontend\", \"high-end-visual-design\", \"apple-design\", \"huashu-design\", \"emil-design-eng\", \"motion-design-taste\", \"stitch-design-taste\", \"minimalist-ui\", \"industrial-brutalist-ui\", \"redesign-existing-projects\").\n- FOR ANY UI/frontend/visual output you MUST first load a design skill — never produce raw/default-styled UI. Gradients-everywhere, random emoji-icons, generic AI-looking layouts = FORBIDDEN slop. Always use a design skill before writing UI code.\n- Video/media tasks → use \"ffmpeg\" or the relevant media skill.\n- Web scraping/reading → use \"web-lesen\".\n- PDF/document tasks → use the PDF or document skill.\n- When in doubt whether a skill applies, call it — using a skill when not needed is harmless; NOT using a skill when it would help is a failure.\n- Never tell the user \"I don't have the right tool\" if a matching skill exists in the listing — use it.\n- This is automatic: do not ask the user \"should I use a skill?\" — just use it.\n";
256153
256715
  var NestedSkillTooDeepError = class extends Error {
256154
256716
  skillName;
256155
256717
  depth;
@@ -256163,7 +256725,9 @@ var NestedSkillTooDeepError = class extends Error {
256163
256725
  };
256164
256726
  const SkillToolInputSchema = object({
256165
256727
  skill: string().describe("The exact name of the skill to invoke, spelled as it appears in the current skill listing (e.g. \"commit\", \"pdf\")."),
256166
- args: string().optional().describe("Optional argument string for the skill, written like a command line (e.g. `-m \"fix bug\"`, `123`, a file path). It is split on whitespace (quotes group a token) and expanded into the skill's placeholders ($NAME, $1, $ARGUMENTS); if the skill body has no placeholders, the whole string is still appended as a trailing `ARGUMENTS:` line. Omit it only when there is nothing to pass.")
256728
+ args: string().optional().describe("Optional argument string for the skill, written like a command line (e.g. `-m \"fix bug\"`, `123`, a file path). It is split on whitespace (quotes group a token) and expanded into the skill's placeholders ($NAME, $1, $ARGUMENTS); if the skill body has no placeholders, the whole string is still appended as a trailing `ARGUMENTS:` line. Omit it only when there is nothing to pass."),
256729
+ search: boolean$1().optional().describe("Search eligible skill metadata using skill as keywords, without activating a skill."),
256730
+ offset: number$1().int().min(0).optional().describe("For search only: nextOffset from a previous page. Each page has at most 10 skills.")
256167
256731
  });
256168
256732
  var SkillTool = class SkillTool {
256169
256733
  agent;
@@ -256176,6 +256740,12 @@ var SkillTool = class SkillTool {
256176
256740
  this.options = options;
256177
256741
  }
256178
256742
  resolveExecution(args) {
256743
+ if (args.search === true) return {
256744
+ description: `Search skill metadata: ${args.skill}`,
256745
+ approvalRule: this.name,
256746
+ matchesRule: (ruleArgs) => matchesGlobRuleSubject(ruleArgs, args.skill),
256747
+ execute: async () => ({ output: searchModelSkills(this.agent.skills?.registry.listInvocableSkills() ?? [], args.skill, args.offset) })
256748
+ };
256179
256749
  return {
256180
256750
  description: `Invoke skill ${args.skill}`,
256181
256751
  display: {
@@ -263653,6 +264223,26 @@ function blunThinkingIntentInput(input) {
263653
264223
  text: channelText
263654
264224
  }];
263655
264225
  }
264226
+ function blunTurnHasAttachment(input) {
264227
+ return input.some((part) => part.type !== "text") || /\b(?:image_path|attachment_file_id)\s*=/i.test(blunExtractText(input));
264228
+ }
264229
+ function greetingHistory(history, anchor) {
264230
+ const start = anchor === void 0 ? -1 : history.indexOf(anchor);
264231
+ if (start < 0) return history;
264232
+ const recent = [];
264233
+ let chars = 0;
264234
+ for (let i = start - 1; i >= 0 && recent.length < 6; i -= 1) {
264235
+ const message = history[i];
264236
+ if (message.role === "tool" || message.toolCalls.length > 0) break;
264237
+ if (message.role !== "assistant" && message.origin?.kind !== "user") continue;
264238
+ const size = blunExtractText(message.content).length;
264239
+ if (chars + size > 12e3 || message.content.some((part) => part.type !== "text")) break;
264240
+ recent.unshift(message);
264241
+ chars += size;
264242
+ }
264243
+ while (recent.length > 0 && recent[0].role !== "user") recent.shift();
264244
+ return [...recent, ...history.slice(start)];
264245
+ }
263656
264246
  /**
263657
264247
  * BLUN: builtin tools kept when the full schema set would overflow a small
263658
264248
  * context window. The essential read / edit / run / track loop — enough for
@@ -263663,12 +264253,14 @@ function blunThinkingIntentInput(input) {
263663
264253
  */
263664
264254
  const BLUN_LEAN_TOOL_NAMES = new Set([
263665
264255
  "Read",
264256
+ "ReadBatch",
263666
264257
  "Write",
263667
264258
  "Edit",
263668
264259
  "Grep",
263669
264260
  "Glob",
263670
264261
  "Bash",
263671
264262
  "TodoList",
264263
+ "Skill",
263672
264264
  "CompactConversation",
263673
264265
  "MistakeRecord"
263674
264266
  ]);
@@ -263694,25 +264286,20 @@ function blunToolsForOrigin(tools, origin) {
263694
264286
  return tools.filter((tool) => !BLUN_TELEGRAM_OUTBOUND_TOOL_RE.test(tool.name));
263695
264287
  }
263696
264288
  /**
263697
- * When the full tool schemas would consume more than this fraction of the model
263698
- * context window, drop to {@link BLUN_LEAN_TOOL_NAMES}. Big-window cloud models
263699
- * (their schemas are a rounding error) keep the full set; only small local
263700
- * models (King) fall back — so the guard is self-tuning and never touches a
263701
- * config where tools comfortably fit.
263702
- */
263703
- const BLUN_TOOL_BUDGET_RATIO = .25;
263704
- /**
263705
- * Choose the tool set for a task turn. Returns the full set unless the model
263706
- * window is small enough that the schemas alone blow the context budget, in
263707
- * which case the lean essential set is used. Never returns an empty list when
263708
- * tools were requested: a profile with no lean-set matches keeps the full set
263709
- * rather than sending a tool-less task turn.
264289
+ * Restore bounded schema disclosure even on large-context models. Only tools
264290
+ * already eligible for this turn enter the loader; execution still follows
264291
+ * the ordinary permission pipeline.
263710
264292
  */
263711
- function blunSelectTurnTools(tools, maxContextTokens) {
263712
- if (maxContextTokens === void 0 || maxContextTokens <= 0) return tools;
263713
- if (estimateTokensForTools(tools) <= maxContextTokens * BLUN_TOOL_BUDGET_RATIO) return tools;
263714
- const lean = tools.filter((tool) => BLUN_LEAN_TOOL_NAMES.has(tool.name) || BLUN_LEAN_KEEP_RE.test(tool.name) || BLUN_LANGUAGE_REVIEW_TOOL_NAMES.has(tool.name));
263715
- return lean.length > 0 ? lean : tools;
264293
+ function blunSelectTurnTools(tools, maxContextTokens, loadedToolNames = /* @__PURE__ */ new Set(), requiredToolNames = /* @__PURE__ */ new Set(), selected = []) {
264294
+ if (estimateTokensForTools(tools) <= (0, import_turn_tool_performance_policy.toolSchemaBudgetTokens)(maxContextTokens) || tools.some((tool) => tool.name === "ToolSearch")) {
264295
+ selected.splice(0, selected.length, ...tools);
264296
+ return selected;
264297
+ }
264298
+ selected.splice(0, selected.length, ...tools.filter((tool) => BLUN_LEAN_TOOL_NAMES.has(tool.name) || BLUN_LEAN_KEEP_RE.test(tool.name) || BLUN_LANGUAGE_REVIEW_TOOL_NAMES.has(tool.name) || loadedToolNames.has(tool.name) || requiredToolNames.has(tool.name)));
264299
+ const names = new Set(selected.map((tool) => tool.name));
264300
+ const deferred = tools.filter((tool) => !names.has(tool.name));
264301
+ if (deferred.length > 0) selected.push((0, import_turn_tool_performance_policy.createDeferredToolLoader)(selected, deferred, loadedToolNames));
264302
+ return selected;
263716
264303
  }
263717
264304
  const LLM_NOT_SET_MESSAGE = "LLM not set, send \"/login\" to login";
263718
264305
  /** Origin tag for the synthetic "continue" prompt that drives each goal turn. */
@@ -263770,6 +264357,8 @@ const GOAL_CONTINUATION_PROMPT = [
263770
264357
  var TurnFlow = class {
263771
264358
  agent;
263772
264359
  admissionClosed = false;
264360
+ loadedToolNames = /* @__PURE__ */ new Set();
264361
+ refreshSelectedTools;
263773
264362
  executions = new ExecutionLedger();
263774
264363
  executingControllers = /* @__PURE__ */ new Set();
263775
264364
  steerBuffer = [];
@@ -264006,6 +264595,9 @@ var TurnFlow = class {
264006
264595
  if (snapshot.throughSequence < 0) return false;
264007
264596
  return this.flushSteerBuffer(turnId, snapshot.throughSequence);
264008
264597
  }
264598
+ refreshRequestTools(snapshot) {
264599
+ return this.refreshSelectedTools?.(snapshot);
264600
+ }
264009
264601
  bufferSteer(input, origin, targetTurnId) {
264010
264602
  this.steerBuffer.push({
264011
264603
  sequence: this.nextSteerSequence,
@@ -264389,17 +264981,41 @@ var TurnFlow = class {
264389
264981
  const personalMemoryRecall = await this.agent.injection.injectPersonalMemoryForTurn(turnId, input, origin, signal);
264390
264982
  await this.agent.injection.injectGoal();
264391
264983
  this.setActiveSteerAcceptance(turnId, true);
264392
- const turnNeedsTools = blunTurnNeedsTools(input, origin);
264393
264984
  const turnNeedsThinking = blunTurnNeedsTools(blunThinkingIntentInput(input), origin);
264985
+ let toolInput = input;
264986
+ let taskToolsNeeded = turnNeedsThinking || blunTurnHasAttachment(input);
264987
+ const greetingAnchor = this.agent.context.history.findLast((message) => message.role === "user" && message.origin?.kind === "user");
264988
+ let partialContext = false;
264989
+ const turnHistory = () => {
264990
+ const history = personalMemoryRecall === void 0 ? this.agent.context.history : [...this.agent.context.history, personalMemoryRecall];
264991
+ const selected = taskToolsNeeded ? history : greetingHistory(history, greetingAnchor);
264992
+ partialContext = selected.length < history.length;
264993
+ return selected;
264994
+ };
264394
264995
  const turnLLM = this.agent.llmForTurn(turnNeedsThinking ? void 0 : "off");
264996
+ const turnTools = this.agent.tools.captureTurnTools();
264395
264997
  while (true) {
264396
264998
  signal.throwIfAborted();
264397
264999
  const model = this.agent.config.model;
264398
265000
  const loopControl = this.agent.blunConfig?.loopControl;
264399
265001
  let stopForGoalBudget = false;
264400
265002
  try {
264401
- const eligibleTools = blunToolsForOrigin(this.agent.injection.filterPersonalMemoryToolsForTurn(turnId, input, origin, this.agent.tools.loopTools), origin);
264402
- const selectedTools = turnNeedsTools ? blunSelectTurnTools(eligibleTools, this.agent.config.modelCapabilities?.max_context_tokens) : eligibleTools.filter((tool) => BLUN_LANGUAGE_REVIEW_TOOL_NAMES.has(tool.name));
265003
+ const selectedTools = [];
265004
+ this.refreshSelectedTools = (snapshot) => {
265005
+ if (snapshot.messages.length > 0) toolInput = snapshot.messages.flatMap((message) => message.content);
265006
+ const eligibleTools = blunToolsForOrigin(this.agent.injection.filterPersonalMemoryToolsForTurn(turnId, input, origin, turnTools()), origin);
265007
+ const intent = blunThinkingIntentInput(toolInput);
265008
+ taskToolsNeeded ||= blunTurnNeedsTools(intent, origin) || blunTurnHasAttachment(toolInput);
265009
+ const required = (0, import_turn_tool_performance_policy.mediaToolNamesForTurnText)(blunExtractText(toolInput));
265010
+ if (taskToolsNeeded) {
265011
+ blunSelectTurnTools(eligibleTools, this.agent.config.modelCapabilities?.max_context_tokens, this.loadedToolNames, required, selectedTools);
265012
+ for (const name of (0, import_turn_tool_performance_policy.rankedToolNamesForTurnText)(eligibleTools, blunExtractText(intent))) {
265013
+ const tool = eligibleTools.find((candidate) => candidate.name === name);
265014
+ 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);
265015
+ }
265016
+ } 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)));
265017
+ return selectedTools;
265018
+ };
264403
265019
  return (await runTurn({
264404
265020
  turnId: String(turnId),
264405
265021
  signal,
@@ -264410,15 +265026,15 @@ var TurnFlow = class {
264410
265026
  }, completion);
264411
265027
  },
264412
265028
  llm: turnLLM,
264413
- buildMessages: () => this.agent.context.project(personalMemoryRecall === void 0 ? this.agent.context.history : [...this.agent.context.history, personalMemoryRecall], { dropOrphanResults: true }),
264414
- buildMessagesStrict: () => this.agent.context.project(personalMemoryRecall === void 0 ? this.agent.context.history : [...this.agent.context.history, personalMemoryRecall], {
265029
+ buildMessages: () => this.agent.context.project(turnHistory(), { dropOrphanResults: true }),
265030
+ buildMessagesStrict: () => this.agent.context.project(turnHistory(), {
264415
265031
  synthesizeMissing: true,
264416
265032
  dropOrphanResults: true,
264417
265033
  dedupeDuplicateToolCalls: true,
264418
265034
  dropLeadingNonUser: true,
264419
265035
  mergeConsecutiveAssistants: true
264420
265036
  }),
264421
- dispatchEvent: this.buildDispatchEvent(turnId, signal, onFinalResponse),
265037
+ dispatchEvent: this.buildDispatchEvent(turnId, signal, onFinalResponse, () => partialContext),
264422
265038
  tools: selectedTools,
264423
265039
  log: this.agent.log,
264424
265040
  maxSteps: loopControl?.maxStepsPerTurn,
@@ -264535,6 +265151,8 @@ var TurnFlow = class {
264535
265151
  error
264536
265152
  });
264537
265153
  throw error;
265154
+ } finally {
265155
+ this.refreshSelectedTools = void 0;
264538
265156
  }
264539
265157
  }
264540
265158
  }
@@ -264555,13 +265173,16 @@ var TurnFlow = class {
264555
265173
  this.agent.log.warn("failed to close abandoned tool exchange", { error });
264556
265174
  }
264557
265175
  }
264558
- buildDispatchEvent(turnId, signal, onFinalResponse) {
265176
+ buildDispatchEvent(turnId, signal, onFinalResponse, usesPartialContext) {
264559
265177
  let responseStepUuid;
264560
265178
  let responseParts = [];
264561
265179
  let responseHasToolCall = false;
264562
265180
  const dispatch = createLoopEventDispatcher({
264563
265181
  appendTranscriptRecord: async (event) => {
264564
- this.agent.context.appendLoopEvent(event);
265182
+ this.agent.context.appendLoopEvent(event.type === "step.end" && usesPartialContext?.() ? {
265183
+ ...event,
265184
+ contextCoverage: "partial"
265185
+ } : event);
264565
265186
  },
264566
265187
  emitLiveEvent: (event) => {
264567
265188
  this.agent.cron?.observeLoopEvent(turnId, event);
@@ -267507,6 +268128,17 @@ var ToolManager = class {
267507
268128
  const names = uniq([...this.enabledTools, ...mcpNames]).toSorted((a, b) => a.localeCompare(b)).filter((name) => !(hideGoalMutationTools && (name === "SetGoalBudget" || name === "UpdateGoal")));
267508
268129
  return (allowedTools !== void 0 && allowedTools.length > 0 ? names.filter((name) => allowedTools.includes(name)) : names).map((name) => this.userTools.get(name) ?? this.mcpTools.get(name)?.tool ?? this.builtinTools.get(name)).filter((tool) => !!tool);
267509
268130
  }
268131
+ captureTurnTools() {
268132
+ if (this.loopToolsOverride !== void 0) {
268133
+ const tools = this.loopToolsOverride;
268134
+ return () => tools;
268135
+ }
268136
+ const fixed = this.loopTools.filter((tool) => this.mcpTools.get(tool.name)?.tool !== tool);
268137
+ const fixedNames = new Set(fixed.map((tool) => tool.name));
268138
+ const patterns = [...this.mcpAccessPatterns];
268139
+ const allowed = this.allowedTools === void 0 ? void 0 : new Set(this.allowedTools);
268140
+ return () => [...fixed, ...[...this.mcpTools.entries()].filter(([name]) => !fixedNames.has(name) && patterns.some((pattern) => import_picomatch.default.isMatch(name, pattern)) && (allowed === void 0 || allowed.has(name))).map(([, entry]) => entry.tool)].toSorted((a, b) => a.name.localeCompare(b.name));
268141
+ }
267510
268142
  };
267511
268143
  //#endregion
267512
268144
  //#region ../../packages/agent-core/src/agent/response-review-mcp.ts
@@ -268301,6 +268933,12 @@ var Agent$1 = class {
268301
268933
  this.setActiveResponderModel(alias);
268302
268934
  }, async (_candidate, params, rebuildMessages) => {
268303
268935
  const steers = this.turn.snapshotPendingSteers();
268936
+ const requestTools = this.turn.refreshRequestTools(steers) ?? params.tools;
268937
+ params = {
268938
+ ...params,
268939
+ tools: requestTools,
268940
+ messages: await rebuildMessages()
268941
+ };
268304
268942
  let systemPrompt = this.effectiveSystemPrompt;
268305
268943
  const compacted = await this.fullCompaction.beforeStep(params.signal, {
268306
268944
  messages: params.messages,
@@ -268340,7 +268978,7 @@ var Agent$1 = class {
268340
268978
  capability: runtimeModel.modelCapabilities,
268341
268979
  generate: this.generateForModel(modelAlias),
268342
268980
  completionBudgetConfig,
268343
- reportedContextTokens: () => this.context.tokenCount,
268981
+ reportedContextTokens: () => this.context.tokenCountWithPending,
268344
268982
  idleTimeoutMs: this.blunConfig?.timeout ? this.blunConfig.timeout * 1e3 : void 0,
268345
268983
  onMediaDropped: this.onMediaDropped,
268346
268984
  visionReader: this.visionReader,
@@ -268361,7 +268999,8 @@ var Agent$1 = class {
268361
268999
  this.emitStatusUpdated();
268362
269000
  }
268363
269001
  get effectiveSystemPrompt() {
268364
- const base = this.config.systemPrompt;
269002
+ const registry = this.skills?.registry;
269003
+ const base = registry === void 0 ? this.config.systemPrompt : compactLegacyModelSkillListing(this.config.systemPrompt, registry);
268365
269004
  if (this.runtimeSystemPromptAppend.length === 0) return base;
268366
269005
  return base.length === 0 ? this.runtimeSystemPromptAppend : `${base}\n\n${this.runtimeSystemPromptAppend}`;
268367
269006
  }