blun-king-cli 9.1.509 → 9.1.511

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.
Files changed (37) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/LIESMICH.txt +12 -1
  3. package/README.md +12 -1
  4. package/agent-spine-plugin/.claude-plugin/marketplace.json +1 -1
  5. package/agent-spine-plugin/.claude-plugin/plugin.json +1 -1
  6. package/agent-spine-plugin/.codex-plugin/plugin.json +2 -1
  7. package/agent-spine-plugin/CHANGELOG.md +70 -8
  8. package/agent-spine-plugin/README.md +1 -1
  9. package/agent-spine-plugin/blun.plugin.json +33 -33
  10. package/agent-spine-plugin/docs/acceptance.md +2 -2
  11. package/agent-spine-plugin/docs/gateway-runtime.md +8 -1
  12. package/agent-spine-plugin/docs/host-integration.md +34 -34
  13. package/agent-spine-plugin/docs/preflight-recall.md +69 -0
  14. package/agent-spine-plugin/docs/relationships.md +6 -0
  15. package/agent-spine-plugin/hooks/codex.json +47 -0
  16. package/agent-spine-plugin/hooks/hooks.json +11 -0
  17. package/agent-spine-plugin/hooks/version.json +2 -2
  18. package/agent-spine-plugin/package.json +4 -4
  19. package/agent-spine-plugin/scripts/check-hosts.js +53 -51
  20. package/agent-spine-plugin/scripts/check-install.js +53 -35
  21. package/agent-spine-plugin/scripts/release-check.js +11 -10
  22. package/agent-spine-plugin/skills/agent-spine/SKILL.md +1 -1
  23. package/agent-spine-plugin/src/cli.js +46 -1
  24. package/agent-spine-plugin/src/hook.js +168 -90
  25. package/agent-spine-plugin/src/index.js +6 -0
  26. package/agent-spine-plugin/src/lib/acceptance.js +40 -0
  27. package/agent-spine-plugin/src/lib/audit.js +9 -2
  28. package/agent-spine-plugin/src/lib/graph.js +22 -4
  29. package/agent-spine-plugin/src/lib/persona-runtime.js +103 -31
  30. package/agent-spine-plugin/src/lib/preflight.js +678 -0
  31. package/agent-spine-plugin/src/lib/source-roots.js +32 -32
  32. package/agent-spine-plugin/src/version.js +1 -1
  33. package/agent-spine-plugin/src/worker.js +20 -3
  34. package/bin/read-batch-policy.cjs +32 -0
  35. package/bin/turn-tool-performance-policy.cjs +1 -0
  36. package/blun.mjs +58 -2
  37. package/package.json +3 -2
@@ -9,12 +9,12 @@ import { purgeIndexedMemoryCache, resolveIndexedMemory } from "./indexed-memory.
9
9
 
10
10
  export const SOURCE_REGISTRY_SCHEMA = "agentspine.source-roots/v1";
11
11
  const MAX_REGISTRY_BYTES = 1024 * 1024;
12
- const MAX_SOURCES = 256;
13
- const MAX_RULE_FILES = 128;
14
- const MAX_PROJECT_FILES = 240;
15
- const MAX_TOTAL_SOURCE_BYTES = 8 * 1024 * 1024;
16
- const MAX_DIRECTORY_ENTRIES = 4096;
17
- const MAX_PROJECT_DIRECTORY_ENTRIES = 8192;
12
+ const MAX_SOURCES = 256;
13
+ const MAX_RULE_FILES = 128;
14
+ const MAX_PROJECT_FILES = 240;
15
+ const MAX_TOTAL_SOURCE_BYTES = 8 * 1024 * 1024;
16
+ const MAX_DIRECTORY_ENTRIES = 4096;
17
+ const MAX_PROJECT_DIRECTORY_ENTRIES = 8192;
18
18
  const SOURCE_RESOLUTION_MS = 2000;
19
19
  const SAFE_NAME = /^[A-Za-z0-9._-]{1,128}$/;
20
20
  const SKIP_EXTRA_DIRS = new Set([".git", ".hg", ".svn", ".claude", ".codex", "node_modules", "vendor", "dist", "build", "coverage"]);
@@ -160,7 +160,7 @@ async function findRoot(cwd, markers) {
160
160
  }
161
161
  }
162
162
 
