blun-king-cli 9.1.508 → 9.1.510

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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 9.1.510 - 2026-08-31
4
+
5
+ - Adds `ReadBatch` for two to twelve independent known UTF-8 files or ranges in one concurrent tool call, reducing repeated model round trips over a large working context.
6
+ - Reuses the exact `Read` path resolution, sensitive-file checks, permissions, UTF-8 validation, paging, and byte limits while preserving request order and reporting partial failures individually.
7
+ - Keeps `ReadBatch` in the bounded core tool catalogue for main, coder, explore, and plan profiles, with focused concurrency, access, partial-failure, registration, and full-package regressions.
8
+
9
+ ## 9.1.509 - 2026-08-30
10
+
11
+ - Requires every authorized bot-to-bot Telegram work message to mention its intended King explicitly.
12
+ - Keeps unmentioned bot messages visible without buffering, queueing, delivering, or replaying them into that King's model context.
13
+ - Prevents a priority bot message addressed to one King from becoming work for another King, with focused runtime and full-package regressions.
14
+
3
15
  ## 9.1.508 - 2026-08-30
4
16
 
5
17
  - Keeps pending TodoList markers visible on reduced-color terminals and rejects a newly tracked multi-step list unless it has exactly one active item.
package/LIESMICH.txt CHANGED
@@ -9,7 +9,7 @@ Installation
9
9
  ------------
10
10
  Die geprüfte Version exakt global installieren:
11
11
 
12
- npm install -g blun-king-cli@9.1.508
12
+ npm install -g blun-king-cli@9.1.510
13
13
 
14
14
  Start
15
15
  -----
package/README.md CHANGED
@@ -9,7 +9,7 @@ Voraussetzung ist Node.js 24.15 oder neuer. Die geprüfte Version wird exakt
9
9
  installiert:
10
10
 
11
11
  ```powershell
12
- npm install -g blun-king-cli@9.1.508
12
+ npm install -g blun-king-cli@9.1.510
13
13
  ```
14
14
 
15
15
  ## Reproduzierbares Staging und Packen
