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.
@@ -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.runWithActiveChild(id, options, async (runOptions) => {
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.runWithActiveChild(agentId, options, async (runOptions) => {
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.runWithActiveChild(agentId, options, async (runOptions) => {
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));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "9.1.283",
3
+ "version": "9.1.285",
4
4
  "description": "BLUN CLI - your own AI agent with a Telegram channel. Get it done. With BLUN.",
5
5
  "license": "MIT",
6
6
  "bin": {