blun-king-cli 9.1.287 → 9.1.288
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/launcher-runtime.js +16 -6
- package/bin/mnemo-connect-heartbeat.cjs +173 -0
- package/blun.mjs +41 -2
- package/package.json +1 -1
package/bin/launcher-runtime.js
CHANGED
|
@@ -26,6 +26,7 @@ const {
|
|
|
26
26
|
const { CORE_LOADED_MESSAGE, RUNTIME_READY_MESSAGE } = require('./core-bootstrap');
|
|
27
27
|
const { prepareManagedNodeRuntime } = require('./node-runtime');
|
|
28
28
|
const { repairConfiguredNativeModules } = require('./native-module-repair');
|
|
29
|
+
const { startMnemoConnectHeartbeat } = require('./mnemo-connect-heartbeat.cjs');
|
|
29
30
|
const { acquireSharedRuntimeLease, tryAcquireUpdateLease } = require('./update-lease');
|
|
30
31
|
const { compareSemver, runExplicitUpdate, runUpdateNotice } = require('./update-notice');
|
|
31
32
|
const {
|
|
@@ -923,13 +924,22 @@ async function runLauncher(options = {}) {
|
|
|
923
924
|
env.BLUN_LOG_HOME = privatePaths.sharedHome;
|
|
924
925
|
env.BLUN_PROFILE = PROFILE.profileName;
|
|
925
926
|
env.BLUN_MODEL_MAX_COMPLETION_TOKENS = env.BLUN_MODEL_MAX_COMPLETION_TOKENS || '32768';
|
|
926
|
-
|
|
927
|
-
ARGS,
|
|
927
|
+
const mnemoConnect = startMnemoConnectHeartbeat({
|
|
928
928
|
env,
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
929
|
+
profileName: PROFILE.profileName,
|
|
930
|
+
version: readPackageVersion(),
|
|
931
|
+
});
|
|
932
|
+
try {
|
|
933
|
+
process.exitCode = await superviseProtectedCore(
|
|
934
|
+
ARGS,
|
|
935
|
+
env,
|
|
936
|
+
callerCwd,
|
|
937
|
+
releaseNotice,
|
|
938
|
+
{ startupPendingRuntime, staleActiveRuntime },
|
|
939
|
+
);
|
|
940
|
+
} finally {
|
|
941
|
+
await mnemoConnect.stop();
|
|
942
|
+
}
|
|
933
943
|
} finally {
|
|
934
944
|
await releaseNotice();
|
|
935
945
|
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const os = require('node:os');
|
|
4
|
+
|
|
5
|
+
const DEFAULT_HEARTBEAT_MS = 60_000;
|
|
6
|
+
const DEFAULT_TIMEOUT_MS = 5_000;
|
|
7
|
+
|
|
8
|
+
function resolveMnemoHubUrl(env = process.env) {
|
|
9
|
+
const raw = [
|
|
10
|
+
env.BLUN_MNEMO_HUB_URL,
|
|
11
|
+
env.MNEMO_HUB_URL,
|
|
12
|
+
env.MNEMO_URL,
|
|
13
|
+
].find((value) => typeof value === 'string' && value.trim().length > 0);
|
|
14
|
+
if (!raw) return null;
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
const url = new URL(raw.trim());
|
|
18
|
+
if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password) return null;
|
|
19
|
+
url.pathname = url.pathname.replace(/\/+$/u, '');
|
|
20
|
+
url.search = '';
|
|
21
|
+
url.hash = '';
|
|
22
|
+
return url.toString().replace(/\/$/u, '');
|
|
23
|
+
} catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function resolveMnemoAgentName(profileName, env = process.env) {
|
|
29
|
+
const configured = typeof env.BLUN_MNEMO_AGENT === 'string'
|
|
30
|
+
? env.BLUN_MNEMO_AGENT.trim()
|
|
31
|
+
: '';
|
|
32
|
+
return configured || String(profileName || '').trim();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function postMnemoTool(baseUrl, tool, payload, options = {}) {
|
|
36
|
+
const controller = new AbortController();
|
|
37
|
+
const timeout = (options.setTimeoutImpl || setTimeout)(
|
|
38
|
+
() => controller.abort(),
|
|
39
|
+
options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
40
|
+
);
|
|
41
|
+
timeout.unref?.();
|
|
42
|
+
try {
|
|
43
|
+
const response = await (options.fetchImpl || fetch)(
|
|
44
|
+
`${baseUrl}/tool/${encodeURIComponent(tool)}`,
|
|
45
|
+
{
|
|
46
|
+
method: 'POST',
|
|
47
|
+
headers: { 'content-type': 'application/json' },
|
|
48
|
+
body: JSON.stringify(payload),
|
|
49
|
+
signal: controller.signal,
|
|
50
|
+
},
|
|
51
|
+
);
|
|
52
|
+
if (!response.ok) throw new Error(`Mnemo ${tool} HTTP ${response.status}`);
|
|
53
|
+
const body = await response.json();
|
|
54
|
+
if (body && body.error) {
|
|
55
|
+
throw new Error(typeof body.error === 'string' ? body.error : JSON.stringify(body.error));
|
|
56
|
+
}
|
|
57
|
+
return body;
|
|
58
|
+
} finally {
|
|
59
|
+
(options.clearTimeoutImpl || clearTimeout)(timeout);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function startMnemoConnectHeartbeat(options = {}) {
|
|
64
|
+
const env = options.env || process.env;
|
|
65
|
+
const baseUrl = options.baseUrl || resolveMnemoHubUrl(env);
|
|
66
|
+
const agentName = options.agentName || resolveMnemoAgentName(options.profileName, env);
|
|
67
|
+
if (!baseUrl || !agentName) {
|
|
68
|
+
return Object.freeze({ enabled: false, ready: Promise.resolve(false), stop: async () => {} });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const intervalMs = options.intervalMs ?? DEFAULT_HEARTBEAT_MS;
|
|
72
|
+
const hostname = options.hostname || os.hostname();
|
|
73
|
+
const pid = options.pid ?? process.pid;
|
|
74
|
+
const callTool = options.callTool || ((tool, payload) => postMnemoTool(baseUrl, tool, payload, options));
|
|
75
|
+
const setIntervalImpl = options.setIntervalImpl || setInterval;
|
|
76
|
+
const clearIntervalImpl = options.clearIntervalImpl || clearInterval;
|
|
77
|
+
const onState = typeof options.onState === 'function' ? options.onState : () => {};
|
|
78
|
+
let stopped = false;
|
|
79
|
+
let registered = false;
|
|
80
|
+
let inFlight = null;
|
|
81
|
+
|
|
82
|
+
const register = async () => {
|
|
83
|
+
await callTool('mem_connect_register', {
|
|
84
|
+
agent_name: agentName,
|
|
85
|
+
display_name: agentName,
|
|
86
|
+
host: hostname,
|
|
87
|
+
pid,
|
|
88
|
+
skills: ['blun-king'],
|
|
89
|
+
meta: {
|
|
90
|
+
source: 'blun-king-cli',
|
|
91
|
+
runtime: 'king',
|
|
92
|
+
profile: String(options.profileName || agentName),
|
|
93
|
+
version: options.version || null,
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
registered = true;
|
|
97
|
+
onState({ kind: 'registered', agentName, baseUrl });
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const runPulse = async () => {
|
|
101
|
+
if (stopped) return false;
|
|
102
|
+
if (!registered) await register();
|
|
103
|
+
const result = await callTool('mem_connect_heartbeat', {
|
|
104
|
+
agent_name: agentName,
|
|
105
|
+
status: 'online',
|
|
106
|
+
meta: {
|
|
107
|
+
source: 'blun-king-cli',
|
|
108
|
+
runtime: 'king',
|
|
109
|
+
profile: String(options.profileName || agentName),
|
|
110
|
+
version: options.version || null,
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
const payload = result && typeof result === 'object' && result.result
|
|
114
|
+
? result.result
|
|
115
|
+
: result;
|
|
116
|
+
if (payload && payload.updated === false) {
|
|
117
|
+
registered = false;
|
|
118
|
+
await register();
|
|
119
|
+
}
|
|
120
|
+
onState({ kind: 'heartbeat', agentName, baseUrl });
|
|
121
|
+
return true;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const pulse = () => {
|
|
125
|
+
if (stopped) return Promise.resolve(false);
|
|
126
|
+
if (inFlight) return inFlight;
|
|
127
|
+
inFlight = runPulse()
|
|
128
|
+
.catch((error) => {
|
|
129
|
+
registered = false;
|
|
130
|
+
onState({ kind: 'error', agentName, baseUrl, error });
|
|
131
|
+
return false;
|
|
132
|
+
})
|
|
133
|
+
.finally(() => { inFlight = null; });
|
|
134
|
+
return inFlight;
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const ready = pulse();
|
|
138
|
+
const timer = setIntervalImpl(() => { void pulse(); }, intervalMs);
|
|
139
|
+
timer.unref?.();
|
|
140
|
+
|
|
141
|
+
return Object.freeze({
|
|
142
|
+
enabled: true,
|
|
143
|
+
agentName,
|
|
144
|
+
baseUrl,
|
|
145
|
+
ready,
|
|
146
|
+
pulse,
|
|
147
|
+
async stop(stopOptions = {}) {
|
|
148
|
+
if (stopped) return;
|
|
149
|
+
stopped = true;
|
|
150
|
+
clearIntervalImpl(timer);
|
|
151
|
+
if (inFlight) await inFlight;
|
|
152
|
+
if (stopOptions.offline === false || !registered) return;
|
|
153
|
+
try {
|
|
154
|
+
await callTool('mem_connect_heartbeat', {
|
|
155
|
+
agent_name: agentName,
|
|
156
|
+
status: 'offline',
|
|
157
|
+
meta: { source: 'blun-king-cli', runtime: 'king' },
|
|
158
|
+
});
|
|
159
|
+
} catch (error) {
|
|
160
|
+
onState({ kind: 'error', agentName, baseUrl, error });
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
module.exports = {
|
|
167
|
+
DEFAULT_HEARTBEAT_MS,
|
|
168
|
+
DEFAULT_TIMEOUT_MS,
|
|
169
|
+
postMnemoTool,
|
|
170
|
+
resolveMnemoAgentName,
|
|
171
|
+
resolveMnemoHubUrl,
|
|
172
|
+
startMnemoConnectHeartbeat,
|
|
173
|
+
};
|
package/blun.mjs
CHANGED
|
@@ -295851,7 +295851,7 @@ async function withTimeout(promise, timeoutMs, onTimeout) {
|
|
|
295851
295851
|
if (timer !== void 0) clearTimeout(timer);
|
|
295852
295852
|
}
|
|
295853
295853
|
}
|
|
295854
|
-
var DEFAULT_STARTUP_TIMEOUT_MS$1, McpConnectionManager;
|
|
295854
|
+
var DEFAULT_STARTUP_TIMEOUT_MS$1, MCP_AUTO_RECONNECT_DELAYS_MS, McpConnectionManager;
|
|
295855
295855
|
var init_connection_manager = __esmMin((() => {
|
|
295856
295856
|
init_errors$8();
|
|
295857
295857
|
init_logger$1();
|
|
@@ -295863,10 +295863,12 @@ var init_connection_manager = __esmMin((() => {
|
|
|
295863
295863
|
init_public_display();
|
|
295864
295864
|
init_types$1();
|
|
295865
295865
|
DEFAULT_STARTUP_TIMEOUT_MS$1 = 3e4;
|
|
295866
|
+
MCP_AUTO_RECONNECT_DELAYS_MS = [1e3, 3e3];
|
|
295866
295867
|
McpConnectionManager = class {
|
|
295867
295868
|
options;
|
|
295868
295869
|
entries = /* @__PURE__ */ new Map();
|
|
295869
295870
|
listeners = /* @__PURE__ */ new Set();
|
|
295871
|
+
reconnectTimers = /* @__PURE__ */ new Map();
|
|
295870
295872
|
initialLoad = Promise.resolve();
|
|
295871
295873
|
initialLoadAttemptId = 0;
|
|
295872
295874
|
initialLoadStartedAt;
|
|
@@ -295941,6 +295943,7 @@ var init_connection_manager = __esmMin((() => {
|
|
|
295941
295943
|
return initialLoad;
|
|
295942
295944
|
}
|
|
295943
295945
|
async connect(name, config) {
|
|
295946
|
+
this.clearReconnectTimer(name);
|
|
295944
295947
|
const previous = this.entries.get(name);
|
|
295945
295948
|
if (previous !== void 0) await this.closeClient(previous);
|
|
295946
295949
|
const disabled = config.enabled === false;
|
|
@@ -295948,6 +295951,7 @@ var init_connection_manager = __esmMin((() => {
|
|
|
295948
295951
|
name,
|
|
295949
295952
|
config,
|
|
295950
295953
|
attemptId: 0,
|
|
295954
|
+
autoReconnectAttempt: 0,
|
|
295951
295955
|
status: disabled ? "disabled" : "pending"
|
|
295952
295956
|
};
|
|
295953
295957
|
this.entries.set(name, entry);
|
|
@@ -295955,6 +295959,7 @@ var init_connection_manager = __esmMin((() => {
|
|
|
295955
295959
|
if (!disabled) await this.connectOne(entry, this.beginConnectAttempt(entry));
|
|
295956
295960
|
}
|
|
295957
295961
|
async remove(name) {
|
|
295962
|
+
this.clearReconnectTimer(name);
|
|
295958
295963
|
const entry = this.entries.get(name);
|
|
295959
295964
|
if (entry === void 0) return false;
|
|
295960
295965
|
await this.closeClient(entry);
|
|
@@ -295984,6 +295989,7 @@ var init_connection_manager = __esmMin((() => {
|
|
|
295984
295989
|
name,
|
|
295985
295990
|
config,
|
|
295986
295991
|
attemptId: 0,
|
|
295992
|
+
autoReconnectAttempt: 0,
|
|
295987
295993
|
status: disabled ? "disabled" : "pending"
|
|
295988
295994
|
};
|
|
295989
295995
|
this.entries.set(name, entry);
|
|
@@ -295996,6 +296002,8 @@ var init_connection_manager = __esmMin((() => {
|
|
|
295996
296002
|
const entry = this.entries.get(name);
|
|
295997
296003
|
if (entry === void 0) throw new BlunError(ErrorCodes.MCP_SERVER_NOT_FOUND, `Unknown MCP server: ${name}`);
|
|
295998
296004
|
if (entry.config.enabled === false) throw new BlunError(ErrorCodes.MCP_SERVER_DISABLED, `MCP server is disabled: ${name}`);
|
|
296005
|
+
this.clearReconnectTimer(name);
|
|
296006
|
+
entry.autoReconnectAttempt = 0;
|
|
295999
296007
|
const attemptId = this.beginConnectAttempt(entry);
|
|
296000
296008
|
await this.closeClient(entry);
|
|
296001
296009
|
if (!this.isCurrent(entry, attemptId)) return;
|
|
@@ -296013,6 +296021,7 @@ var init_connection_manager = __esmMin((() => {
|
|
|
296013
296021
|
}
|
|
296014
296022
|
}
|
|
296015
296023
|
async shutdown() {
|
|
296024
|
+
for (const name of this.reconnectTimers.keys()) this.clearReconnectTimer(name);
|
|
296016
296025
|
const entries = Array.from(this.entries.values());
|
|
296017
296026
|
this.entries.clear();
|
|
296018
296027
|
const tasks = entries.map((entry) => this.closeClient(entry));
|
|
@@ -296035,6 +296044,8 @@ var init_connection_manager = __esmMin((() => {
|
|
|
296035
296044
|
entry.tools = tools;
|
|
296036
296045
|
entry.enabledNames = computeEnabledNames(entry.config, tools);
|
|
296037
296046
|
entry.status = "connected";
|
|
296047
|
+
entry.autoReconnectAttempt = 0;
|
|
296048
|
+
this.clearReconnectTimer(entry.name);
|
|
296038
296049
|
this.watchForUnexpectedClose(entry, startupClient, attemptId);
|
|
296039
296050
|
} catch (error) {
|
|
296040
296051
|
if (!this.isCurrent(entry, attemptId)) {
|
|
@@ -296054,6 +296065,7 @@ var init_connection_manager = __esmMin((() => {
|
|
|
296054
296065
|
}
|
|
296055
296066
|
if (!this.isCurrent(entry, attemptId)) return;
|
|
296056
296067
|
this.emit(entry);
|
|
296068
|
+
this.scheduleReconnectIfNeeded(entry);
|
|
296057
296069
|
}
|
|
296058
296070
|
watchForUnexpectedClose(entry, client, attemptId) {
|
|
296059
296071
|
client.onUnexpectedClose((reason) => {
|
|
@@ -296066,8 +296078,35 @@ var init_connection_manager = __esmMin((() => {
|
|
|
296066
296078
|
entry.client = void 0;
|
|
296067
296079
|
this.closeRuntimeClient(client);
|
|
296068
296080
|
this.emit(entry);
|
|
296081
|
+
this.scheduleReconnectIfNeeded(entry);
|
|
296069
296082
|
});
|
|
296070
296083
|
}
|
|
296084
|
+
clearReconnectTimer(name) {
|
|
296085
|
+
const timer = this.reconnectTimers.get(name);
|
|
296086
|
+
if (timer === void 0) return;
|
|
296087
|
+
clearTimeout(timer);
|
|
296088
|
+
this.reconnectTimers.delete(name);
|
|
296089
|
+
}
|
|
296090
|
+
scheduleReconnectIfNeeded(entry) {
|
|
296091
|
+
if (entry.status !== "failed" || entry.config.enabled === false) return;
|
|
296092
|
+
if (this.reconnectTimers.has(entry.name)) return;
|
|
296093
|
+
const retryIndex = entry.autoReconnectAttempt ?? 0;
|
|
296094
|
+
if (retryIndex >= MCP_AUTO_RECONNECT_DELAYS_MS.length) return;
|
|
296095
|
+
entry.autoReconnectAttempt = retryIndex + 1;
|
|
296096
|
+
const timer = setTimeout(() => {
|
|
296097
|
+
this.reconnectTimers.delete(entry.name);
|
|
296098
|
+
if (this.entries.get(entry.name) !== entry || entry.config.enabled === false) return;
|
|
296099
|
+
const attemptId = this.beginConnectAttempt(entry);
|
|
296100
|
+
entry.status = "pending";
|
|
296101
|
+
entry.tools = void 0;
|
|
296102
|
+
entry.enabledNames = void 0;
|
|
296103
|
+
entry.error = void 0;
|
|
296104
|
+
this.emit(entry);
|
|
296105
|
+
void this.connectOne(entry, attemptId);
|
|
296106
|
+
}, MCP_AUTO_RECONNECT_DELAYS_MS[retryIndex]);
|
|
296107
|
+
timer.unref?.();
|
|
296108
|
+
this.reconnectTimers.set(entry.name, timer);
|
|
296109
|
+
}
|
|
296071
296110
|
beginConnectAttempt(entry) {
|
|
296072
296111
|
entry.attemptId += 1;
|
|
296073
296112
|
return entry.attemptId;
|
|
@@ -504339,7 +504378,7 @@ var EditorKeyboardController = class {
|
|
|
504339
504378
|
this.clearPendingUndoEsc();
|
|
504340
504379
|
return;
|
|
504341
504380
|
}
|
|
504342
|
-
if (host.streamingUI.hasActiveTurn() || host.state.appState.streamingPhase !== "idle") {
|
|
504381
|
+
if (host.queueSteerInFlight !== void 0 || host.streamingUI.hasActiveTurn() || host.state.appState.streamingPhase !== "idle") {
|
|
504343
504382
|
this.cancelCurrentStream();
|
|
504344
504383
|
this.clearPendingUndoEsc();
|
|
504345
504384
|
return;
|