blun-king-cli 9.1.612 → 9.1.614
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 +12 -1
- package/LIESMICH.txt +1 -1
- package/README.md +1 -1
- package/agent-spine-plugin/src/hook.js +4 -0
- package/agent-spine-plugin/src/lib/documents.js +1 -1
- package/agent-spine-plugin/src/lib/hook-context.js +1 -0
- package/agent-spine-plugin/src/lib/host-instruction-budget.js +1 -2
- package/agent-spine-plugin/src/lib/preflight.js +38 -9
- package/agent-spine-plugin/src/lib/source-roots.js +1 -1
- package/blun.mjs +1930 -560
- package/bundled-agent-sources.json +6 -6
- package/package.json +1 -1
- package/worker-host.mjs +30 -239
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
# BLUN King Changelog
|
|
2
2
|
|
|
3
|
-
## 9.1.
|
|
3
|
+
## 9.1.614
|
|
4
|
+
|
|
5
|
+
- Zu große AGENTS.md- und AGENTS.override.md-Dateien stoppen einen Zug nicht mehr allein wegen ihrer Größe. Außerhalb der King-Konsole liest King insgesamt bis zu 32 KiB aus Anweisungsdateien und nennt dir einmal je Sitzung die Anzahl der ausgelassenen Bytes. In der King-Konsole bleiben die Dateien vollständig, solange die Anfragegrenze das zulässt. Verknüpfungen und unsichere Pfade bleiben gesperrt.
|
|
6
|
+
|
|
7
|
+
## 9.1.613
|
|
8
|
+
|
|
9
|
+
- King schätzt vor dem Senden nun die Größe der gesamten Modellanfrage, einschließlich Anweisungen, Verlauf, Werkzeugdefinitionen und Bilddaten. Nähert sie sich der Byte-Grenze des Servers, beginnt eine stufenweise Verdichtung. Lässt sich eine Anfrage nicht sicher aufteilen, bricht King ab, ohne den gespeicherten Verlauf stillschweigend zu verwerfen.
|
|
10
|
+
- Die Nutzungsanzeige übernimmt gesondert gemeldete Denktoken, sofern der Anbieter sie liefert. Eine kürzere Modellwartezeit oder geringere Kosten sind damit nicht belegt.
|
|
11
|
+
|
|
12
|
+
## 9.1.612 (Nicht veröffentlicht)
|
|
13
|
+
|
|
14
|
+
9.1.612 wurde nicht als Kundenupdate freigegeben. Der frühe Byte-Schutz griff bei einer Anfrage über 900 KB nicht, wenn der Verlauf allein unter 600 KB blieb. 9.1.613 ersetzt diesen Stand.
|
|
4
15
|
|
|
5
16
|
- King speichert abgeschlossene Werkzeugaufrufe lokal als Metadaten: Werkzeugname, Zeitpunkt, Status und bei Dateiwerkzeugen gegebenenfalls den Pfad. Eingaben, Ausgaben, Befehle und Schlüssel werden nicht gespeichert. Mit `/memory tools` kannst du die Einträge deines Profils durchsuchen.
|
|
6
17
|
- Die Kontextdiagnose zeigt zusätzliche Zahlen zur Zusammensetzung von Anfragen. Eine zuschaltbare Phasenmessung erfasst die Zeit bis zum Modellaufruf, ohne Inhalte zu protokollieren. Diese Diagnose beschleunigt Modellantworten für sich genommen nicht.
|
package/LIESMICH.txt
CHANGED
package/README.md
CHANGED
|
@@ -431,6 +431,10 @@ async function runHookCore(input, payload, options) {
|
|
|
431
431
|
}
|
|
432
432
|
if (payload) return { blocked: false, context, briefing, preflight, signal, attentionEvent, channelEvent, lessonRecall, catalogPath };
|
|
433
433
|
const output = hookOutput(event, context, process.env, sourceWarning);
|
|
434
|
+
if (preflight?.instructionNotice) {
|
|
435
|
+
output.hookSpecificOutput.message = [output.hookSpecificOutput.message, preflight.instructionNotice]
|
|
436
|
+
.filter(Boolean).join("\n");
|
|
437
|
+
}
|
|
434
438
|
const handoff = await writeContextOutput(output, process.env.BLUN_PLUGIN_ROOT ? "king"
|
|
435
439
|
: process.env.PLUGIN_ROOT ? "codex" : process.env.CLAUDE_PLUGIN_ROOT ? "claude" : scope.host);
|
|
436
440
|
if (event === "UserPromptSubmit" && briefingOrigin) {
|
|
@@ -86,7 +86,7 @@ export async function indexExplicitDocuments(entries) {
|
|
|
86
86
|
if (!metadata.isFile() || metadata.isSymbolicLink()) throw new Error(`source is not a regular non-symlink file: ${entry.id}`);
|
|
87
87
|
buffer = await readFile(path);
|
|
88
88
|
}
|
|
89
|
-
if (buffer.byteLength > (entry.maxBytes ||
|
|
89
|
+
if (buffer.byteLength > (entry.maxBytes || 8 * 1024 * 1024)) throw new Error(`source exceeds its byte limit: ${entry.id}`);
|
|
90
90
|
const classification = classify(entry.id);
|
|
91
91
|
const document = {
|
|
92
92
|
path,
|
|
@@ -44,6 +44,7 @@ return null;
|
|
|
44
44
|
|
|
45
45
|
export function hostContextLimit(preflight) {
|
|
46
46
|
if (preflight?.receipt?.instructionHost === "codex") return 32768 + Buffer.byteLength(JSON.stringify(preflight.briefing || {}));
|
|
47
|
+
if (preflight?.briefing?.instructions?.length) return 32768 + Buffer.byteLength(JSON.stringify(preflight.briefing));
|
|
47
48
|
const mode = preflight?.receipt?.instructionBudget?.mode;
|
|
48
49
|
if (mode === "claude-required-overflow") return MAX_CLAUDE_OVERFLOW_CONTEXT_BYTES;
|
|
49
50
|
return STANDARD_HOST_CONTEXT_BYTES;
|
|
@@ -4,8 +4,7 @@ const KING_TOTAL = 8388608;
|
|
|
4
4
|
export const isKingHost = (env = process.env) => !!(env.BLUN_PLUGIN_ROOT && env.BLUN_HOME);
|
|
5
5
|
|
|
6
6
|
export function instructionBudget(host, usedBytes = 0, env = process.env) {
|
|
7
|
-
const hardLimitBytes =
|
|
8
|
-
: host === "claude" ? 16384 : host === "codex" ? 32768 : STANDARD;
|
|
7
|
+
const hardLimitBytes = KING_TOTAL;
|
|
9
8
|
return { mode: usedBytes > STANDARD ? `${host}-required-overflow` : "standard",
|
|
10
9
|
standardBytes: STANDARD, hardLimitBytes, usedBytes, overflowBytes: Math.max(0, usedBytes - STANDARD) };
|
|
11
10
|
}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { createHash, createHmac, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
|
|
2
2
|
import { constants as fsConstants } from "node:fs";
|
|
3
3
|
import { spawn } from "node:child_process";
|
|
4
|
+
import { StringDecoder } from "node:string_decoder";
|
|
4
5
|
import { lstat, mkdir, open, readFile, realpath, stat, unlink, writeFile } from "node:fs/promises";
|
|
5
6
|
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
6
7
|
import { isFileLockContention, replaceFileWithRetry } from "./filesystem-retry.js";
|
|
7
8
|
import { ancestorsBetween, isInside, stateRoot } from "./paths.js";
|
|
8
9
|
import { preflightDeliveryId } from "./preflight-delivery-id.js";
|
|
9
10
|
import { resolveHostSourceCatalog } from "./source-roots.js";
|
|
10
|
-
import { instructionBudget } from "./host-instruction-budget.js";
|
|
11
|
+
import { instructionBudget, isKingHost } from "./host-instruction-budget.js";
|
|
11
12
|
|
|
12
13
|
export const PREFLIGHT_SCHEMA = "agentspine.preflight/v2";
|
|
13
14
|
export const PREFLIGHT_POLICY_SCHEMA = "agentspine.preflight-policy/v1";
|
|
@@ -326,7 +327,7 @@ async function assertNoSymlinkParents(path, allowedRoot) {
|
|
|
326
327
|
}
|
|
327
328
|
return { root, target };
|
|
328
329
|
}
|
|
329
|
-
async function safeReadRequired(path, allowedRoot, maximum = STANDARD_REQUIRED_INSTRUCTIONS_BYTES, fileHooks = null) {
|
|
330
|
+
async function safeReadRequired(path, allowedRoot, maximum = STANDARD_REQUIRED_INSTRUCTIONS_BYTES, fileHooks = null, contentLimit = null) {
|
|
330
331
|
const checked = await assertNoSymlinkParents(path, allowedRoot);
|
|
331
332
|
const flags = fsConstants.O_RDONLY | (fsConstants.O_NOFOLLOW || 0);
|
|
332
333
|
const handle = await open(checked.target, flags);
|
|
@@ -353,7 +354,10 @@ async function safeReadRequired(path, allowedRoot, maximum = STANDARD_REQUIRED_I
|
|
|
353
354
|
|| current.size !== before.size || current.mtimeMs !== before.mtimeMs || current.ctimeMs !== before.ctimeMs) {
|
|
354
355
|
throw new Error(`required instruction ${basename(path)} was replaced during preflight`);
|
|
355
356
|
}
|
|
356
|
-
|
|
357
|
+
const decoder = new StringDecoder("utf8");
|
|
358
|
+
const content = contentLimit === null ? buffer.toString("utf8") : decoder.write(buffer.subarray(0, contentLimit));
|
|
359
|
+
const readBytes = Buffer.byteLength(content);
|
|
360
|
+
return { content, bytes: buffer.length, readBytes, omittedBytes: buffer.length - readBytes, sha256: sha256(buffer),
|
|
357
361
|
identity: sha256(`${before.dev}\0${before.ino}\0${before.size}\0${before.mtimeMs}\0${before.ctimeMs}`) };
|
|
358
362
|
} finally { await handle.close(); }
|
|
359
363
|
}
|
|
@@ -492,16 +496,19 @@ export async function runPreflight({ input, scope, resolvedSources, prompt, now
|
|
|
492
496
|
const requiredInstructions = [];
|
|
493
497
|
let instructionBytes = 0;
|
|
494
498
|
const maximumInstructionBytes = instructionBudget(instructionHost, 0, env).hardLimitBytes;
|
|
499
|
+
const kingInstructions = instructionHost === "codex" && isKingHost(env);
|
|
500
|
+
const contentBudget = kingInstructions ? 900000 : 32768;
|
|
495
501
|
for (const document of documents) {
|
|
496
502
|
const allowedRoot = document.sourceScope === "user" ? resolvedSources.hostHome : resolvedSources.projectRoot;
|
|
497
|
-
const snapshot = await safeReadRequired(document.path, allowedRoot, maximumInstructionBytes, fileHooks);
|
|
503
|
+
const snapshot = await safeReadRequired(document.path, allowedRoot, maximumInstructionBytes, fileHooks, Math.max(0, contentBudget - instructionBytes));
|
|
498
504
|
if (snapshot.sha256 !== document.sha256 || snapshot.bytes !== document.bytes) throw new Error(`required instruction changed after source resolution: ${document.relativePath}`);
|
|
499
|
-
instructionBytes += snapshot.
|
|
505
|
+
instructionBytes += snapshot.readBytes;
|
|
500
506
|
if (instructionBytes > maximumInstructionBytes) {
|
|
501
507
|
throw new Error(`required host instructions total ${instructionBytes} bytes; mandatory limit is ${maximumInstructionBytes} bytes`);
|
|
502
508
|
}
|
|
503
509
|
requiredInstructions.push({ path: document.path, displayPath: document.relativePath, scope: document.sourceScope,
|
|
504
|
-
bytes: snapshot.bytes,
|
|
510
|
+
bytes: snapshot.bytes, readBytes: snapshot.readBytes, omittedBytes: snapshot.omittedBytes,
|
|
511
|
+
sha256: snapshot.sha256, identity: snapshot.identity, content: snapshot.content });
|
|
505
512
|
}
|
|
506
513
|
const appliedInstructionBudget = instructionBudget(instructionHost, instructionBytes, env);
|
|
507
514
|
const memoryState = validateMemories(await readJson(paths.memories, MAX_STATE_BYTES, emptyMemories));
|
|
@@ -548,6 +555,7 @@ export async function runPreflight({ input, scope, resolvedSources, prompt, now
|
|
|
548
555
|
const key = await signingKey(paths); const bodyDigest = digestObject(receiptBody);
|
|
549
556
|
const receipt = { id: `preflight:${bodyDigest.slice(0, 32)}`, ...receiptBody, bodyDigest,
|
|
550
557
|
signature: createHmac("sha256", key).update(bodyDigest).digest("hex") };
|
|
558
|
+
let instructionNotice = null;
|
|
551
559
|
await withLock(paths, async () => {
|
|
552
560
|
const state = await readVerifiedState(paths);
|
|
553
561
|
state.receipts = state.receipts.filter((item) => new Date(item.receipt?.expiresAt || 0).getTime() >= new Date(createdAt).getTime() - RECEIPT_TTL_MS);
|
|
@@ -557,6 +565,25 @@ export async function runPreflight({ input, scope, resolvedSources, prompt, now
|
|
|
557
565
|
const envelope = { receipt, consumedAt: null, invalidatedAt: null, stateSignature: "" };
|
|
558
566
|
envelope.stateSignature = createHmac("sha256", key).update(envelopeBody(envelope)).digest("hex");
|
|
559
567
|
state.receipts.push(envelope); state.revision += 1;
|
|
568
|
+
if (requiredInstructions.length > 0) {
|
|
569
|
+
const notices = [];
|
|
570
|
+
for (const item of requiredInstructions.filter((instruction) => instruction.bytes > 8192)) {
|
|
571
|
+
const sessionDigest = sha256(`${exactScope.sessionId}\0${item.path}`);
|
|
572
|
+
if (state.history.some((event) => event.event === "instruction-notice" && event.sessionDigest === sessionDigest)) continue;
|
|
573
|
+
state.history.push({ event: "instruction-notice", sessionDigest, at: createdAt, authority: "diagnostic-only" });
|
|
574
|
+
notices.push(`${item.displayPath}: ${item.bytes} bytes; ${item.readBytes} read; ${item.omittedBytes} omitted.`);
|
|
575
|
+
}
|
|
576
|
+
const totalBytes = requiredInstructions.reduce((sum, item) => sum + item.bytes, 0);
|
|
577
|
+
const omittedBytes = totalBytes - instructionBytes;
|
|
578
|
+
if (omittedBytes > 0) {
|
|
579
|
+
const totalDigest = sha256(`${exactScope.sessionId}\0instruction-total`);
|
|
580
|
+
if (!state.history.some((event) => event.event === "instruction-notice" && event.sessionDigest === totalDigest)) {
|
|
581
|
+
state.history.push({ event: "instruction-notice", sessionDigest: totalDigest, at: createdAt, authority: "diagnostic-only" });
|
|
582
|
+
notices.push(`Instruction total: ${totalBytes} bytes; ${instructionBytes} read; ${omittedBytes} omitted.`);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
instructionNotice = notices.join("\n") || null;
|
|
586
|
+
}
|
|
560
587
|
state.lastTurn = { status: "ready", receiptId: receipt.id, at: createdAt, host: exactScope.host,
|
|
561
588
|
instructionHost, instructionFiles: receipt.instructionFiles.length, recall: receipt.providerQueries.map((item) => ({
|
|
562
589
|
providerId: item.providerId, status: item.status, rejected: item.rejected
|
|
@@ -564,7 +591,7 @@ export async function runPreflight({ input, scope, resolvedSources, prompt, now
|
|
|
564
591
|
state.history.push({ event: "ready", receiptId: receipt.id, at: createdAt, authority: "preflight-proof-only" });
|
|
565
592
|
await writeJson(paths.state, state);
|
|
566
593
|
});
|
|
567
|
-
return { receipt, briefing, policy: { configured: Boolean(profile), requiredProviderIds } };
|
|
594
|
+
return { receipt, briefing, instructionNotice, policy: { configured: Boolean(profile), requiredProviderIds } };
|
|
568
595
|
}
|
|
569
596
|
|
|
570
597
|
export async function verifyPreflightReceipt({ receipt, input, scope, resolvedSources, prompt, now = new Date(), env = process.env, consume = false }) {
|
|
@@ -607,12 +634,14 @@ export async function verifyPreflightReceipt({ receipt, input, scope, resolvedSo
|
|
|
607
634
|
|| currentDocuments.some((document, index) => document.path !== receipt.instructionFiles[index]?.path)) return false;
|
|
608
635
|
let instructionBytes = 0;
|
|
609
636
|
const maximumInstructionBytes = instructionBudget(receipt.instructionHost, 0, env).hardLimitBytes;
|
|
637
|
+
const kingInstructions = receipt.instructionHost === "codex" && isKingHost(env);
|
|
638
|
+
const contentBudget = kingInstructions ? 900000 : 32768;
|
|
610
639
|
for (const instruction of receipt.instructionFiles) {
|
|
611
640
|
const allowedRoot = instruction.scope === "user" ? freshSources.hostHome : freshSources.projectRoot;
|
|
612
|
-
const currentSnapshot = await safeReadRequired(instruction.path, allowedRoot, maximumInstructionBytes);
|
|
641
|
+
const currentSnapshot = await safeReadRequired(instruction.path, allowedRoot, maximumInstructionBytes, null, Math.max(0, contentBudget - instructionBytes));
|
|
613
642
|
if (currentSnapshot.sha256 !== instruction.sha256 || currentSnapshot.bytes !== instruction.bytes
|
|
614
643
|
|| currentSnapshot.identity !== instruction.identity) return false;
|
|
615
|
-
instructionBytes += currentSnapshot.
|
|
644
|
+
instructionBytes += currentSnapshot.readBytes;
|
|
616
645
|
}
|
|
617
646
|
if (instructionBytes > maximumInstructionBytes
|
|
618
647
|
|| canonical(receipt.instructionBudget) !== canonical(instructionBudget(receipt.instructionHost, instructionBytes, env))) return false;
|
|
@@ -376,7 +376,7 @@ export async function resolveHostSourceCatalog({ host, cwd = process.cwd(), inpu
|
|
|
376
376
|
const codexHome = isKingHost(env) ? env.BLUN_HOME : env.CODEX_HOME || env.BLUN_HOME || join(homedir(), ".codex");
|
|
377
377
|
hostHome = await existingDirectory(resolve(codexHome)) || resolve(codexHome);
|
|
378
378
|
const config = await codexConfig(hostHome);
|
|
379
|
-
|
|
379
|
+
config.maxBytes = KING_SOURCE_BYTES;
|
|
380
380
|
if (env.AGENTSPINE_ROOT) projectRoot = await canonicalPath(env.AGENTSPINE_ROOT);
|
|
381
381
|
else ({ root: projectRoot, resolution: rootResolution } = await findRoot(canonicalCwd, config.rootMarkers));
|
|
382
382
|
sources = await codexSources({ cwd: canonicalCwd, projectRoot, codexHome: hostHome, config });
|