blun-king-cli 9.1.283 → 9.1.285
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/bin/input-draft-persistence.cjs +77 -0
- package/bin/subagent-usage-rollup-policy.cjs +29 -0
- package/blun.mjs +66 -4
- package/package.json +1 -1
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const crypto = require('node:crypto');
|
|
4
|
+
const fs = require('node:fs/promises');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
|
|
7
|
+
const MAX_INPUT_DRAFT_BYTES = 64 * 1024;
|
|
8
|
+
const MAX_INPUT_DRAFT_FILE_BYTES = MAX_INPUT_DRAFT_BYTES + 1024;
|
|
9
|
+
const SCHEMA_VERSION = 1;
|
|
10
|
+
const VALID_MODES = new Set(['bash', 'prompt']);
|
|
11
|
+
|
|
12
|
+
function normalizeInputDraft(value) {
|
|
13
|
+
if (!value || typeof value !== 'object') return null;
|
|
14
|
+
if (!VALID_MODES.has(value.mode) || typeof value.text !== 'string') return null;
|
|
15
|
+
if (value.text.length === 0) return null;
|
|
16
|
+
if (Buffer.byteLength(value.text, 'utf8') > MAX_INPUT_DRAFT_BYTES) return null;
|
|
17
|
+
return { mode: value.mode, text: value.text };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function clearInputDraft(filePath) {
|
|
21
|
+
await fs.rm(path.resolve(filePath), { force: true });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function readInputDraft(filePath) {
|
|
25
|
+
const target = path.resolve(filePath);
|
|
26
|
+
try {
|
|
27
|
+
const stat = await fs.lstat(target);
|
|
28
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > MAX_INPUT_DRAFT_FILE_BYTES) return null;
|
|
29
|
+
const record = JSON.parse(await fs.readFile(target, 'utf8'));
|
|
30
|
+
if (record?.schemaVersion !== SCHEMA_VERSION) return null;
|
|
31
|
+
return normalizeInputDraft(record);
|
|
32
|
+
} catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function writeInputDraft(filePath, value) {
|
|
38
|
+
const target = path.resolve(filePath);
|
|
39
|
+
const text = typeof value?.text === 'string' ? value.text : '';
|
|
40
|
+
if (Buffer.byteLength(text, 'utf8') > MAX_INPUT_DRAFT_BYTES) {
|
|
41
|
+
await clearInputDraft(target);
|
|
42
|
+
throw new Error('draft_too_large');
|
|
43
|
+
}
|
|
44
|
+
if (text.length === 0) {
|
|
45
|
+
await clearInputDraft(target);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const draft = normalizeInputDraft(value);
|
|
49
|
+
if (draft === null) throw new Error('draft_invalid');
|
|
50
|
+
|
|
51
|
+
const directory = path.dirname(target);
|
|
52
|
+
const temporary = path.join(
|
|
53
|
+
directory,
|
|
54
|
+
`.${path.basename(target)}.${process.pid}.${crypto.randomUUID()}.tmp`,
|
|
55
|
+
);
|
|
56
|
+
await fs.mkdir(directory, { recursive: true, mode: 0o700 });
|
|
57
|
+
try {
|
|
58
|
+
await fs.writeFile(
|
|
59
|
+
temporary,
|
|
60
|
+
`${JSON.stringify({ schemaVersion: SCHEMA_VERSION, ...draft })}\n`,
|
|
61
|
+
{ encoding: 'utf8', flag: 'wx', mode: 0o600 },
|
|
62
|
+
);
|
|
63
|
+
await fs.rename(temporary, target);
|
|
64
|
+
await fs.chmod(target, 0o600).catch(() => {});
|
|
65
|
+
} catch (error) {
|
|
66
|
+
await fs.rm(temporary, { force: true }).catch(() => {});
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
module.exports = {
|
|
72
|
+
MAX_INPUT_DRAFT_BYTES,
|
|
73
|
+
clearInputDraft,
|
|
74
|
+
normalizeInputDraft,
|
|
75
|
+
readInputDraft,
|
|
76
|
+
writeInputDraft,
|
|
77
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const USAGE_FIELDS = [
|
|
4
|
+
'inputOther',
|
|
5
|
+
'output',
|
|
6
|
+
'inputCacheRead',
|
|
7
|
+
'inputCacheCreation',
|
|
8
|
+
];
|
|
9
|
+
|
|
10
|
+
function safeTokenCount(value) {
|
|
11
|
+
return Number.isFinite(value) && value > 0 ? value : 0;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function buildSubagentUsageDelta(after, before) {
|
|
15
|
+
if (after === undefined || after === null || typeof after !== 'object') return null;
|
|
16
|
+
const baseline = before !== null && typeof before === 'object' ? before : {};
|
|
17
|
+
if (USAGE_FIELDS.some((field) => safeTokenCount(after[field]) < safeTokenCount(baseline[field]))) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
const delta = Object.fromEntries(USAGE_FIELDS.map((field) => [
|
|
21
|
+
field,
|
|
22
|
+
safeTokenCount(after[field]) - safeTokenCount(baseline[field]),
|
|
23
|
+
]));
|
|
24
|
+
return USAGE_FIELDS.some((field) => delta[field] > 0) ? delta : null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
module.exports = {
|
|
28
|
+
buildSubagentUsageDelta,
|
|
29
|
+
};
|
package/blun.mjs
CHANGED
|
@@ -252106,7 +252106,7 @@ function shouldSuppressQueuedAttemptFailureEvent(options, error) {
|
|
|
252106
252106
|
if (isProviderRateLimitError(error)) return true;
|
|
252107
252107
|
return isAbortError$4(error) || options.signal.aborted;
|
|
252108
252108
|
}
|
|
252109
|
-
var DEFAULT_SUBAGENT_TIMEOUT_MS, resolveSubagentTimeoutMs, runAgentWithTimeoutContinuation, SUMMARY_MIN_LENGTH, SUMMARY_CONTINUATION_ATTEMPTS, HOOK_TEXT_PREVIEW_LENGTH, SUBAGENT_MAX_TOKENS_ERROR, TOOL_CALL_DISABLED_MESSAGE, SUBAGENT_PROMPT_ORIGIN, SIDE_QUESTION_SYSTEM_REMINDER, SessionSubagentHost;
|
|
252109
|
+
var DEFAULT_SUBAGENT_TIMEOUT_MS, resolveSubagentTimeoutMs, runAgentWithTimeoutContinuation, buildSubagentUsageDelta, SUMMARY_MIN_LENGTH, SUMMARY_CONTINUATION_ATTEMPTS, HOOK_TEXT_PREVIEW_LENGTH, SUBAGENT_MAX_TOKENS_ERROR, TOOL_CALL_DISABLED_MESSAGE, SUBAGENT_PROMPT_ORIGIN, SIDE_QUESTION_SYSTEM_REMINDER, SessionSubagentHost;
|
|
252110
252110
|
var init_subagent_host = __esmMin((() => {
|
|
252111
252111
|
init_src$4();
|
|
252112
252112
|
init_errors$8();
|
|
@@ -252119,6 +252119,7 @@ var init_subagent_host = __esmMin((() => {
|
|
|
252119
252119
|
init_subagent_batch();
|
|
252120
252120
|
init_summary_continuation();
|
|
252121
252121
|
({ DEFAULT_SUBAGENT_TIMEOUT_MS, resolveSubagentTimeoutMs, runAgentWithTimeoutContinuation } = createRequire(import.meta.url)("./bin/subagent-timeout-policy.cjs"));
|
|
252122
|
+
({ buildSubagentUsageDelta } = createRequire(import.meta.url)("./bin/subagent-usage-rollup-policy.cjs"));
|
|
252122
252123
|
SUMMARY_MIN_LENGTH = 200;
|
|
252123
252124
|
SUMMARY_CONTINUATION_ATTEMPTS = 1;
|
|
252124
252125
|
HOOK_TEXT_PREVIEW_LENGTH = 500;
|
|
@@ -252177,7 +252178,7 @@ IMPORTANT:
|
|
|
252177
252178
|
parentAgentId: this.ownerAgentId,
|
|
252178
252179
|
swarmItem: options.swarmItem
|
|
252179
252180
|
});
|
|
252180
|
-
const completion = this.
|
|
252181
|
+
const completion = this.runWithTrackedChildUsage(parent, agent, id, options, async (runOptions) => {
|
|
252181
252182
|
this.emitSubagentSpawned(parent, id, profile.name, runOptions);
|
|
252182
252183
|
try {
|
|
252183
252184
|
await this.configureChild(parent, agent, profile);
|
|
@@ -252201,7 +252202,7 @@ IMPORTANT:
|
|
|
252201
252202
|
agentId,
|
|
252202
252203
|
profileName,
|
|
252203
252204
|
resumed: true,
|
|
252204
|
-
completion: this.
|
|
252205
|
+
completion: this.runWithTrackedChildUsage(parent, child, agentId, options, async (runOptions) => {
|
|
252205
252206
|
this.emitSubagentSpawned(parent, agentId, profileName, runOptions);
|
|
252206
252207
|
try {
|
|
252207
252208
|
child.config.update({
|
|
@@ -252239,7 +252240,7 @@ IMPORTANT:
|
|
|
252239
252240
|
agentId,
|
|
252240
252241
|
profileName,
|
|
252241
252242
|
resumed: true,
|
|
252242
|
-
completion: this.
|
|
252243
|
+
completion: this.runWithTrackedChildUsage(parent, child, agentId, options, async (runOptions) => {
|
|
252243
252244
|
try {
|
|
252244
252245
|
runOptions.signal.throwIfAborted();
|
|
252245
252246
|
child.config.update({
|
|
@@ -252354,6 +252355,20 @@ IMPORTANT:
|
|
|
252354
252355
|
this.activeChildren.delete(childId);
|
|
252355
252356
|
});
|
|
252356
252357
|
}
|
|
252358
|
+
async runWithTrackedChildUsage(parent, child, childId, options, run) {
|
|
252359
|
+
const usageBefore = child.usage.data().total;
|
|
252360
|
+
try {
|
|
252361
|
+
return await this.runWithActiveChild(childId, options, run);
|
|
252362
|
+
} finally {
|
|
252363
|
+
this.recordSubagentUsageDelta(parent, child, usageBefore);
|
|
252364
|
+
}
|
|
252365
|
+
}
|
|
252366
|
+
recordSubagentUsageDelta(parent, child, usageBefore) {
|
|
252367
|
+
const usageDelta = buildSubagentUsageDelta(child.usage.data().total, usageBefore);
|
|
252368
|
+
if (usageDelta === null) return;
|
|
252369
|
+
const model = child.config.modelAlias ?? parent.config.modelAlias ?? "subagent";
|
|
252370
|
+
parent.usage.record(model, usageDelta, "session");
|
|
252371
|
+
}
|
|
252357
252372
|
async runPromptTurn(parent, childId, child, profileName, options) {
|
|
252358
252373
|
options.signal.throwIfAborted();
|
|
252359
252374
|
await this.triggerSubagentStart(parent, profileName, options.prompt, options.signal);
|
|
@@ -328707,6 +328722,7 @@ const BLUN_UPDATE_STATE_FILE_NAME = "latest.json";
|
|
|
328707
328722
|
const BLUN_UPDATE_INSTALL_STATE_FILE_NAME = "install.json";
|
|
328708
328723
|
const BLUN_UPDATE_ROLLOUT_LOG_FILE_NAME = "rollout.log";
|
|
328709
328724
|
const BLUN_INPUT_HISTORY_DIR_NAME = "user-history";
|
|
328725
|
+
const BLUN_INPUT_DRAFT_DIR_NAME = "input-drafts";
|
|
328710
328726
|
const BLUN_BANNER_DIR_NAME = "banner";
|
|
328711
328727
|
const BLUN_BANNER_STATE_FILE_NAME = "state.json";
|
|
328712
328728
|
const BLUN_STARTUP_DIR_NAME = "startup";
|
|
@@ -340161,6 +340177,10 @@ function getInputHistoryFile(workDir) {
|
|
|
340161
340177
|
const hash = createHash("md5").update(workDir, "utf-8").digest("hex");
|
|
340162
340178
|
return join(getDataDir(), BLUN_INPUT_HISTORY_DIR_NAME, `${hash}.jsonl`);
|
|
340163
340179
|
}
|
|
340180
|
+
function getInputDraftFile(workDir, profile) {
|
|
340181
|
+
const hash = createHash("sha256").update(`${profile}\0${workDir}`, "utf-8").digest("hex");
|
|
340182
|
+
return join(getDataDir(), BLUN_INPUT_DRAFT_DIR_NAME, `${hash}.json`);
|
|
340183
|
+
}
|
|
340164
340184
|
//#endregion
|
|
340165
340185
|
//#region src/cli/build-info.ts
|
|
340166
340186
|
function optionalBuildString(value) {
|
|
@@ -496889,6 +496909,7 @@ async function appendJsonlLine(filePath, lineSchema, value) {
|
|
|
496889
496909
|
//#region src/utils/history/input-history.ts
|
|
496890
496910
|
init_zod$1();
|
|
496891
496911
|
const InputHistoryEntrySchema = object({ content: string() });
|
|
496912
|
+
var { readInputDraft, writeInputDraft } = createRequire(import.meta.url)("./bin/input-draft-persistence.cjs");
|
|
496892
496913
|
async function loadInputHistory(file) {
|
|
496893
496914
|
return readJsonlFile(file, InputHistoryEntrySchema);
|
|
496894
496915
|
}
|
|
@@ -504219,6 +504240,7 @@ var EditorKeyboardController = class {
|
|
|
504219
504240
|
if (!editor.wasGhostAcceptance()) this.inlineSuggest?.notifyActivity();
|
|
504220
504241
|
if (this.pendingExit) this.clearPendingExit();
|
|
504221
504242
|
host.updateEditorBorderHighlight(text);
|
|
504243
|
+
host.scheduleInputDraftPersist(text, editor.inputMode);
|
|
504222
504244
|
};
|
|
504223
504245
|
let browseMode = null;
|
|
504224
504246
|
editor.setHistoryFilter((entry) => {
|
|
@@ -504345,6 +504367,7 @@ var EditorKeyboardController = class {
|
|
|
504345
504367
|
};
|
|
504346
504368
|
editor.onInputModeChange = (mode) => {
|
|
504347
504369
|
host.handleInputModeChange(mode);
|
|
504370
|
+
host.scheduleInputDraftPersist(editor.getText(), mode);
|
|
504348
504371
|
};
|
|
504349
504372
|
editor.onOpenExternalEditor = () => {
|
|
504350
504373
|
host.track("shortcut_editor");
|
|
@@ -515537,6 +515560,9 @@ var BlunTUI = class {
|
|
|
515537
515560
|
mediaActivityTickTimer;
|
|
515538
515561
|
mediaActivityExpanded = false;
|
|
515539
515562
|
lastHistoryContent;
|
|
515563
|
+
inputDraftTimer;
|
|
515564
|
+
pendingInputDraft;
|
|
515565
|
+
inputDraftWrite = Promise.resolve();
|
|
515540
515566
|
shellOutputStreams = /* @__PURE__ */ new Map();
|
|
515541
515567
|
streamingUI;
|
|
515542
515568
|
authFlow;
|
|
@@ -515783,6 +515809,7 @@ var BlunTUI = class {
|
|
|
515783
515809
|
this.loadPersistedInputHistory();
|
|
515784
515810
|
this.state.editorContainer.clear();
|
|
515785
515811
|
this.state.editorContainer.addChild(this.state.editor);
|
|
515812
|
+
await this.loadPersistedInputDraft();
|
|
515786
515813
|
this.state.ui.setFocus(this.state.editor);
|
|
515787
515814
|
return shouldReplayHistory;
|
|
515788
515815
|
}
|
|
@@ -516054,6 +516081,7 @@ var BlunTUI = class {
|
|
|
516054
516081
|
async stop(exitCode) {
|
|
516055
516082
|
if (this.isShuttingDown) return;
|
|
516056
516083
|
this.isShuttingDown = true;
|
|
516084
|
+
await this.flushInputDraft();
|
|
516057
516085
|
if (exitCode === 0 && process.connected) try {
|
|
516058
516086
|
process.send({ type: RUNTIME_EXIT_INTENT_MESSAGE });
|
|
516059
516087
|
} catch {}
|
|
@@ -516937,6 +516965,40 @@ var BlunTUI = class {
|
|
|
516937
516965
|
this.managedImageReaderAvailable = false;
|
|
516938
516966
|
}
|
|
516939
516967
|
}
|
|
516968
|
+
inputDraftFile() {
|
|
516969
|
+
return getInputDraftFile(
|
|
516970
|
+
this.state.appState.workDir,
|
|
516971
|
+
process.env["BLUN_PROFILE"] ?? "default"
|
|
516972
|
+
);
|
|
516973
|
+
}
|
|
516974
|
+
async loadPersistedInputDraft() {
|
|
516975
|
+
const draft = await readInputDraft(this.inputDraftFile());
|
|
516976
|
+
if (draft === null || this.state.editor.getText().length > 0) return;
|
|
516977
|
+
this.state.editor.setInputMode(draft.mode);
|
|
516978
|
+
this.state.editor.setText(draft.text);
|
|
516979
|
+
this.updateEditorBorderHighlight(draft.text);
|
|
516980
|
+
}
|
|
516981
|
+
scheduleInputDraftPersist(text, mode) {
|
|
516982
|
+
this.pendingInputDraft = { text, mode };
|
|
516983
|
+
if (this.inputDraftTimer !== void 0) clearTimeout(this.inputDraftTimer);
|
|
516984
|
+
this.inputDraftTimer = setTimeout(() => {
|
|
516985
|
+
this.inputDraftTimer = void 0;
|
|
516986
|
+
void this.flushInputDraft();
|
|
516987
|
+
}, 300);
|
|
516988
|
+
}
|
|
516989
|
+
async flushInputDraft() {
|
|
516990
|
+
if (this.inputDraftTimer !== void 0) {
|
|
516991
|
+
clearTimeout(this.inputDraftTimer);
|
|
516992
|
+
this.inputDraftTimer = void 0;
|
|
516993
|
+
}
|
|
516994
|
+
const draft = this.pendingInputDraft;
|
|
516995
|
+
this.pendingInputDraft = void 0;
|
|
516996
|
+
if (draft === void 0) return this.inputDraftWrite;
|
|
516997
|
+
this.inputDraftWrite = this.inputDraftWrite
|
|
516998
|
+
.then(() => writeInputDraft(this.inputDraftFile(), draft))
|
|
516999
|
+
.catch(() => {});
|
|
517000
|
+
return this.inputDraftWrite;
|
|
517001
|
+
}
|
|
516940
517002
|
async loadPersistedInputHistory() {
|
|
516941
517003
|
try {
|
|
516942
517004
|
const entries = await loadInputHistory(getInputHistoryFile(this.state.appState.workDir));
|