blun-king-cli 9.1.550 → 9.1.562

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,141 @@
1
+ 'use strict';
2
+
3
+ const { randomBytes } = require('node:crypto');
4
+ const fs = require('node:fs/promises');
5
+ const path = require('node:path');
6
+
7
+ const WINDOWS_RENAME_RETRY_CODES = new Set(['EACCES', 'EBUSY', 'EPERM']);
8
+ const DEFAULT_WINDOWS_RENAME_DELAYS_MS = Object.freeze([10, 25, 50, 100]);
9
+
10
+ function isAbortError(error) {
11
+ return error instanceof Error && error.name === 'AbortError';
12
+ }
13
+
14
+ function throwIfAborted(signal) {
15
+ if (!signal?.aborted) return;
16
+ const error = new Error('Atomic tool write aborted before publication.');
17
+ error.name = 'AbortError';
18
+ throw error;
19
+ }
20
+
21
+ function isMissing(error) {
22
+ return error?.code === 'ENOENT' || error?.code === 'ENOTDIR';
23
+ }
24
+
25
+ async function existingFileMode(filePath, statFile) {
26
+ try {
27
+ const info = await statFile(filePath);
28
+ if (!info.isFile()) throw Object.assign(new Error(`Cannot replace non-file target: ${filePath}`), { code: 'EISDIR' });
29
+ return typeof info.mode === 'number' ? info.mode & 0o777 : undefined;
30
+ } catch (error) {
31
+ if (isMissing(error)) return undefined;
32
+ throw error;
33
+ }
34
+ }
35
+
36
+ async function resolvePublicationPath(filePath, operations) {
37
+ try {
38
+ return await operations.realpathFile(filePath);
39
+ } catch (error) {
40
+ if (!isMissing(error)) throw error;
41
+ }
42
+
43
+ try {
44
+ const linkInfo = await operations.lstatFile(filePath);
45
+ if (linkInfo.isSymbolicLink()) {
46
+ throw Object.assign(new Error(`Refused atomic replacement through dangling symbolic link: ${filePath}`), { code: 'ELOOP' });
47
+ }
48
+ } catch (error) {
49
+ if (!isMissing(error)) throw error;
50
+ }
51
+
52
+ const realParent = await operations.realpathFile(path.dirname(filePath));
53
+ return path.join(realParent, path.basename(filePath));
54
+ }
55
+
56
+ async function renameWithWindowsRetry(source, target, options) {
57
+ const delays = options.platform === 'win32'
58
+ ? options.windowsRenameDelaysMs
59
+ : [];
60
+ let attempt = 0;
61
+ while (true) {
62
+ try {
63
+ await options.renameFile(source, target);
64
+ return;
65
+ } catch (error) {
66
+ const delayMs = delays[attempt];
67
+ if (delayMs === undefined || !WINDOWS_RENAME_RETRY_CODES.has(error?.code)) throw error;
68
+ attempt += 1;
69
+ throwIfAborted(options.signal);
70
+ await options.sleep(delayMs);
71
+ }
72
+ }
73
+ }
74
+
75
+ async function writeToolTextAtomic(filePath, content, options = {}) {
76
+ if (typeof filePath !== 'string' || filePath.length === 0) throw new TypeError('filePath must be a non-empty string');
77
+ if (typeof content !== 'string' && !Buffer.isBuffer(content)) throw new TypeError('content must be a string or Buffer');
78
+
79
+ const openFile = options.openFile || fs.open;
80
+ const renameFile = options.renameFile || fs.rename;
81
+ const unlinkFile = options.unlinkFile || fs.unlink;
82
+ const statFile = options.statFile || fs.stat;
83
+ const lstatFile = options.lstatFile || fs.lstat;
84
+ const realpathFile = options.realpathFile || fs.realpath;
85
+ const syncHandle = options.syncHandle || ((handle) => handle.sync());
86
+ const sleep = options.sleep || ((delayMs) => new Promise((resolve) => setTimeout(resolve, delayMs)));
87
+ const signal = options.signal;
88
+ const platform = options.platform || process.platform;
89
+ const windowsRenameDelaysMs = options.windowsRenameDelaysMs || DEFAULT_WINDOWS_RENAME_DELAYS_MS;
90
+
91
+ throwIfAborted(signal);
92
+ const publicationPath = await resolvePublicationPath(filePath, { lstatFile, realpathFile });
93
+ const mode = await existingFileMode(publicationPath, statFile);
94
+ const suffix = `${process.pid}.${randomBytes(6).toString('hex')}`;
95
+ const temporaryPath = path.join(path.dirname(publicationPath), `.${path.basename(publicationPath)}.${suffix}.tmp`);
96
+ let handle;
97
+ let published = false;
98
+
99
+ try {
100
+ handle = await openFile(temporaryPath, 'wx', mode ?? 0o666);
101
+ await handle.writeFile(content, typeof content === 'string' ? { encoding: 'utf8', signal } : { signal });
102
+ await syncHandle(handle);
103
+ if (mode !== undefined && platform !== 'win32') await handle.chmod(mode);
104
+ await handle.close();
105
+ handle = undefined;
106
+
107
+ throwIfAborted(signal);
108
+ await renameWithWindowsRetry(temporaryPath, publicationPath, {
109
+ platform,
110
+ renameFile,
111
+ signal,
112
+ sleep,
113
+ windowsRenameDelaysMs,
114
+ });
115
+ published = true;
116
+ return { bytesWritten: Buffer.byteLength(content) };
117
+ } finally {
118
+ if (handle !== undefined) {
119
+ try {
120
+ await handle.close();
121
+ } catch {}
122
+ }
123
+ if (!published) {
124
+ try {
125
+ await unlinkFile(temporaryPath);
126
+ } catch (error) {
127
+ if (!isMissing(error)) {
128
+ // The primary write error remains authoritative; stale temp names are unique and never read.
129
+ }
130
+ }
131
+ }
132
+ }
133
+ }
134
+
135
+ module.exports = {
136
+ DEFAULT_WINDOWS_RENAME_DELAYS_MS,
137
+ WINDOWS_RENAME_RETRY_CODES,
138
+ renameWithWindowsRetry,
139
+ resolvePublicationPath,
140
+ writeToolTextAtomic,
141
+ };
@@ -28,8 +28,10 @@ function hasToolResultOffloadPressure(options = {}) {
28
28
  return hasContextPressure(options);
29
29
  }
