blun-king-cli 9.1.58 → 9.1.60
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/LIESMICH.txt +1 -1
- package/README.md +1 -1
- package/bin/launcher-mode.js +2 -0
- package/bin/launcher-runtime.js +68 -18
- package/bin/profile-runtime.cjs +128 -0
- package/bin/rate-limit-recovery-policy.cjs +34 -0
- package/bin/standard-tools-bootstrap.js +45 -4
- package/bin/subagent-timeout-policy.cjs +79 -0
- package/blun.mjs +208 -91
- package/package.json +1 -1
- package/standard-tools/manifest.json +3 -3
package/LIESMICH.txt
CHANGED
package/README.md
CHANGED
package/bin/launcher-mode.js
CHANGED
|
@@ -10,6 +10,8 @@ const LAUNCHER_HELP_TEXT = [
|
|
|
10
10
|
'Launchers:',
|
|
11
11
|
' blun Starts the CLI without automatically attaching Telegram.',
|
|
12
12
|
' king Starts the same CLI and automatically attaches the configured Telegram channel.',
|
|
13
|
+
' king <profile> starts an isolated named King profile.',
|
|
14
|
+
' king -c <profile> continues the last session in that profile.',
|
|
13
15
|
' Both use the same version, account, model, and commands.',
|
|
14
16
|
' blun tools list shows optional tools; enable writes only explicit choices.',
|
|
15
17
|
'',
|
package/bin/launcher-runtime.js
CHANGED
|
@@ -31,9 +31,16 @@ const {
|
|
|
31
31
|
securePrivateFile,
|
|
32
32
|
writePrivateFile,
|
|
33
33
|
} = require('./private-paths');
|
|
34
|
+
const {
|
|
35
|
+
parseProfileLaunchArgs,
|
|
36
|
+
resolveProfilePaths,
|
|
37
|
+
seedDefaultProfileFromLegacy,
|
|
38
|
+
} = require('./profile-runtime.cjs');
|
|
34
39
|
|
|
35
40
|
const PKG = path.resolve(__dirname, '..');
|
|
36
|
-
const
|
|
41
|
+
const RAW_ARGS = process.argv.slice(2);
|
|
42
|
+
const PROFILE = parseProfileLaunchArgs(RAW_ARGS, launcherModeFromArgv(process.argv));
|
|
43
|
+
const ARGS = PROFILE.args;
|
|
37
44
|
const CORE_LOAD_TIMEOUT_MS = 30_000;
|
|
38
45
|
|
|
39
46
|
function normalizeWindowsPath(value) {
|
|
@@ -44,10 +51,24 @@ function resolveLauncherPrivatePaths(options = {}) {
|
|
|
44
51
|
const env = options.env || process.env;
|
|
45
52
|
const platform = options.platform || process.platform;
|
|
46
53
|
const configuredHome = String(env.BLUN_HOME || '').trim();
|
|
54
|
+
const configuredSharedHome = String(env.BLUN_SHARED_HOME || '').trim();
|
|
55
|
+
const profileName = options.profileName || PROFILE.profileName;
|
|
47
56
|
if (platform !== 'win32') {
|
|
48
57
|
const userHome = (options.homedir || os.homedir)();
|
|
58
|
+
const sharedHome = configuredSharedHome
|
|
59
|
+
? path.resolve(configuredSharedHome)
|
|
60
|
+
: configuredHome
|
|
61
|
+
? path.resolve(configuredHome)
|
|
62
|
+
: path.join(userHome, '.blun');
|
|
63
|
+
const profilePaths = resolveProfilePaths(sharedHome, profileName);
|
|
64
|
+
if (configuredSharedHome && configuredHome
|
|
65
|
+
&& path.resolve(configuredHome) !== path.resolve(profilePaths.home)) {
|
|
66
|
+
throw new Error(`BLUN_HOME passt nicht zum Profil ${profileName}: ${profilePaths.home}`);
|
|
67
|
+
}
|
|
49
68
|
return {
|
|
50
|
-
blunHome:
|
|
69
|
+
blunHome: profilePaths.home,
|
|
70
|
+
profilePaths,
|
|
71
|
+
sharedHome,
|
|
51
72
|
userHome,
|
|
52
73
|
};
|
|
53
74
|
}
|
|
@@ -71,12 +92,26 @@ function resolveLauncherPrivatePaths(options = {}) {
|
|
|
71
92
|
throw new Error('Das lokale Windows-Benutzerprofil ist kein kanonischer lokaler Pfad.');
|
|
72
93
|
}
|
|
73
94
|
|
|
74
|
-
const
|
|
95
|
+
const sharedHome = path.win32.join(userHome, '.blun');
|
|
96
|
+
const profilePaths = resolveProfilePaths(sharedHome, profileName, path.win32);
|
|
97
|
+
if (configuredSharedHome
|
|
98
|
+
&& normalizeWindowsPath(path.win32.resolve(configuredSharedHome)) !== normalizeWindowsPath(sharedHome)) {
|
|
99
|
+
throw new Error(`BLUN_SHARED_HOME muss unter Windows exakt im Windows-Benutzerprofil liegen: ${sharedHome}`);
|
|
100
|
+
}
|
|
101
|
+
const configuredHomeIsLegacyShared = !configuredSharedHome
|
|
102
|
+
&& configuredHome
|
|
103
|
+
&& normalizeWindowsPath(path.win32.resolve(configuredHome)) === normalizeWindowsPath(sharedHome);
|
|
75
104
|
if (configuredHome
|
|
76
|
-
&&
|
|
77
|
-
|
|
105
|
+
&& !configuredHomeIsLegacyShared
|
|
106
|
+
&& normalizeWindowsPath(path.win32.resolve(configuredHome)) !== normalizeWindowsPath(profilePaths.home)) {
|
|
107
|
+
throw new Error(`BLUN_HOME passt nicht zum Profil ${profileName}: ${profilePaths.home}`);
|
|
78
108
|
}
|
|
79
|
-
return {
|
|
109
|
+
return {
|
|
110
|
+
blunHome: profilePaths.home,
|
|
111
|
+
profilePaths,
|
|
112
|
+
sharedHome,
|
|
113
|
+
userHome,
|
|
114
|
+
};
|
|
80
115
|
}
|
|
81
116
|
|
|
82
117
|
function readPackageVersion() {
|
|
@@ -271,12 +306,12 @@ async function runLauncher(options = {}) {
|
|
|
271
306
|
if (mcpNames.length > 0) {
|
|
272
307
|
seedStandardTools({
|
|
273
308
|
packageRoot: PKG,
|
|
274
|
-
blunDir: privatePaths.
|
|
309
|
+
blunDir: privatePaths.profilePaths.home,
|
|
275
310
|
names: mcpNames,
|
|
276
311
|
});
|
|
277
312
|
}
|
|
278
313
|
if (selected.includes('telegram')) {
|
|
279
|
-
installTelegramForProfile(PKG, privatePaths.
|
|
314
|
+
installTelegramForProfile(PKG, privatePaths.profilePaths.home);
|
|
280
315
|
}
|
|
281
316
|
process.stdout.write(`${selected.join('\n')}\n`);
|
|
282
317
|
return;
|
|
@@ -298,7 +333,7 @@ async function runLauncher(options = {}) {
|
|
|
298
333
|
stdin: process.stdin,
|
|
299
334
|
stdout: process.stdout,
|
|
300
335
|
stderr: process.stderr,
|
|
301
|
-
blunDir: privatePaths.
|
|
336
|
+
blunDir: privatePaths.sharedHome,
|
|
302
337
|
packageRoot: PKG,
|
|
303
338
|
noticeLease,
|
|
304
339
|
request: ARGS[0],
|
|
@@ -307,13 +342,13 @@ async function runLauncher(options = {}) {
|
|
|
307
342
|
return;
|
|
308
343
|
}
|
|
309
344
|
|
|
310
|
-
let
|
|
345
|
+
let privatePathsCache;
|
|
311
346
|
const getPrivatePaths = () => {
|
|
312
|
-
|
|
313
|
-
return
|
|
347
|
+
privatePathsCache ||= resolveLauncherPrivatePaths();
|
|
348
|
+
return privatePathsCache;
|
|
314
349
|
};
|
|
315
350
|
const nodeRuntime = await prepareManagedNodeRuntime({
|
|
316
|
-
getBlunDir: () => getPrivatePaths().
|
|
351
|
+
getBlunDir: () => getPrivatePaths().sharedHome,
|
|
317
352
|
});
|
|
318
353
|
if (nodeRuntime.kind === 'failed') {
|
|
319
354
|
process.stderr.write(`${nodeRuntime.message}\n`);
|
|
@@ -332,12 +367,24 @@ async function runLauncher(options = {}) {
|
|
|
332
367
|
const env = createLauncherEnvironment(process.env, mode, readPackageVersion());
|
|
333
368
|
const informationalEnv = { ...process.env };
|
|
334
369
|
delete informationalEnv.BLUN_HOME;
|
|
335
|
-
|
|
370
|
+
delete informationalEnv.BLUN_SHARED_HOME;
|
|
371
|
+
const privatePaths = resolveLauncherPrivatePaths({ env: informationalEnv });
|
|
372
|
+
const profilePaths = privatePaths.profilePaths;
|
|
373
|
+
env.BLUN_HOME = profilePaths.home;
|
|
374
|
+
env.BLUN_SHARED_HOME = privatePaths.sharedHome;
|
|
375
|
+
env.BLUN_LOG_HOME = privatePaths.sharedHome;
|
|
376
|
+
env.BLUN_PROFILE = PROFILE.profileName;
|
|
336
377
|
process.exitCode = await superviseProtectedCore(ARGS, env, callerCwd, releaseNotice);
|
|
337
378
|
return;
|
|
338
379
|
}
|
|
339
380
|
|
|
340
|
-
const
|
|
381
|
+
const privatePaths = getPrivatePaths();
|
|
382
|
+
const profilePaths = privatePaths.profilePaths;
|
|
383
|
+
ensurePrivateDirectory(privatePaths.sharedHome);
|
|
384
|
+
if (PROFILE.profileName === 'default') {
|
|
385
|
+
seedDefaultProfileFromLegacy(privatePaths.sharedHome);
|
|
386
|
+
}
|
|
387
|
+
const blunDir = profilePaths.home;
|
|
341
388
|
ensurePrivateDirectory(blunDir);
|
|
342
389
|
|
|
343
390
|
// --- 1. Oeffentlicher npm-Update-Dialog. -------------------------------
|
|
@@ -350,7 +397,7 @@ async function runLauncher(options = {}) {
|
|
|
350
397
|
stdin: process.stdin,
|
|
351
398
|
stdout: process.stdout,
|
|
352
399
|
stderr: process.stderr,
|
|
353
|
-
blunDir,
|
|
400
|
+
blunDir: privatePaths.sharedHome,
|
|
354
401
|
packageRoot: PKG,
|
|
355
402
|
noticeLease,
|
|
356
403
|
});
|
|
@@ -382,7 +429,7 @@ async function runLauncher(options = {}) {
|
|
|
382
429
|
});
|
|
383
430
|
|
|
384
431
|
// --- 4. Eigenes BLUN-Grunddesign als einzigen Standard-Skill installieren
|
|
385
|
-
seedStandardDesignSkill({ packageRoot: PKG, blunDir });
|
|
432
|
+
seedStandardDesignSkill({ packageRoot: PKG, blunDir: privatePaths.sharedHome });
|
|
386
433
|
|
|
387
434
|
// --- 5. Telegram-Plugin + Token-Vorlage (nur King) -----------------------
|
|
388
435
|
if (mode === LAUNCHER_MODES.KING) {
|
|
@@ -402,7 +449,10 @@ async function runLauncher(options = {}) {
|
|
|
402
449
|
|
|
403
450
|
// --- 6. Start -----------------------------------------------------------
|
|
404
451
|
const env = createLauncherEnvironment(process.env, mode, readPackageVersion());
|
|
405
|
-
env.BLUN_HOME =
|
|
452
|
+
env.BLUN_HOME = profilePaths.home;
|
|
453
|
+
env.BLUN_SHARED_HOME = privatePaths.sharedHome;
|
|
454
|
+
env.BLUN_LOG_HOME = privatePaths.sharedHome;
|
|
455
|
+
env.BLUN_PROFILE = PROFILE.profileName;
|
|
406
456
|
env.BLUN_MODEL_MAX_COMPLETION_TOKENS = env.BLUN_MODEL_MAX_COMPLETION_TOKENS || '32768';
|
|
407
457
|
process.exitCode = await superviseProtectedCore(
|
|
408
458
|
ARGS,
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
|
|
6
|
+
const DEFAULT_PROFILE = 'default';
|
|
7
|
+
const PROFILE_NAME_PATTERN = /^[a-z0-9][a-z0-9_-]{0,31}$/u;
|
|
8
|
+
const RESERVED_SINGLE_ARGUMENTS = new Set([
|
|
9
|
+
'doctor',
|
|
10
|
+
'export',
|
|
11
|
+
'help',
|
|
12
|
+
'mcp',
|
|
13
|
+
'server',
|
|
14
|
+
'tools',
|
|
15
|
+
'update',
|
|
16
|
+
'upgrade',
|
|
17
|
+
'version',
|
|
18
|
+
]);
|
|
19
|
+
const LEGACY_PROFILE_ENTRIES = Object.freeze([
|
|
20
|
+
'channels',
|
|
21
|
+
'config.toml',
|
|
22
|
+
'mcp.json',
|
|
23
|
+
'plugins',
|
|
24
|
+
'session_index.jsonl',
|
|
25
|
+
'sessions',
|
|
26
|
+
'tui.toml',
|
|
27
|
+
]);
|
|
28
|
+
const MIGRATION_MARKER = '.legacy-profile-migrated-v1';
|
|
29
|
+
|
|
30
|
+
function normalizeProfileName(value) {
|
|
31
|
+
const normalized = String(value || '').trim().toLowerCase();
|
|
32
|
+
if (!PROFILE_NAME_PATTERN.test(normalized)) {
|
|
33
|
+
throw new Error('Profilnamen duerfen nur Buchstaben, Zahlen, Unterstriche und Bindestriche enthalten.');
|
|
34
|
+
}
|
|
35
|
+
return normalized;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function parseProfileLaunchArgs(inputArgs, mode = 'blun') {
|
|
39
|
+
const args = Array.isArray(inputArgs) ? [...inputArgs] : [];
|
|
40
|
+
if (String(mode).toLowerCase() !== 'king') {
|
|
41
|
+
return { args, profileName: DEFAULT_PROFILE };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const profileFlagIndex = args.indexOf('--profile');
|
|
45
|
+
if (profileFlagIndex >= 0) {
|
|
46
|
+
if (profileFlagIndex + 1 >= args.length) {
|
|
47
|
+
throw new Error('--profile braucht einen Profilnamen.');
|
|
48
|
+
}
|
|
49
|
+
const profileName = normalizeProfileName(args[profileFlagIndex + 1]);
|
|
50
|
+
args.splice(profileFlagIndex, 2);
|
|
51
|
+
return { args, profileName };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (args.length === 1) {
|
|
55
|
+
const candidate = String(args[0] || '');
|
|
56
|
+
if (!candidate.startsWith('-') && !RESERVED_SINGLE_ARGUMENTS.has(candidate.toLowerCase())) {
|
|
57
|
+
return { args: [], profileName: normalizeProfileName(candidate) };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (args.length === 2 && args.includes('-c')) {
|
|
62
|
+
const candidate = args.find((arg) => arg !== '-c');
|
|
63
|
+
if (candidate && !candidate.startsWith('-')) {
|
|
64
|
+
return { args: ['-c'], profileName: normalizeProfileName(candidate) };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return { args, profileName: DEFAULT_PROFILE };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function resolveProfilePaths(sharedHome, profileName, pathImpl = path) {
|
|
72
|
+
const normalized = normalizeProfileName(profileName);
|
|
73
|
+
const home = pathImpl.join(sharedHome, 'profile', normalized);
|
|
74
|
+
return Object.freeze({
|
|
75
|
+
profileName: normalized,
|
|
76
|
+
sharedHome,
|
|
77
|
+
home,
|
|
78
|
+
channels: pathImpl.join(home, 'channels'),
|
|
79
|
+
sessions: pathImpl.join(home, 'sessions'),
|
|
80
|
+
plugins: pathImpl.join(home, 'plugins'),
|
|
81
|
+
config: pathImpl.join(home, 'config.toml'),
|
|
82
|
+
mcp: pathImpl.join(home, 'mcp.json'),
|
|
83
|
+
sessionIndex: pathImpl.join(home, 'session_index.jsonl'),
|
|
84
|
+
credentials: pathImpl.join(sharedHome, 'credentials'),
|
|
85
|
+
oauth: pathImpl.join(sharedHome, 'oauth'),
|
|
86
|
+
deviceId: pathImpl.join(sharedHome, 'device_id'),
|
|
87
|
+
skills: pathImpl.join(sharedHome, 'skills'),
|
|
88
|
+
secure: pathImpl.join(sharedHome, 'secure'),
|
|
89
|
+
persona: pathImpl.join(sharedHome, 'persona.json'),
|
|
90
|
+
globalLog: pathImpl.join(sharedHome, 'logs', 'blun.log'),
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function copyLegacyEntry(source, target, fsImpl) {
|
|
95
|
+
if (!fsImpl.existsSync(source) || fsImpl.existsSync(target)) return;
|
|
96
|
+
const stat = fsImpl.lstatSync(source);
|
|
97
|
+
if (stat.isSymbolicLink()) return;
|
|
98
|
+
if (stat.isDirectory()) {
|
|
99
|
+
fsImpl.cpSync(source, target, { recursive: true, errorOnExist: false });
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (stat.isFile()) fsImpl.copyFileSync(source, target);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function seedDefaultProfileFromLegacy(sharedHome, fsImpl = fs) {
|
|
106
|
+
const paths = resolveProfilePaths(sharedHome, DEFAULT_PROFILE);
|
|
107
|
+
const marker = path.join(paths.home, MIGRATION_MARKER);
|
|
108
|
+
if (fsImpl.existsSync(marker)) return paths;
|
|
109
|
+
|
|
110
|
+
fsImpl.mkdirSync(paths.home, { recursive: true });
|
|
111
|
+
for (const entry of LEGACY_PROFILE_ENTRIES) {
|
|
112
|
+
copyLegacyEntry(path.join(sharedHome, entry), path.join(paths.home, entry), fsImpl);
|
|
113
|
+
}
|
|
114
|
+
try {
|
|
115
|
+
fsImpl.writeFileSync(marker, 'legacy profile state copied once\n', { encoding: 'utf8', flag: 'wx' });
|
|
116
|
+
} catch (error) {
|
|
117
|
+
if (error?.code !== 'EEXIST') throw error;
|
|
118
|
+
}
|
|
119
|
+
return paths;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
module.exports = {
|
|
123
|
+
DEFAULT_PROFILE,
|
|
124
|
+
normalizeProfileName,
|
|
125
|
+
parseProfileLaunchArgs,
|
|
126
|
+
resolveProfilePaths,
|
|
127
|
+
seedDefaultProfileFromLegacy,
|
|
128
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const MAX_AUTOMATIC_RATE_LIMIT_RESUMES = 3;
|
|
4
|
+
const RATE_LIMIT_RETRY_BASE_MS = 60_000;
|
|
5
|
+
const RATE_LIMIT_COMPACTION_THRESHOLD_TOKENS = 64_000;
|
|
6
|
+
|
|
7
|
+
function isImmediateGenerateRetryAllowed(statusCode) {
|
|
8
|
+
return statusCode !== 429 && [500, 502, 503, 504].includes(statusCode);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function rateLimitRetryDelayMs(retryCount) {
|
|
12
|
+
if (!Number.isInteger(retryCount) || retryCount < 1) {
|
|
13
|
+
throw new Error("retryCount must be a positive integer.");
|
|
14
|
+
}
|
|
15
|
+
return RATE_LIMIT_RETRY_BASE_MS * (2 ** (retryCount - 1));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function shouldStopAutomaticRateLimitResume(retryCount) {
|
|
19
|
+
return retryCount >= MAX_AUTOMATIC_RATE_LIMIT_RESUMES;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function shouldCompactBeforeRateLimitResume(estimatedRequestTokens) {
|
|
23
|
+
return Number.isFinite(estimatedRequestTokens)
|
|
24
|
+
&& estimatedRequestTokens >= RATE_LIMIT_COMPACTION_THRESHOLD_TOKENS;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
module.exports = {
|
|
28
|
+
MAX_AUTOMATIC_RATE_LIMIT_RESUMES,
|
|
29
|
+
RATE_LIMIT_RETRY_BASE_MS,
|
|
30
|
+
isImmediateGenerateRetryAllowed,
|
|
31
|
+
rateLimitRetryDelayMs,
|
|
32
|
+
shouldCompactBeforeRateLimitResume,
|
|
33
|
+
shouldStopAutomaticRateLimitResume,
|
|
34
|
+
};
|
|
@@ -74,6 +74,41 @@ function hasEquivalentServer(servers, name, tool) {
|
|
|
74
74
|
});
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
function sameStringArray(left, right) {
|
|
78
|
+
return Array.isArray(left)
|
|
79
|
+
&& Array.isArray(right)
|
|
80
|
+
&& left.length === right.length
|
|
81
|
+
&& left.every((value, index) => value === right[index]);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function recognizedBundledServer(current, expected) {
|
|
85
|
+
return isObject(current)
|
|
86
|
+
&& current.transport === expected.transport
|
|
87
|
+
&& current.command === expected.command
|
|
88
|
+
&& sameStringArray(current.args, expected.args);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function migrateBundledTimeouts(name, current, expected) {
|
|
92
|
+
if (!recognizedBundledServer(current, expected)) return false;
|
|
93
|
+
let changed = false;
|
|
94
|
+
if (name === 'agent-browser') {
|
|
95
|
+
if (current.startupTimeoutMs === 30_000) {
|
|
96
|
+
current.startupTimeoutMs = expected.startupTimeoutMs;
|
|
97
|
+
changed = true;
|
|
98
|
+
}
|
|
99
|
+
if (current.toolTimeoutMs === 60_000) {
|
|
100
|
+
current.toolTimeoutMs = expected.toolTimeoutMs;
|
|
101
|
+
changed = true;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (name === 'blun-language-guard'
|
|
105
|
+
&& (current.toolTimeoutMs === 30_000 || current.toolTimeoutMs === 60_000)) {
|
|
106
|
+
current.toolTimeoutMs = expected.toolTimeoutMs;
|
|
107
|
+
changed = true;
|
|
108
|
+
}
|
|
109
|
+
return changed;
|
|
110
|
+
}
|
|
111
|
+
|
|
77
112
|
function writeMcpDocument(configPath, document) {
|
|
78
113
|
const temporaryPath = path.join(
|
|
79
114
|
path.dirname(configPath),
|
|
@@ -160,21 +195,27 @@ function seedStandardTools({
|
|
|
160
195
|
const document = readMcpDocument(configPath);
|
|
161
196
|
const servers = { ...document.mcpServers };
|
|
162
197
|
const added = [];
|
|
198
|
+
const updated = [];
|
|
163
199
|
|
|
164
200
|
for (const name of selectedNames) {
|
|
165
201
|
const tool = catalogue.tools[name];
|
|
166
|
-
|
|
167
|
-
servers[name] = bundledToolConfig(name, tool, {
|
|
202
|
+
const expected = bundledToolConfig(name, tool, {
|
|
168
203
|
packageRoot,
|
|
169
204
|
blunDir,
|
|
170
205
|
platform,
|
|
171
206
|
env,
|
|
172
207
|
});
|
|
208
|
+
if (Object.hasOwn(servers, name)) {
|
|
209
|
+
if (migrateBundledTimeouts(name, servers[name], expected)) updated.push(name);
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
if (hasEquivalentServer(servers, name, tool)) continue;
|
|
213
|
+
servers[name] = expected;
|
|
173
214
|
added.push(name);
|
|
174
215
|
}
|
|
175
216
|
|
|
176
|
-
if (added.length > 0) writeMcpDocument(configPath, { ...document, mcpServers: servers });
|
|
177
|
-
return { added, configPath };
|
|
217
|
+
if (added.length > 0 || updated.length > 0) writeMcpDocument(configPath, { ...document, mcpServers: servers });
|
|
218
|
+
return { added, updated, configPath };
|
|
178
219
|
}
|
|
179
220
|
|
|
180
221
|
function seedStandardDesignSkill({ packageRoot, blunDir }) {
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const DEFAULT_SUBAGENT_TIMEOUT_MS = 4 * 60 * 60 * 1000;
|
|
4
|
+
const MAX_SUBAGENT_TIMEOUT_MINUTES = 7 * 24 * 60;
|
|
5
|
+
const SUBAGENT_TIMEOUT_ENV = "BLUN_SUBAGENT_TIMEOUT_MINUTES";
|
|
6
|
+
|
|
7
|
+
function resolveSubagentTimeoutMs({ perRunMinutes, env = process.env } = {}) {
|
|
8
|
+
const raw = perRunMinutes ?? env[SUBAGENT_TIMEOUT_ENV];
|
|
9
|
+
if (raw === undefined || raw === null || raw === "") {
|
|
10
|
+
return DEFAULT_SUBAGENT_TIMEOUT_MS;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const minutes = typeof raw === "number" ? raw : Number(raw);
|
|
14
|
+
if (!Number.isInteger(minutes) || minutes < 0 || minutes > MAX_SUBAGENT_TIMEOUT_MINUTES) {
|
|
15
|
+
throw new Error(
|
|
16
|
+
`${SUBAGENT_TIMEOUT_ENV} must be an integer from 0 to ${MAX_SUBAGENT_TIMEOUT_MINUTES} minutes.`,
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
return minutes === 0 ? undefined : minutes * 60 * 1000;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function abortError(reason) {
|
|
23
|
+
if (reason instanceof Error) return reason;
|
|
24
|
+
return new Error(reason === undefined ? "Subagent interrupted." : String(reason));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function runAgentWithTimeoutContinuation({
|
|
28
|
+
handle,
|
|
29
|
+
timeoutMs,
|
|
30
|
+
signal,
|
|
31
|
+
abortCurrent,
|
|
32
|
+
retrySameAgent,
|
|
33
|
+
}) {
|
|
34
|
+
let currentHandle = handle;
|
|
35
|
+
|
|
36
|
+
while (true) {
|
|
37
|
+
let timeout;
|
|
38
|
+
let removeAbortListener = () => {};
|
|
39
|
+
const completion = Promise.resolve(currentHandle.completion).then(
|
|
40
|
+
(outcome) => ({ kind: "completed", outcome }),
|
|
41
|
+
(error) => ({ kind: "failed", error }),
|
|
42
|
+
);
|
|
43
|
+
const timedOut = timeoutMs === undefined
|
|
44
|
+
? new Promise(() => {})
|
|
45
|
+
: new Promise((resolve) => {
|
|
46
|
+
timeout = setTimeout(() => resolve({ kind: "timed_out" }), timeoutMs);
|
|
47
|
+
});
|
|
48
|
+
const interrupted = signal.aborted
|
|
49
|
+
? Promise.resolve({ kind: "interrupted", reason: signal.reason })
|
|
50
|
+
: new Promise((resolve) => {
|
|
51
|
+
const onAbort = () => resolve({ kind: "interrupted", reason: signal.reason });
|
|
52
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
53
|
+
removeAbortListener = () => signal.removeEventListener("abort", onAbort);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const result = await Promise.race([completion, timedOut, interrupted]);
|
|
57
|
+
if (timeout !== undefined) clearTimeout(timeout);
|
|
58
|
+
removeAbortListener();
|
|
59
|
+
|
|
60
|
+
if (result.kind === "completed") return { handle: currentHandle, outcome: result.outcome };
|
|
61
|
+
if (result.kind === "failed") throw result.error;
|
|
62
|
+
if (result.kind === "interrupted") {
|
|
63
|
+
abortCurrent(result.reason);
|
|
64
|
+
await Promise.resolve(currentHandle.completion).catch(() => {});
|
|
65
|
+
throw abortError(result.reason);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const reason = new Error("Subagent continuation interval elapsed.");
|
|
69
|
+
abortCurrent(reason);
|
|
70
|
+
await Promise.resolve(currentHandle.completion).catch(() => {});
|
|
71
|
+
currentHandle = await retrySameAgent(currentHandle.agentId);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
module.exports = {
|
|
76
|
+
DEFAULT_SUBAGENT_TIMEOUT_MS,
|
|
77
|
+
resolveSubagentTimeoutMs,
|
|
78
|
+
runAgentWithTimeoutContinuation,
|
|
79
|
+
};
|
package/blun.mjs
CHANGED
|
@@ -1539,7 +1539,6 @@ function isRetryableGenerateError(error) {
|
|
|
1539
1539
|
if (error instanceof APIConnectionError || error instanceof APITimeoutError) return true;
|
|
1540
1540
|
if (error instanceof APIEmptyResponseError) return true;
|
|
1541
1541
|
return error instanceof APIStatusError && [
|
|
1542
|
-
429,
|
|
1543
1542
|
500,
|
|
1544
1543
|
502,
|
|
1545
1544
|
503,
|
|
@@ -10263,6 +10262,11 @@ function resolveBlunHome$1(homeDir) {
|
|
|
10263
10262
|
if (env !== void 0 && env.length > 0) return env;
|
|
10264
10263
|
return join$4(homedir(), ".blun");
|
|
10265
10264
|
}
|
|
10265
|
+
function resolveSharedBlunHome(homeDir) {
|
|
10266
|
+
const shared = process.env["BLUN_SHARED_HOME"];
|
|
10267
|
+
if (shared !== void 0 && shared.length > 0) return shared;
|
|
10268
|
+
return homeDir ?? resolveBlunHome$1();
|
|
10269
|
+
}
|
|
10266
10270
|
function resolveConfigPath(input) {
|
|
10267
10271
|
return input.configPath ?? join$4(resolveBlunHome$1(input.homeDir), "config.toml");
|
|
10268
10272
|
}
|
|
@@ -16884,11 +16888,16 @@ function resolvePayload(payload) {
|
|
|
16884
16888
|
};
|
|
16885
16889
|
}
|
|
16886
16890
|
function mergeCtx(payloadCtx, boundCtx) {
|
|
16887
|
-
|
|
16888
|
-
if (
|
|
16891
|
+
const runtimeCtx = process.env["BLUN_PROFILE"] ? { profile: process.env["BLUN_PROFILE"] } : {};
|
|
16892
|
+
if (!(Object.keys(boundCtx).length > 0) && Object.keys(runtimeCtx).length === 0) return payloadCtx;
|
|
16893
|
+
if (payloadCtx === void 0) return {
|
|
16894
|
+
...boundCtx,
|
|
16895
|
+
...runtimeCtx
|
|
16896
|
+
};
|
|
16889
16897
|
return {
|
|
16890
16898
|
...payloadCtx,
|
|
16891
|
-
...boundCtx
|
|
16899
|
+
...boundCtx,
|
|
16900
|
+
...runtimeCtx
|
|
16892
16901
|
};
|
|
16893
16902
|
}
|
|
16894
16903
|
function resolveGlobalLogPath(homeDir) {
|
|
@@ -28180,6 +28189,8 @@ var init_conduct = __esmMin((() => {}));
|
|
|
28180
28189
|
* read/write the same location: `BLUN_HOME` env override, else `~/.blun`.
|
|
28181
28190
|
*/
|
|
28182
28191
|
function resolveBlunHome(env = process.env) {
|
|
28192
|
+
const shared = env["BLUN_SHARED_HOME"]?.trim();
|
|
28193
|
+
if (shared && shared.length > 0) return shared;
|
|
28183
28194
|
const override = env["BLUN_HOME"]?.trim();
|
|
28184
28195
|
return override && override.length > 0 ? override : join$4(homedir(), ".blun");
|
|
28185
28196
|
}
|
|
@@ -29016,26 +29027,46 @@ var init_agent_task = __esmMin((() => {
|
|
|
29016
29027
|
description;
|
|
29017
29028
|
subagentHost;
|
|
29018
29029
|
abortController;
|
|
29030
|
+
continuationTimeoutMs;
|
|
29031
|
+
runInBackground;
|
|
29032
|
+
parentToolCallId;
|
|
29019
29033
|
kind = "agent";
|
|
29020
29034
|
idPrefix = "agent";
|
|
29021
29035
|
agentId;
|
|
29022
29036
|
subagentType;
|
|
29023
|
-
constructor(handle, description, subagentHost, abortController) {
|
|
29037
|
+
constructor(handle, description, subagentHost, abortController, options) {
|
|
29024
29038
|
this.handle = handle;
|
|
29025
29039
|
this.description = description;
|
|
29026
29040
|
this.subagentHost = subagentHost;
|
|
29027
29041
|
this.abortController = abortController;
|
|
29042
|
+
this.continuationTimeoutMs = options.timeoutMs;
|
|
29043
|
+
this.runInBackground = options.runInBackground;
|
|
29044
|
+
this.parentToolCallId = options.parentToolCallId;
|
|
29028
29045
|
this.agentId = handle.agentId;
|
|
29029
29046
|
this.subagentType = handle.profileName;
|
|
29030
29047
|
}
|
|
29031
29048
|
async start(sink) {
|
|
29032
|
-
const requestAbort = () => {
|
|
29033
|
-
this.abortController.abort(sink.signal.reason);
|
|
29034
|
-
};
|
|
29035
|
-
if (sink.signal.aborted) requestAbort();
|
|
29036
|
-
else sink.signal.addEventListener("abort", requestAbort, { once: true });
|
|
29037
29049
|
try {
|
|
29038
|
-
const outcome = await
|
|
29050
|
+
const { handle, outcome } = await runAgentWithTimeoutContinuation({
|
|
29051
|
+
handle: this.handle,
|
|
29052
|
+
timeoutMs: this.continuationTimeoutMs,
|
|
29053
|
+
signal: sink.signal,
|
|
29054
|
+
abortCurrent: (reason) => {
|
|
29055
|
+
this.abortController.abort(reason);
|
|
29056
|
+
},
|
|
29057
|
+
retrySameAgent: async (agentId) => {
|
|
29058
|
+
this.abortController = new AbortController();
|
|
29059
|
+
this.handle = await this.subagentHost.retry(agentId, {
|
|
29060
|
+
parentToolCallId: this.parentToolCallId,
|
|
29061
|
+
prompt: "Continue from the preserved context after the wall-clock interval.",
|
|
29062
|
+
description: this.description,
|
|
29063
|
+
runInBackground: this.runInBackground,
|
|
29064
|
+
signal: this.abortController.signal
|
|
29065
|
+
});
|
|
29066
|
+
return this.handle;
|
|
29067
|
+
}
|
|
29068
|
+
});
|
|
29069
|
+
this.handle = handle;
|
|
29039
29070
|
sink.appendOutput(outcome.result);
|
|
29040
29071
|
await sink.settle({ status: "completed" });
|
|
29041
29072
|
} catch (error) {
|
|
@@ -29047,16 +29078,16 @@ var init_agent_task = __esmMin((() => {
|
|
|
29047
29078
|
status: "failed",
|
|
29048
29079
|
stopReason: errorMessage$12(error)
|
|
29049
29080
|
});
|
|
29050
|
-
} finally {
|
|
29051
|
-
sink.signal.removeEventListener("abort", requestAbort);
|
|
29052
29081
|
}
|
|
29053
29082
|
}
|
|
29054
29083
|
onDetach() {
|
|
29084
|
+
this.runInBackground = true;
|
|
29055
29085
|
this.subagentHost.markActiveChildDetached(this.agentId);
|
|
29056
29086
|
}
|
|
29057
29087
|
toInfo(base) {
|
|
29058
29088
|
return {
|
|
29059
29089
|
...base,
|
|
29090
|
+
timeoutMs: this.continuationTimeoutMs,
|
|
29060
29091
|
kind: "agent",
|
|
29061
29092
|
agentId: this.agentId,
|
|
29062
29093
|
subagentType: this.subagentType
|
|
@@ -235026,7 +235057,7 @@ async function resolveSkillRoots(options) {
|
|
|
235026
235057
|
const roots = [];
|
|
235027
235058
|
const mergeAllAvailableSkills = options.mergeAllAvailableSkills ?? true;
|
|
235028
235059
|
const { userHomeDir, workDir } = options.paths;
|
|
235029
|
-
const brandHomeDir = options.paths.brandHomeDir ?? process.env["BLUN_HOME"] ?? posix$2.join(userHomeDir, ".blun");
|
|
235060
|
+
const brandHomeDir = options.paths.brandHomeDir ?? process.env["BLUN_SHARED_HOME"] ?? process.env["BLUN_HOME"] ?? posix$2.join(userHomeDir, ".blun");
|
|
235030
235061
|
const projectRoot = await findProjectRoot$1(workDir);
|
|
235031
235062
|
if (options.explicitDirs !== void 0 && options.explicitDirs.length > 0) await pushConfiguredDirs(roots, options.explicitDirs, projectRoot, userHomeDir, "user", isDir, realpath);
|
|
235032
235063
|
else {
|
|
@@ -251078,14 +251109,14 @@ function resolveSwarmMaxConcurrency(env = process.env) {
|
|
|
251078
251109
|
if (!Number.isInteger(value) || value <= 0) throw new Error(`${AGENT_SWARM_MAX_CONCURRENCY_ENV} must be a positive integer, got ${JSON.stringify(raw)}.`);
|
|
251079
251110
|
return value;
|
|
251080
251111
|
}
|
|
251081
|
-
var import_retry, INITIAL_LAUNCH_LIMIT, INITIAL_LAUNCH_INTERVAL_MS, RATE_LIMIT_RETRY_BASE_MS, RATE_LIMIT_RETRY_FACTOR, RATE_LIMIT_CAPACITY_SHRINK_INTERVAL_MS, RATE_LIMIT_CAPACITY_RECOVERY_INTERVAL_MS, RATE_LIMIT_SUSPENDED_REASON, AGENT_SWARM_MAX_CONCURRENCY_ENV, SubagentBatch;
|
|
251112
|
+
var import_retry, INITIAL_LAUNCH_LIMIT, INITIAL_LAUNCH_INTERVAL_MS, RATE_LIMIT_RETRY_BASE_MS, RATE_LIMIT_RETRY_FACTOR, RATE_LIMIT_CAPACITY_SHRINK_INTERVAL_MS, RATE_LIMIT_CAPACITY_RECOVERY_INTERVAL_MS, RATE_LIMIT_SUSPENDED_REASON, AGENT_SWARM_MAX_CONCURRENCY_ENV, MAX_AUTOMATIC_RATE_LIMIT_RESUMES, rateLimitRetryDelayMs, shouldCompactBeforeRateLimitResume, shouldStopAutomaticRateLimitResume, SubagentBatch;
|
|
251082
251113
|
var init_subagent_batch = __esmMin((() => {
|
|
251083
251114
|
init_src$4();
|
|
251084
251115
|
import_retry = /* @__PURE__ */ __toESM(require_retry$1(), 1);
|
|
251085
251116
|
init_abort();
|
|
251117
|
+
({ MAX_AUTOMATIC_RATE_LIMIT_RESUMES, RATE_LIMIT_RETRY_BASE_MS, rateLimitRetryDelayMs, shouldCompactBeforeRateLimitResume, shouldStopAutomaticRateLimitResume } = createRequire(import.meta.url)("./bin/rate-limit-recovery-policy.cjs"));
|
|
251086
251118
|
INITIAL_LAUNCH_LIMIT = 5;
|
|
251087
251119
|
INITIAL_LAUNCH_INTERVAL_MS = 700;
|
|
251088
|
-
RATE_LIMIT_RETRY_BASE_MS = 3e3;
|
|
251089
251120
|
RATE_LIMIT_RETRY_FACTOR = 2;
|
|
251090
251121
|
RATE_LIMIT_CAPACITY_SHRINK_INTERVAL_MS = 2e3;
|
|
251091
251122
|
RATE_LIMIT_CAPACITY_RECOVERY_INTERVAL_MS = 180 * 1e3;
|
|
@@ -251226,6 +251257,7 @@ var init_subagent_batch = __esmMin((() => {
|
|
|
251226
251257
|
description: task.description,
|
|
251227
251258
|
swarmIndex: task.swarmIndex,
|
|
251228
251259
|
runInBackground: task.runInBackground,
|
|
251260
|
+
compactBeforeRetry: attempt.state.retryCount > 0,
|
|
251229
251261
|
signal: attempt.controller.signal,
|
|
251230
251262
|
onReady: () => {
|
|
251231
251263
|
this.markAttemptReady(attempt);
|
|
@@ -251291,17 +251323,27 @@ var init_subagent_batch = __esmMin((() => {
|
|
|
251291
251323
|
handleAttemptOutcome(attempt, outcome) {
|
|
251292
251324
|
if (!this.releaseAttempt(attempt)) return;
|
|
251293
251325
|
if (this.finished) return;
|
|
251294
|
-
if ("status" in outcome
|
|
251295
|
-
|
|
251326
|
+
if ("status" in outcome && attempt.timedOut && outcome.agentId !== void 0) {
|
|
251327
|
+
this.requeueTimedOut(attempt, outcome.agentId);
|
|
251328
|
+
}
|
|
251329
|
+
else if ("status" in outcome) this.results[attempt.state.index] = outcome;
|
|
251330
|
+
else if (shouldStopAutomaticRateLimitResume(attempt.state.retryCount)) this.results[attempt.state.index] = {
|
|
251296
251331
|
task: attempt.state.task,
|
|
251297
251332
|
agentId: outcome.agentId,
|
|
251298
251333
|
status: "failed",
|
|
251299
251334
|
state: "started",
|
|
251300
|
-
error: outcome.error
|
|
251335
|
+
error: `${outcome.error} Automatic continuation stopped after ${String(MAX_AUTOMATIC_RATE_LIMIT_RESUMES)} attempts; agent ${outcome.agentId} remains resumable.`
|
|
251301
251336
|
};
|
|
251302
251337
|
else this.requeueRateLimited(attempt, outcome.agentId);
|
|
251303
251338
|
this.schedule();
|
|
251304
251339
|
}
|
|
251340
|
+
requeueTimedOut(attempt, agentId) {
|
|
251341
|
+
const state = attempt.state;
|
|
251342
|
+
state.agentId = agentId;
|
|
251343
|
+
state.retryAgentId = agentId;
|
|
251344
|
+
state.retryReadyAt = 0;
|
|
251345
|
+
this.pending.unshift(state);
|
|
251346
|
+
}
|
|
251305
251347
|
handleAttemptError(attempt, error) {
|
|
251306
251348
|
if (!this.releaseAttempt(attempt)) return;
|
|
251307
251349
|
if (this.finished) return;
|
|
@@ -251330,12 +251372,7 @@ var init_subagent_batch = __esmMin((() => {
|
|
|
251330
251372
|
const now = Date.now();
|
|
251331
251373
|
this.lastRateLimitAt = now;
|
|
251332
251374
|
state.retryCount += 1;
|
|
251333
|
-
const retryDelay =
|
|
251334
|
-
minTimeout: RATE_LIMIT_RETRY_BASE_MS,
|
|
251335
|
-
maxTimeout: Number.POSITIVE_INFINITY,
|
|
251336
|
-
factor: RATE_LIMIT_RETRY_FACTOR,
|
|
251337
|
-
randomize: false
|
|
251338
|
-
});
|
|
251375
|
+
const retryDelay = rateLimitRetryDelayMs(state.retryCount);
|
|
251339
251376
|
state.retryReadyAt = now + retryDelay;
|
|
251340
251377
|
this.pending.unshift(state);
|
|
251341
251378
|
this.enterRateLimitMode(now);
|
|
@@ -251509,7 +251546,7 @@ function shouldSuppressQueuedAttemptFailureEvent(options, error) {
|
|
|
251509
251546
|
if (isProviderRateLimitError(error)) return true;
|
|
251510
251547
|
return isAbortError$4(error) || options.signal.aborted;
|
|
251511
251548
|
}
|
|
251512
|
-
var DEFAULT_SUBAGENT_TIMEOUT_MS,
|
|
251549
|
+
var DEFAULT_SUBAGENT_TIMEOUT_MS, resolveSubagentTimeoutMs, runAgentWithTimeoutContinuation, SUMMARY_MIN_LENGTH, SUMMARY_CONTINUATION_ATTEMPTS, HOOK_TEXT_PREVIEW_LENGTH, SUBAGENT_MAX_TOKENS_ERROR, TOOL_CALL_DISABLED_MESSAGE, SUBAGENT_PROMPT_ORIGIN, SIDE_QUESTION_SYSTEM_REMINDER, SessionSubagentHost;
|
|
251513
251550
|
var init_subagent_host = __esmMin((() => {
|
|
251514
251551
|
init_src$4();
|
|
251515
251552
|
init_errors$8();
|
|
@@ -251521,8 +251558,7 @@ var init_subagent_host = __esmMin((() => {
|
|
|
251521
251558
|
init_git_context();
|
|
251522
251559
|
init_subagent_batch();
|
|
251523
251560
|
init_summary_continuation();
|
|
251524
|
-
DEFAULT_SUBAGENT_TIMEOUT_MS
|
|
251525
|
-
DEFAULT_SUBAGENT_TIMEOUT_DESCRIPTION = "30 minutes";
|
|
251561
|
+
({ DEFAULT_SUBAGENT_TIMEOUT_MS, resolveSubagentTimeoutMs, runAgentWithTimeoutContinuation } = createRequire(import.meta.url)("./bin/subagent-timeout-policy.cjs"));
|
|
251526
251562
|
SUMMARY_MIN_LENGTH = 200;
|
|
251527
251563
|
SUMMARY_CONTINUATION_ATTEMPTS = 1;
|
|
251528
251564
|
HOOK_TEXT_PREVIEW_LENGTH = 500;
|
|
@@ -251634,6 +251670,13 @@ IMPORTANT:
|
|
|
251634
251670
|
modelAlias: parent.config.modelAlias,
|
|
251635
251671
|
actionStyle: parent.config.actionStyle
|
|
251636
251672
|
});
|
|
251673
|
+
if (runOptions.compactBeforeRetry === true && shouldCompactBeforeRateLimitResume(child.fullCompaction.estimateCurrentRequestTokens())) {
|
|
251674
|
+
child.fullCompaction.begin({
|
|
251675
|
+
source: "manual",
|
|
251676
|
+
instruction: "Preserve completed work, current state, exact evidence, and the next action so this same agent can continue after the provider rate limit."
|
|
251677
|
+
});
|
|
251678
|
+
await child.fullCompaction.block(runOptions.signal);
|
|
251679
|
+
}
|
|
251637
251680
|
this.emitSubagentStarted(parent, agentId);
|
|
251638
251681
|
if (child.turn.retry("agent-host") === null) throw new Error(`Agent instance "${agentId}" could not start a retry turn`);
|
|
251639
251682
|
this.observeFirstRequest(child, runOptions);
|
|
@@ -251865,7 +251908,7 @@ var init_agent_background_enabled = __esmMin((() => {
|
|
|
251865
251908
|
//#region ../../packages/agent-core/src/tools/builtin/collaboration/agent.md?raw
|
|
251866
251909
|
var agent_default;
|
|
251867
251910
|
var init_agent$2 = __esmMin((() => {
|
|
251868
|
-
agent_default = "Launch a subagent to handle a task. The subagent runs as a same-process loop instance with its own context and wire file. Delegating also keeps the bulk of intermediate file contents out of your own context — you get a conclusion back instead of a pile of dumps.\n\nWriting the prompt:\n- The subagent starts with zero context — it has not seen this conversation. Brief it like a colleague who just walked into the room: state the goal, list what you already know, hand over the specifics.\n- Lookups (read this file, run that test): put the exact path or command in the prompt. The subagent should not have to search for things you already know.\n- Investigations (figure out X, find why Y): give the question, not prescribed steps — fixed steps become dead weight when the premise is wrong.\n- Do not delegate understanding. If the task hinges on a file path or line number, find it yourself first and write it into the prompt.\n\nUsage notes:\n- When the task continues earlier work a subagent already did, prefer resuming that agent (pass its `resume` id) over spawning a fresh instance — the resumed agent keeps its prior context.\n- A subagent's result is only visible to you, not to the user. When the user needs to see what a subagent produced, summarize the relevant parts yourself in your own reply.\n-
|
|
251911
|
+
agent_default = "Launch a subagent to handle a task. The subagent runs as a same-process loop instance with its own context and wire file. Delegating also keeps the bulk of intermediate file contents out of your own context — you get a conclusion back instead of a pile of dumps.\n\nWriting the prompt:\n- The subagent starts with zero context — it has not seen this conversation. Brief it like a colleague who just walked into the room: state the goal, list what you already know, hand over the specifics.\n- Lookups (read this file, run that test): put the exact path or command in the prompt. The subagent should not have to search for things you already know.\n- Investigations (figure out X, find why Y): give the question, not prescribed steps — fixed steps become dead weight when the premise is wrong.\n- Do not delegate understanding. If the task hinges on a file path or line number, find it yourself first and write it into the prompt.\n\nUsage notes:\n- When the task continues earlier work a subagent already did, prefer resuming that agent (pass its `resume` id) over spawning a fresh instance — the resumed agent keeps its prior context.\n- A subagent's result is only visible to you, not to the user. When the user needs to see what a subagent produced, summarize the relevant parts yourself in your own reply.\n- The default wall-clock interval is four hours; when it elapses, the same agent continues automatically with its preserved context. Set `timeout_minutes=0` to disable the interval, or provide another minute value for this run.\n\nWhen NOT to use Agent: skip delegation for trivial work you can do directly — reading a file whose path you already know, searching a small known set of files, or any task that takes only a step or two. Delegation has a context-handoff cost; it pays off only when the task is substantial enough to outweigh it.\n\nOnce a subagent is running, leave that scope to it: do not redo its searches or reads in parallel, and do not abandon it midway and finish the job manually. Both undo the context savings the delegation was meant to buy.\n";
|
|
251869
251912
|
}));
|
|
251870
251913
|
//#endregion
|
|
251871
251914
|
//#region ../../packages/agent-core/src/tools/builtin/collaboration/agent.ts
|
|
@@ -251944,7 +251987,8 @@ var init_agent$1 = __esmMin((() => {
|
|
|
251944
251987
|
description: string().describe("Short task description (3-5 words) for UI display"),
|
|
251945
251988
|
subagent_type: string().optional().describe("One of the available agent types (see \"Available agent types\" in this tool description). Defaults to \"coder\" when omitted."),
|
|
251946
251989
|
resume: string().optional().describe("Optional agent ID to resume instead of creating a new instance. When set, do not also pass subagent_type — the resumed agent keeps its own type, and supplying both is rejected."),
|
|
251947
|
-
run_in_background: boolean$1().optional().describe("If true, return immediately without waiting for completion. Prefer false unless the task can run independently and there is a clear benefit to not waiting.")
|
|
251990
|
+
run_in_background: boolean$1().optional().describe("If true, return immediately without waiting for completion. Prefer false unless the task can run independently and there is a clear benefit to not waiting."),
|
|
251991
|
+
timeout_minutes: number$1().int().min(0).max(10080).optional().describe("Wall-clock interval before the same agent is continued automatically. Defaults to 240 minutes. Set 0 to disable the interval for this run.")
|
|
251948
251992
|
}));
|
|
251949
251993
|
object({
|
|
251950
251994
|
result: string().describe("Aggregated text output from the subagent"),
|
|
@@ -252039,9 +252083,12 @@ var init_agent$1 = __esmMin((() => {
|
|
|
252039
252083
|
}
|
|
252040
252084
|
let taskId;
|
|
252041
252085
|
try {
|
|
252042
|
-
taskId = this.backgroundManager.registerTask(new AgentBackgroundTask(handle, args.description, this.subagentHost, controller
|
|
252086
|
+
taskId = this.backgroundManager.registerTask(new AgentBackgroundTask(handle, args.description, this.subagentHost, controller, {
|
|
252087
|
+
timeoutMs: resolveSubagentTimeoutMs({ perRunMinutes: args.timeout_minutes }),
|
|
252088
|
+
runInBackground,
|
|
252089
|
+
parentToolCallId: toolCallId
|
|
252090
|
+
}), {
|
|
252043
252091
|
detached: runInBackground,
|
|
252044
|
-
timeoutMs: DEFAULT_SUBAGENT_TIMEOUT_MS,
|
|
252045
252092
|
signal: runInBackground ? void 0 : signal
|
|
252046
252093
|
});
|
|
252047
252094
|
signal.removeEventListener("abort", abortBeforeRegister);
|
|
@@ -252075,7 +252122,7 @@ var init_agent$1 = __esmMin((() => {
|
|
|
252075
252122
|
if (info?.status === "completed") return { output: formatForegroundAgentSuccess(handle, await this.backgroundManager.readOutput(taskId)) };
|
|
252076
252123
|
const timedOut = info?.status === "timed_out";
|
|
252077
252124
|
return {
|
|
252078
|
-
output: formatForegroundAgentFailure(handle, timedOut ?
|
|
252125
|
+
output: formatForegroundAgentFailure(handle, timedOut ? "Agent timed out before automatic continuation could start." : info?.stopReason === "Interrupted by user" ? USER_INTERRUPTED_SUBAGENT_MESSAGE : info?.stopReason !== void 0 ? info.stopReason : "The subagent was stopped before it finished.", timedOut),
|
|
252079
252126
|
isError: true
|
|
252080
252127
|
};
|
|
252081
252128
|
}
|
|
@@ -252184,7 +252231,8 @@ var init_agent_swarm = __esmMin((() => {
|
|
|
252184
252231
|
subagent_type: string().trim().min(1).optional().describe("Subagent type used for every new subagent spawned from items; defaults to coder when omitted. Resumed subagents always keep their original type, so passing subagent_type together with resume_agent_ids is allowed — it only affects the item-based spawns."),
|
|
252185
252232
|
prompt_template: string().trim().min(1).optional().describe(`Prompt template for each subagent. The ${PROMPT_TEMPLATE_PLACEHOLDER} placeholder is replaced with each item value.`),
|
|
252186
252233
|
items: array(string().trim().min(1)).max(MAX_AGENT_SWARM_SUBAGENTS).optional().describe(`Values used to fill ${PROMPT_TEMPLATE_PLACEHOLDER}. Each item launches one new subagent.`),
|
|
252187
|
-
resume_agent_ids: record(string().trim().min(1), string().trim().min(1)).optional().describe("Map of existing subagent agent_id to the prompt used to resume that subagent. These resumed subagents are launched before new item-based subagents.")
|
|
252234
|
+
resume_agent_ids: record(string().trim().min(1), string().trim().min(1)).optional().describe("Map of existing subagent agent_id to the prompt used to resume that subagent. These resumed subagents are launched before new item-based subagents."),
|
|
252235
|
+
timeout_minutes: number$1().int().min(0).max(10080).optional().describe("Wall-clock interval before each same swarm agent is continued automatically. Defaults to 240 minutes. Set 0 to disable the interval for this run.")
|
|
252188
252236
|
}).strict();
|
|
252189
252237
|
AgentSwarmTool = class {
|
|
252190
252238
|
subagentHost;
|
|
@@ -252235,7 +252283,7 @@ var init_agent_swarm = __esmMin((() => {
|
|
|
252235
252283
|
runInBackground: false,
|
|
252236
252284
|
swarmItem: spec.item,
|
|
252237
252285
|
signal,
|
|
252238
|
-
timeout:
|
|
252286
|
+
timeout: resolveSubagentTimeoutMs({ perRunMinutes: args.timeout_minutes })
|
|
252239
252287
|
};
|
|
252240
252288
|
if (spec.kind === "resume") return {
|
|
252241
252289
|
...common,
|
|
@@ -257129,7 +257177,7 @@ function rgBinaryName() {
|
|
|
257129
257177
|
return process.platform === "win32" ? "rg.exe" : "rg";
|
|
257130
257178
|
}
|
|
257131
257179
|
function getShareDir() {
|
|
257132
|
-
const override = process.env["BLUN_HOME"];
|
|
257180
|
+
const override = process.env["BLUN_SHARED_HOME"] ?? process.env["BLUN_HOME"];
|
|
257133
257181
|
if (override !== void 0 && override !== "") return override;
|
|
257134
257182
|
return join$4(homedir(), ".blun");
|
|
257135
257183
|
}
|
|
@@ -295369,7 +295417,7 @@ var init_session$1 = __esmMin((() => {
|
|
|
295369
295417
|
this.currentMcpConfig = options.mcpConfig;
|
|
295370
295418
|
this.skills = new SessionSkillRegistry({ sessionId: options.id });
|
|
295371
295419
|
this.mcp = new McpConnectionManager({
|
|
295372
|
-
oauthService: new McpOAuthService({ blunHomeDir: options.blunHomeDir }),
|
|
295420
|
+
oauthService: new McpOAuthService({ blunHomeDir: resolveSharedBlunHome(options.blunHomeDir) }),
|
|
295373
295421
|
log: this.log,
|
|
295374
295422
|
stdioCwd: options.kaos.getcwd()
|
|
295375
295423
|
});
|
|
@@ -295805,7 +295853,7 @@ var init_session$1 = __esmMin((() => {
|
|
|
295805
295853
|
const roots = await resolveSkillRoots({
|
|
295806
295854
|
paths: {
|
|
295807
295855
|
userHomeDir: this.options.skills?.userHomeDir ?? homedir(),
|
|
295808
|
-
brandHomeDir: this.options.skills?.brandHomeDir ?? this.options.blunHomeDir,
|
|
295856
|
+
brandHomeDir: this.options.skills?.brandHomeDir ?? resolveSharedBlunHome(this.options.blunHomeDir),
|
|
295809
295857
|
workDir: this.options.kaos.getcwd()
|
|
295810
295858
|
},
|
|
295811
295859
|
explicitDirs: this.options.skills?.explicitDirs,
|
|
@@ -315180,7 +315228,7 @@ var init_core_impl = __esmMin((() => {
|
|
|
315180
315228
|
const explicitDirs = this.skillDirs.length > 0 ? this.skillDirs : void 0;
|
|
315181
315229
|
return {
|
|
315182
315230
|
userHomeDir: this.userHomeDir,
|
|
315183
|
-
brandHomeDir: this.homeDir,
|
|
315231
|
+
brandHomeDir: resolveSharedBlunHome(this.homeDir),
|
|
315184
315232
|
explicitDirs,
|
|
315185
315233
|
extraDirs: config.extraSkillDirs,
|
|
315186
315234
|
pluginSkillRoots: this.plugins.pluginSkillRoots(),
|
|
@@ -315364,7 +315412,7 @@ function resolveLoggingConfig(input) {
|
|
|
315364
315412
|
const env = input.env ?? process.env;
|
|
315365
315413
|
return {
|
|
315366
315414
|
level: parseLevel(env["BLUN_LOG_LEVEL"]) ?? "info",
|
|
315367
|
-
globalLogPath: resolveGlobalLogPath(input.homeDir),
|
|
315415
|
+
globalLogPath: resolveGlobalLogPath(env["BLUN_LOG_HOME"] ?? input.homeDir),
|
|
315368
315416
|
globalMaxBytes: parsePositiveInt(env["BLUN_LOG_GLOBAL_MAX_BYTES"]) ?? 6291456,
|
|
315369
315417
|
globalFiles: parsePositiveInt(env["BLUN_LOG_GLOBAL_FILES"]) ?? 5,
|
|
315370
315418
|
sessionMaxBytes: parsePositiveInt(env["BLUN_LOG_SESSION_MAX_BYTES"]) ?? 5242880,
|
|
@@ -316463,7 +316511,7 @@ var init_managedAuth = __esmMin((() => {
|
|
|
316463
316511
|
constructor(options) {
|
|
316464
316512
|
this.options = options;
|
|
316465
316513
|
this.toolkit = new BlunOAuthToolkit({
|
|
316466
|
-
homeDir: options.homeDir,
|
|
316514
|
+
homeDir: resolveSharedBlunHome(options.homeDir),
|
|
316467
316515
|
configAdapter: {
|
|
316468
316516
|
configPath: options.configPath,
|
|
316469
316517
|
read: () => readConfigFile(options.configPath),
|
|
@@ -325309,9 +325357,9 @@ var BlunAuthFacade = class {
|
|
|
325309
325357
|
managedApiKeyRevocations;
|
|
325310
325358
|
constructor(options) {
|
|
325311
325359
|
this.options = options;
|
|
325312
|
-
this.managedApiKeyRevocations = new FileManagedApiKeyRevocationStore(options.homeDir);
|
|
325360
|
+
this.managedApiKeyRevocations = new FileManagedApiKeyRevocationStore(resolveSharedBlunHome(options.homeDir));
|
|
325313
325361
|
this.toolkit = new BlunOAuthToolkit({
|
|
325314
|
-
homeDir: options.homeDir,
|
|
325362
|
+
homeDir: resolveSharedBlunHome(options.homeDir),
|
|
325315
325363
|
identity: options.identity,
|
|
325316
325364
|
onRefresh: options.onRefresh,
|
|
325317
325365
|
configAdapter: {
|
|
@@ -340448,7 +340496,7 @@ function createCliTelemetryBootstrap() {
|
|
|
340448
340496
|
const homeDir = resolveBlunHome$1();
|
|
340449
340497
|
return {
|
|
340450
340498
|
homeDir,
|
|
340451
|
-
deviceId: createBlunDeviceId(homeDir, { onFirstLaunch: () => {
|
|
340499
|
+
deviceId: createBlunDeviceId(resolveSharedBlunHome(homeDir), { onFirstLaunch: () => {
|
|
340452
340500
|
firstLaunch = true;
|
|
340453
340501
|
} }),
|
|
340454
340502
|
firstLaunch
|
|
@@ -420638,12 +420686,20 @@ const LOOP_CREATE_FILLERS = new Set([
|
|
|
420638
420686
|
"every",
|
|
420639
420687
|
"each"
|
|
420640
420688
|
]);
|
|
420641
|
-
const LOOP_INTERVAL_TOKEN =
|
|
420689
|
+
const LOOP_INTERVAL_TOKEN = /^(\d+)(m|min(?:ute)?n?|minutes?|h|std|stunden?|hours?|d|tage?|days?)$/i;
|
|
420642
420690
|
const LOOP_NATURAL_INTERVAL = /\b(?:alle|jede(?:n|r|s)?|every|each)\s+(\d+)\s*(min(?:ute)?n?|minutes?|stunden?|hours?|tage?|days?)\b/iu;
|
|
420643
420691
|
const LOOP_NATURAL_HALF_HOUR = /\b(?:alle|jede(?:n|r|s)?|every|each)\s+(?:halbe|half(?:\s+an)?)\s+(?:stunde|hour)\b/iu;
|
|
420644
420692
|
const LOOP_NATURAL_SINGLE_INTERVAL = /\b(?:jede(?:n|r|s)?|every|each)\s+(minute|stunde|hour|tag|day)\b/iu;
|
|
420645
420693
|
const LOOP_NATURAL_ADVERB_INTERVAL = /\b(stündlich|stuendlich|hourly|täglich|taeglich|daily)\b/iu;
|
|
420646
420694
|
const LOOP_SELF_CONTROLLED = /\b(?:selbst\s+entscheiden|selbstgesteuert)\b/iu;
|
|
420695
|
+
function normalizeLoopIntervalToken(token) {
|
|
420696
|
+
const match = LOOP_INTERVAL_TOKEN.exec(token);
|
|
420697
|
+
if (match === null) return null;
|
|
420698
|
+
const amount = match[1];
|
|
420699
|
+
const unit = match[2]?.toLowerCase() ?? "";
|
|
420700
|
+
const suffix = unit === "h" || unit === "std" || unit.startsWith("stund") || unit.startsWith("hour") ? "h" : unit === "d" || unit.startsWith("tag") || unit.startsWith("day") ? "d" : "m";
|
|
420701
|
+
return `${amount}${suffix}`;
|
|
420702
|
+
}
|
|
420647
420703
|
function naturalLoopIntervalMatch(args) {
|
|
420648
420704
|
const numeric = LOOP_NATURAL_INTERVAL.exec(args);
|
|
420649
420705
|
if (numeric !== null) {
|
|
@@ -420706,8 +420762,9 @@ function loopInputHint(rawArgs) {
|
|
|
420706
420762
|
if (/^(?:status|pause|paused|resume|stop)$/i.test(args)) return "";
|
|
420707
420763
|
const natural = naturalLoopIntervalMatch(args);
|
|
420708
420764
|
if (natural !== null) return naturalLoopIntervalText(natural.interval);
|
|
420709
|
-
const
|
|
420710
|
-
|
|
420765
|
+
const explicitToken = /^(?:start\s+)?(\S+)/i.exec(args)?.[1];
|
|
420766
|
+
const explicit = explicitToken === void 0 ? null : normalizeLoopIntervalToken(explicitToken);
|
|
420767
|
+
if (explicit !== null) return naturalLoopIntervalText(explicit);
|
|
420711
420768
|
return "Selbstgesteuert";
|
|
420712
420769
|
}
|
|
420713
420770
|
function parseLoopCommand(rawArgs) {
|
|
@@ -420735,8 +420792,9 @@ function parseLoopCommand(rawArgs) {
|
|
|
420735
420792
|
};
|
|
420736
420793
|
}
|
|
420737
420794
|
if (LOOP_CREATE_FILLERS.has(tokens[startIndex]?.toLowerCase() ?? "")) startIndex += 1;
|
|
420738
|
-
const
|
|
420739
|
-
|
|
420795
|
+
const intervalToken = tokens[startIndex];
|
|
420796
|
+
const interval = intervalToken === void 0 ? null : normalizeLoopIntervalToken(intervalToken);
|
|
420797
|
+
if (interval !== null) {
|
|
420740
420798
|
const prompt = tokens.slice(startIndex + 1).join(" ").trim();
|
|
420741
420799
|
if (prompt.length === 0) return { kind: "error" };
|
|
420742
420800
|
return {
|
|
@@ -489065,6 +489123,14 @@ function formatLiveElapsed(seconds) {
|
|
|
489065
489123
|
const remainingMinutes = minutes % 60;
|
|
489066
489124
|
return `${String(hours)}h ${String(remainingMinutes).padStart(2, "0")}m`;
|
|
489067
489125
|
}
|
|
489126
|
+
function formatLiveStartedAt(startedAtMs) {
|
|
489127
|
+
const startedAt = new Date(startedAtMs > 0 ? startedAtMs : Date.now());
|
|
489128
|
+
const day = String(startedAt.getDate()).padStart(2, "0");
|
|
489129
|
+
const month = String(startedAt.getMonth() + 1).padStart(2, "0");
|
|
489130
|
+
const hour = String(startedAt.getHours()).padStart(2, "0");
|
|
489131
|
+
const minute = String(startedAt.getMinutes()).padStart(2, "0");
|
|
489132
|
+
return `${day}.${month}. · ${hour}:${minute}`;
|
|
489133
|
+
}
|
|
489068
489134
|
var ThinkingComponent = class {
|
|
489069
489135
|
text;
|
|
489070
489136
|
showMarker;
|
|
@@ -489142,14 +489208,15 @@ var ThinkingComponent = class {
|
|
|
489142
489208
|
const frame = BLUN_SPINNER_FRAMES[this.spinnerFrame] ?? BLUN_SPINNER_FRAMES[0];
|
|
489143
489209
|
const spinner = currentTheme.fg("primary", `${frame} `);
|
|
489144
489210
|
const elapsed = formatLiveElapsed(this.formatElapsedSeconds());
|
|
489211
|
+
const startedAt = formatLiveStartedAt(this.thinkStartMs ?? Date.now());
|
|
489145
489212
|
const roundedTokenCount = Math.max(0, Math.round(this.estimatedOutputTokens));
|
|
489146
489213
|
const tokenCount = formatLiveTokenCount(roundedTokenCount);
|
|
489147
489214
|
const abortMark = this.thinkAborted ? ` (${uiText("thinking.aborted")})` : "";
|
|
489148
489215
|
const tokenKey = roundedTokenCount === 1 ? "thinking.tokens.one" : "thinking.tokens.other";
|
|
489149
489216
|
const thinkingLabel = uiText("thinking.label", { name: this.persona });
|
|
489150
|
-
const fullLabel = `${thinkingLabel}… (${elapsed} · ↓ ~${uiText(tokenKey, { count: tokenCount })})${abortMark}`;
|
|
489151
|
-
const compactLabel = `${thinkingLabel}… (${elapsed} · ↓${tokenCount})${abortMark}`;
|
|
489152
|
-
const metricsOnly = `(${elapsed} ·
|
|
489217
|
+
const fullLabel = `${thinkingLabel}… (${elapsed} · ↓ ~${uiText(tokenKey, { count: tokenCount })} · ${startedAt})${abortMark}`;
|
|
489218
|
+
const compactLabel = `${thinkingLabel}… (${elapsed} · ↓${tokenCount} · ${startedAt})${abortMark}`;
|
|
489219
|
+
const metricsOnly = `(${elapsed} · ${startedAt})${abortMark}`;
|
|
489153
489220
|
const availableWidth = Math.max(1, width - visibleWidth(spinner));
|
|
489154
489221
|
const label = visibleWidth(fullLabel) <= availableWidth ? fullLabel : visibleWidth(compactLabel) <= availableWidth ? compactLabel : metricsOnly;
|
|
489155
489222
|
rendered = ["", spinner + currentTheme.fg("textDim", truncateToWidth(label, availableWidth))];
|
|
@@ -500355,7 +500422,8 @@ var BtwPanelComponent = class {
|
|
|
500355
500422
|
const outputTokens = estimateLiveOutputTokens(turn.thinking + turn.answer);
|
|
500356
500423
|
const frame = BLUN_SPINNER_FRAMES[this.spinnerFrame] ?? BLUN_SPINNER_FRAMES[0];
|
|
500357
500424
|
const label = uiText("blunTui.activity.thinking", { name: "BTW" });
|
|
500358
|
-
const
|
|
500425
|
+
const startedAt = formatLiveStartedAt(turn.startedAtMs);
|
|
500426
|
+
const metrics = `(${formatLiveElapsed(elapsedSeconds)} · ↓ ~${formatLiveTokenCount(outputTokens)} ${uiText("blunTui.activity.tokens")} · ${startedAt})`;
|
|
500359
500427
|
return chalk.hex(currentTheme.palette.accent)(`${frame} `) + chalk.hex(currentTheme.palette.accent).bold(label) + " " + chalk.hex(currentTheme.palette.text)(metrics);
|
|
500360
500428
|
}
|
|
500361
500429
|
startLiveStatusTimer() {
|
|
@@ -502143,7 +502211,6 @@ var EditorKeyboardController = class {
|
|
|
502143
502211
|
}
|
|
502144
502212
|
if (host.state.appState.isCompacting) {
|
|
502145
502213
|
this.clearPendingExit();
|
|
502146
|
-
if (this.clearEditorTextIfPresent()) return;
|
|
502147
502214
|
this.cancelCurrentCompaction();
|
|
502148
502215
|
return;
|
|
502149
502216
|
}
|
|
@@ -502157,7 +502224,6 @@ var EditorKeyboardController = class {
|
|
|
502157
502224
|
}
|
|
502158
502225
|
if (host.state.appState.streamingPhase !== "idle") {
|
|
502159
502226
|
this.clearPendingExit();
|
|
502160
|
-
if (this.clearEditorTextIfPresent()) return;
|
|
502161
502227
|
this.cancelCurrentStream();
|
|
502162
502228
|
return;
|
|
502163
502229
|
}
|
|
@@ -502177,8 +502243,15 @@ var EditorKeyboardController = class {
|
|
|
502177
502243
|
}
|
|
502178
502244
|
this.armPendingExit("ctrl-d", ctrlDHint());
|
|
502179
502245
|
};
|
|
502180
|
-
editor.onEscape = () => {
|
|
502246
|
+
editor.onEscape = (autocompleteCancelled = false) => {
|
|
502181
502247
|
if (this.pendingExit) this.clearPendingExit();
|
|
502248
|
+
if (host.cancelInFlight !== void 0) {
|
|
502249
|
+
const cancel = host.cancelInFlight;
|
|
502250
|
+
host.cancelInFlight = void 0;
|
|
502251
|
+
this.clearPendingUndoEsc();
|
|
502252
|
+
cancel();
|
|
502253
|
+
return;
|
|
502254
|
+
}
|
|
502182
502255
|
if (host.state.activeDialog === "session-picker") {
|
|
502183
502256
|
host.hideSessionPicker();
|
|
502184
502257
|
this.clearPendingUndoEsc();
|
|
@@ -502198,6 +502271,10 @@ var EditorKeyboardController = class {
|
|
|
502198
502271
|
this.clearPendingUndoEsc();
|
|
502199
502272
|
return;
|
|
502200
502273
|
}
|
|
502274
|
+
if (autocompleteCancelled) {
|
|
502275
|
+
this.clearPendingUndoEsc();
|
|
502276
|
+
return;
|
|
502277
|
+
}
|
|
502201
502278
|
if (this.pendingUndoEsc !== null) {
|
|
502202
502279
|
this.clearPendingUndoEsc();
|
|
502203
502280
|
host.openUndoSelector();
|
|
@@ -504257,6 +504334,13 @@ function mcpServerStatusKey(server) {
|
|
|
504257
504334
|
server.error
|
|
504258
504335
|
]);
|
|
504259
504336
|
}
|
|
504337
|
+
function mcpStartupInventory(servers) {
|
|
504338
|
+
const connected = servers.filter((server) => server.status === "connected");
|
|
504339
|
+
return {
|
|
504340
|
+
serverCount: connected.length,
|
|
504341
|
+
toolCount: connected.reduce((total, server) => total + Math.max(0, Number(server.toolCount) || 0), 0)
|
|
504342
|
+
};
|
|
504343
|
+
}
|
|
504260
504344
|
//#endregion
|
|
504261
504345
|
//#region src/utils/usage/debug-timing.ts
|
|
504262
504346
|
const MIN_STREAM_MS_FOR_TPS = 50;
|
|
@@ -504916,6 +505000,7 @@ var SessionEventHandler = class {
|
|
|
504916
505000
|
renderedMcpServerStatusKeys = /* @__PURE__ */ new Map();
|
|
504917
505001
|
mcpServerStatusSpinners = /* @__PURE__ */ new Map();
|
|
504918
505002
|
mcpServers = /* @__PURE__ */ new Map();
|
|
505003
|
+
mcpInventoryLogged = false;
|
|
504919
505004
|
goalCompletionAwaitingClear = false;
|
|
504920
505005
|
goalCompletionTurnEnded = false;
|
|
504921
505006
|
currentTurnHasAssistantText = false;
|
|
@@ -504935,6 +505020,7 @@ var SessionEventHandler = class {
|
|
|
504935
505020
|
this.renderedPluginCommandActivationIds.clear();
|
|
504936
505021
|
this.renderedMcpServerStatusKeys.clear();
|
|
504937
505022
|
this.mcpServers.clear();
|
|
505023
|
+
this.mcpInventoryLogged = false;
|
|
504938
505024
|
this.goalCompletionAwaitingClear = false;
|
|
504939
505025
|
this.goalCompletionTurnEnded = false;
|
|
504940
505026
|
this.currentTurnHasAssistantText = false;
|
|
@@ -504986,6 +505072,7 @@ var SessionEventHandler = class {
|
|
|
504986
505072
|
}
|
|
504987
505073
|
if (host.session !== session || host.state.appState.sessionId !== session.id) return;
|
|
504988
505074
|
const startupServers = servers.filter((server) => !isManagedTelegramMissingTokenFailure(server));
|
|
505075
|
+
this.logMcpInventoryIfReady(startupServers);
|
|
504989
505076
|
const visible = selectMcpStartupStatusRows(startupServers);
|
|
504990
505077
|
const visibleNames = new Set(visible.map((server) => server.name));
|
|
504991
505078
|
for (const server of visible) {
|
|
@@ -505656,6 +505743,7 @@ var SessionEventHandler = class {
|
|
|
505656
505743
|
this.mcpServers.set(server.name, server);
|
|
505657
505744
|
const summary = formatMcpStartupStatusSummary([...this.mcpServers.values()]);
|
|
505658
505745
|
this.host.setAppState({ mcpServersSummary: summary || null });
|
|
505746
|
+
this.logMcpInventoryIfReady([...this.mcpServers.values()]);
|
|
505659
505747
|
switch (server.status) {
|
|
505660
505748
|
case "connected": {
|
|
505661
505749
|
const tools = uiText(server.toolCount === 1 ? "sessionEvent.mcp.tool.one" : "sessionEvent.mcp.tool.other", { count: server.toolCount });
|
|
@@ -505688,6 +505776,12 @@ var SessionEventHandler = class {
|
|
|
505688
505776
|
return;
|
|
505689
505777
|
}
|
|
505690
505778
|
}
|
|
505779
|
+
logMcpInventoryIfReady(servers) {
|
|
505780
|
+
if (this.mcpInventoryLogged || servers.some((server) => server.status === "pending")) return;
|
|
505781
|
+
const inventory = mcpStartupInventory(servers);
|
|
505782
|
+
log.info("mcp startup inventory", inventory);
|
|
505783
|
+
this.mcpInventoryLogged = true;
|
|
505784
|
+
}
|
|
505691
505785
|
showMcpServerStatusSpinner(name) {
|
|
505692
505786
|
const { state } = this.host;
|
|
505693
505787
|
const label = uiText("sessionEvent.mcp.connecting", { name });
|
|
@@ -507601,7 +507695,7 @@ function formatGoalBadge(goal, colors, wallClockMs) {
|
|
|
507601
507695
|
const label = `${uiText(`goal.panel.status.${goal.status}`)} · ${formatBadgeElapsed(wallClockMs ?? goal.wallClockMs)} · ${turns}`;
|
|
507602
507696
|
return chalk.hex(colors.textMuted)(`[${uiText("footer.goal.label")} `) + chalk.hex(dotColor)("●") + chalk.hex(colors.textMuted)(` ${label}]`);
|
|
507603
507697
|
}
|
|
507604
|
-
function
|
|
507698
|
+
function formatLoopChatIndicator(loop, colors, nowMs = Date.now()) {
|
|
507605
507699
|
if (loop === null || loop === void 0 || loop.status !== "active" || !Number.isFinite(loop.nextFireAt)) return null;
|
|
507606
507700
|
const remainingMs = Math.max(0, loop.nextFireAt - nowMs);
|
|
507607
507701
|
let value;
|
|
@@ -507623,7 +507717,7 @@ function formatLoopBadge(loop, colors, nowMs = Date.now()) {
|
|
|
507623
507717
|
numeric: "always",
|
|
507624
507718
|
style: "short"
|
|
507625
507719
|
}).format(value, unit);
|
|
507626
|
-
return chalk.hex(colors.textMuted)(`
|
|
507720
|
+
return chalk.hex(colors.primary)("\u25cf") + chalk.hex(colors.textMuted)(` Loop ${relative}`);
|
|
507627
507721
|
}
|
|
507628
507722
|
function formatBadgeElapsed(ms) {
|
|
507629
507723
|
const totalSeconds = Math.round(ms / 1e3);
|
|
@@ -507794,6 +507888,41 @@ function formatFooterGitBadge(status, colors) {
|
|
|
507794
507888
|
if (status.pullRequest === null) return base;
|
|
507795
507889
|
return `${base} ${chalk.hex(colors.primary)(formatPullRequestBadge(status.pullRequest, { linkPullRequest: true }))}`;
|
|
507796
507890
|
}
|
|
507891
|
+
var LoopChatIndicatorComponent = class {
|
|
507892
|
+
state;
|
|
507893
|
+
onRefresh;
|
|
507894
|
+
loopTimer = null;
|
|
507895
|
+
constructor(state, onRefresh = () => {}) {
|
|
507896
|
+
this.state = state;
|
|
507897
|
+
this.onRefresh = onRefresh;
|
|
507898
|
+
this.syncTimer(state.loop);
|
|
507899
|
+
}
|
|
507900
|
+
setState(state) {
|
|
507901
|
+
this.state = state;
|
|
507902
|
+
this.syncTimer(state.loop);
|
|
507903
|
+
}
|
|
507904
|
+
render(width) {
|
|
507905
|
+
const indicator = formatLoopChatIndicator(this.state.loop, currentTheme.palette);
|
|
507906
|
+
return indicator === null ? [] : [truncateToWidth(indicator, width, "…")];
|
|
507907
|
+
}
|
|
507908
|
+
syncTimer(loop) {
|
|
507909
|
+
if (loop?.status === "active" && Number.isFinite(loop.nextFireAt)) {
|
|
507910
|
+
if (this.loopTimer !== null) return;
|
|
507911
|
+
this.loopTimer = setInterval(() => {
|
|
507912
|
+
this.onRefresh();
|
|
507913
|
+
}, GOAL_TIMER_INTERVAL_MS);
|
|
507914
|
+
this.loopTimer.unref?.();
|
|
507915
|
+
return;
|
|
507916
|
+
}
|
|
507917
|
+
this.dispose();
|
|
507918
|
+
}
|
|
507919
|
+
dispose() {
|
|
507920
|
+
if (this.loopTimer !== null) {
|
|
507921
|
+
clearInterval(this.loopTimer);
|
|
507922
|
+
this.loopTimer = null;
|
|
507923
|
+
}
|
|
507924
|
+
}
|
|
507925
|
+
};
|
|
507797
507926
|
var FooterComponent = class {
|
|
507798
507927
|
state;
|
|
507799
507928
|
onRefresh;
|
|
@@ -507803,7 +507932,6 @@ var FooterComponent = class {
|
|
|
507803
507932
|
goalSnapshotKey = null;
|
|
507804
507933
|
goalObservedAtMs = Date.now();
|
|
507805
507934
|
goalTimer = null;
|
|
507806
|
-
loopTimer = null;
|
|
507807
507935
|
compactionAttemptStartedAtMs = null;
|
|
507808
507936
|
compactionEstimatedInputTokens;
|
|
507809
507937
|
compactionProgress;
|
|
@@ -507825,7 +507953,6 @@ var FooterComponent = class {
|
|
|
507825
507953
|
this.gitCache = createGitStatusCache(state.workDir, { onChange: this.onRefresh });
|
|
507826
507954
|
this.syncGoalClock(state.goal);
|
|
507827
507955
|
this.syncGoalTimer(state.goal);
|
|
507828
|
-
this.syncLoopTimer(state.loop);
|
|
507829
507956
|
if (state.isCompacting) this.startCompaction();
|
|
507830
507957
|
}
|
|
507831
507958
|
setState(state) {
|
|
@@ -507835,7 +507962,6 @@ var FooterComponent = class {
|
|
|
507835
507962
|
}
|
|
507836
507963
|
this.syncGoalClock(state.goal);
|
|
507837
507964
|
this.syncGoalTimer(state.goal);
|
|
507838
|
-
this.syncLoopTimer(state.loop);
|
|
507839
507965
|
this.state = state;
|
|
507840
507966
|
if (state.isCompacting && this.compactionAttemptStartedAtMs === null) this.startCompaction();
|
|
507841
507967
|
else if (!state.isCompacting) this.finishCompaction();
|
|
@@ -507902,13 +508028,13 @@ var FooterComponent = class {
|
|
|
507902
508028
|
if (modes.length > 0) left.push(modes.join(" "));
|
|
507903
508029
|
const goalBadge = formatGoalBadge(state.goal, colors, this.goalWallClockMs(state.goal));
|
|
507904
508030
|
if (goalBadge !== null) left.push(goalBadge);
|
|
507905
|
-
const loopBadge = formatLoopBadge(state.loop, colors);
|
|
507906
|
-
if (loopBadge !== null) left.push(loopBadge);
|
|
507907
508031
|
const model = modelDisplayName(state);
|
|
507908
508032
|
if (model) {
|
|
507909
508033
|
const sep = chalk.hex(colors.textMuted)(" · ");
|
|
507910
508034
|
const persona = personaName();
|
|
507911
|
-
const
|
|
508035
|
+
const profileLabel = process.env["BLUN_PROFILE"] ?? "default";
|
|
508036
|
+
const profile = chalk.hex(colors.textDim)(profileLabel) + sep;
|
|
508037
|
+
const brand = chalk.hex(colors.primary).bold("● BLUN") + sep + profile + (persona !== void 0 ? chalk.hex(colors.text)(persona) + sep : "");
|
|
507912
508038
|
const modelLabel = model;
|
|
507913
508039
|
const modeLabel = uiText(state.permissionMode === "yolo" ? "footer.mode.god" : state.permissionMode === "auto" ? "footer.mode.auto" : "footer.mode.manual");
|
|
507914
508040
|
const modeSuffix = sep + chalk.hex(colors.textDim)(uiText("footer.mode", { mode: modeLabel }));
|
|
@@ -507968,29 +508094,11 @@ var FooterComponent = class {
|
|
|
507968
508094
|
this.goalTimer = null;
|
|
507969
508095
|
}
|
|
507970
508096
|
}
|
|
507971
|
-
syncLoopTimer(loop) {
|
|
507972
|
-
if (loop?.status === "active" && Number.isFinite(loop.nextFireAt)) {
|
|
507973
|
-
if (this.loopTimer !== null) return;
|
|
507974
|
-
this.loopTimer = setInterval(() => {
|
|
507975
|
-
this.onRefresh();
|
|
507976
|
-
}, GOAL_TIMER_INTERVAL_MS);
|
|
507977
|
-
this.loopTimer.unref?.();
|
|
507978
|
-
return;
|
|
507979
|
-
}
|
|
507980
|
-
if (this.loopTimer !== null) {
|
|
507981
|
-
clearInterval(this.loopTimer);
|
|
507982
|
-
this.loopTimer = null;
|
|
507983
|
-
}
|
|
507984
|
-
}
|
|
507985
508097
|
dispose() {
|
|
507986
508098
|
if (this.goalTimer !== null) {
|
|
507987
508099
|
clearInterval(this.goalTimer);
|
|
507988
508100
|
this.goalTimer = null;
|
|
507989
508101
|
}
|
|
507990
|
-
if (this.loopTimer !== null) {
|
|
507991
|
-
clearInterval(this.loopTimer);
|
|
507992
|
-
this.loopTimer = null;
|
|
507993
|
-
}
|
|
507994
508102
|
this.finishCompaction();
|
|
507995
508103
|
}
|
|
507996
508104
|
goalWallClockMs(goal) {
|
|
@@ -511361,11 +511469,9 @@ var CustomEditor = class extends Editor {
|
|
|
511361
511469
|
}
|
|
511362
511470
|
}
|
|
511363
511471
|
if (matchesKey(normalized, Key.escape)) {
|
|
511364
|
-
|
|
511365
|
-
|
|
511366
|
-
|
|
511367
|
-
}
|
|
511368
|
-
this.onEscape?.();
|
|
511472
|
+
const autocompleteCancelled = this.hasAutocompleteActivity();
|
|
511473
|
+
if (autocompleteCancelled) this.cancelAutocompleteActivity();
|
|
511474
|
+
this.onEscape?.(autocompleteCancelled);
|
|
511369
511475
|
return;
|
|
511370
511476
|
}
|
|
511371
511477
|
if (matchesKey(normalized, Key.tab) && !this.isShowingAutocomplete()) return;
|
|
@@ -511736,6 +511842,11 @@ function createTUIState(options) {
|
|
|
511736
511842
|
const quotaWarningContainer = new GutterContainer(1, 1);
|
|
511737
511843
|
const quotaWarning = new ManagedQuotaWarningComponent();
|
|
511738
511844
|
quotaWarningContainer.addChild(quotaWarning);
|
|
511845
|
+
const loopIndicatorContainer = new GutterContainer(1, 1);
|
|
511846
|
+
const loopIndicator = new LoopChatIndicatorComponent(appState, () => {
|
|
511847
|
+
ui.requestRender();
|
|
511848
|
+
});
|
|
511849
|
+
loopIndicatorContainer.addChild(loopIndicator);
|
|
511739
511850
|
return {
|
|
511740
511851
|
ui,
|
|
511741
511852
|
terminal,
|
|
@@ -511747,6 +511858,8 @@ function createTUIState(options) {
|
|
|
511747
511858
|
btwPanelContainer,
|
|
511748
511859
|
quotaWarningContainer,
|
|
511749
511860
|
quotaWarning,
|
|
511861
|
+
loopIndicatorContainer,
|
|
511862
|
+
loopIndicator,
|
|
511750
511863
|
editorContainer: new GutterContainer(1, 1),
|
|
511751
511864
|
editor: new GhostSuggestEditor(ui, {
|
|
511752
511865
|
disablePasteBurst: initialAppState.disablePasteBurst ?? DEFAULT_TUI_CONFIG.disablePasteBurst,
|
|
@@ -513624,6 +513737,7 @@ var BlunTUI = class {
|
|
|
513624
513737
|
this.editorKeyboard.dispose();
|
|
513625
513738
|
this.scrollbackController.dispose();
|
|
513626
513739
|
this.managedQuotaWarningController.dispose();
|
|
513740
|
+
this.state.loopIndicator.dispose();
|
|
513627
513741
|
this.state.footer.dispose();
|
|
513628
513742
|
for (const dispose of this.reverseRpcDisposers) dispose();
|
|
513629
513743
|
this.reverseRpcDisposers.length = 0;
|
|
@@ -513704,6 +513818,7 @@ var BlunTUI = class {
|
|
|
513704
513818
|
ui.addChild(this.state.queueContainer);
|
|
513705
513819
|
ui.addChild(this.state.quotaWarningContainer);
|
|
513706
513820
|
ui.addChild(this.state.btwPanelContainer);
|
|
513821
|
+
ui.addChild(this.state.loopIndicatorContainer);
|
|
513707
513822
|
ui.addChild(this.state.editorContainer);
|
|
513708
513823
|
if (ui instanceof BottomPinnedTUI) {
|
|
513709
513824
|
ui.pinFromIndex = ui.children.indexOf(this.state.quotaWarningContainer);
|
|
@@ -514686,6 +514801,7 @@ var BlunTUI = class {
|
|
|
514686
514801
|
const busyChanged = "streamingPhase" in effectivePatch || "isCompacting" in effectivePatch;
|
|
514687
514802
|
Object.assign(this.state.appState, effectivePatch);
|
|
514688
514803
|
if ("planMode" in effectivePatch) this.updateEditorBorderHighlight();
|
|
514804
|
+
this.state.loopIndicator.setState(this.state.appState);
|
|
514689
514805
|
this.state.footer.setState(this.state.appState);
|
|
514690
514806
|
if ("sessionId" in effectivePatch || "managedQuotaWindows" in effectivePatch) this.managedQuotaWarningController.update(this.state.appState.sessionId, this.state.appState.managedQuotaWindows);
|
|
514691
514807
|
this.updateActivityPane();
|
|
@@ -515707,10 +515823,11 @@ var BlunTUI = class {
|
|
|
515707
515823
|
const startedAtMs = metrics.startedAtMs ?? this.activitySpinnerFallbackStartMs;
|
|
515708
515824
|
const elapsed = formatLiveElapsed(startedAtMs > 0 ? Math.max(0, Math.floor((Date.now() - startedAtMs) / 1e3)) : 0);
|
|
515709
515825
|
const tokens = formatLiveTokenCount(metrics.estimatedOutputTokens);
|
|
515826
|
+
const startedAt = formatLiveStartedAt(startedAtMs);
|
|
515710
515827
|
return {
|
|
515711
|
-
full: `${this.activitySpinnerBaseLabel} (${elapsed} · ↓ ~${tokens} ${uiText("blunTui.activity.tokens")})`,
|
|
515712
|
-
compact: `${this.activitySpinnerBaseLabel} (${elapsed} · ↓${tokens})`,
|
|
515713
|
-
minimal: `(${elapsed} ·
|
|
515828
|
+
full: `${this.activitySpinnerBaseLabel} (${elapsed} · ↓ ~${tokens} ${uiText("blunTui.activity.tokens")} · ${startedAt})`,
|
|
515829
|
+
compact: `${this.activitySpinnerBaseLabel} (${elapsed} · ↓${tokens} · ${startedAt})`,
|
|
515830
|
+
minimal: `(${elapsed} · ${startedAt})`
|
|
515714
515831
|
};
|
|
515715
515832
|
}
|
|
515716
515833
|
ensureActivitySpinner(style, label = "", colorFn) {
|
|
@@ -516860,7 +516977,7 @@ async function appendRolloutDecisionLog(entry, filePath = getUpdateRolloutLogFil
|
|
|
516860
516977
|
* creates the telemetry device_id before telemetry can emit first_launch.
|
|
516861
516978
|
*/
|
|
516862
516979
|
function resolveUpdateDeviceId() {
|
|
516863
|
-
return readBlunDeviceId(resolveBlunHome$1()) ?? randomUUID();
|
|
516980
|
+
return readBlunDeviceId(resolveSharedBlunHome(resolveBlunHome$1())) ?? randomUUID();
|
|
516864
516981
|
}
|
|
516865
516982
|
/**
|
|
516866
516983
|
* The experimental master switch opts a device out of staged rollouts: the
|
package/package.json
CHANGED
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
"command": "npx",
|
|
12
12
|
"args": ["-y", "agent-browser@0.31.2", "mcp"],
|
|
13
13
|
"enabled": false,
|
|
14
|
-
"startupTimeoutMs":
|
|
15
|
-
"toolTimeoutMs":
|
|
14
|
+
"startupTimeoutMs": 120000,
|
|
15
|
+
"toolTimeoutMs": 120000
|
|
16
16
|
}
|
|
17
17
|
},
|
|
18
18
|
"blun-language-guard": {
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"inheritEnv": false,
|
|
29
29
|
"enabled": false,
|
|
30
30
|
"startupTimeoutMs": 20000,
|
|
31
|
-
"toolTimeoutMs":
|
|
31
|
+
"toolTimeoutMs": 120000
|
|
32
32
|
}
|
|
33
33
|
},
|
|
34
34
|
"telegram": {
|