blun-king-cli 9.1.585 → 9.1.587
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/bin/context-performance-policy.cjs +5 -1
- package/bin/thinking-only-guard.cjs +1 -0
- package/blun.mjs +150 -8
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
## 9.1.587 - 2026-09-10
|
|
2
|
+
|
|
3
|
+
- Preserve the active tool catalog across buffered instructions and the following work step. Required tools remain available; retained optional schemas are reselected when the existing schema budget is exceeded.
|
|
4
|
+
- Restore earlier automatic context compaction and allow another automatic compaction in the same turn after a successful shrinking summary. Smaller model limits and failure limits remain in force.
|
|
5
|
+
- After a thinking-only timeout, use the existing single recovery attempt with thinking disabled so the model can answer or call a tool. Subsequent ordinary steps retain the chosen thinking setting; cancellation, approval checks and timeout bounds remain in force.
|
|
6
|
+
- Set BLUN_STABLE_STEER_TOOLS=0 or BLUN_EARLY_COMPACTION=0 before starting King to restore the previous behavior for the respective feature. No existing sessions or configuration files are rewritten by this update.
|
|
7
|
+
|
|
8
|
+
## 9.1.586 - 2026-09-09
|
|
9
|
+
|
|
10
|
+
- Fix Telegram receiver startup on Windows when an old bridge PID has been reused by an unrelated process. Preserve uncertain process ownership and never stop that unrelated process.
|
|
11
|
+
- Keep context compaction, model settings, conversation history, and existing Telegram delivery behavior unchanged.
|
|
12
|
+
|
|
13
|
+
|
|
1
14
|
## 9.1.585 - 2026-09-08
|
|
2
15
|
|
|
3
16
|
- Managed WebSearch validates result envelopes and source rows even when optional search status metadata is absent.
|
|
@@ -3,8 +3,12 @@
|
|
|
3
3
|
const DEFAULT_COMPACTION_TRIGGER_MAX_TOKENS = 256_000;
|
|
4
4
|
const DEFAULT_COMPACTION_BLOCK_MAX_TOKENS = 288_000;
|
|
5
5
|
|
|
6
|
-
function capCompactionThreshold(threshold,
|
|
6
|
+
function capCompactionThreshold(threshold, phase) {
|
|
7
7
|
if (threshold === undefined) return undefined;
|
|
8
|
+
if (process.env.BLUN_EARLY_COMPACTION !== "0") {
|
|
9
|
+
if (phase === "trigger") return Math.min(threshold, DEFAULT_COMPACTION_TRIGGER_MAX_TOKENS);
|
|
10
|
+
if (phase === "block") return Math.min(threshold, DEFAULT_COMPACTION_BLOCK_MAX_TOKENS);
|
|
11
|
+
}
|
|
8
12
|
return threshold;
|
|
9
13
|
}
|
|
10
14
|
|
package/blun.mjs
CHANGED
|
@@ -75058,7 +75058,8 @@ var init_kosong_llm = __esmMin((() => {
|
|
|
75058
75058
|
this.notifyMediaDropped(dropped);
|
|
75059
75059
|
});
|
|
75060
75060
|
completionBudget = this.describeRequest(outgoingMessages, tools, params.completionBudgetRetry).details;
|
|
75061
|
-
|
|
75061
|
+
const requestProvider = params.thinkingOnlyRecovery === true ? completionBudget.provider.withThinking("off") : completionBudget.provider;
|
|
75062
|
+
result = await this.generate(requestProvider, this.systemPrompt, tools, outgoingMessages, callbacks, options);
|
|
75062
75063
|
} catch (error) {
|
|
75063
75064
|
if (error instanceof APIEmptyResponseError) throw error.withMetadata({ maxCompletionTokens: completionBudget === void 0 ? null : completionBudget.maxCompletionTokens ?? null });
|
|
75064
75065
|
if (error instanceof APIPaymentRequiredError) {
|
|
@@ -75843,6 +75844,8 @@ var init_full = __esmMin((() => {
|
|
|
75843
75844
|
}
|
|
75844
75845
|
markCompleted() {
|
|
75845
75846
|
this.agent.records.logRecord({ type: "full_compaction.complete" });
|
|
75847
|
+
const completedTransaction = this.compacting?.transaction;
|
|
75848
|
+
if (process.env.BLUN_EARLY_COMPACTION !== "0" && completedTransaction?.state === "summarized" && completedTransaction.tokensAfter < completedTransaction.tokensBefore) this.compactionCountInTurn = 0;
|
|
75846
75849
|
this.closeTransaction("success");
|
|
75847
75850
|
this.compacting = null;
|
|
75848
75851
|
this.sessionCompactionState = resetCompactionFailures();
|
|
@@ -263255,6 +263258,52 @@ function blunSelectTurnTools(tools, maxContextTokens, loadedToolNames, requiredT
|
|
|
263255
263258
|
deferredToolCount: deferred.length
|
|
263256
263259
|
};
|
|
263257
263260
|
}
|
|
263261
|
+
function blunSelectStableSteerTools(selectedTools, eligibleTools, loadedToolNames, requiredToolNames, maxContextTokens, log) {
|
|
263262
|
+
const selectedNames = new Set(selectedTools.map((tool) => tool.name));
|
|
263263
|
+
const additions = [];
|
|
263264
|
+
for (const tool of eligibleTools) {
|
|
263265
|
+
if (!requiredToolNames.has(tool.name) || selectedNames.has(tool.name)) continue;
|
|
263266
|
+
additions.push(tool);
|
|
263267
|
+
selectedNames.add(tool.name);
|
|
263268
|
+
}
|
|
263269
|
+
const proposed = [...selectedTools, ...additions];
|
|
263270
|
+
let deferred = eligibleTools.filter((tool) => !selectedNames.has(tool.name));
|
|
263271
|
+
const recoveryLoader = deferred.length > 0 && !selectedNames.has("ToolSearch") ? createDeferredToolLoader(selectedTools, deferred, loadedToolNames) : void 0;
|
|
263272
|
+
if (recoveryLoader) proposed.push(recoveryLoader);
|
|
263273
|
+
const schemaBudgetTokens = toolSchemaBudgetTokens(maxContextTokens);
|
|
263274
|
+
const proposedSchemaTokens = estimateTokensForTools(proposed);
|
|
263275
|
+
// A full-catalog alias must never shrink the eligible catalog itself.
|
|
263276
|
+
const budgetFallback = proposedSchemaTokens > schemaBudgetTokens && selectedTools !== eligibleTools;
|
|
263277
|
+
if (budgetFallback) {
|
|
263278
|
+
const selection = blunSelectTurnTools(eligibleTools, maxContextTokens, loadedToolNames, requiredToolNames);
|
|
263279
|
+
selectedTools.length = 0;
|
|
263280
|
+
for (const tool of selection.tools) if (tool.name !== "ToolSearch") selectedTools.push(tool);
|
|
263281
|
+
const retainedNames = new Set(selectedTools.map((tool) => tool.name));
|
|
263282
|
+
deferred = eligibleTools.filter((tool) => !retainedNames.has(tool.name));
|
|
263283
|
+
// Rebind discovery to the shared array, including former residents.
|
|
263284
|
+
if (deferred.length > 0) selectedTools.push(createDeferredToolLoader(selectedTools, deferred, loadedToolNames));
|
|
263285
|
+
} else {
|
|
263286
|
+
for (const tool of additions) selectedTools.push(tool);
|
|
263287
|
+
if (recoveryLoader) selectedTools.push(recoveryLoader);
|
|
263288
|
+
}
|
|
263289
|
+
const schemaTokenEstimate = estimateTokensForTools(selectedTools);
|
|
263290
|
+
const schemaBudgetExceeded = schemaTokenEstimate > schemaBudgetTokens;
|
|
263291
|
+
if (budgetFallback || schemaBudgetExceeded) log?.info("stable steer budget fallback", {
|
|
263292
|
+
budgetFallback,
|
|
263293
|
+
proposedSchemaTokens,
|
|
263294
|
+
schemaTokenEstimate,
|
|
263295
|
+
schemaBudgetTokens,
|
|
263296
|
+
schemaBudgetExceeded
|
|
263297
|
+
});
|
|
263298
|
+
return {
|
|
263299
|
+
tools: selectedTools,
|
|
263300
|
+
deferredToolCount: deferred.length,
|
|
263301
|
+
budgetFallback,
|
|
263302
|
+
schemaTokenEstimate,
|
|
263303
|
+
schemaBudgetTokens,
|
|
263304
|
+
schemaBudgetExceeded
|
|
263305
|
+
};
|
|
263306
|
+
}
|
|
263258
263307
|
function blunToolSuppressionReason(turnNeedsTools, eligibleTools, deferredToolCount, fastConversation) {
|
|
263259
263308
|
if (fastConversation) return "fast_conversation";
|
|
263260
263309
|
if (!turnNeedsTools) return "greeting_optimization";
|
|
@@ -264496,7 +264545,7 @@ var init_turn = __esmMin((() => {
|
|
|
264496
264545
|
const steerRequiredToolNames = mediaToolNamesForTurnText(steerText);
|
|
264497
264546
|
for (const name of rankedToolNamesForTurnText(eligibleTools, steerText)) steerRequiredToolNames.add(name);
|
|
264498
264547
|
if (steerEfforts.some((effort) => effort === void 0)) {
|
|
264499
|
-
const prioritizedSelection = blunSelectTurnTools(eligibleTools, this.agent.config.modelCapabilities?.max_context_tokens, this.loadedToolNames, steerRequiredToolNames);
|
|
264548
|
+
const prioritizedSelection = process.env.BLUN_STABLE_STEER_TOOLS !== "0" ? blunSelectStableSteerTools(selectedTools, eligibleTools, this.loadedToolNames, steerRequiredToolNames, this.agent.config.modelCapabilities?.max_context_tokens, this.agent.log) : blunSelectTurnTools(eligibleTools, this.agent.config.modelCapabilities?.max_context_tokens, this.loadedToolNames, steerRequiredToolNames);
|
|
264500
264549
|
this.agent.context.appendSystemReminder(ACTIVE_STEER_PRIORITY_REMINDER, {
|
|
264501
264550
|
kind: "injection",
|
|
264502
264551
|
variant: "active_steer_priority"
|
|
@@ -422277,7 +422326,41 @@ function resolveTelegramMcpEntry() {
|
|
|
422277
422326
|
];
|
|
422278
422327
|
for (const candidate of candidates) if (candidate !== void 0 && existsSync(candidate)) return resolveKnownTelegramMcp(candidate, { explicitOverride: candidate === process.env["BLUN_TELEGRAM_MCP_SERVER"] });
|
|
422279
422328
|
}
|
|
422329
|
+
import { fstatSync as __blunTelegramPidFstatSync } from "node:fs";
|
|
422330
|
+
"use strict";
|
|
422331
|
+
const PROCESS_START_LOOKUP_TIMEOUT_MS = 1500;
|
|
422332
|
+
const PID_CLAIM_TIME_TOLERANCE_MS = 1000;
|
|
422333
|
+
function readNonBridgeProcessStartedAt(pid) {
|
|
422334
|
+
if (process.platform !== 'win32' || !Number.isSafeInteger(pid) || pid <= 1)
|
|
422335
|
+
return undefined;
|
|
422336
|
+
try {
|
|
422337
|
+
const output = execFileSync('powershell.exe', [
|
|
422338
|
+
'-NoProfile', '-NonInteractive', '-Command',
|
|
422339
|
+
`$ErrorActionPreference='Stop'; $p=Get-CimInstance Win32_Process -Filter 'ProcessId=${pid}'; ` +
|
|
422340
|
+
// A recognizable bridge is preserved even after a wall-clock correction.
|
|
422341
|
+
`if($null -ne $p -and -not [string]::IsNullOrWhiteSpace($p.CommandLine) -and $p.CommandLine -notmatch 'bridge\\.mjs'){` +
|
|
422342
|
+
`([DateTimeOffset]$p.CreationDate.ToUniversalTime()).ToUnixTimeMilliseconds()}`,
|
|
422343
|
+
], {
|
|
422344
|
+
encoding: 'utf8',
|
|
422345
|
+
windowsHide: true,
|
|
422346
|
+
timeout: PROCESS_START_LOOKUP_TIMEOUT_MS,
|
|
422347
|
+
maxBuffer: 4096,
|
|
422348
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
422349
|
+
}).trim();
|
|
422350
|
+
if (!/^\d+$/.test(output))
|
|
422351
|
+
return undefined;
|
|
422352
|
+
const startedAt = Number(output);
|
|
422353
|
+
return Number.isSafeInteger(startedAt) && startedAt > 0 ? startedAt : undefined;
|
|
422354
|
+
}
|
|
422355
|
+
catch {
|
|
422356
|
+
return undefined;
|
|
422357
|
+
}
|
|
422358
|
+
}
|
|
422359
|
+
|
|
422280
422360
|
var TelegramChannelController = class {
|
|
422361
|
+
bridgeClaimUncertain = false;
|
|
422362
|
+
readProcessStartedAt;
|
|
422363
|
+
|
|
422281
422364
|
host;
|
|
422282
422365
|
dir;
|
|
422283
422366
|
heartbeatTimer;
|
|
@@ -422343,7 +422426,9 @@ var TelegramChannelController = class {
|
|
|
422343
422426
|
return false;
|
|
422344
422427
|
}
|
|
422345
422428
|
});
|
|
422346
|
-
|
|
422429
|
+
|
|
422430
|
+
this.readProcessStartedAt = options.readProcessStartedAt ?? readNonBridgeProcessStartedAt;
|
|
422431
|
+
}
|
|
422347
422432
|
get leaseFile() {
|
|
422348
422433
|
return join(this.dir, "tui.pid");
|
|
422349
422434
|
}
|
|
@@ -422466,11 +422551,68 @@ var TelegramChannelController = class {
|
|
|
422466
422551
|
this.handoffTimer.unref();
|
|
422467
422552
|
}
|
|
422468
422553
|
liveBridgePid() {
|
|
422469
|
-
|
|
422470
|
-
|
|
422471
|
-
|
|
422472
|
-
|
|
422473
|
-
|
|
422554
|
+
if (this.bridgeClaimUncertain)
|
|
422555
|
+
return null;
|
|
422556
|
+
let pid;
|
|
422557
|
+
let claim;
|
|
422558
|
+
let stats;
|
|
422559
|
+
try {
|
|
422560
|
+
const fd = openSync(this.botPidFile, 'r');
|
|
422561
|
+
try {
|
|
422562
|
+
claim = readFileSync(fd, 'utf8');
|
|
422563
|
+
stats = __blunTelegramPidFstatSync(fd);
|
|
422564
|
+
}
|
|
422565
|
+
finally {
|
|
422566
|
+
closeSync(fd);
|
|
422567
|
+
}
|
|
422568
|
+
pid = parseInt(claim, 10);
|
|
422569
|
+
if (!Number.isInteger(pid) || pid <= 1)
|
|
422570
|
+
return null;
|
|
422571
|
+
if (!this.isProcessAlive(pid))
|
|
422572
|
+
return undefined;
|
|
422573
|
+
}
|
|
422574
|
+
catch (error) {
|
|
422575
|
+
// Only an absent claim proves that reading it did not hide an owner.
|
|
422576
|
+
return error.code === 'ENOENT' ? undefined : null;
|
|
422577
|
+
}
|
|
422578
|
+
try {
|
|
422579
|
+
const startedAt = this.readProcessStartedAt(pid);
|
|
422580
|
+
if (startedAt !== undefined && Number.isFinite(startedAt)
|
|
422581
|
+
&& startedAt > stats.mtimeMs + PID_CLAIM_TIME_TOLERANCE_MS
|
|
422582
|
+
&& this.ownsChannel() && this.retireReusedBridgePid(claim, stats))
|
|
422583
|
+
return undefined;
|
|
422584
|
+
}
|
|
422585
|
+
catch {
|
|
422586
|
+
// A failed identity or archive lookup must not launch a second poller.
|
|
422587
|
+
}
|
|
422588
|
+
return pid;
|
|
422589
|
+
}
|
|
422590
|
+
retireReusedBridgePid(claim, original) {
|
|
422591
|
+
const matches = (file) => {
|
|
422592
|
+
const current = lstatSync(file);
|
|
422593
|
+
return current.isFile() && current.dev === original.dev && current.ino === original.ino
|
|
422594
|
+
&& current.size === original.size && current.mtimeMs === original.mtimeMs
|
|
422595
|
+
&& readFileSync(file, 'utf8') === claim;
|
|
422596
|
+
};
|
|
422597
|
+
if (!matches(this.botPidFile))
|
|
422598
|
+
return false;
|
|
422599
|
+
const retired = join(this.dir, `bot.pid.stale-${randomUUID()}`);
|
|
422600
|
+
renameSync(this.botPidFile, retired);
|
|
422601
|
+
try {
|
|
422602
|
+
if (matches(retired))
|
|
422603
|
+
return true;
|
|
422604
|
+
}
|
|
422605
|
+
catch {
|
|
422606
|
+
// Restore the name if the moved claim cannot be verified.
|
|
422607
|
+
}
|
|
422608
|
+
this.bridgeClaimUncertain = true;
|
|
422609
|
+
// Do not overwrite a newer claim if another writer won the rename window.
|
|
422610
|
+
try {
|
|
422611
|
+
linkSync(retired, this.botPidFile);
|
|
422612
|
+
}
|
|
422613
|
+
catch { /* A current claim wins. */ }
|
|
422614
|
+
return false;
|
|
422615
|
+
}
|
|
422474
422616
|
releaseOwnership() {
|
|
422475
422617
|
try {
|
|
422476
422618
|
if (this.readOwner()?.ownerId === this.ownerId) rmSync(this.ownerFile);
|