blun-king-cli 9.1.298 → 9.1.300
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/bin/native-large-file-io.cjs +42 -0
- package/bin/read-continuation-policy.cjs +13 -3
- package/bin/write-continuation-policy.cjs +14 -0
- package/blun.mjs +53 -23
- package/package.json +1 -1
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { createHash, randomUUID } = require('node:crypto');
|
|
4
|
+
const { appendFile, copyFile, readFile, unlink, writeFile } = require('node:fs/promises');
|
|
5
|
+
|
|
6
|
+
async function writeNativeLargeContentOnDisk({ safePath, chunks, mode, atomicWrite }) {
|
|
7
|
+
if (!Array.isArray(chunks) || !chunks.every(Buffer.isBuffer)) {
|
|
8
|
+
throw new TypeError('chunks must be an array of buffers');
|
|
9
|
+
}
|
|
10
|
+
if (mode !== 'overwrite' && mode !== 'append') throw new TypeError('mode must be overwrite or append');
|
|
11
|
+
if (typeof atomicWrite !== 'function') throw new TypeError('atomicWrite must be a function');
|
|
12
|
+
|
|
13
|
+
const stagingPath = `${safePath}.blun-large-write-${String(process.pid)}-${randomUUID()}.tmp`;
|
|
14
|
+
try {
|
|
15
|
+
if (mode === 'append') {
|
|
16
|
+
try {
|
|
17
|
+
await copyFile(safePath, stagingPath);
|
|
18
|
+
} catch (error) {
|
|
19
|
+
if (error?.code !== 'ENOENT') throw error;
|
|
20
|
+
await writeFile(stagingPath, Buffer.alloc(0), { flag: 'wx', mode: 0o600 });
|
|
21
|
+
}
|
|
22
|
+
} else {
|
|
23
|
+
await writeFile(stagingPath, Buffer.alloc(0), { flag: 'wx', mode: 0o600 });
|
|
24
|
+
}
|
|
25
|
+
for (const chunk of chunks) await appendFile(stagingPath, chunk);
|
|
26
|
+
|
|
27
|
+
const assembled = await readFile(stagingPath);
|
|
28
|
+
const sha256 = createHash('sha256').update(assembled).digest('hex');
|
|
29
|
+
await atomicWrite(safePath, assembled);
|
|
30
|
+
const persistedSha256 = createHash('sha256').update(await readFile(safePath)).digest('hex');
|
|
31
|
+
if (persistedSha256 !== sha256) {
|
|
32
|
+
throw new Error(`assembled SHA-256 ${sha256}, persisted SHA-256 ${persistedSha256}`);
|
|
33
|
+
}
|
|
34
|
+
return { sha256, bytes: assembled.length, fragments: chunks.length };
|
|
35
|
+
} finally {
|
|
36
|
+
try {
|
|
37
|
+
await unlink(stagingPath);
|
|
38
|
+
} catch {}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = { writeNativeLargeContentOnDisk };
|
|
@@ -1,18 +1,28 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
const READ_LINE_CAP = 1000;
|
|
4
|
+
const READ_MODEL_VISIBLE_BYTES = 10 * 1024;
|
|
4
5
|
|
|
5
6
|
function readContinuationNotice(input) {
|
|
6
|
-
if (input?.maxLinesReached !== true) return undefined;
|
|
7
7
|
if (!Number.isSafeInteger(input.lineOffset) || input.lineOffset < 1) return undefined;
|
|
8
|
-
|
|
8
|
+
let readLines;
|
|
9
|
+
if (input?.maxBytesReached === true) {
|
|
10
|
+
if (!Number.isSafeInteger(input.renderedLineCount) || input.renderedLineCount < 1) return undefined;
|
|
11
|
+
readLines = input.renderedLineCount;
|
|
12
|
+
} else if (input?.maxLinesReached === true) {
|
|
13
|
+
if (!Number.isSafeInteger(input.effectiveLimit) || input.effectiveLimit < READ_LINE_CAP) return undefined;
|
|
14
|
+
readLines = input.effectiveLimit;
|
|
15
|
+
} else {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
9
18
|
|
|
10
|
-
const nextLineOffset = input.lineOffset +
|
|
19
|
+
const nextLineOffset = input.lineOffset + readLines;
|
|
11
20
|
if (!Number.isSafeInteger(nextLineOffset)) return undefined;
|
|
12
21
|
return `Continue reading with line_offset=${String(nextLineOffset)}. Do not assume you have reached the end of the file.`;
|
|
13
22
|
}
|
|
14
23
|
|
|
15
24
|
module.exports = {
|
|
16
25
|
READ_LINE_CAP,
|
|
26
|
+
READ_MODEL_VISIBLE_BYTES,
|
|
17
27
|
readContinuationNotice,
|
|
18
28
|
};
|
|
@@ -49,7 +49,21 @@ function resolvePlainWriteContract({
|
|
|
49
49
|
};
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
function splitNativeWriteBytes(content, maxChunkBytes) {
|
|
53
|
+
if (typeof content !== 'string') throw new TypeError('content must be a string');
|
|
54
|
+
if (!Number.isSafeInteger(maxChunkBytes) || maxChunkBytes < 1) {
|
|
55
|
+
throw new TypeError('maxChunkBytes must be a positive safe integer');
|
|
56
|
+
}
|
|
57
|
+
const bytes = Buffer.from(content, 'utf8');
|
|
58
|
+
const chunks = [];
|
|
59
|
+
for (let offset = 0; offset < bytes.length; offset += maxChunkBytes) {
|
|
60
|
+
chunks.push(bytes.subarray(offset, Math.min(bytes.length, offset + maxChunkBytes)));
|
|
61
|
+
}
|
|
62
|
+
return chunks;
|
|
63
|
+
}
|
|
64
|
+
|
|
52
65
|
module.exports = {
|
|
53
66
|
resolvePlainWriteContract,
|
|
54
67
|
resolveWriteContinuationContract,
|
|
68
|
+
splitNativeWriteBytes,
|
|
55
69
|
};
|
package/blun.mjs
CHANGED
|
@@ -253233,6 +253233,11 @@ function evaluateSourceFileLineLimit(path, nextContent, options = {}) {
|
|
|
253233
253233
|
allowed: true,
|
|
253234
253234
|
lineCount
|
|
253235
253235
|
};
|
|
253236
|
+
if (options.nativeLargeFile === true) return {
|
|
253237
|
+
allowed: true,
|
|
253238
|
+
lineCount,
|
|
253239
|
+
notice: `Native large-file write accepted ${String(lineCount)} source lines; content was fragmented, assembled, and verified by SHA-256.`
|
|
253240
|
+
};
|
|
253236
253241
|
const currentLineCount = options.currentContent === void 0 ? void 0 : countFileLines(options.currentContent);
|
|
253237
253242
|
if (currentLineCount !== void 0 && currentLineCount > 500 && lineCount < currentLineCount) return {
|
|
253238
253243
|
allowed: true,
|
|
@@ -258997,14 +259002,14 @@ function renderEntries(entries, lineEndingStyle) {
|
|
|
258997
259002
|
for (const entry of entries) {
|
|
258998
259003
|
const rendered = renderLine(entry, lineEndingStyle);
|
|
258999
259004
|
const lineBytes = renderedLineBytes(rendered.line, renderedLines.length === 0);
|
|
259000
|
-
if (renderedLines.length > 0 && bytes + lineBytes >
|
|
259005
|
+
if (renderedLines.length > 0 && bytes + lineBytes > MAX_BYTES) {
|
|
259001
259006
|
maxBytesReached = true;
|
|
259002
259007
|
break;
|
|
259003
259008
|
}
|
|
259004
259009
|
if (rendered.wasTruncated) truncatedLineNumbers.push(entry.lineNo);
|
|
259005
259010
|
renderedLines.push(rendered.line);
|
|
259006
259011
|
bytes += lineBytes;
|
|
259007
|
-
if (bytes >=
|
|
259012
|
+
if (bytes >= MAX_BYTES) {
|
|
259008
259013
|
maxBytesReached = true;
|
|
259009
259014
|
break;
|
|
259010
259015
|
}
|
|
@@ -259035,7 +259040,7 @@ function containsNulByte(text) {
|
|
|
259035
259040
|
function notReadableFileOutput(path) {
|
|
259036
259041
|
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.`;
|
|
259037
259042
|
}
|
|
259038
|
-
var MAX_LINES$1, MAX_LINE_LENGTH, MAX_BYTES, S_IFMT$1, S_IFREG, PositiveLineOffsetSchema, TailLineOffsetSchema, ReadInputSchema, READ_DESCRIPTION, ReadTool, readContinuationNotice;
|
|
259043
|
+
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;
|
|
259039
259044
|
var init_read = __esmMin((() => {
|
|
259040
259045
|
init_zod$1();
|
|
259041
259046
|
init_tool_access();
|
|
@@ -259046,10 +259051,10 @@ var init_read = __esmMin((() => {
|
|
|
259046
259051
|
init_rule_match();
|
|
259047
259052
|
init_line_endings();
|
|
259048
259053
|
init_read$1();
|
|
259049
|
-
({ readContinuationNotice } = createRequire(import.meta.url)("./bin/read-continuation-policy.cjs"));
|
|
259054
|
+
({ readContinuationNotice, READ_MODEL_VISIBLE_BYTES } = createRequire(import.meta.url)("./bin/read-continuation-policy.cjs"));
|
|
259050
259055
|
MAX_LINES$1 = 1e3;
|
|
259051
259056
|
MAX_LINE_LENGTH = 2e3;
|
|
259052
|
-
MAX_BYTES =
|
|
259057
|
+
MAX_BYTES = READ_MODEL_VISIBLE_BYTES;
|
|
259053
259058
|
S_IFMT$1 = 61440;
|
|
259054
259059
|
S_IFREG = 32768;
|
|
259055
259060
|
PositiveLineOffsetSchema = number$1().int().min(1);
|
|
@@ -259297,7 +259302,7 @@ var init_read = __esmMin((() => {
|
|
|
259297
259302
|
let totalBytes = 0;
|
|
259298
259303
|
for (const [index, candidate] of renderedCandidates.entries()) totalBytes += renderedLineBytes(candidate.rendered.line, index === 0);
|
|
259299
259304
|
let maxBytesReached = false;
|
|
259300
|
-
if (totalBytes >
|
|
259305
|
+
if (totalBytes > MAX_BYTES) {
|
|
259301
259306
|
maxBytesReached = true;
|
|
259302
259307
|
const kept = [];
|
|
259303
259308
|
let bytes = 0;
|
|
@@ -259305,7 +259310,7 @@ var init_read = __esmMin((() => {
|
|
|
259305
259310
|
const candidate = renderedCandidates[i];
|
|
259306
259311
|
if (candidate === void 0) continue;
|
|
259307
259312
|
const lineBytes = renderedLineBytes(candidate.rendered.line, kept.length === 0);
|
|
259308
|
-
if (bytes + lineBytes >
|
|
259313
|
+
if (bytes + lineBytes > MAX_BYTES) break;
|
|
259309
259314
|
kept.unshift(candidate);
|
|
259310
259315
|
bytes += lineBytes;
|
|
259311
259316
|
}
|
|
@@ -259343,7 +259348,10 @@ var init_read = __esmMin((() => {
|
|
|
259343
259348
|
if (input.maxLinesReached) parts.push(`Max ${String(MAX_LINES$1)} lines reached.`);
|
|
259344
259349
|
else if (input.maxBytesReached) parts.push(`Max ${String(MAX_BYTES)} bytes reached.`);
|
|
259345
259350
|
else if (lineCount < input.requestedLines) parts.push("End of file reached.");
|
|
259346
|
-
const continuationNotice = readContinuationNotice(
|
|
259351
|
+
const continuationNotice = readContinuationNotice({
|
|
259352
|
+
...input,
|
|
259353
|
+
renderedLineCount: lineCount
|
|
259354
|
+
});
|
|
259347
259355
|
if (continuationNotice !== void 0) parts.push(continuationNotice);
|
|
259348
259356
|
if (input.truncatedLineNumbers.length > 0) parts.push(`Lines [${input.truncatedLineNumbers.join(", ")}] were truncated.`);
|
|
259349
259357
|
if (input.lineEndingStyle === "mixed") parts.push("Mixed or lone carriage-return line endings are shown as \\r. Use exact \\r\\n or \\r escapes in Edit.old_string for those lines.");
|
|
@@ -259613,7 +259621,7 @@ var init_write$1 = __esmMin((() => {
|
|
|
259613
259621
|
}));
|
|
259614
259622
|
//#endregion
|
|
259615
259623
|
write_default = "Create, append to, or completely replace a file. Missing parent directories are created automatically. Overwrite is the default; append adds content at EOF without adding a newline.\n\nUse Write for a new file or a complete replacement. For every incremental change to an existing file, Use Edit instead, even when it is small. Read before overwriting an existing file. Do not create unsolicited documentation, README, summary, or report files unless the user or project instructions require them.\n\nContent is written literally. Never include Read/Edit line prefixes. Supplied LF and CRLF endings are preserved. Source files may contain at most 500 lines; split larger implementations into focused files. Set `single_file_override=true` only when the latest direct user message explicitly requires one source file.\n\nWrite complete files up to 4,096 UTF-8 bytes atomically. For a larger new or completely replaced file, use `continuation`. Keep each chunk within the configured UTF-8 bytes limit and end it on a complete line with `\\n`. Set exact `expected_lines` and, when available, `expected_sha256`. Part 1 uses overwrite, `part=1`, and `start_line=1`; every later part uses append, the next consecutive part, and the exact `next_start_line` returned by Write. Set `final=true` only on the complete final part. The Target file remains unchanged until final line-count and optional SHA-256 checks pass. After an oversized plain Write is refused, that path accepts only continuation calls until the sequence completes. Restart an unfinished sequence only with part 1, overwrite, `start_line=1`, and `reset=true`. Never use continuation for incremental edits.\n\nRunaway generated JavaScript or TypeScript identifiers are rejected before disk I/O; regenerate only the affected section as a smaller chunk.";
|
|
259616
|
-
write_default = "Create, append to, or replace a whole file. Missing parent directories are created automatically. Overwrite is default; append adds content at EOF without a newline.\n\nUse Write only for new files or complete replacements. Use Edit for every incremental change, even a small one. Read before overwriting an existing file. Do not create unsolicited documentation unless the user or project instructions require it.\n\nContent is literal. Never include line prefixes. Supplied LF and CRLF endings are preserved.
|
|
259624
|
+
write_default = "Create, append to, or replace a whole file. Missing parent directories are created automatically. Overwrite is default; append adds content at EOF without a newline.\n\nUse Write only for new files or complete replacements. Use Edit for every incremental change, even a small one. Read before overwriting an existing file. Do not create unsolicited documentation unless the user or project instructions require it.\n\nContent is literal. Never include line prefixes. Supplied LF and CRLF endings are preserved. Large files and source files over 500 lines are accepted natively: the runtime fragments them on disk, assembles them through atomic replacement, and verifies the persisted SHA-256. No manual continuation calls or `single_file_override` are required.\n\nThe explicit `continuation` protocol remains available for streamed generation across several tool calls. Keep each chunk within the configured UTF-8 bytes limit and end it on a complete line with `\\n`. Optionally set exact `expected_lines` and, when available, `expected_sha256`. Part number, start line, and mode are derived from actually staged content when omitted. Set `final=true` only on the complete final part. The target file remains unchanged until final line-count and optional SHA-256 checks pass. Restart an unfinished sequence with `reset=true`. Never use continuation for incremental edits.\n\nRunaway generated JavaScript or TypeScript identifiers are rejected before disk I/O; regenerate only the affected section as a smaller chunk.";
|
|
259617
259625
|
//#region ../../packages/agent-core/src/tools/builtin/file/generated-source-health.ts
|
|
259618
259626
|
/**
|
|
259619
259627
|
* Find runaway generated identifiers while ignoring comments and strings.
|
|
@@ -259736,7 +259744,7 @@ function countCompletedLines(content) {
|
|
|
259736
259744
|
for (const character of content) if (character === "\n") count += 1;
|
|
259737
259745
|
return count;
|
|
259738
259746
|
}
|
|
259739
|
-
var S_IFMT, S_IFDIR, DEFAULT_CONTINUATION_CHUNK_BYTES, MIN_CONTINUATION_CHUNK_BYTES, MAX_CONTINUATION_CHUNK_BYTES, CONTINUATION_CHUNK_BYTES_ENV, WriteContinuationSchema, WriteInputSchema, WriteTool, resolveWriteContinuationContract, resolvePlainWriteContract;
|
|
259747
|
+
var S_IFMT, S_IFDIR, DEFAULT_CONTINUATION_CHUNK_BYTES, MIN_CONTINUATION_CHUNK_BYTES, MAX_CONTINUATION_CHUNK_BYTES, CONTINUATION_CHUNK_BYTES_ENV, WriteContinuationSchema, WriteInputSchema, WriteTool, resolveWriteContinuationContract, resolvePlainWriteContract, splitNativeWriteBytes, writeNativeLargeContentOnDisk;
|
|
259740
259748
|
var init_write = __esmMin((() => {
|
|
259741
259749
|
init_dist$6();
|
|
259742
259750
|
init_zod$1();
|
|
@@ -259748,7 +259756,8 @@ var init_write = __esmMin((() => {
|
|
|
259748
259756
|
init_write$1();
|
|
259749
259757
|
init_generated_source_health();
|
|
259750
259758
|
init_lsp_diagnostics();
|
|
259751
|
-
({ resolveWriteContinuationContract, resolvePlainWriteContract } = createRequire(import.meta.url)("./bin/write-continuation-policy.cjs"));
|
|
259759
|
+
({ resolveWriteContinuationContract, resolvePlainWriteContract, splitNativeWriteBytes } = createRequire(import.meta.url)("./bin/write-continuation-policy.cjs"));
|
|
259760
|
+
({ writeNativeLargeContentOnDisk } = createRequire(import.meta.url)("./bin/native-large-file-io.cjs"));
|
|
259752
259761
|
S_IFMT = 61440;
|
|
259753
259762
|
S_IFDIR = 16384;
|
|
259754
259763
|
DEFAULT_CONTINUATION_CHUNK_BYTES = 2048;
|
|
@@ -259965,7 +259974,8 @@ bytesWritten: number$1().int().nonnegative() });
|
|
|
259965
259974
|
const lineLimit = evaluateSourceFileLineLimit(safePath, mode === "append" ? `${currentContent ?? ""}${args.content}` : args.content, {
|
|
259966
259975
|
currentContent,
|
|
259967
259976
|
singleFileOverride: args.single_file_override,
|
|
259968
|
-
history: this.history
|
|
259977
|
+
history: this.history,
|
|
259978
|
+
nativeLargeFile: true
|
|
259969
259979
|
});
|
|
259970
259980
|
if (!lineLimit.allowed) return {
|
|
259971
259981
|
isError: true,
|
|
@@ -259978,12 +259988,14 @@ bytesWritten: number$1().int().nonnegative() });
|
|
|
259978
259988
|
continuationRequired: this.continuationRequiredPaths.has(safePath)
|
|
259979
259989
|
});
|
|
259980
259990
|
if (!allowLargeContent && !plainWrite.allowed) {
|
|
259981
|
-
|
|
259982
|
-
const
|
|
259983
|
-
return
|
|
259984
|
-
|
|
259985
|
-
|
|
259986
|
-
|
|
259991
|
+
const chunks = splitNativeWriteBytes(args.content, this.maxContinuationChunkBytes);
|
|
259992
|
+
const nativeResult = await this.writeNativeLargeContent(safePath, chunks, mode);
|
|
259993
|
+
if (nativeResult.isError === true) return nativeResult;
|
|
259994
|
+
this.continuationRequiredPaths.delete(safePath);
|
|
259995
|
+
const notice = lineLimit.notice === void 0 ? "" : ` ${lineLimit.notice}`;
|
|
259996
|
+
return appendLspDiagnostics({
|
|
259997
|
+
output: `Native large-file write complete: ${String(contentBytes)} bytes in ${String(chunks.length)} fragments, SHA-256 ${nativeResult.sha256}.${notice}`
|
|
259998
|
+
}, safePath, this.lsp);
|
|
259987
259999
|
}
|
|
259988
260000
|
const parentError = await this.ensureParentDirectory(safePath);
|
|
259989
260001
|
if (parentError !== void 0) return {
|
|
@@ -260007,6 +260019,22 @@ bytesWritten: number$1().int().nonnegative() });
|
|
|
260007
260019
|
};
|
|
260008
260020
|
}
|
|
260009
260021
|
}
|
|
260022
|
+
async writeNativeLargeContent(safePath, chunks, mode) {
|
|
260023
|
+
try {
|
|
260024
|
+
const result = await writeNativeLargeContentOnDisk({
|
|
260025
|
+
safePath,
|
|
260026
|
+
chunks,
|
|
260027
|
+
mode,
|
|
260028
|
+
atomicWrite
|
|
260029
|
+
});
|
|
260030
|
+
return { isError: false, sha256: result.sha256 };
|
|
260031
|
+
} catch (error) {
|
|
260032
|
+
return {
|
|
260033
|
+
isError: true,
|
|
260034
|
+
output: `Native large-file write failed: ${error instanceof Error ? error.message : String(error)}. The target was left unchanged when atomic replacement had not completed.`
|
|
260035
|
+
};
|
|
260036
|
+
}
|
|
260037
|
+
}
|
|
260010
260038
|
/**
|
|
260011
260039
|
* Best-effort check that the parent directory is usable, creating it when
|
|
260012
260040
|
* it is missing.
|
|
@@ -295887,7 +295915,7 @@ var init_connection_manager = __esmMin((() => {
|
|
|
295887
295915
|
init_public_display();
|
|
295888
295916
|
init_types$1();
|
|
295889
295917
|
DEFAULT_STARTUP_TIMEOUT_MS$1 = 3e4;
|
|
295890
|
-
MCP_AUTO_RECONNECT_DELAYS_MS = [1e4];
|
|
295918
|
+
MCP_AUTO_RECONNECT_DELAYS_MS = [1e4, 1e4, 1e4];
|
|
295891
295919
|
McpConnectionManager = class {
|
|
295892
295920
|
options;
|
|
295893
295921
|
entries = /* @__PURE__ */ new Map();
|
|
@@ -507411,7 +507439,7 @@ var SessionEventHandler = class {
|
|
|
507411
507439
|
renderedMcpServerStatusKeys = /* @__PURE__ */ new Map();
|
|
507412
507440
|
mcpServerStatusSpinners = /* @__PURE__ */ new Map();
|
|
507413
507441
|
mcpServers = /* @__PURE__ */ new Map();
|
|
507414
|
-
|
|
507442
|
+
mcpInventorySignature;
|
|
507415
507443
|
goalCompletionAwaitingClear = false;
|
|
507416
507444
|
goalCompletionTurnEnded = false;
|
|
507417
507445
|
currentTurnHasAssistantText = false;
|
|
@@ -507433,7 +507461,7 @@ var SessionEventHandler = class {
|
|
|
507433
507461
|
this.renderedPluginCommandActivationIds.clear();
|
|
507434
507462
|
this.renderedMcpServerStatusKeys.clear();
|
|
507435
507463
|
this.mcpServers.clear();
|
|
507436
|
-
this.
|
|
507464
|
+
this.mcpInventorySignature = void 0;
|
|
507437
507465
|
this.goalCompletionAwaitingClear = false;
|
|
507438
507466
|
this.goalCompletionTurnEnded = false;
|
|
507439
507467
|
this.currentTurnHasAssistantText = false;
|
|
@@ -508224,10 +508252,12 @@ var SessionEventHandler = class {
|
|
|
508224
508252
|
}
|
|
508225
508253
|
}
|
|
508226
508254
|
logMcpInventoryIfReady(servers) {
|
|
508227
|
-
if (
|
|
508255
|
+
if (servers.some((server) => server.status === "pending")) return;
|
|
508228
508256
|
const inventory = mcpStartupInventory(servers);
|
|
508257
|
+
const signature = `${inventory.serverCount}:${inventory.toolCount}`;
|
|
508258
|
+
if (this.mcpInventorySignature === signature) return;
|
|
508229
508259
|
log.info("mcp startup inventory", inventory);
|
|
508230
|
-
this.
|
|
508260
|
+
this.mcpInventorySignature = signature;
|
|
508231
508261
|
}
|
|
508232
508262
|
showMcpServerStatusSpinner(name) {
|
|
508233
508263
|
const { state } = this.host;
|