blun-king-cli 9.1.58 → 9.1.61
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/core-bootstrap.js +3 -6
- package/bin/launcher-mode.js +2 -0
- package/bin/launcher-runtime.js +203 -30
- package/bin/profile-runtime.cjs +264 -0
- package/bin/rate-limit-recovery-policy.cjs +34 -0
- package/bin/running-update.cjs +389 -0
- package/bin/standard-tools-bootstrap.js +45 -4
- package/bin/subagent-timeout-policy.cjs +79 -0
- package/bin/update-notice.js +24 -0
- package/blun.mjs +296 -109
- package/package.json +1 -1
- package/standard-tools/manifest.json +3 -3
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();
|
|
@@ -503022,8 +503099,8 @@ registerUiCatalogFragment({
|
|
|
503022
503099
|
* 0 = most recent clipped lines visible
|
|
503023
503100
|
* N = N lines up from the bottom
|
|
503024
503101
|
*/
|
|
503025
|
-
/**
|
|
503026
|
-
const MAX_BUFFER_LINES =
|
|
503102
|
+
/** Retain the complete rendered session unless the process itself ends. */
|
|
503103
|
+
const MAX_BUFFER_LINES = Number.POSITIVE_INFINITY;
|
|
503027
503104
|
var ScrollbackBuffer = class {
|
|
503028
503105
|
lines = [];
|
|
503029
503106
|
scrollOffset = 0;
|
|
@@ -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) {
|
|
@@ -505177,6 +505264,7 @@ var SessionEventHandler = class {
|
|
|
505177
505264
|
this.goalCompletionTurnEnded = true;
|
|
505178
505265
|
this.scheduleQueuedGoalPromotion();
|
|
505179
505266
|
if (event.reason === "completed") this.host.refreshManagedQuotaWindows();
|
|
505267
|
+
requestRunningUpdateAtSafeBoundary(this.host);
|
|
505180
505268
|
}
|
|
505181
505269
|
handleStepBegin(event) {
|
|
505182
505270
|
this.host.commitQueuedSteerAtStepStart(event.turnId);
|
|
@@ -505224,12 +505312,13 @@ var SessionEventHandler = class {
|
|
|
505224
505312
|
const duration = durationMs === void 0 ? "—" : formatResponseDuration(durationMs);
|
|
505225
505313
|
const tokens = this.currentTurnTokenCount === void 0 ? "—" : formatTokenCount(this.currentTurnTokenCount);
|
|
505226
505314
|
const tokenKey = this.currentTurnTokenCount === 1 ? "thinking.tokens.one" : "thinking.tokens.other";
|
|
505315
|
+
const startedAt = formatLiveStartedAt(this.currentTurnStartedAtMs ?? Date.now());
|
|
505227
505316
|
this.host.appendTranscriptEntry({
|
|
505228
505317
|
id: nextTranscriptId(),
|
|
505229
505318
|
kind: "status",
|
|
505230
505319
|
turnId: String(event.turnId),
|
|
505231
505320
|
renderMode: "plain",
|
|
505232
|
-
content: `${duration} · ${uiText(tokenKey, { count: tokens })}`
|
|
505321
|
+
content: `${duration} · ${uiText(tokenKey, { count: tokens })} · ${startedAt}`
|
|
505233
505322
|
});
|
|
505234
505323
|
}
|
|
505235
505324
|
markActiveAgentSwarmsCancelled() {
|
|
@@ -505656,6 +505745,7 @@ var SessionEventHandler = class {
|
|
|
505656
505745
|
this.mcpServers.set(server.name, server);
|
|
505657
505746
|
const summary = formatMcpStartupStatusSummary([...this.mcpServers.values()]);
|
|
505658
505747
|
this.host.setAppState({ mcpServersSummary: summary || null });
|
|
505748
|
+
this.logMcpInventoryIfReady([...this.mcpServers.values()]);
|
|
505659
505749
|
switch (server.status) {
|
|
505660
505750
|
case "connected": {
|
|
505661
505751
|
const tools = uiText(server.toolCount === 1 ? "sessionEvent.mcp.tool.one" : "sessionEvent.mcp.tool.other", { count: server.toolCount });
|
|
@@ -505688,6 +505778,12 @@ var SessionEventHandler = class {
|
|
|
505688
505778
|
return;
|
|
505689
505779
|
}
|
|
505690
505780
|
}
|
|
505781
|
+
logMcpInventoryIfReady(servers) {
|
|
505782
|
+
if (this.mcpInventoryLogged || servers.some((server) => server.status === "pending")) return;
|
|
505783
|
+
const inventory = mcpStartupInventory(servers);
|
|
505784
|
+
log.info("mcp startup inventory", inventory);
|
|
505785
|
+
this.mcpInventoryLogged = true;
|
|
505786
|
+
}
|
|
505691
505787
|
showMcpServerStatusSpinner(name) {
|
|
505692
505788
|
const { state } = this.host;
|
|
505693
505789
|
const label = uiText("sessionEvent.mcp.connecting", { name });
|
|
@@ -506203,12 +506299,6 @@ function createReplayRenderContext() {
|
|
|
506203
506299
|
suppressNextPlanModeOffNotice: false
|
|
506204
506300
|
};
|
|
506205
506301
|
}
|
|
506206
|
-
function limitReplayRecordsByTurn(records, maxTurns) {
|
|
506207
|
-
if (maxTurns <= 0) return [];
|
|
506208
|
-
const turnStarts = records.flatMap((record, index) => isReplayUserTurnRecord(record) ? [index] : []);
|
|
506209
|
-
if (turnStarts.length <= maxTurns) return records;
|
|
506210
|
-
return records.slice(turnStarts[turnStarts.length - maxTurns]);
|
|
506211
|
-
}
|
|
506212
506302
|
function replayEntry(context, kind, content, renderMode, extras = {}) {
|
|
506213
506303
|
return {
|
|
506214
506304
|
id: nextTranscriptId(),
|
|
@@ -506536,7 +506626,7 @@ var SessionReplayRenderer = class {
|
|
|
506536
506626
|
}
|
|
506537
506627
|
renderRecords(agent) {
|
|
506538
506628
|
const context = createReplayRenderContext();
|
|
506539
|
-
for (const record of
|
|
506629
|
+
for (const record of agent.replay) this.renderRecord(context, record);
|
|
506540
506630
|
this.flushAssistant(context);
|
|
506541
506631
|
this.cleanupRuntime(context);
|
|
506542
506632
|
}
|
|
@@ -507601,7 +507691,7 @@ function formatGoalBadge(goal, colors, wallClockMs) {
|
|
|
507601
507691
|
const label = `${uiText(`goal.panel.status.${goal.status}`)} · ${formatBadgeElapsed(wallClockMs ?? goal.wallClockMs)} · ${turns}`;
|
|
507602
507692
|
return chalk.hex(colors.textMuted)(`[${uiText("footer.goal.label")} `) + chalk.hex(dotColor)("●") + chalk.hex(colors.textMuted)(` ${label}]`);
|
|
507603
507693
|
}
|
|
507604
|
-
function
|
|
507694
|
+
function formatLoopChatIndicator(loop, colors, nowMs = Date.now()) {
|
|
507605
507695
|
if (loop === null || loop === void 0 || loop.status !== "active" || !Number.isFinite(loop.nextFireAt)) return null;
|
|
507606
507696
|
const remainingMs = Math.max(0, loop.nextFireAt - nowMs);
|
|
507607
507697
|
let value;
|
|
@@ -507623,7 +507713,7 @@ function formatLoopBadge(loop, colors, nowMs = Date.now()) {
|
|
|
507623
507713
|
numeric: "always",
|
|
507624
507714
|
style: "short"
|
|
507625
507715
|
}).format(value, unit);
|
|
507626
|
-
return chalk.hex(colors.textMuted)(`
|
|
507716
|
+
return chalk.hex(colors.primary)("\u25cf") + chalk.hex(colors.textMuted)(` Loop ${relative}`);
|
|
507627
507717
|
}
|
|
507628
507718
|
function formatBadgeElapsed(ms) {
|
|
507629
507719
|
const totalSeconds = Math.round(ms / 1e3);
|
|
@@ -507794,6 +507884,41 @@ function formatFooterGitBadge(status, colors) {
|
|
|
507794
507884
|
if (status.pullRequest === null) return base;
|
|
507795
507885
|
return `${base} ${chalk.hex(colors.primary)(formatPullRequestBadge(status.pullRequest, { linkPullRequest: true }))}`;
|
|
507796
507886
|
}
|
|
507887
|
+
var LoopChatIndicatorComponent = class {
|
|
507888
|
+
state;
|
|
507889
|
+
onRefresh;
|
|
507890
|
+
loopTimer = null;
|
|
507891
|
+
constructor(state, onRefresh = () => {}) {
|
|
507892
|
+
this.state = state;
|
|
507893
|
+
this.onRefresh = onRefresh;
|
|
507894
|
+
this.syncTimer(state.loop);
|
|
507895
|
+
}
|
|
507896
|
+
setState(state) {
|
|
507897
|
+
this.state = state;
|
|
507898
|
+
this.syncTimer(state.loop);
|
|
507899
|
+
}
|
|
507900
|
+
render(width) {
|
|
507901
|
+
const indicator = formatLoopChatIndicator(this.state.loop, currentTheme.palette);
|
|
507902
|
+
return indicator === null ? [] : [truncateToWidth(indicator, width, "…")];
|
|
507903
|
+
}
|
|
507904
|
+
syncTimer(loop) {
|
|
507905
|
+
if (loop?.status === "active" && Number.isFinite(loop.nextFireAt)) {
|
|
507906
|
+
if (this.loopTimer !== null) return;
|
|
507907
|
+
this.loopTimer = setInterval(() => {
|
|
507908
|
+
this.onRefresh();
|
|
507909
|
+
}, GOAL_TIMER_INTERVAL_MS);
|
|
507910
|
+
this.loopTimer.unref?.();
|
|
507911
|
+
return;
|
|
507912
|
+
}
|
|
507913
|
+
this.dispose();
|
|
507914
|
+
}
|
|
507915
|
+
dispose() {
|
|
507916
|
+
if (this.loopTimer !== null) {
|
|
507917
|
+
clearInterval(this.loopTimer);
|
|
507918
|
+
this.loopTimer = null;
|
|
507919
|
+
}
|
|
507920
|
+
}
|
|
507921
|
+
};
|
|
507797
507922
|
var FooterComponent = class {
|
|
507798
507923
|
state;
|
|
507799
507924
|
onRefresh;
|
|
@@ -507803,7 +507928,6 @@ var FooterComponent = class {
|
|
|
507803
507928
|
goalSnapshotKey = null;
|
|
507804
507929
|
goalObservedAtMs = Date.now();
|
|
507805
507930
|
goalTimer = null;
|
|
507806
|
-
loopTimer = null;
|
|
507807
507931
|
compactionAttemptStartedAtMs = null;
|
|
507808
507932
|
compactionEstimatedInputTokens;
|
|
507809
507933
|
compactionProgress;
|
|
@@ -507825,7 +507949,6 @@ var FooterComponent = class {
|
|
|
507825
507949
|
this.gitCache = createGitStatusCache(state.workDir, { onChange: this.onRefresh });
|
|
507826
507950
|
this.syncGoalClock(state.goal);
|
|
507827
507951
|
this.syncGoalTimer(state.goal);
|
|
507828
|
-
this.syncLoopTimer(state.loop);
|
|
507829
507952
|
if (state.isCompacting) this.startCompaction();
|
|
507830
507953
|
}
|
|
507831
507954
|
setState(state) {
|
|
@@ -507835,7 +507958,6 @@ var FooterComponent = class {
|
|
|
507835
507958
|
}
|
|
507836
507959
|
this.syncGoalClock(state.goal);
|
|
507837
507960
|
this.syncGoalTimer(state.goal);
|
|
507838
|
-
this.syncLoopTimer(state.loop);
|
|
507839
507961
|
this.state = state;
|
|
507840
507962
|
if (state.isCompacting && this.compactionAttemptStartedAtMs === null) this.startCompaction();
|
|
507841
507963
|
else if (!state.isCompacting) this.finishCompaction();
|
|
@@ -507902,13 +508024,13 @@ var FooterComponent = class {
|
|
|
507902
508024
|
if (modes.length > 0) left.push(modes.join(" "));
|
|
507903
508025
|
const goalBadge = formatGoalBadge(state.goal, colors, this.goalWallClockMs(state.goal));
|
|
507904
508026
|
if (goalBadge !== null) left.push(goalBadge);
|
|
507905
|
-
const loopBadge = formatLoopBadge(state.loop, colors);
|
|
507906
|
-
if (loopBadge !== null) left.push(loopBadge);
|
|
507907
508027
|
const model = modelDisplayName(state);
|
|
507908
508028
|
if (model) {
|
|
507909
508029
|
const sep = chalk.hex(colors.textMuted)(" · ");
|
|
507910
508030
|
const persona = personaName();
|
|
507911
|
-
const
|
|
508031
|
+
const profileLabel = process.env["BLUN_PROFILE"] ?? "default";
|
|
508032
|
+
const profile = chalk.hex(colors.textDim)(profileLabel) + sep;
|
|
508033
|
+
const brand = chalk.hex(colors.primary).bold("● BL") + chalk.hex(colors.text)("UN") + sep + profile + (persona !== void 0 ? chalk.hex(colors.text)(persona) + sep : "");
|
|
507912
508034
|
const modelLabel = model;
|
|
507913
508035
|
const modeLabel = uiText(state.permissionMode === "yolo" ? "footer.mode.god" : state.permissionMode === "auto" ? "footer.mode.auto" : "footer.mode.manual");
|
|
507914
508036
|
const modeSuffix = sep + chalk.hex(colors.textDim)(uiText("footer.mode", { mode: modeLabel }));
|
|
@@ -507968,29 +508090,11 @@ var FooterComponent = class {
|
|
|
507968
508090
|
this.goalTimer = null;
|
|
507969
508091
|
}
|
|
507970
508092
|
}
|
|
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
508093
|
dispose() {
|
|
507986
508094
|
if (this.goalTimer !== null) {
|
|
507987
508095
|
clearInterval(this.goalTimer);
|
|
507988
508096
|
this.goalTimer = null;
|
|
507989
508097
|
}
|
|
507990
|
-
if (this.loopTimer !== null) {
|
|
507991
|
-
clearInterval(this.loopTimer);
|
|
507992
|
-
this.loopTimer = null;
|
|
507993
|
-
}
|
|
507994
508098
|
this.finishCompaction();
|
|
507995
508099
|
}
|
|
507996
508100
|
goalWallClockMs(goal) {
|
|
@@ -511288,12 +511392,18 @@ var CustomEditor = class extends Editor {
|
|
|
511288
511392
|
if (isKeyRelease(normalized)) return;
|
|
511289
511393
|
if (!matchesKey(normalized, Key.escape)) this.onNonEscapeInput?.();
|
|
511290
511394
|
if (this.consumingPaste) {
|
|
511291
|
-
|
|
511292
|
-
if (
|
|
511395
|
+
const cancelPaste = matchesKey(normalized, Key.ctrl("c")) || matchesKey(normalized, Key.escape);
|
|
511396
|
+
if (cancelPaste) {
|
|
511293
511397
|
this.consumingPaste = false;
|
|
511294
511398
|
this.consumeBuffer = "";
|
|
511399
|
+
} else {
|
|
511400
|
+
this.consumeBuffer += normalized;
|
|
511401
|
+
if (this.consumeBuffer.includes(BRACKET_PASTE_END)) {
|
|
511402
|
+
this.consumingPaste = false;
|
|
511403
|
+
this.consumeBuffer = "";
|
|
511404
|
+
}
|
|
511405
|
+
return;
|
|
511295
511406
|
}
|
|
511296
|
-
return;
|
|
511297
511407
|
}
|
|
511298
511408
|
if (normalized.includes(BRACKET_PASTE_START) && this.expandPasteMarkerAtCursor()) {
|
|
511299
511409
|
if (!normalized.includes(BRACKET_PASTE_END)) this.consumingPaste = true;
|
|
@@ -511348,6 +511458,7 @@ var CustomEditor = class extends Editor {
|
|
|
511348
511458
|
if (this.inputMode === "bash" && this.getText().length === 0 && (matchesKey(normalized, Key.escape) || matchesKey(normalized, Key.backspace))) {
|
|
511349
511459
|
this.inputMode = "prompt";
|
|
511350
511460
|
this.onInputModeChange?.("prompt");
|
|
511461
|
+
if (matchesKey(normalized, Key.escape)) this.onEscape?.(true);
|
|
511351
511462
|
return;
|
|
511352
511463
|
}
|
|
511353
511464
|
if (matchesKey(normalized, Key.up)) {
|
|
@@ -511361,11 +511472,9 @@ var CustomEditor = class extends Editor {
|
|
|
511361
511472
|
}
|
|
511362
511473
|
}
|
|
511363
511474
|
if (matchesKey(normalized, Key.escape)) {
|
|
511364
|
-
|
|
511365
|
-
|
|
511366
|
-
|
|
511367
|
-
}
|
|
511368
|
-
this.onEscape?.();
|
|
511475
|
+
const autocompleteCancelled = this.hasAutocompleteActivity();
|
|
511476
|
+
if (autocompleteCancelled) this.cancelAutocompleteActivity();
|
|
511477
|
+
this.onEscape?.(autocompleteCancelled);
|
|
511369
511478
|
return;
|
|
511370
511479
|
}
|
|
511371
511480
|
if (matchesKey(normalized, Key.tab) && !this.isShowingAutocomplete()) return;
|
|
@@ -511636,6 +511745,7 @@ var GhostSuggestEditor = class extends CustomEditor {
|
|
|
511636
511745
|
if (matchesKey(normalized, Key.escape)) {
|
|
511637
511746
|
this.ghostSuggestion = null;
|
|
511638
511747
|
this.onGhostDismissed?.();
|
|
511748
|
+
this.onEscape?.(true);
|
|
511639
511749
|
return;
|
|
511640
511750
|
}
|
|
511641
511751
|
if (this.isCursorAtTextEnd() && !this.isShowingAutocomplete()) {
|
|
@@ -511736,6 +511846,11 @@ function createTUIState(options) {
|
|
|
511736
511846
|
const quotaWarningContainer = new GutterContainer(1, 1);
|
|
511737
511847
|
const quotaWarning = new ManagedQuotaWarningComponent();
|
|
511738
511848
|
quotaWarningContainer.addChild(quotaWarning);
|
|
511849
|
+
const loopIndicatorContainer = new GutterContainer(1, 1);
|
|
511850
|
+
const loopIndicator = new LoopChatIndicatorComponent(appState, () => {
|
|
511851
|
+
ui.requestRender();
|
|
511852
|
+
});
|
|
511853
|
+
loopIndicatorContainer.addChild(loopIndicator);
|
|
511739
511854
|
return {
|
|
511740
511855
|
ui,
|
|
511741
511856
|
terminal,
|
|
@@ -511747,6 +511862,8 @@ function createTUIState(options) {
|
|
|
511747
511862
|
btwPanelContainer,
|
|
511748
511863
|
quotaWarningContainer,
|
|
511749
511864
|
quotaWarning,
|
|
511865
|
+
loopIndicatorContainer,
|
|
511866
|
+
loopIndicator,
|
|
511750
511867
|
editorContainer: new GutterContainer(1, 1),
|
|
511751
511868
|
editor: new GhostSuggestEditor(ui, {
|
|
511752
511869
|
disablePasteBurst: initialAppState.disablePasteBurst ?? DEFAULT_TUI_CONFIG.disablePasteBurst,
|
|
@@ -512936,13 +513053,13 @@ function readEnvInt(name, fallback) {
|
|
|
512936
513053
|
return value;
|
|
512937
513054
|
}
|
|
512938
513055
|
/** Keep the most recent N turns. `0` disables trimming. */
|
|
512939
|
-
const TRANSCRIPT_MAX_TURNS = readEnvInt("BLUN_TUI_MAX_TURNS",
|
|
513056
|
+
const TRANSCRIPT_MAX_TURNS = readEnvInt("BLUN_TUI_MAX_TURNS", 0);
|
|
512940
513057
|
/** Only the most recent E turns are allowed to expand (Ctrl+O). `0` disables expanding. */
|
|
512941
513058
|
const TRANSCRIPT_EXPAND_TURNS = readEnvInt("BLUN_TUI_EXPAND_TURNS", 3);
|
|
512942
513059
|
/** Only trim once the window exceeds maxTurns by this much (avoids churn). */
|
|
512943
513060
|
const TRANSCRIPT_HYSTERESIS = readEnvInt("BLUN_TUI_HYSTERESIS", 5);
|
|
512944
513061
|
/** Keep this many recent steps untouched inside a turn; older steps are merged into a summary. `0` disables merging. */
|
|
512945
|
-
const TRANSCRIPT_KEEP_RECENT_STEPS = readEnvInt("BLUN_TUI_KEEP_RECENT_STEPS",
|
|
513062
|
+
const TRANSCRIPT_KEEP_RECENT_STEPS = readEnvInt("BLUN_TUI_KEEP_RECENT_STEPS", 0);
|
|
512946
513063
|
/**
|
|
512947
513064
|
* Group consecutive entries into turns by `turnId`. Entries with the same
|
|
512948
513065
|
* non-undefined `turnId` that are adjacent belong to the same turn.
|
|
@@ -512999,6 +513116,60 @@ function turnsToTrim(turns, maxTurns, hysteresis) {
|
|
|
512999
513116
|
}
|
|
513000
513117
|
//#endregion
|
|
513001
513118
|
//#region src/tui/blun-tui.ts
|
|
513119
|
+
const {
|
|
513120
|
+
RUNNING_UPDATE_HANDOFF_EXIT_CODE,
|
|
513121
|
+
RUNNING_UPDATE_HANDOFF_MESSAGE,
|
|
513122
|
+
RUNNING_UPDATE_PREPARED_MESSAGE,
|
|
513123
|
+
RUNTIME_READY_MESSAGE,
|
|
513124
|
+
isSafeRuntimeBoundary
|
|
513125
|
+
} = __require("./bin/running-update.cjs");
|
|
513126
|
+
function notifyRunningRuntimeReady(tui) {
|
|
513127
|
+
if (!process.connected) return;
|
|
513128
|
+
const sessionId = tui.getCurrentSessionId();
|
|
513129
|
+
if (sessionId.length === 0) return;
|
|
513130
|
+
try {
|
|
513131
|
+
process.send({
|
|
513132
|
+
type: RUNTIME_READY_MESSAGE,
|
|
513133
|
+
sessionId
|
|
513134
|
+
});
|
|
513135
|
+
} catch {}
|
|
513136
|
+
}
|
|
513137
|
+
function requestRunningUpdateAtSafeBoundary(tui) {
|
|
513138
|
+
if (tui.runningUpdatePreparedVersion === void 0 || tui.runningUpdateHandoffStarted || !process.connected) return false;
|
|
513139
|
+
if (!isSafeRuntimeBoundary({
|
|
513140
|
+
isShuttingDown: tui.isShuttingDown,
|
|
513141
|
+
streamingPhase: tui.state.appState.streamingPhase,
|
|
513142
|
+
isCompacting: tui.state.appState.isCompacting,
|
|
513143
|
+
queuedMessages: tui.state.queuedMessages.length,
|
|
513144
|
+
activeToolCalls: tui.streamingUI.hasActiveToolCalls() ? 1 : 0,
|
|
513145
|
+
shellCommands: tui.shellOutputStreams.size,
|
|
513146
|
+
queueCommandRunning: tui.queueCommandRunning
|
|
513147
|
+
})) return false;
|
|
513148
|
+
const sessionId = tui.getCurrentSessionId();
|
|
513149
|
+
if (sessionId.length === 0) return false;
|
|
513150
|
+
tui.runningUpdateHandoffStarted = true;
|
|
513151
|
+
try {
|
|
513152
|
+
process.send({
|
|
513153
|
+
type: RUNNING_UPDATE_HANDOFF_MESSAGE,
|
|
513154
|
+
sessionId,
|
|
513155
|
+
version: tui.runningUpdatePreparedVersion
|
|
513156
|
+
});
|
|
513157
|
+
} catch {
|
|
513158
|
+
tui.runningUpdateHandoffStarted = false;
|
|
513159
|
+
return false;
|
|
513160
|
+
}
|
|
513161
|
+
void tui.stop(RUNNING_UPDATE_HANDOFF_EXIT_CODE);
|
|
513162
|
+
return true;
|
|
513163
|
+
}
|
|
513164
|
+
function installRunningUpdateListener(tui) {
|
|
513165
|
+
const handler = (message) => {
|
|
513166
|
+
if (message?.type !== RUNNING_UPDATE_PREPARED_MESSAGE || typeof message.version !== "string") return;
|
|
513167
|
+
tui.runningUpdatePreparedVersion = message.version;
|
|
513168
|
+
requestRunningUpdateAtSafeBoundary(tui);
|
|
513169
|
+
};
|
|
513170
|
+
process.on("message", handler);
|
|
513171
|
+
return () => process.off("message", handler);
|
|
513172
|
+
}
|
|
513002
513173
|
function loadingTipKind(mode) {
|
|
513003
513174
|
if (mode === "waiting" || mode === "tool") return "blun";
|
|
513004
513175
|
if (mode === "composing") return "composing";
|
|
@@ -513083,6 +513254,9 @@ var BlunTUI = class {
|
|
|
513083
513254
|
startupLoginRequired = false;
|
|
513084
513255
|
startupWorkspaceSelectionPending = false;
|
|
513085
513256
|
startupGoalPromptedSessionId;
|
|
513257
|
+
runningUpdatePreparedVersion;
|
|
513258
|
+
runningUpdateHandoffStarted = false;
|
|
513259
|
+
runningUpdateMessageDispose;
|
|
513086
513260
|
startupPhaseMs = {};
|
|
513087
513261
|
lastActivityMode;
|
|
513088
513262
|
currentLoadingTip = void 0;
|
|
@@ -513196,6 +513370,7 @@ var BlunTUI = class {
|
|
|
513196
513370
|
this.editorKeyboard.install();
|
|
513197
513371
|
this.scrollbackController = new ScrollbackController(this.state);
|
|
513198
513372
|
this.scrollbackController.install();
|
|
513373
|
+
this.runningUpdateMessageDispose = installRunningUpdateListener(this);
|
|
513199
513374
|
this.buildLayout();
|
|
513200
513375
|
}
|
|
513201
513376
|
getSlashCommands() {
|
|
@@ -513624,7 +513799,10 @@ var BlunTUI = class {
|
|
|
513624
513799
|
this.editorKeyboard.dispose();
|
|
513625
513800
|
this.scrollbackController.dispose();
|
|
513626
513801
|
this.managedQuotaWarningController.dispose();
|
|
513802
|
+
this.state.loopIndicator.dispose();
|
|
513627
513803
|
this.state.footer.dispose();
|
|
513804
|
+
this.runningUpdateMessageDispose?.();
|
|
513805
|
+
this.runningUpdateMessageDispose = void 0;
|
|
513628
513806
|
for (const dispose of this.reverseRpcDisposers) dispose();
|
|
513629
513807
|
this.reverseRpcDisposers.length = 0;
|
|
513630
513808
|
this.disposeTerminalTracking();
|
|
@@ -513704,6 +513882,7 @@ var BlunTUI = class {
|
|
|
513704
513882
|
ui.addChild(this.state.queueContainer);
|
|
513705
513883
|
ui.addChild(this.state.quotaWarningContainer);
|
|
513706
513884
|
ui.addChild(this.state.btwPanelContainer);
|
|
513885
|
+
ui.addChild(this.state.loopIndicatorContainer);
|
|
513707
513886
|
ui.addChild(this.state.editorContainer);
|
|
513708
513887
|
if (ui instanceof BottomPinnedTUI) {
|
|
513709
513888
|
ui.pinFromIndex = ui.children.indexOf(this.state.quotaWarningContainer);
|
|
@@ -513847,6 +514026,7 @@ var BlunTUI = class {
|
|
|
513847
514026
|
if (this.shellOutputStreams.size === 0) {
|
|
513848
514027
|
this.setAppState({ streamingPhase: "idle" });
|
|
513849
514028
|
this.drainOneQueuedMessage();
|
|
514029
|
+
requestRunningUpdateAtSafeBoundary(this);
|
|
513850
514030
|
}
|
|
513851
514031
|
}
|
|
513852
514032
|
queueDrainTimer;
|
|
@@ -514686,6 +514866,7 @@ var BlunTUI = class {
|
|
|
514686
514866
|
const busyChanged = "streamingPhase" in effectivePatch || "isCompacting" in effectivePatch;
|
|
514687
514867
|
Object.assign(this.state.appState, effectivePatch);
|
|
514688
514868
|
if ("planMode" in effectivePatch) this.updateEditorBorderHighlight();
|
|
514869
|
+
this.state.loopIndicator.setState(this.state.appState);
|
|
514689
514870
|
this.state.footer.setState(this.state.appState);
|
|
514690
514871
|
if ("sessionId" in effectivePatch || "managedQuotaWindows" in effectivePatch) this.managedQuotaWarningController.update(this.state.appState.sessionId, this.state.appState.managedQuotaWindows);
|
|
514691
514872
|
this.updateActivityPane();
|
|
@@ -515707,10 +515888,11 @@ var BlunTUI = class {
|
|
|
515707
515888
|
const startedAtMs = metrics.startedAtMs ?? this.activitySpinnerFallbackStartMs;
|
|
515708
515889
|
const elapsed = formatLiveElapsed(startedAtMs > 0 ? Math.max(0, Math.floor((Date.now() - startedAtMs) / 1e3)) : 0);
|
|
515709
515890
|
const tokens = formatLiveTokenCount(metrics.estimatedOutputTokens);
|
|
515891
|
+
const startedAt = formatLiveStartedAt(startedAtMs);
|
|
515710
515892
|
return {
|
|
515711
|
-
full: `${this.activitySpinnerBaseLabel} (${elapsed} · ↓ ~${tokens} ${uiText("blunTui.activity.tokens")})`,
|
|
515712
|
-
compact: `${this.activitySpinnerBaseLabel} (${elapsed} · ↓${tokens})`,
|
|
515713
|
-
minimal: `(${elapsed} ·
|
|
515893
|
+
full: `${this.activitySpinnerBaseLabel} (${elapsed} · ↓ ~${tokens} ${uiText("blunTui.activity.tokens")} · ${startedAt})`,
|
|
515894
|
+
compact: `${this.activitySpinnerBaseLabel} (${elapsed} · ↓${tokens} · ${startedAt})`,
|
|
515895
|
+
minimal: `(${elapsed} · ${startedAt})`
|
|
515714
515896
|
};
|
|
515715
515897
|
}
|
|
515716
515898
|
ensureActivitySpinner(style, label = "", colorFn) {
|
|
@@ -516292,22 +516474,27 @@ async function runShell(opts, version, updateStartupNotice) {
|
|
|
516292
516474
|
tui.onExit = async (exitCode = 0) => {
|
|
516293
516475
|
const sessionId = tui.getCurrentSessionId();
|
|
516294
516476
|
const hasContent = tui.hasSessionContent();
|
|
516477
|
+
const runningUpdateHandoff = exitCode === RUNNING_UPDATE_HANDOFF_EXIT_CODE;
|
|
516295
516478
|
setCrashPhase("shutdown");
|
|
516296
516479
|
trackLifecycle("exit", { duration_ms: Date.now() - startedAt });
|
|
516297
516480
|
await shutdownTelemetry({ timeoutMs: CLI_SHUTDOWN_TIMEOUT_MS });
|
|
516298
516481
|
const gutter = " ".repeat(1);
|
|
516299
|
-
process.stdout.write(`${gutter}Bye!\n`);
|
|
516482
|
+
if (!runningUpdateHandoff) process.stdout.write(`${gutter}Bye!\n`);
|
|
516300
516483
|
const hints = [];
|
|
516301
|
-
if (sessionId !== "" && hasContent) hints.push(`${gutter}To resume this session: blun -r ${sessionId}`);
|
|
516302
|
-
if (tui.exitOpenUrl !== void 0) hints.push(`${gutter}open ${toTerminalHyperlink$1(tui.exitOpenUrl, tui.exitOpenUrl)}`);
|
|
516484
|
+
if (!runningUpdateHandoff && sessionId !== "" && hasContent) hints.push(`${gutter}To resume this session: blun -r ${sessionId}`);
|
|
516485
|
+
if (!runningUpdateHandoff && tui.exitOpenUrl !== void 0) hints.push(`${gutter}open ${toTerminalHyperlink$1(tui.exitOpenUrl, tui.exitOpenUrl)}`);
|
|
516303
516486
|
if (hints.length > 0) process.stderr.write(`\n${hints.join("\n")}\n`);
|
|
516304
516487
|
removeCrashHandlers();
|
|
516305
516488
|
restoreStty();
|
|
516489
|
+
if (process.connected) try {
|
|
516490
|
+
process.disconnect();
|
|
516491
|
+
} catch {}
|
|
516306
516492
|
process.exit(exitCode);
|
|
516307
516493
|
};
|
|
516308
516494
|
try {
|
|
516309
516495
|
const initStartedAt = Date.now();
|
|
516310
516496
|
await tui.start();
|
|
516497
|
+
notifyRunningRuntimeReady(tui);
|
|
516311
516498
|
const initMs = Date.now() - initStartedAt;
|
|
516312
516499
|
const startupSessionId = tui.getCurrentSessionId();
|
|
516313
516500
|
const mcpMs = await tui.getStartupMcpMs();
|
|
@@ -516860,7 +517047,7 @@ async function appendRolloutDecisionLog(entry, filePath = getUpdateRolloutLogFil
|
|
|
516860
517047
|
* creates the telemetry device_id before telemetry can emit first_launch.
|
|
516861
517048
|
*/
|
|
516862
517049
|
function resolveUpdateDeviceId() {
|
|
516863
|
-
return readBlunDeviceId(resolveBlunHome$1()) ?? randomUUID();
|
|
517050
|
+
return readBlunDeviceId(resolveSharedBlunHome(resolveBlunHome$1())) ?? randomUUID();
|
|
516864
517051
|
}
|
|
516865
517052
|
/**
|
|
516866
517053
|
* The experimental master switch opts a device out of staged rollouts: the
|