30
30
 
31
- function shouldOffloadToolResult(textLength) {
32
- return Number.isFinite(textLength) && textLength > TOOL_RESULT_MAX_CHARS;
31
+ function shouldOffloadToolResult(textLength, options = {}) {
32
+ return Number.isFinite(textLength)
33
+ && textLength > TOOL_RESULT_MAX_CHARS
34
+ && hasToolResultOffloadPressure(options);
33
35
  }
34
36
 
35
37
  function shouldKeepFreshToolResult(toolName) {
@@ -165,33 +167,8 @@ function compactPersistedToolResultReference(content) {
165
167
  }
166
168
 
167
169
  function compactHistoricalSuccessfulToolResults(messages, options = {}) {
168
- if (!Array.isArray(messages) || messages.length === 0) return messages;
169
- if (!hasToolResultOffloadPressure(options)) return messages;
170
-
171
- const recentStart = Math.max(0, messages.length - TOOL_RESULT_SUCCESS_KEEP_RECENT_MESSAGES);
172
- const freshIds = freshToolResultIds(messages);
173
- let changed = false;
174
- const projected = messages.map((message, index) => {
175
- if (
176
- index >= recentStart
177
- || message?.role !== 'tool'
178
- || freshIds.has(message.toolCallId)
179
- || message.isError === true
180
- || !Array.isArray(message.content)
181
- || isPersistedToolResultReference(message.content)
182
- || !message.content.every((part) => part?.type === 'text' && typeof part.text === 'string')
183
- ) return message;
184
-
185
- const textChars = message.content.reduce((total, part) => total + part.text.length, 0);
186
- if (textChars <= TOOL_RESULT_HISTORICAL_SUCCESS_MARKER.length) return message;
187
- changed = true;
188
- return {
189
- ...message,
190
- content: [{ type: 'text', text: TOOL_RESULT_HISTORICAL_SUCCESS_MARKER }],
191
- };
192
- });
193
-
194
- return changed ? projected : messages;
170
+ void options;
171
+ return messages;
195
172
  }
196
173
 
197
174
  function toolCallSignaturesById(messages) {
@@ -280,10 +257,12 @@ function dedupeRepeatedSuccessfulToolResults(sourceMessages, projectedMessages =
280
257
  ...projectedMessage,
281
258
  content: [{ type: 'text', text: reference }],
282
259
  };
283
- projected[newest.index] = {
284
- ...projected[newest.index],
285
- content: sourceMessages[newest.index].content,
286
- };
260
+ if (projected[newest.index] !== sourceMessages[newest.index]) {
261
+ projected[newest.index] = {
262
+ ...projected[newest.index],
263
+ content: sourceMessages[newest.index].content,
264
+ };
265
+ }
287
266
  changed = true;
288
267
  }
289
268
 
@@ -7,9 +7,6 @@ const BOSNIAN_STANDALONE_CHAT = /^(?:zdravo|pozdrav|hvala|ćao|cao)[.!?]*$/iu;
7
7
  const IDENTITY_CHAT = /^(?:wer bist du|who are you|wie geht(?: es dir)?|how are you|na|kako si|kako je king|ko si ti|ko stoji iza tebe)[.!?]*$/iu;
8
8
  const STATUS_ONLY_CHECK_IN = /(?:\bstandabfrage\b|\bstatusabfrage\b|\bkurzes?\s+update\b|\bwo\s+stehst\s+du\b|\bwie\s+weit\s+bist\s+du\b|\bwhere\s+are\s+you\b|\bquick\s+status\b)/iu;
9
9
  const STATUS_FOLLOW_UP_ACTION = /\b(?:aendere|arbeite|baue|build|change|delete|deploy|deploye|fahr|fix|fixe|implement|implementiere|install|installiere|loesche|mach|patch|pruefe|repariere|run|schreibe|setze|start|starte|stop|stoppe|test|teste|write)\b/iu;
10
- const STANDALONE_CHAT_COMPLETION_TOKENS = 1024;
11
- const ADAPTIVE_LOW_COMPLETION_TOKENS = 4096;
12
- const STANDARD_TURN_MAX_COMPLETION_TOKENS = 24_576;
13
10
 
14
11
  function selectThinkingEffortForTurn(text, originKind) {
15
12
  if (originKind !== "user") return undefined;
@@ -45,25 +42,8 @@ function selectThinkingEffortForWorkStep(_stepNumber, _previousToolOutcome) {
45
42
  }
46
43
 
47
44
  function capCompletionBudgetForAdaptiveEffort(budget, thinkingEffort) {
48
- if ((thinkingEffort !== "off" && thinkingEffort !== "low") || budget === undefined) return budget;
49
- const configuredCap = budget.hardCap ?? budget.fallback;
50
- const adaptiveCap = thinkingEffort === "off"
51
- ? STANDALONE_CHAT_COMPLETION_TOKENS
52
- : ADAPTIVE_LOW_COMPLETION_TOKENS;
53
- return {
54
- ...budget,
55
- hardCap: Math.min(
56
- configuredCap ?? adaptiveCap,
57
- adaptiveCap,
58
- ),
59
- };
60
- }
61
-
62
- function capTurnCompletionTokens(maxOutputSize) {
63
- if (!Number.isFinite(maxOutputSize) || maxOutputSize <= 0) {
64
- return STANDARD_TURN_MAX_COMPLETION_TOKENS;
65
- }
66
- return Math.min(Math.floor(maxOutputSize), STANDARD_TURN_MAX_COMPLETION_TOKENS);
45
+ void thinkingEffort;
46
+ return budget;
67
47
  }
68
48
 
69
49
  function selectThinkingEffortForBufferedSteer(text, originKind) {
@@ -77,12 +57,8 @@ function selectThinkingEffortForBufferedSteer(text, originKind) {
77
57
 
78
58
  module.exports = {
79
59
  ACTIONABLE_PROMPT,
80
- ADAPTIVE_LOW_COMPLETION_TOKENS,
81
60
  EXPLICIT_THINKING_INTENT,
82
- STANDALONE_CHAT_COMPLETION_TOKENS,
83
- STANDARD_TURN_MAX_COMPLETION_TOKENS,
84
61
  capCompletionBudgetForAdaptiveEffort,
85
- capTurnCompletionTokens,
86
62
  selectThinkingEffortForBufferedSteer,
87
63
  selectThinkingEffortForWorkStep,
88
64
  selectThinkingEffortForTurn,
@@ -82,6 +82,15 @@ const ENDPOINT_LIMITS = new Map([
82
82
  [FALLBACK_MANIFEST_URL, 64 * 1024],
83
83
  ]);
84
84
 
85
+ function isInternalPackagePinned(packageRoot) {
86
+ try {
87
+ const manifest = JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8'));
88
+ return manifest?.blunUpdateChannel === 'internal';
89
+ } catch {
90
+ return false;
91
+ }
92
+ }
93
+
85
94
  function parseSemver(value) {
86
95
  if (typeof value !== 'string' || value.length > 128) return undefined;
87
96
  const match = SEMVER_PATTERN.exec(value);
@@ -1393,6 +1402,12 @@ async function runUpdateFlow(options, explicitUpdate) {
1393
1402
  const now = (options.now || Date.now)();
1394
1403
 
1395
1404
  const packageRoot = path.resolve(options.packageRoot || path.resolve(__dirname, '..'));
1405
+ if (isInternalPackagePinned(packageRoot)) {
1406
+ if (explicitUpdate) explicitNoTarget(stdout, 'current', currentVersion, uiLocale);
1407
+ return explicitUpdate
1408
+ ? { kind: 'current', reason: 'internal_channel' }
1409
+ : { kind: 'continue', reason: 'internal_channel' };
1410
+ }
1396
1411
  let leaseResult;
1397
1412
  const ownsNoticeLease = options.noticeLease === undefined;
1398
1413
  if (ownsNoticeLease) {
@@ -1632,6 +1647,7 @@ module.exports = {
1632
1647
  compareSemver,
1633
1648
  createInstallInvocation,
1634
1649
  installPinnedVersion,
1650
+ isInternalPackagePinned,
1635
1651
  preparePinnedInstaller,
1636
1652
  isInteractiveUpdateStart,
1637
1653
  loadReleaseSources,