blun-king-cli 9.1.36 → 9.1.38

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.
@@ -23,6 +23,7 @@ const {
23
23
  } = require('./standard-tools-bootstrap');
24
24
  const { CORE_LOADED_MESSAGE } = require('./core-bootstrap');
25
25
  const { prepareManagedNodeRuntime } = require('./node-runtime');
26
+ const { repairConfiguredNativeModules } = require('./native-module-repair');
26
27
  const { acquireSharedRuntimeLease, tryAcquireUpdateLease } = require('./update-lease');
27
28
  const { runExplicitUpdate, runUpdateNotice } = require('./update-notice');
28
29
  const {
@@ -388,6 +389,17 @@ async function runLauncher(options = {}) {
388
389
  installTelegramForProfile(PKG, blunDir);
389
390
  }
390
391
 
392
+ const nativeModules = repairConfiguredNativeModules({
393
+ blunDir,
394
+ callerCwd,
395
+ onRepairStart() {
396
+ process.stdout.write('BLUN richtet native Node.js-Module fuer die neue Laufzeit ein.\n');
397
+ },
398
+ });
399
+ if (nativeModules.repaired.length > 0) {
400
+ process.stdout.write('Native Node.js-Module sind wieder einsatzbereit.\n');
401
+ }
402
+
391
403
  // --- 6. Start -----------------------------------------------------------
392
404
  const env = createLauncherEnvironment(process.env, mode, readPackageVersion());
393
405
  env.BLUN_HOME = blunDir;
@@ -0,0 +1,201 @@
1
+ 'use strict';
2
+
3
+ const { spawnSync } = require('node:child_process');
4
+ const fs = require('node:fs');
5
+ const path = require('node:path');
6
+
7
+ const SQLITE_PROBE = [
8
+ "const Database = require('better-sqlite3');",
9
+ "const db = new Database(':memory:');",
10
+ "const row = db.prepare('SELECT 1 AS ok').get();",
11
+ 'db.close();',
12
+ "if (row?.ok !== 1) throw new Error('SQLITE_PROBE_FAILED');",
13
+ ].join(' ');
14
+
15
+ function commandFailure(result) {
16
+ return String(result?.stderr || result?.stdout || result?.error?.message || '').trim();
17
+ }
18
+
19
+ function packageHasDependency(document, dependency) {
20
+ return ['dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies']
21
+ .some((field) => Object.hasOwn(document?.[field] || {}, dependency));
22
+ }
23
+
24
+ function nearestPackageDirectory(startDir) {
25
+ let current = path.resolve(startDir);
26
+ while (true) {
27
+ if (fs.existsSync(path.join(current, 'package.json'))) return current;
28
+ const parent = path.dirname(current);
29
+ if (parent === current) return null;
30
+ current = parent;
31
+ }
32
+ }
33
+
34
+ function projectRoot(startDir) {
35
+ let current = path.resolve(startDir);
36
+ while (true) {
37
+ if (fs.existsSync(path.join(current, '.git'))) return current;
38
+ const parent = path.dirname(current);
39
+ if (parent === current) return path.resolve(startDir);
40
+ current = parent;
41
+ }
42
+ }
43
+
44
+ function readMcpDocument(configPath) {
45
+ if (!fs.existsSync(configPath)) return null;
46
+ try {
47
+ const document = JSON.parse(fs.readFileSync(configPath, 'utf8'));
48
+ return document && typeof document === 'object' ? document : null;
49
+ } catch {
50
+ return null;
51
+ }
52
+ }
53
+
54
+ function serverProjectDirectory(server, configPath) {
55
+ if (!server || typeof server !== 'object' || server.enabled === false) return null;
56
+ if (typeof server.cwd === 'string' && server.cwd.trim()) {
57
+ const cwd = server.cwd.trim();
58
+ return nearestPackageDirectory(
59
+ path.isAbsolute(cwd) ? cwd : path.resolve(path.dirname(configPath), cwd),
60
+ );
61
+ }
62
+ const args = Array.isArray(server.args) ? server.args : [];
63
+ const script = args.find((arg) => typeof arg === 'string' && /\.[cm]?js$/iu.test(arg));
64
+ if (!script) return null;
65
+ const scriptPath = path.isAbsolute(script)
66
+ ? script
67
+ : path.resolve(path.dirname(configPath), script);
68
+ return nearestPackageDirectory(path.dirname(scriptPath));
69
+ }
70
+
71
+ function configuredNativeProjectPaths({ blunDir, callerCwd }) {
72
+ const root = projectRoot(callerCwd);
73
+ const configPaths = [...new Set([
74
+ path.join(blunDir, 'mcp.json'),
75
+ path.join(root, '.mcp.json'),
76
+ path.join(callerCwd, '.blun', 'mcp.json'),
77
+ ].map((entry) => path.resolve(entry)))];
78
+ const candidates = [nearestPackageDirectory(callerCwd)];
79
+ for (const configPath of configPaths) {
80
+ const document = readMcpDocument(configPath);
81
+ const servers = document?.mcpServers;
82
+ if (!servers || typeof servers !== 'object' || Array.isArray(servers)) continue;
83
+ for (const server of Object.values(servers)) {
84
+ candidates.push(serverProjectDirectory(server, configPath));
85
+ }
86
+ }
87
+
88
+ const seen = new Set();
89
+ const projects = [];
90
+ for (const candidate of candidates) {
91
+ if (!candidate) continue;
92
+ const absolute = path.resolve(candidate);
93
+ const key = process.platform === 'win32' ? absolute.toLowerCase() : absolute;
94
+ if (seen.has(key)) continue;
95
+ seen.add(key);
96
+ projects.push(absolute);
97
+ }
98
+ return projects;
99
+ }
100
+
101
+ function probeBetterSqlite({ nodeBinary, projectDir, spawnSyncImpl }) {
102
+ return spawnSyncImpl(nodeBinary, ['-e', SQLITE_PROBE], {
103
+ cwd: projectDir,
104
+ encoding: 'utf8',
105
+ timeout: 30_000,
106
+ windowsHide: true,
107
+ });
108
+ }
109
+
110
+ function npmInvocation(nodeBinary) {
111
+ const candidates = [
112
+ path.join(path.dirname(nodeBinary), 'node_modules', 'npm', 'bin', 'npm-cli.js'),
113
+ path.resolve(path.dirname(nodeBinary), '..', 'lib', 'node_modules', 'npm', 'bin', 'npm-cli.js'),
114
+ ];
115
+ const npmCli = candidates.find((candidate) => fs.existsSync(candidate));
116
+ if (!npmCli) {
117
+ throw new Error('npm-cli.js wurde fuer die aktive Node.js-Laufzeit nicht gefunden.');
118
+ }
119
+ return { args: [npmCli], command: nodeBinary };
120
+ }
121
+
122
+ function repairNativeProject({
123
+ nodeBinary = process.execPath,
124
+ npmCommand,
125
+ onRepairStart,
126
+ projectDir,
127
+ spawnSyncImpl = spawnSync,
128
+ }) {
129
+ if (typeof projectDir !== 'string' || !path.isAbsolute(projectDir)) {
130
+ throw new Error('Der Projektpfad fuer die native Ladeprobe muss absolut sein.');
131
+ }
132
+ const absoluteProjectDir = path.resolve(projectDir);
133
+ const packagePath = path.join(absoluteProjectDir, 'package.json');
134
+ let packageDocument;
135
+ try {
136
+ packageDocument = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
137
+ } catch {
138
+ return { kind: 'skipped', reason: 'package_unreadable' };
139
+ }
140
+ if (!packageHasDependency(packageDocument, 'better-sqlite3')) {
141
+ return { kind: 'skipped', reason: 'dependency_missing' };
142
+ }
143
+
144
+ const before = probeBetterSqlite({ nodeBinary, projectDir: absoluteProjectDir, spawnSyncImpl });
145
+ if (before.status === 0) return { kind: 'healthy' };
146
+
147
+ const npm = npmCommand === undefined
148
+ ? npmInvocation(nodeBinary)
149
+ : { args: [], command: npmCommand };
150
+ onRepairStart?.(absoluteProjectDir);
151
+ const rebuilt = spawnSyncImpl(npm.command, [...npm.args, 'rebuild', 'better-sqlite3'], {
152
+ cwd: absoluteProjectDir,
153
+ encoding: 'utf8',
154
+ timeout: 300_000,
155
+ windowsHide: true,
156
+ });
157
+ if (rebuilt.status !== 0) {
158
+ throw new Error(
159
+ `Native Node.js-Module konnten nicht repariert werden: ${commandFailure(rebuilt) || 'ohne Fehlertext'}`,
160
+ );
161
+ }
162
+
163
+ const after = probeBetterSqlite({ nodeBinary, projectDir: absoluteProjectDir, spawnSyncImpl });
164
+ if (after.status !== 0) {
165
+ throw new Error(
166
+ `Ladeprobe nach dem Rebuild fehlgeschlagen: ${commandFailure(after) || 'ohne Fehlertext'}`,
167
+ );
168
+ }
169
+ return { kind: 'repaired' };
170
+ }
171
+
172
+ function repairConfiguredNativeModules({
173
+ blunDir,
174
+ callerCwd,
175
+ nodeBinary = process.execPath,
176
+ onRepairStart,
177
+ projectPaths,
178
+ repairProject = repairNativeProject,
179
+ spawnSyncImpl = spawnSync,
180
+ }) {
181
+ const projects = projectPaths || configuredNativeProjectPaths({ blunDir, callerCwd });
182
+ const result = { checked: projects.length, healthy: [], repaired: [], skipped: [] };
183
+ for (const projectDir of projects) {
184
+ const firstResult = repairProject({
185
+ nodeBinary,
186
+ projectDir,
187
+ spawnSyncImpl,
188
+ onRepairStart,
189
+ });
190
+ if (firstResult.kind === 'healthy') result.healthy.push(projectDir);
191
+ else if (firstResult.kind === 'repaired') result.repaired.push(projectDir);
192
+ else result.skipped.push(projectDir);
193
+ }
194
+ return result;
195
+ }
196
+
197
+ module.exports = {
198
+ configuredNativeProjectPaths,
199
+ repairConfiguredNativeModules,
200
+ repairNativeProject,
201
+ };
package/blun.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- // BLUN_BUILD_INPUT_SHA256:0a9b399298f68e4bffadcc1ead801f14012d39becc3a208a737071e6bb201f97
2
+ // BLUN_BUILD_INPUT_SHA256:e0036ad7d958609c5cf630953c5e1b6b6ec22fda3e8c09f26e7c97767acf846b
3
3
  import { fileURLToPath as __cjsShimFileURLToPath } from 'node:url';
4
4
  import { dirname as __cjsShimDirname } from 'node:path';
5
5
  const __filename = __cjsShimFileURLToPath(import.meta.url);
@@ -3021,6 +3021,8 @@ async function generate(provider, systemPrompt, tools, history, callbacks, optio
3021
3021
  serverDecodeMs,
3022
3022
  clientConsumeMs
3023
3023
  });