@@ -0,0 +1,32 @@
1
+ 'use strict';
2
+
3
+ const MAX_READ_BATCH_REQUESTS = 12;
4
+
5
+ function readBatchAllFailed(results) {
6
+ return results.length === 0 || results.every((result) => result?.isError === true);
7
+ }
8
+
9
+ function renderReadBatchOutput(requests, results) {
10
+ return requests.map((request, index) => {
11
+ const result = results[index] ?? {
12
+ isError: true,
13
+ output: 'ReadBatch did not receive a result for this request.',
14
+ };
15
+ const range = [
16
+ request.line_offset === undefined ? null : `line_offset=${String(request.line_offset)}`,
17
+ request.n_lines === undefined ? null : `n_lines=${String(request.n_lines)}`,
18
+ ].filter(Boolean);
19
+ const rangeSuffix = range.length === 0 ? '' : ` [${range.join(', ')}]`;
20
+ const status = result.isError === true ? 'error' : 'ok';
21
+ return [
22
+ `--- Read ${String(index + 1)}/${String(requests.length)}: ${JSON.stringify(request.path)}${rangeSuffix} (${status}) ---`,
23
+ String(result.output ?? ''),
24
+ ].join('\n');
25
+ }).join('\n\n');
26
+ }
27
+
28
+ module.exports = {
29
+ MAX_READ_BATCH_REQUESTS,
30
+ readBatchAllFailed,
31
+ renderReadBatchOutput,
32
+ };
@@ -42,6 +42,7 @@ const TOOL_SEARCH_SINGLE_TERM_RELATED_FALLBACKS = new Set([
42
42
  const CORE_TOOL_NAMES = Object.freeze([
43
43
  'Bash',
44
44
  'Read',
45
+ 'ReadBatch',
45
46
  'Edit',
46
47
  'Grep',
47
48
  'Write',
package/blun.mjs CHANGED
@@ -28544,6 +28544,7 @@ var init_load = __esmMin((() => {
28544
28544
  var agent_default$1;
28545
28545
  var init_agent$3 = __esmMin((() => {
28546
28546
  agent_default$1 = "name: agent\ndescription: Default BLUN King agent\n\nsystemPromptPath: ./system.md\npromptVars:\n roleAdditional: ''\n\ntools:\n - Read\n - Write\n - Edit\n - Grep\n - Glob\n - Bash\n - TaskList\n - TaskOutput\n - TaskUpdate\n - TaskStop\n - CronCreate\n - CronList\n - CronDelete\n - ReadMediaFile\n - TodoList\n - Skill\n - WebSearch\n - Agent\n - AgentSwarm\n - FetchURL\n - GenerateImage\n - GenerateVideo\n - GenerateSpeech\n - UnderstandImage\n - UnderstandVideo\n - DubVideo\n - LipSyncMedia\n - GetMedia\n - AskUserQuestion\n - MistakeRecord\n - CodebaseSearch\n - EnterPlanMode\n - ExitPlanMode\n - CreateGoal\n - GetGoal\n - SetGoalBudget\n - UpdateGoal\n - mcp__*\n\nsubagents:\n coder:\n description: General software engineering agent — the only subagent type with file-editing tools; use it for any delegated task that must modify code.\n explore:\n description: Fast codebase exploration with prompt-enforced read-only behavior.\n plan:\n description: Read-only implementation planning and architecture design.\n";
28547
+ agent_default$1 = agent_default$1.replace(" - Read\n", " - Read\n - ReadBatch\n");
28547
28548
  agent_default$1 = agent_default$1.replace(" - TodoList\n", " - TodoList\n - CompactConversation\n");
28548
28549
  }));
28549
28550
  //#endregion
@@ -28551,12 +28552,14 @@ var init_agent$3 = __esmMin((() => {
28551
28552
  var coder_default;
28552
28553
  var init_coder = __esmMin((() => {
28553
28554
  coder_default = "extends: agent\nname: coder\npromptVars:\n roleAdditional: |\n You are now running as a subagent. All the `user` messages are sent by the main agent. The main agent cannot see your context, it can only see your last message when you finish the task. You must treat the parent agent as your caller. Do not directly ask the end user questions. If something is unclear, explain the ambiguity in your final summary to the parent agent.\n\n Your final message is the entire handoff — the parent sees nothing else from your run. Make it technically complete: what you changed and why, the path of every file you touched, how you verified the change (tests or commands run, with results), and anything left undone or worth follow-up. A final message of only a sentence or two is treated as too brief and sent back to you for expansion, costing an extra turn.\nwhenToUse: |\n Use this agent for non-trivial software engineering work that may require reading files, editing code, running commands, and returning a compact but technically complete summary to the parent agent.\ntools:\n - Bash\n - Read\n - ReadMediaFile\n - Glob\n - Grep\n - Write\n - Edit\n - MistakeRecord\n - WebSearch\n - FetchURL\n - mcp__*\n";
28555
+ coder_default = coder_default.replace(" - Read\n", " - Read\n - ReadBatch\n");
28554
28556
  }));
28555
28557
  //#endregion
28556
28558
  //#region ../../packages/agent-core/src/profile/default/explore.yaml?raw
28557
28559
  var explore_default;
28558
28560
  var init_explore = __esmMin((() => {
28559
28561
  explore_default = "extends: agent\nname: explore\npromptVars:\n roleAdditional: |\n You are now running as a subagent. All the `user` messages are sent by the main agent. The main agent cannot see your context, it can only see your last message when you finish the task. You must treat the parent agent as your caller. Do not directly ask the end user questions. If something is unclear, explain the ambiguity in your final summary to the parent agent.\n\n You are a codebase exploration specialist. Your role is EXCLUSIVELY to search, read, and analyze existing code and resources. You do NOT have access to project file editing tools. MistakeRecord is the sole write-capable exception: it may record a concrete refutation in shared BLUN learning memory, never modify project files.\n\n Your strengths:\n - Rapidly finding files using glob patterns\n - Searching code and text with powerful regex patterns\n - Reading and analyzing file contents\n - Running read-only shell commands (git log, git diff, ls, find, etc.)\n\n Guidelines:\n - Use Glob for broad file pattern matching. Prefer patterns with a literal anchor (extension or subdirectory); pure wildcards like `*` or `**/*` are allowed but usually truncate at the match cap.\n - Use Grep for searching file contents with regex\n - Use Read when you know the specific file path\n - Use Bash ONLY for read-only operations (ls, git status, git log, git diff, find)\n - NEVER use Bash for any file creation or modification commands\n - Use WebSearch or FetchURL when a question needs external context (library documentation, error messages, upstream APIs); the local codebase remains your primary domain\n - Adapt your search depth based on the thoroughness level specified by the caller\n - Wherever possible, spawn multiple parallel tool calls for grepping and reading files to maximize speed\n\n If the prompt includes a <git-context> block, use it to orient yourself about the repository state before starting your investigation.\n\n You are meant to be a fast agent. Complete the search request efficiently and report your findings clearly in a structured format.\nwhenToUse: |\n Fast agent specialized for exploring codebases. Use this when you need to quickly find files by patterns (e.g. \"src/**/*.yaml\"), search code for keywords (e.g. \"database connection\"), or answer questions about the codebase (e.g. \"how does the auth module work?\"). When calling this agent, specify the desired thoroughness level: \"quick\" for basic searches, \"medium\" for moderate exploration, or \"thorough\" for comprehensive analysis across multiple locations and naming conventions. Use this agent for any read-only exploration that will clearly require more than 3 search queries. Prefer launching multiple explore agents concurrently when investigating independent questions.\ntools:\n - Bash\n - Read\n - ReadMediaFile\n - Glob\n - Grep\n - MistakeRecord\n - WebSearch\n - FetchURL\n";
28562
+ explore_default = explore_default.replace(" - Read\n", " - Read\n - ReadBatch\n");
28560
28563
  }));
28561
28564
  //#endregion
28562
28565
  //#region ../../packages/agent-core/src/profile/default/init.md?raw
@@ -28569,6 +28572,7 @@ var init_init = __esmMin((() => {
28569
28572
  var plan_default;
28570
28573
  var init_plan$1 = __esmMin((() => {
28571
28574
  plan_default = "extends: agent\nname: plan\npromptVars:\n roleAdditional: |\n You are now running as a subagent. All the `user` messages are sent by the main agent. The main agent cannot see your context, it can only see your last message when you finish the task. You must treat the parent agent as your caller. Do not directly ask the end user questions. If something is unclear, explain the ambiguity in your final summary to the parent agent.\n\n Before designing your implementation plan, consider whether you fully understand the codebase areas relevant to the task. If not, recommend the parent agent to use the explore agent (subagent_type=\"explore\") to investigate key questions first. In your response, clearly state:\n 1. What you already know from the information provided\n 2. What questions remain unanswered that would benefit from explore agent investigation\n 3. Your implementation plan (either preliminary if questions remain, or final if sufficient context exists)\n\n You are a read-only planning agent: you can read and search files (Read, Glob, Grep, ReadMediaFile) and consult the web (WebSearch, FetchURL), but you have no shell and no project file-editing tools. MistakeRecord is the sole write-capable exception: it may record a concrete refutation in shared BLUN learning memory, never modify project files. Where the general instructions tell you to make changes with tools, that does not apply to you — do not attempt to run commands or modify project files. Your deliverable is the plan itself, returned as your final message.\nwhenToUse: |\n Use this agent when the parent agent needs a step-by-step implementation plan, key file identification, and architectural trade-off analysis before code changes are made.\ntools:\n - Read\n - ReadMediaFile\n - Glob\n - Grep\n - MistakeRecord\n - WebSearch\n - FetchURL\n";
28575
+ plan_default = plan_default.replace(" - Read\n", " - Read\n - ReadBatch\n");
28572
28576
  }));
28573
28577
  //#endregion
28574
28578
  //#region ../../packages/agent-core/src/profile/default/system.md?raw
@@ -259401,7 +259405,7 @@ function containsNulByte(text) {
259401
259405
  function notReadableFileOutput(path) {
259402
259406
  return `"${path}" is not readable as UTF-8 text. If it is an image or video, use ReadMediaFile. For other binary formats, use Bash or an MCP tool if available.`;
259403
259407
  }
259404
- var MAX_LINES$1, MAX_LINE_LENGTH, MAX_BYTES, S_IFMT$1, S_IFREG, PositiveLineOffsetSchema, TailLineOffsetSchema, ReadInputSchema, READ_DESCRIPTION, ReadTool, readContinuationNotice, READ_MODEL_VISIBLE_BYTES;
259408
+ var MAX_LINES$1, MAX_LINE_LENGTH, MAX_BYTES, S_IFMT$1, S_IFREG, PositiveLineOffsetSchema, TailLineOffsetSchema, ReadInputSchema, ReadBatchInputSchema, READ_DESCRIPTION, READ_BATCH_DESCRIPTION, ReadTool, ReadBatchTool, readContinuationNotice, READ_MODEL_VISIBLE_BYTES, MAX_READ_BATCH_REQUESTS, readBatchAllFailed, renderReadBatchOutput;
259405
259409
  var init_read = __esmMin((() => {
259406
259410
  init_zod$1();
259407
259411
  init_tool_access();
@@ -259413,6 +259417,7 @@ var init_read = __esmMin((() => {
259413
259417
  init_line_endings();
259414
259418
  init_read$1();
259415
259419
  ({ readContinuationNotice, READ_MODEL_VISIBLE_BYTES } = createRequire(import.meta.url)("./bin/read-continuation-policy.cjs"));
259420
+ ({ MAX_READ_BATCH_REQUESTS, readBatchAllFailed, renderReadBatchOutput } = createRequire(import.meta.url)("./bin/read-batch-policy.cjs"));
259416
259421
  MAX_LINES$1 = 1e3;
259417
259422
  MAX_LINE_LENGTH = 2e3;
259418
259423
  MAX_BYTES = READ_MODEL_VISIBLE_BYTES;
@@ -259425,6 +259430,7 @@ var init_read = __esmMin((() => {
259425
259430
  line_offset: union([PositiveLineOffsetSchema, TailLineOffsetSchema]).optional().describe(`The line number to start reading from. Omit to start at line 1. Negative values read from the end of the file; the absolute value cannot exceed ${String(MAX_LINES$1)}.`),
259426
259431
  n_lines: number$1().int().positive().optional().describe(`The number of lines to read; the tool also applies its internal cap. Omit to read up to the internal cap of ${String(MAX_LINES$1)} lines.`)
259427
259432
  });
259433
+ ReadBatchInputSchema = object({ requests: array(ReadInputSchema).min(2).max(MAX_READ_BATCH_REQUESTS).describe(`Two to ${String(MAX_READ_BATCH_REQUESTS)} independent known text-file reads. Do not batch a read whose path or range depends on an earlier result.`) });
259428
259434
  object({
259429
259435
  content: string(),
259430
259436
  lineCount: number$1().int().nonnegative()
@@ -259434,12 +259440,13 @@ var init_read = __esmMin((() => {
259434
259440
  MAX_BYTES_KB: MAX_BYTES / 1024,
259435
259441
  MAX_LINE_LENGTH
259436
259442
  });
259437
- read_default = "Read a known UTF-8 text file. Invalid paths error; directories are unsupported. Relative paths use the working directory; outside paths must be absolute. Use Glob for unknown names, Grep for content, and ReadMediaFile for media.\n\nEach call returns at most {{ MAX_LINES }} lines or {{ MAX_BYTES_KB }} KB and truncates lines beyond {{ MAX_LINE_LENGTH }} characters. Page large files with 1-based `line_offset` and `n_lines`; omit `n_lines` for the cap. Negative `line_offset` reads from the end, up to {{ MAX_LINES }} lines. A capped result names the exact next `line_offset`. Issue independent Read calls together in one response when several files or ranges may be useful.\n\nSensitive files such as `.env` and credential stores are refused; templates and public keys remain readable. Only UTF-8 text without NUL bytes is accepted; use Bash or an MCP tool for other binaries.\n\nOutput is `<line-number>\\t<content>` per line. The trailing `<system>...</system>` block is status, not file content. Pure CRLF appears as LF and is preserved by Edit. Mixed or lone carriage returns appear as `\\r` and require exact `Edit.old_string` escapes.";
259443
+ read_default = "Read a known UTF-8 text file. Invalid paths error; directories are unsupported. Relative paths use the working directory; outside paths must be absolute. Use Glob for unknown names, Grep for content, and ReadMediaFile for media.\n\nEach call returns at most {{ MAX_LINES }} lines or {{ MAX_BYTES_KB }} KB and truncates lines beyond {{ MAX_LINE_LENGTH }} characters. Page large files with 1-based `line_offset` and `n_lines`; omit `n_lines` for the cap. Negative `line_offset` reads from the end, up to {{ MAX_LINES }} lines. A capped result names the exact next `line_offset`. Issue independent Read calls together in one response when several files or ranges may be useful. Prefer ReadBatch when two or more independent paths or ranges are already known.\n\nSensitive files such as `.env` and credential stores are refused; templates and public keys remain readable. Only UTF-8 text without NUL bytes is accepted; use Bash or an MCP tool for other binaries.\n\nOutput is `<line-number>\\t<content>` per line. The trailing `<system>...</system>` block is status, not file content. Pure CRLF appears as LF and is preserved by Edit. Mixed or lone carriage returns appear as `\\r` and require exact `Edit.old_string` escapes.";
259438
259444
  READ_DESCRIPTION = renderPrompt(read_default, {
259439
259445
  MAX_LINES: MAX_LINES$1,
259440
259446
  MAX_BYTES_KB: MAX_BYTES / 1024,
259441
259447
  MAX_LINE_LENGTH
259442
259448
  });
259449
+ READ_BATCH_DESCRIPTION = `Read ${String(MAX_READ_BATCH_REQUESTS)} or fewer independent known UTF-8 text files or ranges concurrently in one tool call. Use this instead of serial Read calls only when every path and range is already known and no later request depends on an earlier result. Each request uses the same path, sensitive-file, UTF-8, line, and byte rules as Read. Results remain in request order and identify partial failures individually.`;
259443
259450
  ReadTool = class {
259444
259451
  kaos;
259445
259452
  workspace;
@@ -259719,6 +259726,54 @@ var init_read = __esmMin((() => {
259719
259726
  return parts.join(" ");
259720
259727
  }
259721
259728
  };
259729
+ ReadBatchTool = class {
259730
+ kaos;
259731
+ workspace;
259732
+ reader;
259733
+ name = "ReadBatch";
259734
+ description = READ_BATCH_DESCRIPTION;
259735
+ parameters = toInputJsonSchema(ReadBatchInputSchema);
259736
+ constructor(kaos, workspace) {
259737
+ this.kaos = kaos;
259738
+ this.workspace = workspace;
259739
+ this.reader = new ReadTool(kaos, workspace);
259740
+ }
259741
+ resolveExecution(args) {
259742
+ const items = args.requests.map((request) => ({
259743
+ request,
259744
+ safePath: resolvePathAccessPath(request.path, {
259745
+ kaos: this.kaos,
259746
+ workspace: this.workspace,
259747
+ operation: "read"
259748
+ })
259749
+ }));
259750
+ const ruleOptions = {
259751
+ cwd: this.workspace.workspaceDir,
259752
+ pathClass: this.kaos.pathClass(),
259753
+ homeDir: this.kaos.gethome()
259754
+ };
259755
+ return {
259756
+ accesses: items.flatMap((item) => ToolAccesses.readFile(item.safePath)),
259757
+ description: `Reading ${String(items.length)} files or ranges`,
259758
+ display: {
259759
+ kind: "file_io",
259760
+ operation: "read",
259761
+ path: items[0].safePath,
259762
+ detail: `${String(items.length)} reads`
259763
+ },
259764
+ approvalRule: this.name,
259765
+ matchesRule: (ruleArgs) => items.every((item) => matchesPathRuleSubject(ruleArgs, item.safePath, ruleOptions)),
259766
+ execute: () => this.execution(items)
259767
+ };
259768
+ }
259769
+ async execution(items) {
259770
+ const results = await Promise.all(items.map((item) => this.reader.execution(item.request, item.safePath)));
259771
+ return {
259772
+ isError: readBatchAllFailed(results),
259773
+ output: renderReadBatchOutput(items.map((item) => item.request), results)
259774
+ };
259775
+ }
259776
+ };
259722
259777
  }));
259723
259778
  //#endregion
259724
259779
  //#region ../../packages/agent-core/src/tools/builtin/file/read-media.md?raw
@@ -265157,6 +265212,7 @@ var init_tool$1 = __esmMin((() => {
265157
265212
  const goalToolsEnabled = this.agent.type === "main";
265158
265213
  this.builtinTools = new Map([
265159
265214
  new ReadTool(kaos, workspace),
265215
+ new ReadBatchTool(kaos, workspace),
265160
265216
  new WriteTool(kaos, workspace, () => this.agent.context.history, toolServices?.lsp),
265161
265217
  new EditTool(kaos, workspace, () => this.agent.context.history, toolServices?.lsp),
265162
265218
  new GrepTool(kaos, workspace, this.agent.telemetry),
@@ -419689,7 +419745,12 @@ function telegramRemoteCommand(text) {
419689
419745
  */
419690
419746
  function injectChannelEnvelope(host, envelope, preamble) {
419691
419747
  const origin = channelOrigin(envelope);
419748
+ const targetedElsewhere = ["true", "1"].includes(envelope.meta["targeted_elsewhere"]?.trim().toLowerCase() ?? "");
419692
419749
  const contextOnly = !channelMessageAddressed(envelope) && !telegramBotPriorityMessage(envelope);
419750
+ if (targetedElsewhere) {
419751
+ host.displayContext?.(envelope.text, origin);
419752
+ return "ignored";
419753
+ }
419693
419754
  const reportSource = {
419694
419755
  source: "telegram",
419695
419756
  chatId: envelope.meta.chat_id,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "9.1.508",
3
+ "version": "9.1.510",
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": {
@@ -61,5 +61,6 @@
61
61
  "dependencies": {
62
62
  "blun-king-cli": "^9.1.62",
63
63
  "node-addon-api": "^7.1.1"
64
- }
64
+ },
65
+ "devDependencies": {}
65
66
  }
@@ -210,10 +210,12 @@ function gate(ctx, botUsername) {
210
210
  if (groupAllowFrom.length > 0 && !groupAllowFrom.includes(senderId)) return { action: "drop" };
211
211
  const mentionBypass = from.isBot !== true && (policy.alwaysAllowFrom ?? []).includes(senderId);
212
212
  const addressed = !matchesAny(ctx.text, access.ignorePatterns) && (mentionBypass || isMentioned(ctx, botUsername, access.mentionPatterns));
213
+ const targetedElsewhere = from.isBot === true && !addressed;
213
214
  return {
214
215
  action: "deliver",
215
216
  access,
216
217
  addressed,
218
+ targetedElsewhere,
217
219
  ...from.isBot === true ? { priority: "bot" } : {}
218
220
  };
219
221
  }
@@ -4684,6 +4686,7 @@ async function handleInbound(event) {
4684
4686
  }
4685
4687
  const msgId = ctx.messageId;
4686
4688
  const addressed = result.action === "deliver" ? result.addressed ?? true : true;
4689
+ const targetedElsewhere = result.action === "deliver" ? result.targetedElsewhere ?? false : false;
4687
4690
  const priority = result.action === "deliver" ? result.priority : void 0;
4688
4691
  if (addressed) {
4689
4692
  bot.api.sendChatAction(chatId, "typing").catch(() => {});
@@ -4701,6 +4704,7 @@ async function handleInbound(event) {
4701
4704
  ...ctx.chatType !== void 0 ? { chat_type: ctx.chatType } : {},
4702
4705
  ...ctx.chatTitle !== void 0 ? { chat_title: ctx.chatTitle } : {},
4703
4706
  addressed: String(addressed),
4707
+ targeted_elsewhere: String(targetedElsewhere),
4704
4708
  ...priority === void 0 ? {} : { priority },
4705
4709
  user: ctx.from?.username ?? String(ctx.from?.id),
4706
4710
  is_bot: String(ctx.from?.is_bot === true),