163
- async function containsProjectMarker(directory) {
163
+ async function containsProjectMarker(directory) {
164
164
  try {
165
165
  await lstat(join(directory, ".git"));
166
166
  return true;
@@ -168,37 +168,37 @@ async function containsProjectMarker(directory) {
168
168
  if (error.code === "ENOENT") return false;
169
169
  throw error;
170
170
  }
171
- }
172
-
173
- async function containsEmbeddedHostProfile(directory) {
174
- return Boolean(
175
- await existingDirectory(join(directory, ".runtime-private"))
176
- && await existingRegular(join(directory, "config.toml"))
177
- );
178
- }
179
-
180
- async function boundedMarkdownTree(directory, prefix, host, scope, precedenceStart, deadline, {
181
- projectBoundary = false,
182
- maxFiles = MAX_RULE_FILES,
183
- maxDirectoryEntries = MAX_DIRECTORY_ENTRIES
184
- } = {}) {
171
+ }
172
+
173
+ async function containsEmbeddedHostProfile(directory) {
174
+ return Boolean(
175
+ await existingDirectory(join(directory, ".runtime-private"))
176
+ && await existingRegular(join(directory, "config.toml"))
177
+ );
178
+ }
179
+
180
+ async function boundedMarkdownTree(directory, prefix, host, scope, precedenceStart, deadline, {
181
+ projectBoundary = false,
182
+ maxFiles = MAX_RULE_FILES,
183
+ maxDirectoryEntries = MAX_DIRECTORY_ENTRIES
184
+ } = {}) {
185
185
  const root = await existingDirectory(directory);
186
186
  if (!root) return [];
187
187
  const output = [];
188
188
  let visitedEntries = 0;
189
189
  async function walk(current) {
190
190
  if (Date.now() > deadline) throw new Error(`host-native source resolution exceeded ${SOURCE_RESOLUTION_MS} ms`);
191
- if (projectBoundary && current !== root
192
- && (await containsProjectMarker(current) || await containsEmbeddedHostProfile(current))) return;
191
+ if (projectBoundary && current !== root
192
+ && (await containsProjectMarker(current) || await containsEmbeddedHostProfile(current))) return;
193
193
  const entries = [];
194
194
  for await (const entry of await opendir(current)) {
195
195
  visitedEntries += 1;
196
- if (visitedEntries > maxDirectoryEntries) throw new Error(`host-native source tree exceeds ${maxDirectoryEntries} entries`);
196
+ if (visitedEntries > maxDirectoryEntries) throw new Error(`host-native source tree exceeds ${maxDirectoryEntries} entries`);
197
197
  entries.push(entry);
198
198
  }
199
199
  entries.sort((a, b) => a.name.localeCompare(b.name));
200
200
  for (const entry of entries) {
201
- if (output.length >= maxFiles) throw new Error(`host-native rule tree exceeds ${maxFiles} files`);
201
+ if (output.length >= maxFiles) throw new Error(`host-native rule tree exceeds ${maxFiles} files`);
202
202
  if (entry.isSymbolicLink()) continue;
203
203
  const path = join(current, entry.name);
204
204
  if (entry.isDirectory() && !entry.name.startsWith(".") && !SKIP_EXTRA_DIRS.has(entry.name)) await walk(path);
@@ -388,11 +388,11 @@ export async function resolveHostSourceCatalog({ host, cwd = process.cwd(), inpu
388
388
  const { registry } = await readRegistry(env);
389
389
  let hostHome;
390
390
  let projectRoot;
391
- let sources;
392
- let hostDetails = {};
393
- if (host === "codex") {
394
- const codexHome = env.CODEX_HOME || env.BLUN_HOME || join(homedir(), ".codex");
395
- hostHome = await existingDirectory(resolve(codexHome)) || resolve(codexHome);
391
+ let sources;
392
+ let hostDetails = {};
393
+ if (host === "codex") {
394
+ const codexHome = env.CODEX_HOME || env.BLUN_HOME || join(homedir(), ".codex");
395
+ hostHome = await existingDirectory(resolve(codexHome)) || resolve(codexHome);
396
396
  const config = await codexConfig(hostHome);
397
397
  projectRoot = env.AGENTSPINE_ROOT ? await canonicalPath(env.AGENTSPINE_ROOT) : await findRoot(canonicalCwd, config.rootMarkers);
398
398
  sources = await codexSources({ cwd: canonicalCwd, projectRoot, codexHome: hostHome, config });
@@ -407,8 +407,8 @@ export async function resolveHostSourceCatalog({ host, cwd = process.cwd(), inpu
407
407
  memoryDiagnostics: result.memoryDiagnostics };
408
408
  }
409
409
  if (projectRoot !== homedir() && projectRoot !== dirname(hostHome)) {
410
- sources.push(...await boundedMarkdownTree(projectRoot, "agentspine:project", host, "project", 3000, deadline,
411
- { projectBoundary: true, maxFiles: MAX_PROJECT_FILES, maxDirectoryEntries: MAX_PROJECT_DIRECTORY_ENTRIES }));
410
+ sources.push(...await boundedMarkdownTree(projectRoot, "agentspine:project", host, "project", 3000, deadline,
411
+ { projectBoundary: true, maxFiles: MAX_PROJECT_FILES, maxDirectoryEntries: MAX_PROJECT_DIRECTORY_ENTRIES }));
412
412
  }
413
413
  const nativeNames = new Set(host === "codex"
414
414
  ? ["AGENTS.override.md", "AGENTS.md", ...(hostDetails.fallbackNames || [])]
@@ -1 +1 @@
1
- export const VERSION = "0.8.0";
1
+ export const VERSION = "0.10.1";
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import { spawn } from "node:child_process";
3
3
  import { watch } from "node:fs";
4
- import { realpath } from "node:fs/promises";
5
- import { isAbsolute, resolve } from "node:path";
4
+ import { lstat, realpath } from "node:fs/promises";
5
+ import { isAbsolute, join, resolve } from "node:path";
6
6
  import { claimGatewayWork, completeGatewayRun, deliverPrepared, failGatewayRun, loadGatewayRuntime, reconcileGateway, updateGatewayHealth } from "./lib/gateway-runtime.js";
7
7
  import { createTelegramAdapter } from "./lib/telegram-adapter.js";
8
8
  import { acknowledgeChannelDelivery, loadChannelRuntime } from "./lib/channel-runtime.js";
@@ -12,11 +12,25 @@ import { isMainModule } from "./lib/runtime.js";
12
12
  const MAX_FRAME = 64 * 1024;
13
13
  const WAKE_FILES = new Set(["attention.json", "channel-runtime.json", "gateway-policy.json", "persona-policy.json", "persona-runtime.json"]);
14
14
 
15
- export async function waitForGatewayWake(root, delayMs, { watchFactory = watch, realpathFactory = realpath } = {}) {
15
+ async function wakeSnapshot(directory) {
16
+ return Promise.all([...WAKE_FILES].sort().map(async (name) => {
17
+ try {
18
+ const metadata = await lstat(join(directory, name), { bigint: true });
19
+ return `${name}:${metadata.dev}:${metadata.ino}:${metadata.size}:${metadata.mtimeNs}`;
20
+ } catch (error) {
21
+ if (error.code === "ENOENT") return `${name}:missing`;
22
+ throw error;
23
+ }
24
+ })).then((entries) => entries.join("\n"));
25
+ }
26
+
27
+ export async function waitForGatewayWake(root, delayMs, { watchFactory = watch, realpathFactory = realpath, onReady = null } = {}) {
16
28
  const delay = Math.max(250, Math.min(60000, Number(delayMs) || 60000));
17
29
  const { directory } = await loadGatewayRuntime(root);
18
30
  let watchPath;
19
31
  try { watchPath = await realpathFactory(directory); } catch { return "watch-unavailable"; }
32
+ let before;
33
+ try { before = await wakeSnapshot(watchPath); } catch { return "watch-unavailable"; }
20
34
  return new Promise((resolvePromise) => {
21
35
  let settled = false; let watcher;
22
36
  const finish = (reason) => {
@@ -32,6 +46,9 @@ export async function waitForGatewayWake(root, delayMs, { watchFactory = watch,
32
46
  if (name === null || WAKE_FILES.has(name)) finish("event");
33
47
  });
34
48
  watcher.on?.("error", () => finish("watch-error"));
49
+ Promise.resolve().then(() => onReady?.(watchPath)).then(() => wakeSnapshot(watchPath)).then((after) => {
50
+ if (after !== before) finish("event");
51
+ }).catch(() => finish("watch-error"));
35
52
  } catch { finish("watch-unavailable"); }
36
53
  });
37
54
  }
@@ -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),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "9.1.509",
3
+ "version": "9.1.511",
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
  }