3024
+ const hasToolCall = message.toolCalls.length > 0 || pendingPart !== null && pendingPart.type === "function";
3025
+ if (stream.finishReason === null && hasToolCall) throw new APIConnectionError("The provider stream ended before its terminal finish reason. The incomplete response was discarded and no tool call was executed.");
3024
3026
  if (pendingPart !== null) flushPart(message, pendingPart, toolCallIndexMap);
3025
3027
  if (message.content.length === 0 && message.toolCalls.length === 0) throw new APIEmptyResponseError("The API returned an empty response (no content, no tool calls)." + formatFinishReasonHint(stream) + ` Provider: ${provider.name}, model: ${provider.modelName}`, {
3026
3028
  finishReason: stream.finishReason,
@@ -258792,7 +258794,116 @@ var init_read_media = __esmMin((() => {
258792
258794
  //#region ../../packages/agent-core/src/tools/builtin/file/write.md?raw
258793
258795
  var write_default;
258794
258796
  var init_write$1 = __esmMin((() => {
258795
- write_default = "Create, append to, or replace a file entirely.\n\n- Missing parent directories are created automatically (like `mkdir(parents=True, exist_ok=True)`).\n- Mode defaults to overwrite; append adds content at EOF without adding a newline.\n- Write is NOT ALLOWED for incremental changes to existing files, including trivial, one-line, quick, or cosmetic edits. Use Edit instead.\n- Use Write only when the file does not exist, you intend a complete replacement, or the new contents have little continuity with the old contents.\n- Do not create unsolicited documentation files (`*.md` write-ups, `README`s, summaries) just because a task finished — write one only when the user asks for it, or when a task or project instruction requires it (e.g. the plan-mode plan file, created with Write when plan mode directs you to, or a changeset the repo mandates).\n- Read before overwriting an existing file.\n- Write ignores the Read/Edit line-number view. NEVER include line prefixes.\n- Write outputs content literally, including supplied line endings: \\n stays LF, \\r\\n stays CRLF.\n- Source files may contain at most 500 lines. Split larger implementations into focused files; append cannot bypass the limit.\n- Set `single_file_override=true` only when the latest direct user message explicitly requires one single file. The accepted override is reported visibly.\n- For non-source content too large for one call, overwrite the first chunk, then append subsequent chunks. Never chunk Write to modify an existing file.\n";
258797
+ write_default = "Create, append to, or replace a file entirely.\n\n- Missing parent directories are created automatically (like `mkdir(parents=True, exist_ok=True)`).\n- Mode defaults to overwrite; append adds content at EOF without adding a newline.\n- Write is NOT ALLOWED for incremental changes to existing files, including trivial, one-line, quick, or cosmetic edits. Use Edit instead.\n- Use Write only when the file does not exist, you intend a complete replacement, or the new contents have little continuity with the old contents.\n- Do not create unsolicited documentation files (`*.md` write-ups, `README`s, summaries) just because a task finished — write one only when the user asks for it, or when a task or project instruction requires it (e.g. the plan-mode plan file, created with Write when plan mode directs you to, or a changeset the repo mandates).\n- Read before overwriting an existing file.\n- Write ignores the Read/Edit line-number view. NEVER include line prefixes.\n- Write outputs content literally, including supplied line endings: \\n stays LF, \\r\\n stays CRLF.\n- Source files may contain at most 500 lines. Split larger implementations into focused files; append cannot bypass the limit.\n- Set `single_file_override=true` only when the latest direct user message explicitly requires one single file. The accepted override is reported visibly.\n- For non-source content too large for one call, overwrite the first chunk, then append subsequent chunks. Never chunk Write to modify an existing file.\n- Runaway generated JavaScript/TypeScript identifiers are rejected before disk I/O. Regenerate only the affected section as a smaller focused chunk.\n";
258798
+ }));
258799
+ //#endregion
258800
+ //#region ../../packages/agent-core/src/tools/builtin/file/generated-source-health.ts
258801
+ /**
258802
+ * Find runaway generated identifiers while ignoring comments and strings.
258803
+ * A 512-character identifier is already far beyond normal generated source,
258804
+ * while quoted assets and long comments remain valid input.
258805
+ */
258806
+ function findDegenerateGeneratedIdentifier(path, content) {
258807
+ if (!JAVASCRIPT_LIKE_EXTENSIONS.has(extname$1(path).toLowerCase())) return null;
258808
+ let index = 0;
258809
+ let line = 1;
258810
+ while (index < content.length) {
258811
+ const current = content[index] ?? "";
258812
+ const next = content[index + 1] ?? "";
258813
+ if (current === "\n") {
258814
+ line += 1;
258815
+ index += 1;
258816
+ continue;
258817
+ }
258818
+ if (current === "/" && next === "/") {
258819
+ index = skipLineComment(content, index + 2);
258820
+ continue;
258821
+ }
258822
+ if (current === "/" && next === "*") {
258823
+ const skipped = skipBlockComment(content, index + 2, line);
258824
+ index = skipped.index;
258825
+ line = skipped.line;
258826
+ continue;
258827
+ }
258828
+ if (current === "'" || current === "\"" || current === "`") {
258829
+ const skipped = skipQuoted(content, index + 1, line, current);
258830
+ index = skipped.index;
258831
+ line = skipped.line;
258832
+ continue;
258833
+ }
258834
+ if (isIdentifierStart(current)) {
258835
+ const start = index;
258836
+ index += 1;
258837
+ while (index < content.length && isIdentifierPart(content[index] ?? "")) index += 1;
258838
+ const length = index - start;
258839
+ if (length > 512) return {
258840
+ length,
258841
+ line,
258842
+ preview: content.slice(start, start + 80)
258843
+ };
258844
+ continue;
258845
+ }
258846
+ index += 1;
258847
+ }
258848
+ return null;
258849
+ }
258850
+ function skipLineComment(content, index) {
258851
+ const newline = content.indexOf("\n", index);
258852
+ return newline < 0 ? content.length : newline;
258853
+ }
258854
+ function skipBlockComment(content, start, initialLine) {
258855
+ let index = start;
258856
+ let line = initialLine;
258857
+ while (index < content.length) {
258858
+ if (content[index] === "\n") line += 1;
258859
+ if (content[index] === "*" && content[index + 1] === "/") return {
258860
+ index: index + 2,
258861
+ line
258862
+ };
258863
+ index += 1;
258864
+ }
258865
+ return {
258866
+ index: content.length,
258867
+ line
258868
+ };
258869
+ }
258870
+ function skipQuoted(content, start, initialLine, quote) {
258871
+ let index = start;
258872
+ let line = initialLine;
258873
+ while (index < content.length) {
258874
+ const current = content[index] ?? "";
258875
+ if (current === "\\") {
258876
+ index += 2;
258877
+ continue;
258878
+ }
258879
+ if (current === "\n") line += 1;
258880
+ index += 1;
258881
+ if (current === quote) break;
258882
+ }
258883
+ return {
258884
+ index,
258885
+ line
258886
+ };
258887
+ }
258888
+ function isIdentifierStart(value) {
258889
+ return /[A-Za-z_$]/.test(value);
258890
+ }
258891
+ function isIdentifierPart(value) {
258892
+ return /[A-Za-z0-9_$]/.test(value);
258893
+ }
258894
+ var JAVASCRIPT_LIKE_EXTENSIONS;
258895
+ var init_generated_source_health = __esmMin((() => {
258896
+ init_dist$6();
258897
+ JAVASCRIPT_LIKE_EXTENSIONS = new Set([
258898
+ ".cjs",
258899
+ ".cts",
258900
+ ".js",
258901
+ ".jsx",
258902
+ ".mjs",
258903
+ ".mts",
258904
+ ".ts",
258905
+ ".tsx"
258906
+ ]);
258796
258907
  })), S_IFMT, S_IFDIR, WriteInputSchema, WriteTool;
258797
258908
  var init_write = __esmMin((() => {
258798
258909
  init_dist$6();
@@ -258803,6 +258914,7 @@ var init_write = __esmMin((() => {
258803
258914
  init_rule_match();
258804
258915
  init_source_file_line_limit();
258805
258916
  init_write$1();
258917
+ init_generated_source_health();
258806
258918
  init_lsp_diagnostics();
258807
258919
  S_IFMT = 61440;
258808
258920
  S_IFDIR = 16384;
@@ -258855,6 +258967,11 @@ bytesWritten: number$1().int().nonnegative() });
258855
258967
  }
258856
258968
  async execution(args, safePath) {
258857
258969
  const mode = args.mode ?? "overwrite";
258970
+ const degenerateIdentifier = findDegenerateGeneratedIdentifier(safePath, args.content);
258971
+ if (degenerateIdentifier !== null) return {
258972
+ isError: true,
258973
+ output: `Refused to write ${args.path}: generated source contains a runaway ${String(degenerateIdentifier.length)}-character identifier on line ${String(degenerateIdentifier.line)} (starts with ${JSON.stringify(degenerateIdentifier.preview)}). Regenerate that section in a smaller focused chunk. No file was written.`
258974
+ };
258858
258975
  let currentContent;
258859
258976
  if (isSourceFilePath(safePath)) try {
258860
258977
  currentContent = await this.kaos.readText(safePath);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "9.1.36",
3
+ "version": "9.1.38",
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": {