blun-king-cli 9.1.33 → 9.1.34

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.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/blun.mjs +960 -474
  3. package/package.json +1 -1
package/blun.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- // BLUN_BUILD_INPUT_SHA256:cf0e1b17c2ea28e2e1baaa88f43c3a551cddc9c62e205a307c513cfd60c19a5a
2
+ // BLUN_BUILD_INPUT_SHA256:ec5eb56eb8addc29b2ddab22ca4987f2156e16b88a8f468ce3248d312afa717f
3
3
  import { fileURLToPath as __cjsShimFileURLToPath } from 'node:url';
4
4
  import { dirname as __cjsShimDirname } from 'node:path';
5
5
  const __filename = __cjsShimFileURLToPath(import.meta.url);
@@ -569,6 +569,7 @@ var init_codes = __esmMin((() => {
569
569
  GOAL_NOT_RESUMABLE: "goal.not_resumable",
570
570
  MODEL_NOT_CONFIGURED: "model.not_configured",
571
571
  MODEL_CONFIG_INVALID: "model.config_invalid",
572
+ MODEL_EMPTY_RESPONSE: "model.empty_response",
572
573
  AUTH_LOGIN_REQUIRED: "auth.login_required",
573
574
  CONTEXT_OVERFLOW: "context.overflow",
574
575
  LOOP_ALREADY_EXISTS: "loop.already_exists",
@@ -613,6 +614,12 @@ var init_codes = __esmMin((() => {
613
614
  public: true,
614
615
  action: "Check config.toml and provider/model settings."
615
616
  },
617
+ "model.empty_response": {
618
+ title: "Model returned no content",
619
+ retryable: true,
620
+ public: true,
621
+ action: "Send the message again."
622
+ },
616
623
  "session.not_found": {
617
624
  title: "Session not found",
618
625
  retryable: false,
@@ -1663,14 +1670,35 @@ var init_errors$10 = __esmMin((() => {
1663
1670
  this.retryAfterMs = typeof metadata.retryAfterMs === "number" && Number.isFinite(metadata.retryAfterMs) && metadata.retryAfterMs >= 0 ? metadata.retryAfterMs : null;
1664
1671
  }
1665
1672
  };
1666
- APIEmptyResponseError = class extends ChatProviderError {
1673
+ APIEmptyResponseError = class APIEmptyResponseError extends ChatProviderError {
1667
1674
  finishReason;
1668
1675
  rawFinishReason;
1676
+ emptyResponseKind;
1677
+ completionTokens;
1678
+ reasoningLength;
1679
+ maxCompletionTokens;
1680
+ attempts;
1669
1681
  constructor(message, options = {}) {
1670
1682
  super(message);
1671
1683
  this.name = "APIEmptyResponseError";
1672
1684
  this.finishReason = options.finishReason ?? null;
1673
1685
  this.rawFinishReason = options.rawFinishReason ?? null;
1686
+ this.emptyResponseKind = options.emptyResponseKind ?? "other";
1687
+ this.completionTokens = options.completionTokens ?? null;
1688
+ this.reasoningLength = options.reasoningLength ?? 0;
1689
+ this.maxCompletionTokens = options.maxCompletionTokens ?? null;
1690
+ this.attempts = options.attempts ?? 1;
1691
+ }
1692
+ withMetadata(options) {
1693
+ return new APIEmptyResponseError(this.message, {
1694
+ finishReason: this.finishReason,
1695
+ rawFinishReason: this.rawFinishReason,
1696
+ emptyResponseKind: this.emptyResponseKind,
1697
+ completionTokens: this.completionTokens,
1698
+ reasoningLength: this.reasoningLength,
1699
+ maxCompletionTokens: options.maxCompletionTokens ?? this.maxCompletionTokens,
1700
+ attempts: options.attempts ?? this.attempts
1701
+ });
1674
1702
  }
1675
1703
  };
1676
1704
  CompactionStallError$1 = class extends ChatProviderError {
@@ -2996,14 +3024,19 @@ async function generate(provider, systemPrompt, tools, history, callbacks, optio
2996
3024
  if (pendingPart !== null) flushPart(message, pendingPart, toolCallIndexMap);
2997
3025
  if (message.content.length === 0 && message.toolCalls.length === 0) throw new APIEmptyResponseError("The API returned an empty response (no content, no tool calls)." + formatFinishReasonHint(stream) + ` Provider: ${provider.name}, model: ${provider.modelName}`, {
2998
3026
  finishReason: stream.finishReason,
2999
- rawFinishReason: stream.rawFinishReason
3027
+ rawFinishReason: stream.rawFinishReason,
3028
+ emptyResponseKind: classifyEmptyResponse(stream),
3029
+ completionTokens: stream.usage?.output ?? null
3000
3030
  });
3001
3031
  const hasThink = message.content.some((p) => p.type === "think");
3002
3032
  const hasText = message.content.some((p) => p.type === "text" && p.text.trim().length > 0);
3003
3033
  const hasToolCalls = message.toolCalls.length > 0;
3004
3034
  if (hasThink && !hasText && !hasToolCalls) throw new APIEmptyResponseError("The API returned a response containing only thinking content without any text or tool calls. This usually indicates the stream was interrupted or the output token budget was exhausted during reasoning." + formatFinishReasonHint(stream) + ` Provider: ${provider.name}, model: ${provider.modelName}`, {
3005
3035
  finishReason: stream.finishReason,
3006
- rawFinishReason: stream.rawFinishReason
3036
+ rawFinishReason: stream.rawFinishReason,
3037
+ emptyResponseKind: classifyEmptyResponse(stream),
3038
+ completionTokens: stream.usage?.output ?? null,
3039
+ reasoningLength: message.content.reduce((total, part) => total + (part.type === "think" ? part.think.length : 0), 0)
3007
3040
  });
3008
3041
  if (callbacks?.onToolCall !== void 0) for (const toolCall of message.toolCalls) {
3009
3042
  await throwIfAborted$2(signal, stream);
@@ -3022,6 +3055,14 @@ async function generate(provider, systemPrompt, tools, history, callbacks, optio
3022
3055
  signal?.removeEventListener("abort", abortListener);
3023
3056
  }
3024
3057
  }
3058
+ function classifyEmptyResponse(stream) {
3059
+ if (stream.finishReason === "truncated") return "length";
3060
+ if (stream.finishReason === "completed") return "stop";
3061
+ const raw = stream.rawFinishReason?.toLowerCase() ?? "";
3062
+ if (raw === "length" || raw.includes("max_token") || raw.includes("max_output")) return "length";
3063
+ if (raw === "stop" || raw === "completed") return "stop";
3064
+ return "other";
3065
+ }
3025
3066
  function throwAbortError() {
3026
3067
  throw new DOMException("The operation was aborted.", "AbortError");
3027
3068
  }
@@ -3255,16 +3296,24 @@ function toBlunErrorPayload(error) {
3255
3296
  name: error.name,
3256
3297
  retryable: BLUN_ERROR_INFO[ErrorCodes.PROVIDER_CONNECTION_ERROR].retryable
3257
3298
  };
3258
- if (error instanceof APIEmptyResponseError) return {
3259
- code: ErrorCodes.PROVIDER_API_ERROR,
3260
- message: error.message,
3261
- name: error.name,
3262
- details: {
3263
- finishReason: error.finishReason,
3264
- rawFinishReason: error.rawFinishReason
3265
- },
3266
- retryable: BLUN_ERROR_INFO[ErrorCodes.PROVIDER_API_ERROR].retryable
3267
- };
3299
+ if (error instanceof APIEmptyResponseError) {
3300
+ const isRepeatedStop = error.emptyResponseKind === "stop" && error.attempts >= 2;
3301
+ const code = isRepeatedStop ? ErrorCodes.MODEL_EMPTY_RESPONSE : ErrorCodes.PROVIDER_API_ERROR;
3302
+ return {
3303
+ code,
3304
+ message: isRepeatedStop ? "King ended twice without producing content. Please send the message again." : error.message,
3305
+ name: error.name,
3306
+ details: {
3307
+ finishReason: error.finishReason,
3308
+ rawFinishReason: error.rawFinishReason,
3309
+ emptyResponseKind: error.emptyResponseKind,
3310
+ attempts: error.attempts,
3311
+ completionTokens: error.completionTokens,
3312
+ maxCompletionTokens: error.maxCompletionTokens
3313
+ },
3314
+ retryable: BLUN_ERROR_INFO[code].retryable
3315
+ };
3316
+ }
3268
3317
  if (error instanceof ChatProviderError) return {
3269
3318
  code: ErrorCodes.PROVIDER_API_ERROR,
3270
3319
  message: error.message,
@@ -9923,6 +9972,7 @@ var init_schema = __esmMin((() => {
9923
9972
  ]);
9924
9973
  ActionStyleSchema = _enum([
9925
9974
  "default",
9975
+ "concise",
9926
9976
  "proactive",
9927
9977
  "explanatory",
9928
9978
  "learning"
@@ -30052,9 +30102,38 @@ async function chatWithRetry(input) {
30052
30102
  }
30053
30103
  }
30054
30104
  const delays = retryBackoffDelays(maxAttempts);
30105
+ let completionBudgetRetry;
30106
+ let emptyRetryStarted = false;
30055
30107
  for (let attempt = 1;; attempt += 1) try {
30056
- return await input.llm.chat(paramsForAttempt(input, attempt, maxAttempts));
30108
+ return await input.llm.chat(paramsForAttempt(input, attempt, maxAttempts, completionBudgetRetry));
30057
30109
  } catch (error) {
30110
+ if (error instanceof APIEmptyResponseError && (emptyRetryStarted || error.emptyResponseKind === "length" || error.emptyResponseKind === "stop")) {
30111
+ const emptyAttemptLimit = Math.min(maxAttempts, 2);
30112
+ logEmptyResponse(input, error, attempt);
30113
+ if (attempt >= emptyAttemptLimit) {
30114
+ const terminal = error.withMetadata({ attempts: attempt });
30115
+ logRequestFailure(input, terminal, attempt, emptyAttemptLimit);
30116
+ throw terminal;
30117
+ }
30118
+ completionBudgetRetry = error.emptyResponseKind === "length" ? {
30119
+ minimumCompletionTokens: 1024,
30120
+ multiplier: 2
30121
+ } : void 0;
30122
+ emptyRetryStarted = true;
30123
+ input.params.signal.throwIfAborted();
30124
+ input.dispatchEvent({
30125
+ type: "step.retrying",
30126
+ turnId: input.turnId,
30127
+ step: input.currentStep,
30128
+ stepUuid: input.stepUuid,
30129
+ failedAttempt: attempt,
30130
+ nextAttempt: attempt + 1,
30131
+ maxAttempts: emptyAttemptLimit,
30132
+ delayMs: 0,
30133
+ ...retryErrorFields(error)
30134
+ });
30135
+ continue;
30136
+ }
30058
30137
  if (attempt >= maxAttempts || !input.llm.isRetryableError(error)) {
30059
30138
  logRequestFailure(input, error, attempt, maxAttempts);
30060
30139
  throw error;
@@ -30084,16 +30163,26 @@ function logRequestFailure(input, error, attempt, maxAttempts) {
30084
30163
  ...retryErrorFields(error)
30085
30164
  });
30086
30165
  }
30087
- function paramsForAttempt(input, attempt, maxAttempts) {
30166
+ function paramsForAttempt(input, attempt, maxAttempts, completionBudgetRetry) {
30088
30167
  const turnStep = `${input.turnId}.${String(input.currentStep)}`;
30089
30168
  return {
30090
30169
  ...input.params,
30170
+ completionBudgetRetry,
30091
30171
  requestLogFields: attempt === 1 ? { turnStep } : {
30092
30172
  turnStep,
30093
30173
  attempt: `${String(attempt)}/${String(maxAttempts)}`
30094
30174
  }
30095
30175
  };
30096
30176
  }
30177
+ function logEmptyResponse(input, error, attempt) {
30178
+ input.log?.warn(`[leer] fall=${error.emptyResponseKind}`, {
30179
+ turnStep: `${input.turnId}.${String(input.currentStep)}`,
30180
+ attempt,
30181
+ budget: error.maxCompletionTokens,
30182
+ completion: error.completionTokens,
30183
+ reasoningLength: error.reasoningLength
30184
+ });
30185
+ }
30097
30186
  function retryBackoffDelays(maxAttempts) {
30098
30187
  return import_retry$1.timeouts({
30099
30188
  retries: Math.max(maxAttempts - 1, 0),
@@ -30122,6 +30211,7 @@ function maybeStatusCode(error) {
30122
30211
  var import_retry$1, RETRY_MIN_TIMEOUT_MS, RETRY_MAX_TIMEOUT_MS, RETRY_FACTOR;
30123
30212
  var init_retry = __esmMin((() => {
30124
30213
  init_dist$4();
30214
+ init_src$4();
30125
30215
  import_retry$1 = /* @__PURE__ */ __toESM(require_retry$1(), 1);
30126
30216
  init_abort();
30127
30217
  init_errors$4();
@@ -30783,18 +30873,25 @@ function computeCompletionBudgetCap(args) {
30783
30873
  * in `BlunChatProvider._clone()`.
30784
30874
  */
30785
30875
  function applyCompletionBudget(args) {
30786
- if (args.budget === void 0) return args.provider;
30787
- if (args.provider.withMaxCompletionTokens === void 0) return args.provider;
30876
+ return applyCompletionBudgetWithDetails(args).provider;
30877
+ }
30878
+ function applyCompletionBudgetWithDetails(args) {
30879
+ if (args.budget === void 0) return { provider: args.provider };
30880
+ if (args.provider.withMaxCompletionTokens === void 0) return { provider: args.provider };
30788
30881
  let cap = computeCompletionBudgetCap({
30789
30882
  budget: args.budget,
30790
30883
  capability: args.capability
30791
30884
  });
30885
+ if (args.retry !== void 0) cap = Math.max(args.retry.minimumCompletionTokens, Math.ceil(cap * args.retry.multiplier));
30792
30886
  const maxContextTokens = args.capability?.max_context_tokens;
30793
30887
  if (args.usedContextTokens !== void 0 && maxContextTokens !== void 0 && maxContextTokens > 0) cap = Math.max(MIN_FLOOR, Math.min(cap, maxContextTokens - args.usedContextTokens));
30794
- return args.provider.withMaxCompletionTokens(cap, {
30795
- usedContextTokens: args.usedContextTokens,
30796
- maxContextTokens
30797
- });
30888
+ return {
30889
+ provider: args.provider.withMaxCompletionTokens(cap, {
30890
+ usedContextTokens: args.usedContextTokens,
30891
+ maxContextTokens
30892
+ }),
30893
+ maxCompletionTokens: cap
30894
+ };
30798
30895
  }
30799
30896
  var MIN_FLOOR, DEFAULT_UNKNOWN_CONTEXT_FALLBACK;
30800
30897
  var init_completion_budget = __esmMin((() => {
@@ -74499,6 +74596,7 @@ var init_kosong_llm = __esmMin((() => {
74499
74596
  requestLogFields: params.requestLogFields
74500
74597
  };
74501
74598
  let result;
74599
+ let completionBudget;
74502
74600
  try {
74503
74601
  const enrichedMessages = this.visionReader === void 0 ? params.messages : await enrichMessagesWithVision(params.messages, this.visionReader, params.signal, this.onVisionUsage);
74504
74602
  const tools = [...params.tools];
@@ -74508,14 +74606,16 @@ var init_kosong_llm = __esmMin((() => {
74508
74606
  const outgoingRequestTokens = estimateTokens$1(this.systemPrompt) + estimateTokensForTools(tools) + estimateTokensForMessages(outgoingMessages);
74509
74607
  const reportedContextTokens = this.reportedContextTokens?.() ?? 0;
74510
74608
  const usedContextTokens = Math.max(outgoingRequestTokens, reportedContextTokens);
74511
- const effectiveProvider = applyCompletionBudget({
74609
+ completionBudget = applyCompletionBudgetWithDetails({
74512
74610
  provider: this.provider,
74513
74611
  budget: this.completionBudgetConfig,
74514
74612
  capability: this.capability,
74515
- usedContextTokens
74613
+ usedContextTokens,
74614
+ retry: params.completionBudgetRetry
74516
74615
  });
74517
- result = await this.generate(effectiveProvider, this.systemPrompt, tools, outgoingMessages, callbacks, options);
74616
+ result = await this.generate(completionBudget.provider, this.systemPrompt, tools, outgoingMessages, callbacks, options);
74518
74617
  } catch (error) {
74618
+ if (error instanceof APIEmptyResponseError) throw error.withMetadata({ maxCompletionTokens: completionBudget === void 0 ? null : completionBudget.maxCompletionTokens ?? null });
74519
74619
  if (error instanceof APIPaymentRequiredError) {
74520
74620
  const accountQuotaExhausted = error.statusCode === 429 && error.apiCode === "quota_exhausted";
74521
74621
  throw new BlunError(ErrorCodes.PROVIDER_QUOTA_EXHAUSTED, accountQuotaExhausted ? "Das Kontingent ist aufgebraucht." : "Das Modell-Guthaben ist erschöpft. Bitte lade dein Konto beim Anbieter nach und versuche es erneut.", {
@@ -74630,8 +74730,8 @@ var init_compaction_instruction = __esmMin((() => {
74630
74730
  var DEFAULT_COMPACTION_CONFIG, DefaultCompactionStrategy;
74631
74731
  var init_strategy = __esmMin((() => {
74632
74732
  DEFAULT_COMPACTION_CONFIG = {
74633
- triggerRatio: .65,
74634
- blockRatio: .65,
74733
+ triggerRatio: .9,
74734
+ blockRatio: .95,
74635
74735
  reservedContextSize: 1e4,
74636
74736
  maxCompactionPerTurn: Infinity,
74637
74737
  maxOverflowCompactionAttempts: 3
@@ -74919,6 +75019,35 @@ function isModelFallbackStatus(error) {
74919
75019
  function compactionTimingKey(provider) {
74920
75020
  return `${provider.name}\u0000${provider.modelName}`;
74921
75021
  }
75022
+ function estimateCompactionProgressPercent(initialInputTokens, currentInputTokens) {
75023
+ if (initialInputTokens <= 0) return 0;
75024
+ const reducedTokens = Math.max(0, initialInputTokens - currentInputTokens);
75025
+ return Math.min(99, Math.floor(reducedTokens / initialInputTokens * 100));
75026
+ }
75027
+ function selectHierarchicalCompactionChunk(history, targetRequestTokens, hardRequestLimit, build) {
75028
+ const safeEnds = [];
75029
+ for (let end = 1; end < history.length; end++) if (history[end]?.role !== "tool") safeEnds.push(end);
75030
+ if (safeEnds.length === 0) return void 0;
75031
+ const pickLargestWithin = (limit) => {
75032
+ let low = 0;
75033
+ let high = safeEnds.length - 1;
75034
+ let best;
75035
+ while (low <= high) {
75036
+ const middle = Math.floor((low + high) / 2);
75037
+ const end = safeEnds[middle];
75038
+ const request = build(history.slice(0, end));
75039
+ if (request.estimatedTokens < limit) {
75040
+ best = {
75041
+ end,
75042
+ ...request
75043
+ };
75044
+ low = middle + 1;
75045
+ } else high = middle - 1;
75046
+ }
75047
+ return best;
75048
+ };
75049
+ return pickLargestWithin(targetRequestTokens) ?? pickLargestWithin(hardRequestLimit);
75050
+ }
74922
75051
  function shrinkCompactionHistoryAfterOverflow(messages, attempt) {
74923
75052
  if (messages.length <= 1) return messages.slice();
74924
75053
  const ratio = COMPACTION_OVERFLOW_SHRINK_RATIOS[Math.min(attempt - 1, COMPACTION_OVERFLOW_SHRINK_RATIOS.length - 1)];
@@ -75002,7 +75131,7 @@ function extractCompactionSummary(response) {
75002
75131
  if (summary.trim().length === 0) throw new APIEmptyResponseError("The compaction response did not contain a non-empty summary.");
75003
75132
  return summary;
75004
75133
  }
75005
- var DEFAULT_COMPACTION_MAX_COMPLETION_TOKENS, CompactionTruncatedError, CompactionStallError, COMPACTION_STALL_MEASUREMENT_MULTIPLIER, REBUILT_AFTER_COMPACTION_INJECTION_VARIANTS, FullCompaction, MAX_COMPACTION_OVERFLOW_SHRINK_ATTEMPTS, COMPACTION_OVERFLOW_SHRINK_RATIOS;
75134
+ var DEFAULT_COMPACTION_MAX_COMPLETION_TOKENS, COMPACTION_SUMMARY_RESERVE_RATIO, MAX_HIERARCHICAL_COMPACTION_PASSES, HIERARCHICAL_COMPACTION_PREFIX, CompactionTruncatedError, CompactionStallError, COMPACTION_STALL_MEASUREMENT_MULTIPLIER, REBUILT_AFTER_COMPACTION_INJECTION_VARIANTS, FullCompaction, MAX_COMPACTION_OVERFLOW_SHRINK_ATTEMPTS, COMPACTION_OVERFLOW_SHRINK_RATIOS;
75006
75135
  var init_full = __esmMin((() => {
75007
75136
  init_errors$8();
75008
75137
  init_src$4();
@@ -75018,6 +75147,9 @@ var init_full = __esmMin((() => {
75018
75147
  init_strategy();
75019
75148
  init_handoff();
75020
75149
  DEFAULT_COMPACTION_MAX_COMPLETION_TOKENS = 128 * 1024;
75150
+ COMPACTION_SUMMARY_RESERVE_RATIO = .1;
75151
+ MAX_HIERARCHICAL_COMPACTION_PASSES = 64;
75152
+ HIERARCHICAL_COMPACTION_PREFIX = "This is a complete summary of an earlier chronological segment. Preserve it as source material when merging it with the following conversation:";
75021
75153
  CompactionTruncatedError = class extends Error {
75022
75154
  constructor() {
75023
75155
  super("Compaction response was truncated before producing a complete summary.");
@@ -75067,7 +75199,7 @@ var init_full = __esmMin((() => {
75067
75199
  ...DEFAULT_COMPACTION_CONFIG,
75068
75200
  reservedContextSize,
75069
75201
  triggerRatio,
75070
- blockRatio: triggerRatio
75202
+ blockRatio: Math.max(triggerRatio, DEFAULT_COMPACTION_CONFIG.blockRatio)
75071
75203
  });
75072
75204
  }
75073
75205
  get isCompacting() {
@@ -75236,8 +75368,9 @@ var init_full = __esmMin((() => {
75236
75368
  }
75237
75369
  async compactionWorker(signal, data) {
75238
75370
  try {
75239
- const result = await this.compactionRound(signal, data);
75240
- if (!result) return;
75371
+ const output = await this.compactionRound(signal, data);
75372
+ if (!output) return;
75373
+ const { result, stageCount } = output;
75241
75374
  try {
75242
75375
  await this.agent.refreshSystemPrompt();
75243
75376
  } catch (error) {
@@ -75255,7 +75388,8 @@ var init_full = __esmMin((() => {
75255
75388
  this.agent.emitEvent({
75256
75389
  type: "compaction.completed",
75257
75390
  result: eventResult,
75258
- projectedContextTokens
75391
+ projectedContextTokens,
75392
+ ...stageCount > 1 ? { stageCount } : {}
75259
75393
  });
75260
75394
  this.triggerPostCompactHook(data, result);
75261
75395
  } catch (error) {
@@ -75320,35 +75454,72 @@ var init_full = __esmMin((() => {
75320
75454
  let droppedCount = 0;
75321
75455
  let overflowShrinkCount = 0;
75322
75456
  let emptyOrTruncatedShrinkCount = 0;
75457
+ let hierarchicalPassCount = 0;
75323
75458
  const typicalDurationMs = readTypicalCompactionDuration(provider.name, provider.modelName, this.agent.blunHomeDir)?.typicalDurationMs;
75324
75459
  let attemptCount = 0;
75325
75460
  let charsReceived = 0;
75326
75461
  let lastProgressEmitAt = 0;
75462
+ let initialCompactionRequestTokens;
75327
75463
  const compactionTools = [];
75328
- while (true) {
75329
- const compactionRequestLimit = this.getEffectiveMaxContextTokens();
75330
- const messages = stripMediaForCompaction(downgradeUnsupportedMedia([...this.agent.context.project(historyForModel, {
75464
+ const buildRequestMessages = (history, requestInstruction) => {
75465
+ return stripMediaForCompaction(downgradeUnsupportedMedia([...this.agent.context.project(history, {
75331
75466
  synthesizeMissing: true,
75332
75467
  dropOrphanResults: true
75333
- }), createUserMessage(instruction)], capability));
75334
- const estimatedCompactionRequestTokens = this.estimateRequestTokens(messages, this.agent.effectiveSystemPrompt, compactionTools);
75335
- if (compactionRequestLimit > 0 && estimatedCompactionRequestTokens >= compactionRequestLimit) throw new BlunError(ErrorCodes.CONTEXT_OVERFLOW, `Compaction request requires about ${String(estimatedCompactionRequestTokens)} tokens but the active model window is ${String(compactionRequestLimit)}.`, { details: {
75336
- contextBrakeBlocked: true,
75337
- estimatedRequestTokens: estimatedCompactionRequestTokens,
75338
- maxContextTokens: compactionRequestLimit,
75339
- requestBudgetTokens: compactionRequestLimit,
75340
- contextUnchanged: true
75341
- } });
75468
+ }), createUserMessage(requestInstruction)], capability));
75469
+ };
75470
+ while (true) {
75471
+ const compactionRequestLimit = this.getEffectiveMaxContextTokens();
75472
+ const safeCompactionRequestLimit = compactionRequestLimit > 0 ? Math.max(1, Math.floor(compactionRequestLimit * (1 - COMPACTION_SUMMARY_RESERVE_RATIO))) : compactionRequestLimit;
75473
+ let messages = buildRequestMessages(historyForModel, instruction);
75474
+ let estimatedCompactionRequestTokens = this.estimateRequestTokens(messages, this.agent.effectiveSystemPrompt, compactionTools);
75475
+ initialCompactionRequestTokens ??= estimatedCompactionRequestTokens;
75476
+ let hierarchicalChunkEnd;
75477
+ if (safeCompactionRequestLimit > 0 && estimatedCompactionRequestTokens >= safeCompactionRequestLimit) {
75478
+ if (historyForModel.length > 1) {
75479
+ const chunkInstruction = `${instruction}\n\nThis request contains only the oldest chronological segment. Summarize every fact needed by a later merge pass. Do not assume later messages are visible.`;
75480
+ const chunk = selectHierarchicalCompactionChunk(historyForModel, safeCompactionRequestLimit, safeCompactionRequestLimit, (candidate) => {
75481
+ const candidateMessages = buildRequestMessages(candidate, chunkInstruction);
75482
+ return {
75483
+ messages: candidateMessages,
75484
+ estimatedTokens: this.estimateRequestTokens(candidateMessages, this.agent.effectiveSystemPrompt, compactionTools)
75485
+ };
75486
+ });
75487
+ if (chunk !== void 0) {
75488
+ hierarchicalChunkEnd = chunk.end;
75489
+ messages = chunk.messages;
75490
+ estimatedCompactionRequestTokens = chunk.estimatedTokens;
75491
+ }
75492
+ }
75493
+ if (hierarchicalChunkEnd === void 0) throw new BlunError(ErrorCodes.CONTEXT_OVERFLOW, `Compaction stopped before upload: the conversation (${String(tokensBefore)} tokens) could not be divided into a request below the active model window (${String(compactionRequestLimit)} tokens).`, { details: {
75494
+ contextBrakeBlocked: true,
75495
+ estimatedRequestTokens: estimatedCompactionRequestTokens,
75496
+ maxContextTokens: compactionRequestLimit,
75497
+ requestBudgetTokens: safeCompactionRequestLimit,
75498
+ summaryReserveTokens: compactionRequestLimit - safeCompactionRequestLimit,
75499
+ contextUnchanged: true
75500
+ } });
75501
+ }
75342
75502
  provider = buildCompactionProvider(estimatedCompactionRequestTokens);
75343
75503
  attemptCount += 1;
75344
75504
  charsReceived = 0;
75505
+ const estimatedProgressPercent = estimateCompactionProgressPercent(initialCompactionRequestTokens, estimatedCompactionRequestTokens);
75506
+ this.agent.log.info("compaction stage request", {
75507
+ source: data.source,
75508
+ stage: hierarchicalPassCount + 1,
75509
+ activeContextTokens: this.estimateProjectedRequestTokens(),
75510
+ estimatedInputTokens: estimatedCompactionRequestTokens,
75511
+ safeInputLimitTokens: safeCompactionRequestLimit,
75512
+ maxContextTokens: compactionRequestLimit
75513
+ });
75345
75514
  const emitCompactionProgress = (force = false) => {
75346
75515
  const now = Date.now();
75347
75516
  if (!force && now - lastProgressEmitAt < 150) return;
75348
75517
  lastProgressEmitAt = now;
75349
75518
  this.agent.emitEvent({
75350
75519
  type: "compaction.progress",
75520
+ stage: hierarchicalPassCount + 1,
75351
75521
  estimatedInputTokens: estimatedCompactionRequestTokens,
75522
+ estimatedProgressPercent,
75352
75523
  charsReceived,
75353
75524
  attempt: attemptCount,
75354
75525
  ...typicalDurationMs !== void 0 ? { typicalDurationMs } : {}
@@ -75389,9 +75560,29 @@ var init_full = __esmMin((() => {
75389
75560
  if (stalled && !signal.aborted && stallPolicy !== void 0) throw new CompactionStallError(stallPolicy.timeoutMs, stallPolicy.measuredIdleMs);
75390
75561
  maxObservedIdleMs = Math.max(maxObservedIdleMs, Date.now() - lastProgressAt);
75391
75562
  if (response.finishReason === "truncated") throw new CompactionTruncatedError();
75392
- usage = response.usage;
75393
- summary = extractCompactionSummary(response);
75563
+ if (response.usage !== null) usage = usage === null ? response.usage : addUsage$1(usage, response.usage);
75564
+ const extractedSummary = extractCompactionSummary(response);
75394
75565
  this.observeCompactionTiming(timingKey, maxObservedIdleMs);
75566
+ if (hierarchicalChunkEnd !== void 0) {
75567
+ hierarchicalPassCount += 1;
75568
+ if (hierarchicalPassCount > MAX_HIERARCHICAL_COMPACTION_PASSES) throw new BlunError(ErrorCodes.COMPACTION_FAILED, `Compaction stopped after ${String(MAX_HIERARCHICAL_COMPACTION_PASSES)} staged requests without reaching a final summary. The conversation history was not changed.`, { details: {
75569
+ contextUnchanged: true,
75570
+ hierarchicalPassCount
75571
+ } });
75572
+ const previousTokens = estimateTokensForMessages(historyForModel);
75573
+ const nextHistory = [createUserMessage(`${HIERARCHICAL_COMPACTION_PREFIX}\n${extractedSummary}`), ...historyForModel.slice(hierarchicalChunkEnd)];
75574
+ const nextTokens = estimateTokensForMessages(nextHistory);
75575
+ if (nextTokens >= previousTokens) throw new BlunError(ErrorCodes.COMPACTION_FAILED, "A staged compaction response did not reduce the pending history. The conversation history was not changed.", { details: {
75576
+ contextUnchanged: true,
75577
+ hierarchicalPassCount,
75578
+ previousTokens,
75579
+ nextTokens
75580
+ } });
75581
+ historyForModel = nextHistory;
75582
+ retryCount = 0;
75583
+ continue;
75584
+ }
75585
+ summary = extractedSummary;
75395
75586
  appendCompactionTiming({
75396
75587
  ts: Date.now(),
75397
75588
  provider: provider.name,
@@ -75408,6 +75599,10 @@ var init_full = __esmMin((() => {
75408
75599
  if (isContextOverflow && historyForModel.length > 1) {
75409
75600
  if (data.source === "auto") {
75410
75601
  const learnedMaxContextTokens = this.getEffectiveMaxContextTokens();
75602
+ if (learnedMaxContextTokens > 0 && learnedMaxContextTokens < compactionRequestLimit) {
75603
+ retryCount = 0;
75604
+ continue;
75605
+ }
75411
75606
  throw new BlunError(ErrorCodes.CONTEXT_OVERFLOW, "The active model rejected the full compaction request; refusing to drop unsummarized history.", {
75412
75607
  cause: error,
75413
75608
  details: {
@@ -75486,7 +75681,10 @@ var init_full = __esmMin((() => {
75486
75681
  output_tokens: usage.output
75487
75682
  }
75488
75683
  });
75489
- return result;
75684
+ return {
75685
+ result,
75686
+ stageCount: hierarchicalPassCount + 1
75687
+ };
75490
75688
  } catch (error) {
75491
75689
  if (isAbortError$4(error) || signal.aborted) return void 0;
75492
75690
  this.agent.telemetry.track("compaction_failed", {
@@ -75520,6 +75718,15 @@ var init_full = __esmMin((() => {
75520
75718
  contextUnchanged: true
75521
75719
  }
75522
75720
  });
75721
+ if (error instanceof APIStatusError && error.statusCode === 408) throw new BlunError(ErrorCodes.COMPACTION_FAILED, `Compaction stopped: the conversation (${String(tokensBefore)} tokens) could not be sent completely. The conversation history was not changed.`, {
75722
+ cause: error,
75723
+ details: {
75724
+ statusCode: error.statusCode,
75725
+ requestId: error.requestId,
75726
+ estimatedHistoryTokens: tokensBefore,
75727
+ contextUnchanged: true
75728
+ }
75729
+ });
75523
75730
  throw new BlunError(ErrorCodes.COMPACTION_FAILED, String(error), { cause: error });
75524
75731
  }
75525
75732
  }
@@ -230311,6 +230518,7 @@ var init_action_style = __esmMin((() => {
230311
230518
  init_injector();
230312
230519
  STYLE_GUIDANCE = {
230313
230520
  default: "Complete coding tasks efficiently. Keep responses concise while still reporting concrete results, blockers, and decisions.",
230521
+ concise: "Answer briefly and directly. Include only the information needed to act, while still reporting concrete results, blockers, and decisions.",
230314
230522
  proactive: "Act immediately when the path is safe and clear. Minimize interruptions, continue through ordinary implementation decisions, and ask only when missing information or a higher-priority rule makes it necessary.",
230315
230523
  explanatory: "Explain implementation choices and relevant codebase patterns while you work. Keep the explanation tied to the concrete task and avoid delaying safe progress.",
230316
230524
  learning: "Create hands-on learning moments by inviting the user to write small, useful pieces of code. Do this only when the higher-priority rules allow a pause and it will not obstruct the requested result."
@@ -244836,6 +245044,7 @@ var init_events$1 = __esmMin((() => {
244836
245044
  "goal.not_resumable",
244837
245045
  "model.not_configured",
244838
245046
  "model.config_invalid",
245047
+ "model.empty_response",
244839
245048
  "auth.login_required",
244840
245049
  "context.overflow",
244841
245050
  "loop.already_exists",
@@ -245186,6 +245395,8 @@ var init_events$1 = __esmMin((() => {
245186
245395
  compactionCancelledEventSchema = object({ type: literal("compaction.cancelled") });
245187
245396
  compactionProgressEventSchema = object({
245188
245397
  type: literal("compaction.progress"),
245398
+ stage: number$1().int().positive().optional(),
245399
+ estimatedProgressPercent: number$1().int().min(0).max(99).optional(),
245189
245400
  estimatedInputTokens: number$1().optional(),
245190
245401
  charsReceived: number$1(),
245191
245402
  attempt: number$1(),
@@ -245194,7 +245405,8 @@ var init_events$1 = __esmMin((() => {
245194
245405
  compactionCompletedEventSchema = object({
245195
245406
  type: literal("compaction.completed"),
245196
245407
  result: compactionResultSchema,
245197
- projectedContextTokens: number$1().optional()
245408
+ projectedContextTokens: number$1().optional(),
245409
+ stageCount: number$1().int().positive().optional()
245198
245410
  });
245199
245411
  backgroundTaskStartedEventSchema = object({
245200
245412
  type: literal("background.task.started"),
@@ -261611,7 +261823,7 @@ var init_blun_media$1 = __esmMin((() => {
261611
261823
  return {
261612
261824
  output: [{
261613
261825
  type: "text",
261614
- text: `Media job ${result.id} is complete. Local file: ${localPath}. Attach this absolute path with the channel reply tool; do not search for another copy.`
261826
+ text: `Media job ${result.id} is complete. Local file: ${localPath}. The BLUN host automatically delivers this file for channel-origin turns; do not attach it again. Outside a channel-origin turn, use this local path as needed.`
261615
261827
  }, mediaPart],
261616
261828
  isError: false
261617
261829
  };
@@ -410954,12 +411166,30 @@ const STREAMING_ARGS_PREVIEW_MAX_CHARS = 64 * 1024;
410954
411166
  //#endregion
410955
411167
  //#region src/tui/utils/event-payload.copy.ts
410956
411168
  registerUiCatalogFragment({
410957
- en: { "eventPayload.providerFiltered": "Provider filtered the response before visible output (finishReason={finishReason}{raw})." },
410958
- de: { "eventPayload.providerFiltered": "Der Anbieter hat die Antwort vor der sichtbaren Ausgabe gefiltert (finishReason={finishReason}{raw})." },
410959
- es: { "eventPayload.providerFiltered": "El proveedor filtró la respuesta antes de que se mostrara la salida (finishReason={finishReason}{raw})." },
410960
- fr: { "eventPayload.providerFiltered": "Le fournisseur a filtré la réponse avant l’affichage de la sortie (finishReason={finishReason}{raw})." },
410961
- sv: { "eventPayload.providerFiltered": "Leverantören filtrerade svaret innan någon utdata visades (finishReason={finishReason}{raw})." },
410962
- cs: { "eventPayload.providerFiltered": "Poskytovatel filtroval odpověď před viditelným výstupem (finishReason={finishReason}{raw})." }
411169
+ en: {
411170
+ "eventPayload.providerFiltered": "Provider filtered the response before visible output (finishReason={finishReason}{raw}).",
411171
+ "eventPayload.modelEmptyResponse": "King ended twice without producing content. Please send the message again."
411172
+ },
411173
+ de: {
411174
+ "eventPayload.providerFiltered": "Der Anbieter hat die Antwort vor der sichtbaren Ausgabe gefiltert (finishReason={finishReason}{raw}).",
411175
+ "eventPayload.modelEmptyResponse": "King hat die Antwort zweimal ohne Inhalt beendet. Bitte sende die Nachricht noch einmal."
411176
+ },
411177
+ es: {
411178
+ "eventPayload.providerFiltered": "El proveedor filtró la respuesta antes de que se mostrara la salida (finishReason={finishReason}{raw}).",
411179
+ "eventPayload.modelEmptyResponse": "King terminó dos veces sin generar contenido. Envía el mensaje de nuevo."
411180
+ },
411181
+ fr: {
411182
+ "eventPayload.providerFiltered": "Le fournisseur a filtré la réponse avant l’affichage de la sortie (finishReason={finishReason}{raw}).",
411183
+ "eventPayload.modelEmptyResponse": "King a terminé deux fois sans produire de contenu. Envoyez à nouveau le message."
411184
+ },
411185
+ sv: {
411186
+ "eventPayload.providerFiltered": "Leverantören filtrerade svaret innan någon utdata visades (finishReason={finishReason}{raw}).",
411187
+ "eventPayload.modelEmptyResponse": "King avslutade två gånger utan att skapa något innehåll. Skicka meddelandet igen."
411188
+ },
411189
+ cs: {
411190
+ "eventPayload.providerFiltered": "Poskytovatel filtroval odpověď před viditelným výstupem (finishReason={finishReason}{raw}).",
411191
+ "eventPayload.modelEmptyResponse": "King dvakrát ukončil odpověď bez obsahu. Odešlete zprávu znovu."
411192
+ }
410963
411193
  });
410964
411194
  //#endregion
410965
411195
  //#region src/tui/utils/event-payload.ts
@@ -411032,6 +411262,7 @@ function formatErrorMessage$2(error) {
411032
411262
  return projectBlunIdentity(error instanceof Error ? error.message : String(error));
411033
411263
  }
411034
411264
  function formatErrorPayload(error) {
411265
+ if (error.code === "model.empty_response") return uiText("eventPayload.modelEmptyResponse");
411035
411266
  const filteredMessage = formatProviderFilteredMessage(error.details);
411036
411267
  if (filteredMessage !== void 0) return projectBlunIdentity(`[${error.code}] ${filteredMessage}`);
411037
411268
  return projectBlunIdentity(`[${error.code}] ${error.message}`);
@@ -411120,6 +411351,8 @@ registerUiCatalogFragment({
411120
411351
  "actionStyle.scope": "This setting changes how BLUN responds. Plan and permission rules still take priority.",
411121
411352
  "actionStyle.default.label": "Default",
411122
411353
  "actionStyle.default.description": "Completes coding tasks efficiently and keeps responses concise.",
411354
+ "actionStyle.concise.label": "Concise",
411355
+ "actionStyle.concise.description": "Answers briefly and directly, including only the information needed to act.",
411123
411356
  "actionStyle.proactive.label": "Proactive",
411124
411357
  "actionStyle.proactive.description": "Acts immediately when the path is clear, minimizes interruptions, and asks only when necessary.",
411125
411358
  "actionStyle.explanatory.label": "Explanatory",
@@ -411194,6 +411427,8 @@ registerUiCatalogFragment({
411194
411427
  "actionStyle.scope": "Diese Einstellung bestimmt, wie BLUN antwortet. Plan- und Berechtigungsregeln haben weiterhin Vorrang.",
411195
411428
  "actionStyle.default.label": "Standard",
411196
411429
  "actionStyle.default.description": "Erledigt Programmieraufgaben effizient und hält Antworten knapp.",
411430
+ "actionStyle.concise.label": "Knapp",
411431
+ "actionStyle.concise.description": "Antwortet kurz und direkt und nennt nur die Informationen, die zum Handeln nötig sind.",
411197
411432
  "actionStyle.proactive.label": "Proaktiv",
411198
411433
  "actionStyle.proactive.description": "Handelt sofort, wenn der Weg klar ist, unterbricht dich so selten wie möglich und fragt nur nach, wenn es nötig ist.",
411199
411434
  "actionStyle.explanatory.label": "Erklärend",
@@ -411268,6 +411503,8 @@ registerUiCatalogFragment({
411268
411503
  "actionStyle.scope": "Esta opción determina cómo responde BLUN. Las reglas del plan y de permisos siguen teniendo prioridad.",
411269
411504
  "actionStyle.default.label": "Predeterminado",
411270
411505
  "actionStyle.default.description": "Completa las tareas de programación con eficiencia y mantiene las respuestas concisas.",
411506
+ "actionStyle.concise.label": "Conciso",
411507
+ "actionStyle.concise.description": "Responde de forma breve y directa e incluye solo la información necesaria para actuar.",
411271
411508
  "actionStyle.proactive.label": "Proactivo",
411272
411509
  "actionStyle.proactive.description": "Actúa de inmediato cuando el camino está claro, reduce al mínimo las interrupciones y solo pregunta cuando es necesario.",
411273
411510
  "actionStyle.explanatory.label": "Explicativo",
@@ -411342,6 +411579,8 @@ registerUiCatalogFragment({
411342
411579
  "actionStyle.scope": "Ce réglage détermine la manière dont BLUN répond. Les règles du plan et des autorisations restent prioritaires.",
411343
411580
  "actionStyle.default.label": "Par défaut",
411344
411581
  "actionStyle.default.description": "Réalise efficacement les tâches de programmation et fournit des réponses concises.",
411582
+ "actionStyle.concise.label": "Concis",
411583
+ "actionStyle.concise.description": "Répond brièvement et directement, en indiquant uniquement les informations nécessaires pour agir.",
411345
411584
  "actionStyle.proactive.label": "Proactif",
411346
411585
  "actionStyle.proactive.description": "Agit immédiatement lorsque la marche à suivre est claire, réduit les interruptions au minimum et ne pose une question que si nécessaire.",
411347
411586
  "actionStyle.explanatory.label": "Explicatif",
@@ -411416,6 +411655,8 @@ registerUiCatalogFragment({
411416
411655
  "actionStyle.scope": "Den här inställningen styr hur BLUN svarar. Plan- och behörighetsregler har fortfarande företräde.",
411417
411656
  "actionStyle.default.label": "Standard",
411418
411657
  "actionStyle.default.description": "Slutför programmeringsuppgifter effektivt och håller svaren kortfattade.",
411658
+ "actionStyle.concise.label": "Kortfattad",
411659
+ "actionStyle.concise.description": "Svarar kort och direkt och tar bara med den information som behövs för att agera.",
411419
411660
  "actionStyle.proactive.label": "Proaktiv",
411420
411661
  "actionStyle.proactive.description": "Agerar direkt när vägen framåt är tydlig, minimerar avbrott och frågar bara när det behövs.",
411421
411662
  "actionStyle.explanatory.label": "Förklarande",
@@ -411490,6 +411731,8 @@ registerUiCatalogFragment({
411490
411731
  "actionStyle.scope": "Toto nastavení určuje, jak BLUN odpovídá. Pravidla plánu a oprávnění mají i nadále přednost.",
411491
411732
  "actionStyle.default.label": "Výchozí",
411492
411733
  "actionStyle.default.description": "Efektivně plní programátorské úkoly a odpovídá stručně.",
411734
+ "actionStyle.concise.label": "Stručný",
411735
+ "actionStyle.concise.description": "Odpovídá krátce a přímo a uvádí pouze informace potřebné k dalšímu postupu.",
411493
411736
  "actionStyle.proactive.label": "Proaktivní",
411494
411737
  "actionStyle.proactive.description": "Jedná okamžitě, když je další postup jasný, omezuje vyrušování na minimum a ptá se jen tehdy, když je to nutné.",
411495
411738
  "actionStyle.explanatory.label": "Vysvětlující",
@@ -412420,6 +412663,8 @@ registerUiCatalogFragment({
412420
412663
  "btw.error.send": "Failed to send /btw prompt: {error}",
412421
412664
  "btw.error.cancel": "Failed to cancel /btw: {error}",
412422
412665
  "btw.busy": "Wait for /btw to finish before sending another question.",
412666
+ "btw.retrying": "BTW did not send any activity for two minutes. Retrying once...",
412667
+ "btw.timeout": "BTW did not respond after the automatic retry. Press Esc to close it and send the question again.",
412423
412668
  "btw.turn.cancelled": "Interrupted by user",
412424
412669
  "btw.turn.filtered": "Provider safety policy blocked the response.",
412425
412670
  "btw.turn.ended": "BTW turn ended with reason: {reason}"
@@ -412429,6 +412674,8 @@ registerUiCatalogFragment({
412429
412674
  "btw.error.send": "Die /btw-Frage konnte nicht übermittelt werden: {error}",
412430
412675
  "btw.error.cancel": "/btw konnte nicht abgebrochen werden: {error}",
412431
412676
  "btw.busy": "Warte, bis /btw beendet ist, bevor du eine weitere Frage sendest.",
412677
+ "btw.retrying": "BTW hat zwei Minuten lang keine Aktivität gesendet. Ein automatischer Wiederholungsversuch wird gestartet ...",
412678
+ "btw.timeout": "BTW hat auch nach dem automatischen Wiederholungsversuch nicht geantwortet. Drücke Esc, um das Fenster zu schließen, und sende die Frage erneut.",
412432
412679
  "btw.turn.cancelled": "Vom Benutzer unterbrochen",
412433
412680
  "btw.turn.filtered": "Die Sicherheitsrichtlinie des Anbieters hat die Antwort blockiert.",
412434
412681
  "btw.turn.ended": "Die BTW-Runde wurde mit folgendem Grund beendet: {reason}"
@@ -412438,6 +412685,8 @@ registerUiCatalogFragment({
412438
412685
  "btw.error.send": "No se pudo enviar la pregunta de /btw: {error}",
412439
412686
  "btw.error.cancel": "No se pudo cancelar /btw: {error}",
412440
412687
  "btw.busy": "Espera a que termine /btw antes de enviar otra pregunta.",
412688
+ "btw.retrying": "BTW no ha enviado ninguna actividad durante dos minutos. Se realizará un reintento automático...",
412689
+ "btw.timeout": "BTW tampoco ha respondido tras el reintento automático. Pulsa Esc para cerrar el panel y vuelve a enviar la pregunta.",
412441
412690
  "btw.turn.cancelled": "Interrumpido por el usuario",
412442
412691
  "btw.turn.filtered": "La política de seguridad del proveedor bloqueó la respuesta.",
412443
412692
  "btw.turn.ended": "El turno de BTW terminó por este motivo: {reason}"
@@ -412447,6 +412696,8 @@ registerUiCatalogFragment({
412447
412696
  "btw.error.send": "Impossible d’envoyer la question /btw : {error}",
412448
412697
  "btw.error.cancel": "Impossible d’annuler /btw : {error}",
412449
412698
  "btw.busy": "Attendez la fin de /btw avant d’envoyer une autre question.",
412699
+ "btw.retrying": "BTW n’a envoyé aucune activité pendant deux minutes. Une nouvelle tentative automatique va être effectuée…",
412700
+ "btw.timeout": "BTW n’a toujours pas répondu après la nouvelle tentative automatique. Appuyez sur Échap pour fermer le panneau, puis renvoyez la question.",
412450
412701
  "btw.turn.cancelled": "Interrompu par l’utilisateur",
412451
412702
  "btw.turn.filtered": "La politique de sécurité du fournisseur a bloqué la réponse.",
412452
412703
  "btw.turn.ended": "Le tour BTW s’est terminé pour la raison suivante : {reason}"
@@ -412456,6 +412707,8 @@ registerUiCatalogFragment({
412456
412707
  "btw.error.send": "Det gick inte att skicka /btw-frågan: {error}",
412457
412708
  "btw.error.cancel": "Det gick inte att avbryta /btw: {error}",
412458
412709
  "btw.busy": "Vänta tills /btw är klart innan du skickar en ny fråga.",
412710
+ "btw.retrying": "BTW har inte skickat någon aktivitet på två minuter. Ett automatiskt nytt försök görs ...",
412711
+ "btw.timeout": "BTW svarade inte heller efter det automatiska försöket. Tryck på Esc för att stänga panelen och skicka frågan igen.",
412459
412712
  "btw.turn.cancelled": "Avbröts av användaren",
412460
412713
  "btw.turn.filtered": "Leverantörens säkerhetspolicy blockerade svaret.",
412461
412714
  "btw.turn.ended": "BTW-rundan avslutades av följande orsak: {reason}"
@@ -412465,6 +412718,8 @@ registerUiCatalogFragment({
412465
412718
  "btw.error.send": "Odeslání dotazu /btw selhalo: {error}",
412466
412719
  "btw.error.cancel": "Nepodařilo se zrušit /btw: {error}",
412467
412720
  "btw.busy": "Než odešlete další otázku, počkejte na dokončení /btw.",
412721
+ "btw.retrying": "BTW dvě minuty nevykázal žádnou aktivitu. Proběhne jeden automatický opakovaný pokus…",
412722
+ "btw.timeout": "BTW neodpověděl ani po automatickém opakovaném pokusu. Stisknutím Esc panel zavřete a poté otázku odešlete znovu.",
412468
412723
  "btw.turn.cancelled": "Přerušeno uživatelem",
412469
412724
  "btw.turn.filtered": "Bezpečnostní zásada poskytovatele zablokovala odpověď.",
412470
412725
  "btw.turn.ended": "Kolo BTW skončilo z důvodu: {reason}"
@@ -413285,6 +413540,7 @@ var EffortSelectorComponent = class extends Container {
413285
413540
  //#region src/tui/components/dialogs/action-style-selector.ts
413286
413541
  const ACTION_STYLES = [
413287
413542
  "default",
413543
+ "concise",
413288
413544
  "proactive",
413289
413545
  "explanatory",
413290
413546
  "learning"
@@ -425490,6 +425746,9 @@ var CompactionComponent = class extends Container {
425490
425746
  tokensAfter;
425491
425747
  estimatedInputTokens;
425492
425748
  attempt = 1;
425749
+ stage = 1;
425750
+ stageCount;
425751
+ estimatedProgressPercent;
425493
425752
  constructor(ui, instruction, tip, showRunning = true) {
425494
425753
  super();
425495
425754
  this.showRunning = showRunning;
@@ -425511,11 +425770,12 @@ var CompactionComponent = class extends Container {
425511
425770
  if (!this.showRunning && !this.done && !this.canceled && !this.failed) return [];
425512
425771
  return super.render(width);
425513
425772
  }
425514
- markDone(tokensBefore, tokensAfter) {
425773
+ markDone(tokensBefore, tokensAfter, stageCount) {
425515
425774
  if (this.done || this.canceled || this.failed) return;
425516
425775
  this.done = true;
425517
425776
  this.tokensBefore = tokensBefore;
425518
425777
  this.tokensAfter = tokensAfter;
425778
+ this.stageCount = stageCount;
425519
425779
  this.stopTicking();
425520
425780
  this.statusText.setText(this.buildStatusLine());
425521
425781
  this.ui?.requestRender();
@@ -425542,6 +425802,8 @@ var CompactionComponent = class extends Container {
425542
425802
  if (progress.estimatedInputTokens !== void 0) this.estimatedInputTokens = progress.estimatedInputTokens;
425543
425803
  if (progress.attempt !== this.attempt) this.attemptStartedAtMs = Date.now();
425544
425804
  this.attempt = progress.attempt;
425805
+ this.stage = progress.stage ?? this.stage;
425806
+ this.estimatedProgressPercent = progress.estimatedProgressPercent;
425545
425807
  this.statusText.setText(this.buildStatusLine());
425546
425808
  if (this.showRunning) this.ui?.requestRender();
425547
425809
  }
@@ -425552,16 +425814,21 @@ var CompactionComponent = class extends Container {
425552
425814
  return Math.max(0, Math.round((Date.now() - this.startedAtMs) / 1e3));
425553
425815
  }
425554
425816
  buildStatusLine() {
425555
- if (this.done) return `${currentTheme.fg("success", `[█${"█".repeat(BAR_WIDTH - 1)}]`)} ${currentTheme.boldFg("success", `${uiText("compaction.complete")} 100 %`)}${this.tokensBefore !== void 0 && this.tokensAfter !== void 0 ? currentTheme.dim(uiText("compaction.tokens", {
425556
- before: this.tokensBefore.toLocaleString(getCurrentUiLocale()),
425557
- after: this.tokensAfter.toLocaleString(getCurrentUiLocale())
425558
- })) : ""}`;
425817
+ if (this.done) {
425818
+ const bar = currentTheme.fg("success", `[█${"█".repeat(BAR_WIDTH - 1)}]`);
425819
+ const completeText = `${uiText("compaction.complete")}${this.stageCount === void 0 ? "" : ` · ${String(this.stageCount)}/${String(this.stageCount)}`}`;
425820
+ return `${bar} ${currentTheme.boldFg("success", `${completeText} 100 %`)}${this.tokensBefore !== void 0 && this.tokensAfter !== void 0 ? currentTheme.dim(uiText("compaction.tokens", {
425821
+ before: this.tokensBefore.toLocaleString(getCurrentUiLocale()),
425822
+ after: this.tokensAfter.toLocaleString(getCurrentUiLocale())
425823
+ })) : ""}`;
425824
+ }
425559
425825
  if (this.failed) return `${currentTheme.fg("error", STATUS_BULLET)}${currentTheme.boldFg("error", this.failureTitle ?? uiText("compaction.failed"))}${currentTheme.fg("textDim", ` · ${this.failureDetail ?? uiText("compaction.failureDetail")}`)}`;
425560
425826
  if (this.canceled) return `${currentTheme.fg("warning", STATUS_BULLET)}${currentTheme.boldFg("warning", uiText("compaction.canceled"))}`;
425561
- const percent = estimateCompactionPercent(Math.max(0, Date.now() - this.attemptStartedAtMs), this.estimatedInputTokens);
425827
+ const elapsedMs = Math.max(0, Date.now() - this.attemptStartedAtMs);
425828
+ const percent = this.estimatedProgressPercent ?? estimateCompactionPercent(elapsedMs, this.estimatedInputTokens);
425562
425829
  const filled = Math.round(percent / 100 * BAR_WIDTH);
425563
425830
  const bar = `[${"█".repeat(filled)}${"░".repeat(BAR_WIDTH - filled)}]`;
425564
- const runningText = formatCompactionRunningText(this.estimatedInputTokens);
425831
+ const runningText = `${formatCompactionRunningText(this.estimatedInputTokens)} · ${String(this.stage)}/?`;
425565
425832
  return `${currentTheme.fg("primary", bar)} ${currentTheme.boldFg("primary", runningText)}${currentTheme.fg("textDim", ` ~${String(percent)} %`)}${currentTheme.fg("textDim", ` · ${String(this.elapsedSeconds())}s`)}${this.attempt > 1 ? currentTheme.fg("textDim", uiText("compaction.attempt", { attempt: this.attempt })) : ""}${currentTheme.fg("textDim", ` · ${uiText("compaction.cancelHint")}`)}${this.instruction ? currentTheme.fg("textDim", ` · ${this.instruction}`) : ""}${this.tip ? currentTheme.fg("textDim", ` · ${this.tip}`) : ""}`;
425566
425833
  }
425567
425834
  startTicking() {
@@ -498107,6 +498374,406 @@ registerUiCatalogFragment({
498107
498374
  }
498108
498375
  });
498109
498376
  //#endregion
498377
+ //#region src/tui/blun-tui.copy.ts
498378
+ registerUiCatalogFragment({
498379
+ en: {
498380
+ "blunTui.provider.modelAdded.one": "{providerName} · +{count} model.",
498381
+ "blunTui.provider.modelAdded.other": "{providerName} · +{count} models.",
498382
+ "blunTui.provider.refreshSkipped": "Skipped refreshing {provider}: {reason}",
498383
+ "blunTui.warning": "Warning: {warning}",
498384
+ "blunTui.startup.sessionNotFound": "Session \"{sessionId}\" not found.",
498385
+ "blunTui.startup.sessionDifferentDirectory": "Session \"{sessionId}\" was created under a different directory.",
498386
+ "blunTui.startup.noSessionsToContinue": "No sessions to continue under \"{workDir}\"; starting a fresh session.",
498387
+ "blunTui.startup.sessionNotInitialized": "Startup session was not initialized.",
498388
+ "blunTui.input.replayBlocked": "Cannot send input while session history is replaying.",
498389
+ "blunTui.shell.noSession": "No active session for shell command.",
498390
+ "blunTui.shell.runFailed": "Shell command failed: {error}",
498391
+ "blunTui.shell.cancelFailed": "Failed to cancel shell command: {error}",
498392
+ "blunTui.channel.steerFailed": "Failed to steer channel message: {error}",
498393
+ "blunTui.session.sendFailed": "Failed to send: {error}",
498394
+ "blunTui.media.imageUnsupported": "Current model does not support image input.",
498395
+ "blunTui.media.videoUnsupported": "Current model does not support video input.",
498396
+ "blunTui.skill.failed": "Skill \"{skillName}\" failed: {error}",
498397
+ "blunTui.pluginCommand.failed": "Command \"{command}\" failed: {error}",
498398
+ "blunTui.steer.failed": "Failed to steer: {error}",
498399
+ "blunTui.session.otherWorkDir": "Current session is in a different working directory.",
498400
+ "blunTui.session.resumeCommand": "To resume, run: {command}",
498401
+ "blunTui.clipboard.commandCopied": "Command copied to clipboard",
498402
+ "blunTui.clipboard.commandCopyFailed": "Failed to copy command to clipboard",
498403
+ "blunTui.session.alreadyCurrent": "Already on this session.",
498404
+ "blunTui.session.switchStreamingBlocked": "Cannot switch sessions while streaming — press Esc or Ctrl-C first.",
498405
+ "blunTui.session.switchReplayBlocked": "Cannot switch sessions while history is replaying.",
498406
+ "blunTui.session.resumeFailed": "Failed to resume session {sessionId}: {error}",
498407
+ "blunTui.session.resumed": "Resumed session ({sessionId}).",
498408
+ "blunTui.session.replayFailed": "Failed to replay session history: {error}",
498409
+ "blunTui.session.createReplayBlocked": "Cannot start a new session while history is replaying.",
498410
+ "blunTui.session.createFailed": "Failed to start a new session: {error}",
498411
+ "blunTui.session.postCreateFailed": "Post-create setup failed: {error}",
498412
+ "blunTui.session.started": "Started a new session ({sessionId}).",
498413
+ "blunTui.error": "Error: {message}",
498414
+ "blunTui.login.title": "Sign in to BLUN",
498415
+ "blunTui.login.hint": "Press Ctrl-C to cancel",
498416
+ "blunTui.login.waiting": "Waiting for authorization…",
498417
+ "blunTui.detach.noShell": "No shell command running.",
498418
+ "blunTui.detach.shellStarting": "Command is still starting — try again.",
498419
+ "blunTui.detach.shellFinished": "Command already finished.",
498420
+ "blunTui.detach.moveFailed": "Failed to move to background: {error}",
498421
+ "blunTui.detach.movedTranscript": "Moved to background.",
498422
+ "blunTui.detach.movedView": "Moved to background. /tasks to view.",
498423
+ "blunTui.detach.noForeground": "No foreground task running.",
498424
+ "blunTui.detach.listFailed": "Failed to list tasks: {error}",
498425
+ "blunTui.detach.taskFailed": "Failed to detach {taskId}: {error}",
498426
+ "blunTui.detach.finished.one": "Task already finished.",
498427
+ "blunTui.detach.finished.other": "Tasks already finished.",
498428
+ "blunTui.detach.moved.one": "Moved {count} task to background.",
498429
+ "blunTui.detach.moved.other": "Moved {count} tasks to background.",
498430
+ "blunTui.detach.partial": "Moved {detached} of {total} tasks to background.",
498431
+ "blunTui.detach.viewSuffix": "/tasks to view.",
498432
+ "blunTui.startup.flagsFailed": "Failed to apply startup flags: {error}",
498433
+ "blunTui.notification.approvalRequired": "BLUN approval required",
498434
+ "blunTui.notification.answerRequired": "BLUN needs your answer",
498435
+ "blunTui.telegram.fallbackDelivered": "Reply delivered to Telegram automatically (fallback).",
498436
+ "blunTui.telegram.attachDisabled": "Telegram attachment disabled by BLUN_TELEGRAM_ATTACH=off — headless mode active.",
498437
+ "blunTui.telegram.noToken": "Telegram attachment: no token detected — headless mode active.",
498438
+ "blunTui.telegram.attached": "Telegram channel attached (lease PID {pid}) — messages appear in this window.",
498439
+ "blunTui.auto.status": "Auto: {label}",
498440
+ "blunTui.activity.thinking": "{name} is thinking…",
498441
+ "blunTui.activity.working": "{name} is working…",
498442
+ "blunTui.activity.composing": "working...",
498443
+ "blunTui.activity.tokens": "Tokens"
498444
+ },
498445
+ de: {
498446
+ "blunTui.provider.modelAdded.one": "{providerName} · +{count} Modell.",
498447
+ "blunTui.provider.modelAdded.other": "{providerName} · +{count} Modelle.",
498448
+ "blunTui.provider.refreshSkipped": "Aktualisierung von {provider} übersprungen: {reason}",
498449
+ "blunTui.warning": "Warnung: {warning}",
498450
+ "blunTui.startup.sessionNotFound": "Sitzung „{sessionId}“ wurde nicht gefunden.",
498451
+ "blunTui.startup.sessionDifferentDirectory": "Sitzung „{sessionId}“ wurde in einem anderen Arbeitsverzeichnis erstellt.",
498452
+ "blunTui.startup.noSessionsToContinue": "Unter „{workDir}“ gibt es keine Sitzung zum Fortsetzen; eine neue Sitzung wird gestartet.",
498453
+ "blunTui.startup.sessionNotInitialized": "Die Sitzung konnte beim Start nicht initialisiert werden.",
498454
+ "blunTui.input.replayBlocked": "Während der Wiedergabe des Sitzungsverlaufs können keine Eingaben gesendet werden.",
498455
+ "blunTui.shell.noSession": "Keine aktive Sitzung für den Shell-Befehl.",
498456
+ "blunTui.shell.runFailed": "Shell-Befehl fehlgeschlagen: {error}",
498457
+ "blunTui.shell.cancelFailed": "Shell-Befehl konnte nicht abgebrochen werden: {error}",
498458
+ "blunTui.channel.steerFailed": "Die Kanalnachricht konnte nicht zur laufenden Antwort hinzugefügt werden: {error}",
498459
+ "blunTui.session.sendFailed": "Senden fehlgeschlagen: {error}",
498460
+ "blunTui.media.imageUnsupported": "Das aktuelle Modell unterstützt keine Bildeingaben.",
498461
+ "blunTui.media.videoUnsupported": "Das aktuelle Modell unterstützt keine Videoeingaben.",
498462
+ "blunTui.skill.failed": "Skill „{skillName}“ fehlgeschlagen: {error}",
498463
+ "blunTui.pluginCommand.failed": "Befehl „{command}“ fehlgeschlagen: {error}",
498464
+ "blunTui.steer.failed": "Nachsteuern fehlgeschlagen: {error}",
498465
+ "blunTui.session.otherWorkDir": "Die aktuelle Sitzung befindet sich in einem anderen Arbeitsverzeichnis.",
498466
+ "blunTui.session.resumeCommand": "Zum Fortsetzen ausführen: {command}",
498467
+ "blunTui.clipboard.commandCopied": "Befehl in die Zwischenablage kopiert",
498468
+ "blunTui.clipboard.commandCopyFailed": "Befehl konnte nicht in die Zwischenablage kopiert werden",
498469
+ "blunTui.session.alreadyCurrent": "Diese Sitzung ist bereits aktiv.",
498470
+ "blunTui.session.switchStreamingBlocked": "Während einer laufenden Antwort kann die Sitzung nicht gewechselt werden. Drücke zuerst Esc oder Ctrl-C.",
498471
+ "blunTui.session.switchReplayBlocked": "Während der Wiedergabe des Sitzungsverlaufs kann die Sitzung nicht gewechselt werden.",
498472
+ "blunTui.session.resumeFailed": "Sitzung {sessionId} konnte nicht fortgesetzt werden: {error}",
498473
+ "blunTui.session.resumed": "Sitzung fortgesetzt ({sessionId}).",
498474
+ "blunTui.session.replayFailed": "Der Sitzungsverlauf konnte nicht wiedergegeben werden: {error}",
498475
+ "blunTui.session.createReplayBlocked": "Während der Wiedergabe des Sitzungsverlaufs kann keine neue Sitzung gestartet werden.",
498476
+ "blunTui.session.createFailed": "Neue Sitzung konnte nicht gestartet werden: {error}",
498477
+ "blunTui.session.postCreateFailed": "Die neue Sitzung konnte nicht eingerichtet werden: {error}",
498478
+ "blunTui.session.started": "Neue Sitzung gestartet ({sessionId}).",
498479
+ "blunTui.error": "Fehler: {message}",
498480
+ "blunTui.login.title": "Bei BLUN anmelden",
498481
+ "blunTui.login.hint": "Zum Abbrechen Ctrl-C drücken",
498482
+ "blunTui.login.waiting": "Warten auf Autorisierung…",
498483
+ "blunTui.detach.noShell": "Es wird kein Shell-Befehl ausgeführt.",
498484
+ "blunTui.detach.shellStarting": "Der Befehl wird noch gestartet — versuche es erneut.",
498485
+ "blunTui.detach.shellFinished": "Der Befehl ist bereits beendet.",
498486
+ "blunTui.detach.moveFailed": "Verschieben in den Hintergrund fehlgeschlagen: {error}",
498487
+ "blunTui.detach.movedTranscript": "In den Hintergrund verschoben.",
498488
+ "blunTui.detach.movedView": "In den Hintergrund verschoben. Mit /tasks anzeigen.",
498489
+ "blunTui.detach.noForeground": "Es wird keine Aufgabe im Vordergrund ausgeführt.",
498490
+ "blunTui.detach.listFailed": "Aufgaben konnten nicht aufgelistet werden: {error}",
498491
+ "blunTui.detach.taskFailed": "Aufgabe {taskId} konnte nicht in den Hintergrund verschoben werden: {error}",
498492
+ "blunTui.detach.finished.one": "Aufgabe ist bereits beendet.",
498493
+ "blunTui.detach.finished.other": "Aufgaben sind bereits beendet.",
498494
+ "blunTui.detach.moved.one": "{count} Aufgabe in den Hintergrund verschoben.",
498495
+ "blunTui.detach.moved.other": "{count} Aufgaben in den Hintergrund verschoben.",
498496
+ "blunTui.detach.partial": "{detached} von {total} Aufgaben in den Hintergrund verschoben.",
498497
+ "blunTui.detach.viewSuffix": "Mit /tasks anzeigen.",
498498
+ "blunTui.startup.flagsFailed": "Startoptionen konnten nicht angewendet werden: {error}",
498499
+ "blunTui.notification.approvalRequired": "BLUN-Genehmigung erforderlich",
498500
+ "blunTui.notification.answerRequired": "BLUN benötigt deine Antwort",
498501
+ "blunTui.telegram.fallbackDelivered": "Antwort automatisch nach Telegram zugestellt (Fallback).",
498502
+ "blunTui.telegram.attachDisabled": "Telegram-Anbindung durch BLUN_TELEGRAM_ATTACH=off deaktiviert — Headless-Modus aktiv.",
498503
+ "blunTui.telegram.noToken": "Telegram-Anbindung: kein Token erkannt — Headless-Modus aktiv.",
498504
+ "blunTui.telegram.attached": "Telegram-Kanal angebunden (Lease-PID {pid}) — Nachrichten erscheinen in diesem Fenster.",
498505
+ "blunTui.auto.status": "Auto: {label}",
498506
+ "blunTui.activity.thinking": "{name} denkt…",
498507
+ "blunTui.activity.working": "{name} arbeitet…",
498508
+ "blunTui.activity.composing": "arbeitet...",
498509
+ "blunTui.activity.tokens": "Token"
498510
+ },
498511
+ es: {
498512
+ "blunTui.provider.modelAdded.one": "{providerName} · +{count} modelo.",
498513
+ "blunTui.provider.modelAdded.other": "{providerName} · +{count} modelos.",
498514
+ "blunTui.provider.refreshSkipped": "Se omitió la actualización de {provider}: {reason}",
498515
+ "blunTui.warning": "Advertencia: {warning}",
498516
+ "blunTui.startup.sessionNotFound": "No se encontró la sesión «{sessionId}».",
498517
+ "blunTui.startup.sessionDifferentDirectory": "La sesión «{sessionId}» se creó en otro directorio de trabajo.",
498518
+ "blunTui.startup.noSessionsToContinue": "No hay sesiones que reanudar en «{workDir}»; se iniciará una sesión nueva.",
498519
+ "blunTui.startup.sessionNotInitialized": "No se pudo inicializar la sesión durante el arranque.",
498520
+ "blunTui.input.replayBlocked": "No se puede enviar ninguna entrada mientras se reproduce el historial de la sesión.",
498521
+ "blunTui.shell.noSession": "No hay ninguna sesión activa para el comando de shell.",
498522
+ "blunTui.shell.runFailed": "El comando de shell falló: {error}",
498523
+ "blunTui.shell.cancelFailed": "No se pudo cancelar el comando de shell: {error}",
498524
+ "blunTui.channel.steerFailed": "No se pudo añadir el mensaje del canal a la respuesta en curso: {error}",
498525
+ "blunTui.session.sendFailed": "No se pudo enviar: {error}",
498526
+ "blunTui.media.imageUnsupported": "El modelo actual no admite entradas de imagen.",
498527
+ "blunTui.media.videoUnsupported": "El modelo actual no admite entradas de vídeo.",
498528
+ "blunTui.skill.failed": "El skill «{skillName}» falló: {error}",
498529
+ "blunTui.pluginCommand.failed": "El comando «{command}» falló: {error}",
498530
+ "blunTui.steer.failed": "No se pudo reorientar la respuesta: {error}",
498531
+ "blunTui.session.otherWorkDir": "La sesión actual se encuentra en otro directorio de trabajo.",
498532
+ "blunTui.session.resumeCommand": "Para reanudarla, ejecuta: {command}",
498533
+ "blunTui.clipboard.commandCopied": "Comando copiado al portapapeles",
498534
+ "blunTui.clipboard.commandCopyFailed": "No se pudo copiar el comando al portapapeles",
498535
+ "blunTui.session.alreadyCurrent": "Esta sesión ya está activa.",
498536
+ "blunTui.session.switchStreamingBlocked": "No se puede cambiar de sesión mientras se genera una respuesta. Pulsa primero Esc o Ctrl-C.",
498537
+ "blunTui.session.switchReplayBlocked": "No se puede cambiar de sesión mientras se reproduce el historial.",
498538
+ "blunTui.session.resumeFailed": "No se pudo reanudar la sesión {sessionId}: {error}",
498539
+ "blunTui.session.resumed": "Sesión reanudada ({sessionId}).",
498540
+ "blunTui.session.replayFailed": "No se pudo reproducir el historial de la sesión: {error}",
498541
+ "blunTui.session.createReplayBlocked": "No se puede iniciar una sesión nueva mientras se reproduce el historial.",
498542
+ "blunTui.session.createFailed": "No se pudo iniciar una sesión nueva: {error}",
498543
+ "blunTui.session.postCreateFailed": "No se pudo configurar la sesión recién creada: {error}",
498544
+ "blunTui.session.started": "Se inició una sesión nueva ({sessionId}).",
498545
+ "blunTui.error": "Error: {message}",
498546
+ "blunTui.login.title": "Iniciar sesión en BLUN",
498547
+ "blunTui.login.hint": "Pulsa Ctrl-C para cancelar",
498548
+ "blunTui.login.waiting": "Esperando autorización…",
498549
+ "blunTui.detach.noShell": "No hay ningún comando de shell en ejecución.",
498550
+ "blunTui.detach.shellStarting": "El comando todavía se está iniciando; inténtalo de nuevo.",
498551
+ "blunTui.detach.shellFinished": "El comando ya ha finalizado.",
498552
+ "blunTui.detach.moveFailed": "No se pudo mover a segundo plano: {error}",
498553
+ "blunTui.detach.movedTranscript": "Se movió a segundo plano.",
498554
+ "blunTui.detach.movedView": "Se movió a segundo plano. Consulta /tasks.",
498555
+ "blunTui.detach.noForeground": "No hay ninguna tarea en ejecución en primer plano.",
498556
+ "blunTui.detach.listFailed": "No se pudieron obtener las tareas: {error}",
498557
+ "blunTui.detach.taskFailed": "No se pudo mover la tarea {taskId} a segundo plano: {error}",
498558
+ "blunTui.detach.finished.one": "La tarea ya ha finalizado.",
498559
+ "blunTui.detach.finished.other": "Las tareas ya han finalizado.",
498560
+ "blunTui.detach.moved.one": "Se ha movido {count} tarea a segundo plano.",
498561
+ "blunTui.detach.moved.other": "Se han movido {count} tareas a segundo plano.",
498562
+ "blunTui.detach.partial": "Se han movido {detached} de {total} tareas a segundo plano.",
498563
+ "blunTui.detach.viewSuffix": "Consulta /tasks.",
498564
+ "blunTui.startup.flagsFailed": "No se pudieron aplicar las opciones de inicio: {error}",
498565
+ "blunTui.notification.approvalRequired": "Se requiere aprobación de BLUN",
498566
+ "blunTui.notification.answerRequired": "BLUN necesita tu respuesta",
498567
+ "blunTui.telegram.fallbackDelivered": "La respuesta se envió automáticamente a Telegram (modo alternativo).",
498568
+ "blunTui.telegram.attachDisabled": "Conexión con Telegram desactivada mediante BLUN_TELEGRAM_ATTACH=off — modo headless activo.",
498569
+ "blunTui.telegram.noToken": "Conexión con Telegram: no se detectó ningún token — modo headless activo.",
498570
+ "blunTui.telegram.attached": "Canal de Telegram conectado (PID de lease {pid}) — los mensajes aparecen en esta ventana.",
498571
+ "blunTui.auto.status": "Automático: {label}",
498572
+ "blunTui.activity.thinking": "{name} está pensando…",
498573
+ "blunTui.activity.working": "{name} está trabajando…",
498574
+ "blunTui.activity.composing": "trabajando...",
498575
+ "blunTui.activity.tokens": "tokens"
498576
+ },
498577
+ fr: {
498578
+ "blunTui.provider.modelAdded.one": "{providerName} · +{count} modèle.",
498579
+ "blunTui.provider.modelAdded.other": "{providerName} · +{count} modèles.",
498580
+ "blunTui.provider.refreshSkipped": "Actualisation de {provider} ignorée : {reason}",
498581
+ "blunTui.warning": "Avertissement : {warning}",
498582
+ "blunTui.startup.sessionNotFound": "Session « {sessionId} » introuvable.",
498583
+ "blunTui.startup.sessionDifferentDirectory": "La session « {sessionId} » a été créée dans un autre répertoire de travail.",
498584
+ "blunTui.startup.noSessionsToContinue": "Aucune session à reprendre dans « {workDir} » ; démarrage d’une nouvelle session.",
498585
+ "blunTui.startup.sessionNotInitialized": "La session de démarrage n’a pas été initialisée.",
498586
+ "blunTui.input.replayBlocked": "Impossible d’envoyer une saisie pendant la relecture de l’historique de la session.",
498587
+ "blunTui.shell.noSession": "Aucune session active pour la commande shell.",
498588
+ "blunTui.shell.runFailed": "Échec de la commande shell : {error}",
498589
+ "blunTui.shell.cancelFailed": "Impossible d’annuler la commande shell : {error}",
498590
+ "blunTui.channel.steerFailed": "Impossible d’ajouter le message du canal à la réponse en cours : {error}",
498591
+ "blunTui.session.sendFailed": "Échec de l’envoi : {error}",
498592
+ "blunTui.media.imageUnsupported": "Le modèle actuel ne prend pas en charge les images en entrée.",
498593
+ "blunTui.media.videoUnsupported": "Le modèle actuel ne prend pas en charge les vidéos en entrée.",
498594
+ "blunTui.skill.failed": "Échec du skill « {skillName} » : {error}",
498595
+ "blunTui.pluginCommand.failed": "Échec de la commande « {command} » : {error}",
498596
+ "blunTui.steer.failed": "Impossible de réorienter la réponse : {error}",
498597
+ "blunTui.session.otherWorkDir": "La session actuelle se trouve dans un autre répertoire de travail.",
498598
+ "blunTui.session.resumeCommand": "Pour la reprendre, exécutez : {command}",
498599
+ "blunTui.clipboard.commandCopied": "Commande copiée dans le presse-papiers",
498600
+ "blunTui.clipboard.commandCopyFailed": "Impossible de copier la commande dans le presse-papiers",
498601
+ "blunTui.session.alreadyCurrent": "Cette session est déjà active.",
498602
+ "blunTui.session.switchStreamingBlocked": "Impossible de changer de session pendant la génération d’une réponse. Appuyez d’abord sur Esc ou Ctrl-C.",
498603
+ "blunTui.session.switchReplayBlocked": "Impossible de changer de session pendant la relecture de l’historique.",
498604
+ "blunTui.session.resumeFailed": "Impossible de reprendre la session {sessionId} : {error}",
498605
+ "blunTui.session.resumed": "Session reprise ({sessionId}).",
498606
+ "blunTui.session.replayFailed": "Impossible de relire l’historique de la session : {error}",
498607
+ "blunTui.session.createReplayBlocked": "Impossible de démarrer une nouvelle session pendant la relecture de l’historique.",
498608
+ "blunTui.session.createFailed": "Impossible de démarrer une nouvelle session : {error}",
498609
+ "blunTui.session.postCreateFailed": "Impossible de configurer la nouvelle session : {error}",
498610
+ "blunTui.session.started": "Nouvelle session démarrée ({sessionId}).",
498611
+ "blunTui.error": "Erreur : {message}",
498612
+ "blunTui.login.title": "Se connecter à BLUN",
498613
+ "blunTui.login.hint": "Appuyez sur Ctrl-C pour annuler",
498614
+ "blunTui.login.waiting": "En attente de l’autorisation…",
498615
+ "blunTui.detach.noShell": "Aucune commande shell en cours.",
498616
+ "blunTui.detach.shellStarting": "La commande est encore en cours de démarrage — réessayez.",
498617
+ "blunTui.detach.shellFinished": "La commande est déjà terminée.",
498618
+ "blunTui.detach.moveFailed": "Impossible de passer la commande en arrière-plan : {error}",
498619
+ "blunTui.detach.movedTranscript": "Commande passée en arrière-plan.",
498620
+ "blunTui.detach.movedView": "Commande passée en arrière-plan. Consultez /tasks.",
498621
+ "blunTui.detach.noForeground": "Aucune tâche en cours au premier plan.",
498622
+ "blunTui.detach.listFailed": "Impossible de répertorier les tâches : {error}",
498623
+ "blunTui.detach.taskFailed": "Impossible de passer la tâche {taskId} en arrière-plan : {error}",
498624
+ "blunTui.detach.finished.one": "La tâche est déjà terminée.",
498625
+ "blunTui.detach.finished.other": "Les tâches sont déjà terminées.",
498626
+ "blunTui.detach.moved.one": "{count} tâche passée en arrière-plan.",
498627
+ "blunTui.detach.moved.other": "{count} tâches passées en arrière-plan.",
498628
+ "blunTui.detach.partial": "{detached} tâches sur {total} passées en arrière-plan.",
498629
+ "blunTui.detach.viewSuffix": "Consultez /tasks.",
498630
+ "blunTui.startup.flagsFailed": "Impossible d’appliquer les options de démarrage : {error}",
498631
+ "blunTui.notification.approvalRequired": "Approbation BLUN requise",
498632
+ "blunTui.notification.answerRequired": "BLUN attend votre réponse",
498633
+ "blunTui.telegram.fallbackDelivered": "Réponse envoyée automatiquement sur Telegram (solution de secours).",
498634
+ "blunTui.telegram.attachDisabled": "Connexion à Telegram désactivée via BLUN_TELEGRAM_ATTACH=off — mode headless actif.",
498635
+ "blunTui.telegram.noToken": "Connexion à Telegram\xA0: aucun jeton détecté — mode headless actif.",
498636
+ "blunTui.telegram.attached": "Canal Telegram connecté (PID de lease\xA0: {pid}) — les messages apparaissent dans cette fenêtre.",
498637
+ "blunTui.auto.status": "Auto\xA0: {label}",
498638
+ "blunTui.activity.thinking": "{name} réfléchit…",
498639
+ "blunTui.activity.working": "{name} travaille…",
498640
+ "blunTui.activity.composing": "travail en cours...",
498641
+ "blunTui.activity.tokens": "jetons"
498642
+ },
498643
+ sv: {
498644
+ "blunTui.provider.modelAdded.one": "{providerName} · +{count} modell.",
498645
+ "blunTui.provider.modelAdded.other": "{providerName} · +{count} modeller.",
498646
+ "blunTui.provider.refreshSkipped": "Uppdateringen av {provider} hoppades över: {reason}",
498647
+ "blunTui.warning": "Varning: {warning}",
498648
+ "blunTui.startup.sessionNotFound": "Sessionen ”{sessionId}” hittades inte.",
498649
+ "blunTui.startup.sessionDifferentDirectory": "Sessionen ”{sessionId}” skapades i en annan arbetskatalog.",
498650
+ "blunTui.startup.noSessionsToContinue": "Det finns inga sessioner att återuppta i ”{workDir}”; en ny session startas.",
498651
+ "blunTui.startup.sessionNotInitialized": "Sessionen initierades inte vid start.",
498652
+ "blunTui.input.replayBlocked": "Det går inte att skicka indata medan sessionshistoriken spelas upp.",
498653
+ "blunTui.shell.noSession": "Det finns ingen aktiv session för skalkommandot.",
498654
+ "blunTui.shell.runFailed": "Skalkommandot misslyckades: {error}",
498655
+ "blunTui.shell.cancelFailed": "Det gick inte att avbryta skalkommandot: {error}",
498656
+ "blunTui.channel.steerFailed": "Det gick inte att lägga till kanalmeddelandet i det pågående svaret: {error}",
498657
+ "blunTui.session.sendFailed": "Det gick inte att skicka: {error}",
498658
+ "blunTui.media.imageUnsupported": "Den aktuella modellen stöder inte bildindata.",
498659
+ "blunTui.media.videoUnsupported": "Den aktuella modellen stöder inte videoindata.",
498660
+ "blunTui.skill.failed": "Skill ”{skillName}” misslyckades: {error}",
498661
+ "blunTui.pluginCommand.failed": "Kommandot ”{command}” misslyckades: {error}",
498662
+ "blunTui.steer.failed": "Det gick inte att styra om svaret: {error}",
498663
+ "blunTui.session.otherWorkDir": "Den aktuella sessionen finns i en annan arbetskatalog.",
498664
+ "blunTui.session.resumeCommand": "Kör följande för att återuppta den: {command}",
498665
+ "blunTui.clipboard.commandCopied": "Kommandot kopierades till urklipp",
498666
+ "blunTui.clipboard.commandCopyFailed": "Det gick inte att kopiera kommandot till urklipp",
498667
+ "blunTui.session.alreadyCurrent": "Den här sessionen är redan aktiv.",
498668
+ "blunTui.session.switchStreamingBlocked": "Det går inte att byta session medan ett svar genereras. Tryck först på Esc eller Ctrl-C.",
498669
+ "blunTui.session.switchReplayBlocked": "Det går inte att byta session medan historiken spelas upp.",
498670
+ "blunTui.session.resumeFailed": "Det gick inte att återuppta sessionen {sessionId}: {error}",
498671
+ "blunTui.session.resumed": "Sessionen återupptogs ({sessionId}).",
498672
+ "blunTui.session.replayFailed": "Det gick inte att spela upp sessionshistoriken: {error}",
498673
+ "blunTui.session.createReplayBlocked": "Det går inte att starta en ny session medan historiken spelas upp.",
498674
+ "blunTui.session.createFailed": "Det gick inte att starta en ny session: {error}",
498675
+ "blunTui.session.postCreateFailed": "Det gick inte att konfigurera den nya sessionen: {error}",
498676
+ "blunTui.session.started": "En ny session startades ({sessionId}).",
498677
+ "blunTui.error": "Fel: {message}",
498678
+ "blunTui.login.title": "Logga in på BLUN",
498679
+ "blunTui.login.hint": "Tryck på Ctrl-C för att avbryta",
498680
+ "blunTui.login.waiting": "Väntar på auktorisering…",
498681
+ "blunTui.detach.noShell": "Inget skalkommando körs.",
498682
+ "blunTui.detach.shellStarting": "Kommandot håller fortfarande på att startas – försök igen.",
498683
+ "blunTui.detach.shellFinished": "Kommandot är redan slutfört.",
498684
+ "blunTui.detach.moveFailed": "Det gick inte att flytta kommandot till bakgrunden: {error}",
498685
+ "blunTui.detach.movedTranscript": "Flyttades till bakgrunden.",
498686
+ "blunTui.detach.movedView": "Flyttades till bakgrunden. Visa med /tasks.",
498687
+ "blunTui.detach.noForeground": "Ingen uppgift körs i förgrunden.",
498688
+ "blunTui.detach.listFailed": "Det gick inte att lista uppgifterna: {error}",
498689
+ "blunTui.detach.taskFailed": "Det gick inte att flytta uppgiften {taskId} till bakgrunden: {error}",
498690
+ "blunTui.detach.finished.one": "Uppgiften är redan slutförd.",
498691
+ "blunTui.detach.finished.other": "Uppgifterna är redan slutförda.",
498692
+ "blunTui.detach.moved.one": "{count} uppgift flyttades till bakgrunden.",
498693
+ "blunTui.detach.moved.other": "{count} uppgifter flyttades till bakgrunden.",
498694
+ "blunTui.detach.partial": "{detached} av {total} uppgifter flyttades till bakgrunden.",
498695
+ "blunTui.detach.viewSuffix": "Visa med /tasks.",
498696
+ "blunTui.startup.flagsFailed": "Det gick inte att tillämpa startalternativen: {error}",
498697
+ "blunTui.notification.approvalRequired": "BLUN-godkännande krävs",
498698
+ "blunTui.notification.answerRequired": "BLUN behöver ditt svar",
498699
+ "blunTui.telegram.fallbackDelivered": "Svaret skickades automatiskt till Telegram (reservlösning).",
498700
+ "blunTui.telegram.attachDisabled": "Telegram-anslutningen inaktiverades via BLUN_TELEGRAM_ATTACH=off — headless-läget är aktivt.",
498701
+ "blunTui.telegram.noToken": "Telegram-anslutning: ingen token hittades — headless-läget är aktivt.",
498702
+ "blunTui.telegram.attached": "Telegram-kanalen är ansluten (lease-PID {pid}) — meddelanden visas i det här fönstret.",
498703
+ "blunTui.auto.status": "Automatiskt: {label}",
498704
+ "blunTui.activity.thinking": "{name} tänker…",
498705
+ "blunTui.activity.working": "{name} arbetar…",
498706
+ "blunTui.activity.composing": "arbetar...",
498707
+ "blunTui.activity.tokens": "token"
498708
+ },
498709
+ cs: {
498710
+ "blunTui.provider.modelAdded.one": "{providerName} · +{count} model.",
498711
+ "blunTui.provider.modelAdded.other": "{providerName} · nové modely: +{count}.",
498712
+ "blunTui.provider.refreshSkipped": "Přeskočeno obnovení {provider}: {reason}",
498713
+ "blunTui.warning": "Upozornění: {warning}",
498714
+ "blunTui.startup.sessionNotFound": "Relace \"{sessionId}\" nebyla nalezena.",
498715
+ "blunTui.startup.sessionDifferentDirectory": "Relace \"{sessionId}\" byla vytvořena v jiném adresáři.",
498716
+ "blunTui.startup.noSessionsToContinue": "V adresáři \"{workDir}\" nejsou žádné relace, ve kterých by bylo možné pokračovat; spouští se nová relace.",
498717
+ "blunTui.startup.sessionNotInitialized": "Relace při spuštění nebyla inicializována.",
498718
+ "blunTui.input.replayBlocked": "Nelze odeslat vstup během přehrávání historie relace.",
498719
+ "blunTui.shell.noSession": "Žádná aktivní relace pro příkaz shellu.",
498720
+ "blunTui.shell.runFailed": "Příkaz shellu selhal: {error}",
498721
+ "blunTui.shell.cancelFailed": "Selhalo zrušení příkazu shellu: {error}",
498722
+ "blunTui.channel.steerFailed": "Předání zprávy z kanálu do probíhající odpovědi selhalo: {error}",
498723
+ "blunTui.session.sendFailed": "Selhalo odeslání: {error}",
498724
+ "blunTui.media.imageUnsupported": "Aktuální model nepodporuje vstup obrázku.",
498725
+ "blunTui.media.videoUnsupported": "Aktuální model nepodporuje vstup videa.",
498726
+ "blunTui.skill.failed": "Dovednost \"{skillName}\" selhala: {error}",
498727
+ "blunTui.pluginCommand.failed": "Příkaz \"{command}\" selhal: {error}",
498728
+ "blunTui.steer.failed": "Doplnění pokynu selhalo: {error}",
498729
+ "blunTui.session.otherWorkDir": "Aktuální relace je v jiném pracovním adresáři.",
498730
+ "blunTui.session.resumeCommand": "Chcete-li pokračovat, spusťte: {command}",
498731
+ "blunTui.clipboard.commandCopied": "Příkaz zkopírován do schránky",
498732
+ "blunTui.clipboard.commandCopyFailed": "Selhalo kopírování příkazu do schránky",
498733
+ "blunTui.session.alreadyCurrent": "Již jste v této relaci.",
498734
+ "blunTui.session.switchStreamingBlocked": "Nelze přepínat relace během streamování — nejdříve stiskněte Esc nebo Ctrl-C.",
498735
+ "blunTui.session.switchReplayBlocked": "Nelze přepínat relace během přehrávání historie.",
498736
+ "blunTui.session.resumeFailed": "Selhalo obnovení relace {sessionId}: {error}",
498737
+ "blunTui.session.resumed": "Obnovena relace ({sessionId}).",
498738
+ "blunTui.session.replayFailed": "Selhalo přehrávání historie relace: {error}",
498739
+ "blunTui.session.createReplayBlocked": "Nelze spustit novou relaci během přehrávání historie.",
498740
+ "blunTui.session.createFailed": "Selhalo spuštění nové relace: {error}",
498741
+ "blunTui.session.postCreateFailed": "Selhalo nastavení po vytvoření: {error}",
498742
+ "blunTui.session.started": "Spuštěna nová relace ({sessionId}).",
498743
+ "blunTui.error": "Chyba: {message}",
498744
+ "blunTui.login.title": "Přihlaste se do BLUN",
498745
+ "blunTui.login.hint": "Stiskněte Ctrl-C pro zrušení",
498746
+ "blunTui.login.waiting": "Čekání na autorizaci…",
498747
+ "blunTui.detach.noShell": "Žádný příkaz shellu není spuštěn.",
498748
+ "blunTui.detach.shellStarting": "Příkaz se stále spouští — zkuste znovu.",
498749
+ "blunTui.detach.shellFinished": "Příkaz již skončil.",
498750
+ "blunTui.detach.moveFailed": "Selhalo přesunutí na pozadí: {error}",
498751
+ "blunTui.detach.movedTranscript": "Přesunuto na pozadí.",
498752
+ "blunTui.detach.movedView": "Přesunuto na pozadí. Zobrazíte příkazem /tasks.",
498753
+ "blunTui.detach.noForeground": "Žádný úkol na popředí není spuštěn.",
498754
+ "blunTui.detach.listFailed": "Selhalo vypsání úkolů: {error}",
498755
+ "blunTui.detach.taskFailed": "Přesunutí úlohy {taskId} na pozadí selhalo: {error}",
498756
+ "blunTui.detach.finished.one": "Úkol již skončil.",
498757
+ "blunTui.detach.finished.other": "Úkoly již skončily.",
498758
+ "blunTui.detach.moved.one": "Přesunut {count} úkol na pozadí.",
498759
+ "blunTui.detach.moved.other": "Úkoly přesunuté na pozadí: {count}.",
498760
+ "blunTui.detach.partial": "Přesunuto {detached} z {total} úkolů na pozadí.",
498761
+ "blunTui.detach.viewSuffix": "/tasks k zobrazení.",
498762
+ "blunTui.startup.flagsFailed": "Nepodařilo se použít spouštěcí příznaky: {error}",
498763
+ "blunTui.notification.approvalRequired": "Vyžadováno schválení BLUN",
498764
+ "blunTui.notification.answerRequired": "BLUN potřebuje vaši odpověď",
498765
+ "blunTui.telegram.fallbackDelivered": "Odpověď byla automaticky doručena do Telegramu (náhradním způsobem).",
498766
+ "blunTui.telegram.attachDisabled": "Připojení Telegramu je zakázáno nastavením BLUN_TELEGRAM_ATTACH=off — aktivní je režim bez uživatelského rozhraní.",
498767
+ "blunTui.telegram.noToken": "Připojení Telegramu: nebyl nalezen žádný token — aktivní je režim bez uživatelského rozhraní.",
498768
+ "blunTui.telegram.attached": "Kanál Telegramu je připojen (PID držitele připojení {pid}) — zprávy se zobrazují v tomto okně.",
498769
+ "blunTui.auto.status": "Automaticky: {label}",
498770
+ "blunTui.activity.thinking": "{name} přemýšlí…",
498771
+ "blunTui.activity.working": "{name} pracuje…",
498772
+ "blunTui.activity.composing": "pracuje…",
498773
+ "blunTui.activity.tokens": "Tokeny"
498774
+ }
498775
+ });
498776
+ //#endregion
498110
498777
  //#region src/tui/components/panes/btw-panel.ts
498111
498778
  const MIN_COLLAPSED_PANEL_LINES = 3;
498112
498779
  var BtwPanelComponent = class {
@@ -498117,6 +498784,8 @@ var BtwPanelComponent = class {
498117
498784
  followTail = true;
498118
498785
  scrollTop = 0;
498119
498786
  maxScrollTop = 0;
498787
+ spinnerFrame = 0;
498788
+ liveStatusTimer;
498120
498789
  constructor(options) {
498121
498790
  this.options = options;
498122
498791
  }
@@ -498128,10 +498797,12 @@ var BtwPanelComponent = class {
498128
498797
  this.transientNotices.length = 0;
498129
498798
  this.turns.push({
498130
498799
  prompt: normalized,
498800
+ startedAtMs: Date.now(),
498131
498801
  answer: "",
498132
498802
  thinking: "",
498133
498803
  phase: "running"
498134
498804
  });
498805
+ this.startLiveStatusTimer();
498135
498806
  this.options.onPrompt(normalized);
498136
498807
  }
498137
498808
  addTransientNotice(message) {
@@ -498148,31 +498819,48 @@ var BtwPanelComponent = class {
498148
498819
  if (turn === void 0) return;
498149
498820
  turn.thinking += delta;
498150
498821
  }
498822
+ restartCurrentTurn(notice) {
498823
+ const turn = this.currentTurn();
498824
+ if (turn === void 0 || turn.phase !== "running") return;
498825
+ turn.startedAtMs = Date.now();
498826
+ turn.answer = "";
498827
+ turn.thinking = "";
498828
+ turn.error = void 0;
498829
+ this.transientNotices.length = 0;
498830
+ this.transientNotices.push(notice);
498831
+ }
498151
498832
  markDone(resultSummary) {
498152
498833
  const turn = this.currentTurn();
498153
498834
  if (turn === void 0) return;
498154
498835
  if (turn.answer.trim().length === 0 && resultSummary !== void 0) turn.answer = resultSummary;
498155
498836
  this.transientNotices.length = 0;
498156
498837
  turn.phase = "done";
498838
+ this.stopLiveStatusTimer();
498157
498839
  }
498158
498840
  markFailed(error) {
498159
498841
  const turn = this.currentTurn();
498160
498842
  if (turn === void 0 || turn.phase !== "running") {
498161
498843
  this.turns.push({
498162
498844
  prompt: "",
498845
+ startedAtMs: Date.now(),
498163
498846
  answer: "",
498164
498847
  thinking: "",
498165
498848
  error,
498166
498849
  phase: "failed"
498167
498850
  });
498168
498851
  this.transientNotices.length = 0;
498852
+ this.stopLiveStatusTimer();
498169
498853
  return;
498170
498854
  }
498171
498855
  turn.error = error;
498172
498856
  this.transientNotices.length = 0;
498173
498857
  turn.phase = "failed";
498858
+ this.stopLiveStatusTimer();
498174
498859
  }
498175
498860
  invalidate() {}
498861
+ dispose() {
498862
+ this.stopLiveStatusTimer();
498863
+ }
498176
498864
  render(width) {
498177
498865
  const safeWidth = Math.max(4, width);
498178
498866
  const contentWidth = Math.max(1, safeWidth - 4);
@@ -498245,7 +498933,8 @@ var BtwPanelComponent = class {
498245
498933
  const thinkingLines = new Text(chalk.hex(currentTheme.palette.textDim)(thinking), 0, 0).render(width);
498246
498934
  const visibleThinking = thinkingLines.length > 2 ? thinkingLines.slice(thinkingLines.length - 2) : thinkingLines;
498247
498935
  lines.push(...visibleThinking);
498248
- } else if (turn.error === void 0) lines.push(chalk.hex(currentTheme.palette.textDim)(uiText("btw.panel.waiting")));
498936
+ }
498937
+ if (turn.phase === "running" && turn.error === void 0) lines.push(this.renderLiveStatus(turn));
498249
498938
  if (turn.error !== void 0) {
498250
498939
  const error = chalk.hex(currentTheme.palette.error)(turn.error);
498251
498940
  lines.push(...new Text(error, 0, 0).render(width));
@@ -498262,6 +498951,26 @@ var BtwPanelComponent = class {
498262
498951
  currentTurn() {
498263
498952
  return this.turns.at(-1);
498264
498953
  }
498954
+ renderLiveStatus(turn) {
498955
+ const elapsedSeconds = Math.max(0, Math.floor((Date.now() - turn.startedAtMs) / 1e3));
498956
+ const outputTokens = estimateLiveOutputTokens(turn.thinking + turn.answer);
498957
+ const frame = BLUN_SPINNER_FRAMES[this.spinnerFrame] ?? BLUN_SPINNER_FRAMES[0];
498958
+ const label = uiText("blunTui.activity.thinking", { name: "BTW" });
498959
+ const metrics = `(${formatLiveElapsed(elapsedSeconds)} · ↓ ~${formatLiveTokenCount(outputTokens)} ${uiText("blunTui.activity.tokens")})`;
498960
+ return chalk.hex(currentTheme.palette.accent)(`${frame} `) + chalk.hex(currentTheme.palette.accent).bold(label) + " " + chalk.hex(currentTheme.palette.text)(metrics);
498961
+ }
498962
+ startLiveStatusTimer() {
498963
+ if (this.liveStatusTimer !== void 0) return;
498964
+ this.liveStatusTimer = setInterval(() => {
498965
+ this.spinnerFrame = (this.spinnerFrame + 1) % BLUN_SPINNER_FRAMES.length;
498966
+ this.options.requestRender();
498967
+ }, 120);
498968
+ }
498969
+ stopLiveStatusTimer() {
498970
+ if (this.liveStatusTimer === void 0) return;
498971
+ clearInterval(this.liveStatusTimer);
498972
+ this.liveStatusTimer = void 0;
498973
+ }
498265
498974
  isRunning() {
498266
498975
  return this.currentTurn()?.phase === "running";
498267
498976
  }
@@ -498328,6 +499037,7 @@ function formatHookResultBody(event) {
498328
499037
  }
498329
499038
  //#endregion
498330
499039
  //#region src/tui/controllers/btw-panel.ts
499040
+ const BTW_INACTIVITY_TIMEOUT_MS = 12e4;
498331
499041
  var BtwPanelController = class {
498332
499042
  host;
498333
499043
  active;
@@ -498341,13 +499051,16 @@ var BtwPanelController = class {
498341
499051
  markdownTheme: createMarkdownTheme(),
498342
499052
  canUseScrollKeys: () => this.host.state.editor.getText().length === 0,
498343
499053
  terminalRows: () => this.host.state.terminal.rows,
499054
+ requestRender: () => this.host.state.ui.requestRender(),
498344
499055
  onPrompt: (prompt) => {
498345
- this.promptAgent(agentId, prompt, panel);
499056
+ this.promptPanel(panel, prompt);
498346
499057
  }
498347
499058
  });
498348
499059
  this.active = {
498349
499060
  agentId,
498350
- panel
499061
+ panel,
499062
+ prompt: "",
499063
+ retryCount: 0
498351
499064
  };
498352
499065
  this.panelsByAgentId.set(agentId, panel);
498353
499066
  this.mount(panel);
@@ -498356,6 +499069,8 @@ var BtwPanelController = class {
498356
499069
  clear() {
498357
499070
  const active = this.active;
498358
499071
  if (active !== void 0 && this.shouldCancelOnUnmount(active.panel)) this.cancelAgent(active.agentId);
499072
+ for (const panel of this.panelsByAgentId.values()) panel.dispose();
499073
+ this.clearInactivityTimer(active);
498359
499074
  this.active = void 0;
498360
499075
  this.panelsByAgentId.clear();
498361
499076
  this.host.state.btwPanelContainer.clear();
@@ -498398,19 +499113,23 @@ var BtwPanelController = class {
498398
499113
  if (panel === void 0) return false;
498399
499114
  switch (event.type) {
498400
499115
  case "assistant.delta":
499116
+ this.clearInactivityTimer(this.activeForAgent(event.agentId));
498401
499117
  panel.appendAnswer(event.delta);
498402
499118
  this.host.state.ui.requestRender();
498403
499119
  return true;
498404
499120
  case "thinking.delta":
498405
499121
  panel.appendThinking(event.delta);
499122
+ this.armInactivityTimer(this.activeForAgent(event.agentId));
498406
499123
  this.host.state.ui.requestRender();
498407
499124
  return true;
498408
499125
  case "hook.result":
499126
+ this.clearInactivityTimer(this.activeForAgent(event.agentId));
498409
499127
  panel.appendAnswer(formatHookResultPlain(event));
498410
499128
  this.host.state.ui.requestRender();
498411
499129
  return true;
498412
499130
  case "turn.ended":
498413
- if (event.reason === "completed") panel.markDone();
499131
+ this.clearInactivityTimer(this.activeForAgent(event.agentId));
499132
+ if (event.reason === "completed") panel.markDone(uiText("eventPayload.modelEmptyResponse"));
498414
499133
  else panel.markFailed(formatBtwTurnEnd(event));
498415
499134
  this.host.state.ui.requestRender();
498416
499135
  return true;
@@ -498427,7 +499146,9 @@ var BtwPanelController = class {
498427
499146
  }
498428
499147
  close(panel) {
498429
499148
  if (!this.host.state.btwPanelContainer.children.includes(panel)) return;
499149
+ this.clearInactivityTimer(this.active?.panel === panel ? this.active : void 0);
498430
499150
  this.unregister(panel);
499151
+ panel.dispose();
498431
499152
  this.host.state.btwPanelContainer.clear();
498432
499153
  this.host.state.editor.connectedAbove = false;
498433
499154
  this.host.state.ui.setFocus(this.host.state.editor);
@@ -498442,6 +499163,14 @@ var BtwPanelController = class {
498442
499163
  active.panel.addTransientNotice(uiText("btw.busy"));
498443
499164
  this.host.state.ui.requestRender();
498444
499165
  }
499166
+ promptPanel(panel, prompt) {
499167
+ const active = this.active;
499168
+ if (active === void 0 || active.panel !== panel) return;
499169
+ active.prompt = prompt;
499170
+ active.retryCount = 0;
499171
+ this.promptAgent(active.agentId, prompt, panel);
499172
+ this.armInactivityTimer(active);
499173
+ }
498445
499174
  promptAgent(agentId, prompt, panel) {
498446
499175
  const session = this.host.session;
498447
499176
  if (session === void 0) {
@@ -498450,16 +499179,76 @@ var BtwPanelController = class {
498450
499179
  return;
498451
499180
  }
498452
499181
  this.withInteractiveAgent(agentId, () => session.prompt(prompt)).catch((error) => {
499182
+ if (this.panelsByAgentId.get(agentId) !== panel) return;
499183
+ this.clearInactivityTimer(this.activeForAgent(agentId));
498453
499184
  panel.markFailed(uiText("btw.error.send", { error: formatErrorMessage$2(error) }));
498454
499185
  this.host.state.ui.requestRender();
498455
499186
  });
498456
499187
  }
498457
499188
  async cancelAgent(agentId) {
498458
499189
  const session = this.host.session;
498459
- if (session === void 0) return;
498460
- await this.withInteractiveAgent(agentId, () => session.cancel()).catch((error) => {
498461
- this.host.showError(uiText("btw.error.cancel", { error: formatErrorMessage$2(error) }));
498462
- });
499190
+ if (session === void 0) return noActiveSessionMessage();
499191
+ try {
499192
+ await this.withInteractiveAgent(agentId, () => session.cancel());
499193
+ return;
499194
+ } catch (error) {
499195
+ const message = formatErrorMessage$2(error);
499196
+ this.host.showError(uiText("btw.error.cancel", { error: message }));
499197
+ return message;
499198
+ }
499199
+ }
499200
+ activeForAgent(agentId) {
499201
+ return this.active?.agentId === agentId ? this.active : void 0;
499202
+ }
499203
+ armInactivityTimer(active) {
499204
+ if (active === void 0 || !active.panel.isRunning()) return;
499205
+ this.clearInactivityTimer(active);
499206
+ active.inactivityTimer = setTimeout(() => {
499207
+ active.inactivityTimer = void 0;
499208
+ this.handleInactivity(active);
499209
+ }, BTW_INACTIVITY_TIMEOUT_MS);
499210
+ active.inactivityTimer.unref?.();
499211
+ }
499212
+ clearInactivityTimer(active) {
499213
+ if (active?.inactivityTimer === void 0) return;
499214
+ clearTimeout(active.inactivityTimer);
499215
+ active.inactivityTimer = void 0;
499216
+ }
499217
+ async handleInactivity(active) {
499218
+ if (this.active !== active || !active.panel.isRunning()) return;
499219
+ const staleAgentId = active.agentId;
499220
+ this.panelsByAgentId.delete(staleAgentId);
499221
+ const cancelError = await this.cancelAgent(staleAgentId);
499222
+ if (this.active !== active || !active.panel.isRunning()) return;
499223
+ if (cancelError !== void 0) {
499224
+ active.panel.markFailed(uiText("btw.error.cancel", { error: cancelError }));
499225
+ this.host.state.ui.requestRender();
499226
+ return;
499227
+ }
499228
+ if (active.retryCount >= 1) {
499229
+ active.panel.markFailed(uiText("btw.timeout"));
499230
+ this.host.state.ui.requestRender();
499231
+ return;
499232
+ }
499233
+ active.retryCount += 1;
499234
+ active.panel.restartCurrentTurn(uiText("btw.retrying"));
499235
+ try {
499236
+ const session = this.host.session;
499237
+ if (session === void 0) throw new Error(noActiveSessionMessage());
499238
+ const nextAgentId = await session.startBtw();
499239
+ if (this.active !== active || !active.panel.isRunning()) {
499240
+ await this.cancelAgent(nextAgentId);
499241
+ return;
499242
+ }
499243
+ active.agentId = nextAgentId;
499244
+ this.panelsByAgentId.set(nextAgentId, active.panel);
499245
+ this.promptAgent(nextAgentId, active.prompt, active.panel);
499246
+ this.armInactivityTimer(active);
499247
+ this.host.state.ui.requestRender();
499248
+ } catch (error) {
499249
+ active.panel.markFailed(uiText("btw.error.start", { error: formatErrorMessage$2(error) }));
499250
+ this.host.state.ui.requestRender();
499251
+ }
498463
499252
  }
498464
499253
  shouldCancelOnUnmount(panel) {
498465
499254
  return panel.isRunning() || panel.isEmpty();
@@ -503010,6 +503799,7 @@ var SessionEventHandler = class {
503010
503799
  synthetic: event.synthetic
503011
503800
  };
503012
503801
  const matchedCall = streamingUI.completeToolResult(event.toolCallId, resultData);
503802
+ if (matchedCall?.name === "GetMedia" && event.isError !== true) this.host.runChannelMediaFallback(event.output);
503013
503803
  this.subAgentEventHandler.handleAgentSwarmToolResult(event.toolCallId, resultData, event.isError === true);
503014
503804
  if (matchedCall !== void 0 && matchedCall.name === "TodoList" && !event.isError) {
503015
503805
  const rawTodos = matchedCall.args.todos;
@@ -503398,7 +504188,7 @@ var SessionEventHandler = class {
503398
504188
  }
503399
504189
  handleCompactionEnd(event) {
503400
504190
  this.host.setAppState({ contextTokens: event.projectedContextTokens ?? event.result.tokensAfter });
503401
- this.host.streamingUI.endCompaction(event.result.tokensBefore, event.result.tokensAfter);
504191
+ this.host.streamingUI.endCompaction(event.result.tokensBefore, event.result.tokensAfter, event.stageCount);
503402
504192
  this.lastCompactionInstruction = void 0;
503403
504193
  this.finishCompaction();
503404
504194
  }
@@ -506358,7 +507148,7 @@ var StreamingUIController = class {
506358
507148
  this._activeCompactionBlock.markDone();
506359
507149
  this._activeCompactionBlock = void 0;
506360
507150
  }
506361
- const block = new CompactionComponent(state.ui, instruction, currentWorkingTip()?.text, false);
507151
+ const block = new CompactionComponent(state.ui, instruction, currentWorkingTip()?.text, true);
506362
507152
  this._activeCompactionBlock = block;
506363
507153
  state.transcriptContainer.addChild(block);
506364
507154
  state.ui.requestRender();
@@ -506370,11 +507160,11 @@ var StreamingUIController = class {
506370
507160
  block.setProgress(progress);
506371
507161
  this.host.state.ui.requestRender();
506372
507162
  }
506373
- endCompaction(tokensBefore, tokensAfter) {
507163
+ endCompaction(tokensBefore, tokensAfter, stageCount) {
506374
507164
  this.host.state.footer.finishCompaction();
506375
507165
  const block = this._activeCompactionBlock;
506376
507166
  if (block === void 0) return;
506377
- block.markDone(tokensBefore, tokensAfter);
507167
+ block.markDone(tokensBefore, tokensAfter, stageCount);
506378
507168
  this._activeCompactionBlock = void 0;
506379
507169
  this._cancelledCompactionBlock = void 0;
506380
507170
  this.host.state.ui.requestRender();
@@ -509353,6 +510143,10 @@ function channelDir() {
509353
510143
  function outboxPath() {
509354
510144
  return join$4(channelDir(), "outbox.jsonl");
509355
510145
  }
510146
+ function mediaDir() {
510147
+ const base = process.env["BLUN_HOME"]?.trim();
510148
+ return join$4(base !== void 0 && base.length > 0 ? base : join$4(homedir(), ".blun"), "media");
510149
+ }
509356
510150
  /** Host-owned channel setting from process env or the channel .env. */
509357
510151
  function channelSetting(name) {
509358
510152
  const configured = process.env[name]?.trim();
@@ -509395,6 +510189,49 @@ function outboxGrewForChat(marker, chatId) {
509395
510189
  return false;
509396
510190
  }
509397
510191
  const TELEGRAM_TEXT_LIMIT = 4096;
510192
+ const TELEGRAM_ATTACHMENT_LIMIT = 50 * 1024 * 1024;
510193
+ function mediaTelegramTarget(filePath) {
510194
+ const lower = filePath.toLowerCase();
510195
+ if (/\.(?:png|jpe?g|webp|gif)$/u.test(lower)) return {
510196
+ method: "sendPhoto",
510197
+ field: "photo",
510198
+ mimeType: lower.endsWith(".png") ? "image/png" : lower.endsWith(".webp") ? "image/webp" : lower.endsWith(".gif") ? "image/gif" : "image/jpeg"
510199
+ };
510200
+ if (lower.endsWith(".mp4")) return {
510201
+ method: "sendVideo",
510202
+ field: "video",
510203
+ mimeType: "video/mp4"
510204
+ };
510205
+ if (/\.(?:mp3|wav)$/u.test(lower)) return {
510206
+ method: "sendAudio",
510207
+ field: "audio",
510208
+ mimeType: lower.endsWith(".mp3") ? "audio/mpeg" : "audio/wav"
510209
+ };
510210
+ return {
510211
+ method: "sendDocument",
510212
+ field: "document",
510213
+ mimeType: "application/octet-stream"
510214
+ };
510215
+ }
510216
+ function safeCompletedMediaPath(filePath) {
510217
+ try {
510218
+ const root = realpathSync(mediaDir());
510219
+ const resolved = realpathSync(filePath);
510220
+ const withinRoot = relative(root, resolved);
510221
+ if (withinRoot.length === 0 || withinRoot.startsWith("..") || isAbsolute(withinRoot)) return;
510222
+ const info = statSync(resolved);
510223
+ if (!info.isFile() || info.size <= 0 || info.size > TELEGRAM_ATTACHMENT_LIMIT) return void 0;
510224
+ return resolved;
510225
+ } catch {
510226
+ return;
510227
+ }
510228
+ }
510229
+ /** Extract the host-owned local result path from a successful GetMedia output. */
510230
+ function completedMediaLocalPath(output) {
510231
+ const text = Array.isArray(output) ? output.filter((part) => typeof part === "object" && part !== null && part.type === "text" && typeof part.text === "string").map((part) => part.text).join("\n") : typeof output === "string" ? output : "";
510232
+ const candidate = /Media job [^\r\n]+ is complete\. Local file: (.+?)\. The BLUN host/u.exec(text)?.[1]?.trim();
510233
+ return candidate === void 0 || candidate.length === 0 ? void 0 : safeCompletedMediaPath(candidate);
510234
+ }
509398
510235
  /** Telegram group/supergroup ids are negative; DMs are the positive user id. */
509399
510236
  function isGroupChat(chatId) {
509400
510237
  return chatId.startsWith("-");
@@ -509467,6 +510304,40 @@ async function sendReplyFallback(chatId, text, contextOnly = false) {
509467
510304
  return false;
509468
510305
  }
509469
510306
  }
510307
+ /**
510308
+ * Deliver a completed BLUN media file immediately for a channel-origin turn.
510309
+ * The path must resolve to a non-empty file below BLUN_HOME/media. Returns true
510310
+ * only after Telegram accepted the upload and the outbox receipt was appended.
510311
+ */
510312
+ async function sendMediaReplyFallback(chatId, filePath) {
510313
+ const safePath = safeCompletedMediaPath(filePath);
510314
+ const token = botToken();
510315
+ if (safePath === void 0 || token === void 0) return false;
510316
+ const target = mediaTelegramTarget(safePath);
510317
+ const form = new FormData();
510318
+ form.append("chat_id", chatId);
510319
+ form.append(target.field, new Blob([new Uint8Array(readFileSync(safePath))], { type: target.mimeType }), basename(safePath));
510320
+ try {
510321
+ const payload = await (await fetch(`https://api.telegram.org/bot${token}/${target.method}`, {
510322
+ method: "POST",
510323
+ body: form
510324
+ })).json();
510325
+ if (payload.ok !== true) return false;
510326
+ try {
510327
+ appendFileSync(outboxPath(), `${JSON.stringify({
510328
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
510329
+ direction: "out",
510330
+ kind: "media-reply-fallback",
510331
+ chat_id: String(chatId),
510332
+ message_ids: payload.result?.message_id === void 0 ? [] : [payload.result.message_id],
510333
+ files: [safePath]
510334
+ })}\n`);
510335
+ } catch {}
510336
+ return true;
510337
+ } catch {
510338
+ return false;
510339
+ }
510340
+ }
509470
510341
  //#endregion
509471
510342
  //#region src/tui/utils/dead-terminal.ts
509472
510343
  /**
@@ -510427,406 +511298,6 @@ function turnsToTrim(turns, maxTurns, hysteresis) {
510427
511298
  return toRemove;
510428
511299
  }
510429
511300
  //#endregion
510430
- //#region src/tui/blun-tui.copy.ts
510431
- registerUiCatalogFragment({
510432
- en: {
510433
- "blunTui.provider.modelAdded.one": "{providerName} · +{count} model.",
510434
- "blunTui.provider.modelAdded.other": "{providerName} · +{count} models.",
510435
- "blunTui.provider.refreshSkipped": "Skipped refreshing {provider}: {reason}",
510436
- "blunTui.warning": "Warning: {warning}",
510437
- "blunTui.startup.sessionNotFound": "Session \"{sessionId}\" not found.",
510438
- "blunTui.startup.sessionDifferentDirectory": "Session \"{sessionId}\" was created under a different directory.",
510439
- "blunTui.startup.noSessionsToContinue": "No sessions to continue under \"{workDir}\"; starting a fresh session.",
510440
- "blunTui.startup.sessionNotInitialized": "Startup session was not initialized.",
510441
- "blunTui.input.replayBlocked": "Cannot send input while session history is replaying.",
510442
- "blunTui.shell.noSession": "No active session for shell command.",
510443
- "blunTui.shell.runFailed": "Shell command failed: {error}",
510444
- "blunTui.shell.cancelFailed": "Failed to cancel shell command: {error}",
510445
- "blunTui.channel.steerFailed": "Failed to steer channel message: {error}",
510446
- "blunTui.session.sendFailed": "Failed to send: {error}",
510447
- "blunTui.media.imageUnsupported": "Current model does not support image input.",
510448
- "blunTui.media.videoUnsupported": "Current model does not support video input.",
510449
- "blunTui.skill.failed": "Skill \"{skillName}\" failed: {error}",
510450
- "blunTui.pluginCommand.failed": "Command \"{command}\" failed: {error}",
510451
- "blunTui.steer.failed": "Failed to steer: {error}",
510452
- "blunTui.session.otherWorkDir": "Current session is in a different working directory.",
510453
- "blunTui.session.resumeCommand": "To resume, run: {command}",
510454
- "blunTui.clipboard.commandCopied": "Command copied to clipboard",
510455
- "blunTui.clipboard.commandCopyFailed": "Failed to copy command to clipboard",
510456
- "blunTui.session.alreadyCurrent": "Already on this session.",
510457
- "blunTui.session.switchStreamingBlocked": "Cannot switch sessions while streaming — press Esc or Ctrl-C first.",
510458
- "blunTui.session.switchReplayBlocked": "Cannot switch sessions while history is replaying.",
510459
- "blunTui.session.resumeFailed": "Failed to resume session {sessionId}: {error}",
510460
- "blunTui.session.resumed": "Resumed session ({sessionId}).",
510461
- "blunTui.session.replayFailed": "Failed to replay session history: {error}",
510462
- "blunTui.session.createReplayBlocked": "Cannot start a new session while history is replaying.",
510463
- "blunTui.session.createFailed": "Failed to start a new session: {error}",
510464
- "blunTui.session.postCreateFailed": "Post-create setup failed: {error}",
510465
- "blunTui.session.started": "Started a new session ({sessionId}).",
510466
- "blunTui.error": "Error: {message}",
510467
- "blunTui.login.title": "Sign in to BLUN",
510468
- "blunTui.login.hint": "Press Ctrl-C to cancel",
510469
- "blunTui.login.waiting": "Waiting for authorization…",
510470
- "blunTui.detach.noShell": "No shell command running.",
510471
- "blunTui.detach.shellStarting": "Command is still starting — try again.",
510472
- "blunTui.detach.shellFinished": "Command already finished.",
510473
- "blunTui.detach.moveFailed": "Failed to move to background: {error}",
510474
- "blunTui.detach.movedTranscript": "Moved to background.",
510475
- "blunTui.detach.movedView": "Moved to background. /tasks to view.",
510476
- "blunTui.detach.noForeground": "No foreground task running.",
510477
- "blunTui.detach.listFailed": "Failed to list tasks: {error}",
510478
- "blunTui.detach.taskFailed": "Failed to detach {taskId}: {error}",
510479
- "blunTui.detach.finished.one": "Task already finished.",
510480
- "blunTui.detach.finished.other": "Tasks already finished.",
510481
- "blunTui.detach.moved.one": "Moved {count} task to background.",
510482
- "blunTui.detach.moved.other": "Moved {count} tasks to background.",
510483
- "blunTui.detach.partial": "Moved {detached} of {total} tasks to background.",
510484
- "blunTui.detach.viewSuffix": "/tasks to view.",
510485
- "blunTui.startup.flagsFailed": "Failed to apply startup flags: {error}",
510486
- "blunTui.notification.approvalRequired": "BLUN approval required",
510487
- "blunTui.notification.answerRequired": "BLUN needs your answer",
510488
- "blunTui.telegram.fallbackDelivered": "Reply delivered to Telegram automatically (fallback).",
510489
- "blunTui.telegram.attachDisabled": "Telegram attachment disabled by BLUN_TELEGRAM_ATTACH=off — headless mode active.",
510490
- "blunTui.telegram.noToken": "Telegram attachment: no token detected — headless mode active.",
510491
- "blunTui.telegram.attached": "Telegram channel attached (lease PID {pid}) — messages appear in this window.",
510492
- "blunTui.auto.status": "Auto: {label}",
510493
- "blunTui.activity.thinking": "{name} is thinking…",
510494
- "blunTui.activity.working": "{name} is working…",
510495
- "blunTui.activity.composing": "working...",
510496
- "blunTui.activity.tokens": "Tokens"
510497
- },
510498
- de: {
510499
- "blunTui.provider.modelAdded.one": "{providerName} · +{count} Modell.",
510500
- "blunTui.provider.modelAdded.other": "{providerName} · +{count} Modelle.",
510501
- "blunTui.provider.refreshSkipped": "Aktualisierung von {provider} übersprungen: {reason}",
510502
- "blunTui.warning": "Warnung: {warning}",
510503
- "blunTui.startup.sessionNotFound": "Sitzung „{sessionId}“ wurde nicht gefunden.",
510504
- "blunTui.startup.sessionDifferentDirectory": "Sitzung „{sessionId}“ wurde in einem anderen Arbeitsverzeichnis erstellt.",
510505
- "blunTui.startup.noSessionsToContinue": "Unter „{workDir}“ gibt es keine Sitzung zum Fortsetzen; eine neue Sitzung wird gestartet.",
510506
- "blunTui.startup.sessionNotInitialized": "Die Sitzung konnte beim Start nicht initialisiert werden.",
510507
- "blunTui.input.replayBlocked": "Während der Wiedergabe des Sitzungsverlaufs können keine Eingaben gesendet werden.",
510508
- "blunTui.shell.noSession": "Keine aktive Sitzung für den Shell-Befehl.",
510509
- "blunTui.shell.runFailed": "Shell-Befehl fehlgeschlagen: {error}",
510510
- "blunTui.shell.cancelFailed": "Shell-Befehl konnte nicht abgebrochen werden: {error}",
510511
- "blunTui.channel.steerFailed": "Die Kanalnachricht konnte nicht zur laufenden Antwort hinzugefügt werden: {error}",
510512
- "blunTui.session.sendFailed": "Senden fehlgeschlagen: {error}",
510513
- "blunTui.media.imageUnsupported": "Das aktuelle Modell unterstützt keine Bildeingaben.",
510514
- "blunTui.media.videoUnsupported": "Das aktuelle Modell unterstützt keine Videoeingaben.",
510515
- "blunTui.skill.failed": "Skill „{skillName}“ fehlgeschlagen: {error}",
510516
- "blunTui.pluginCommand.failed": "Befehl „{command}“ fehlgeschlagen: {error}",
510517
- "blunTui.steer.failed": "Nachsteuern fehlgeschlagen: {error}",
510518
- "blunTui.session.otherWorkDir": "Die aktuelle Sitzung befindet sich in einem anderen Arbeitsverzeichnis.",
510519
- "blunTui.session.resumeCommand": "Zum Fortsetzen ausführen: {command}",
510520
- "blunTui.clipboard.commandCopied": "Befehl in die Zwischenablage kopiert",
510521
- "blunTui.clipboard.commandCopyFailed": "Befehl konnte nicht in die Zwischenablage kopiert werden",
510522
- "blunTui.session.alreadyCurrent": "Diese Sitzung ist bereits aktiv.",
510523
- "blunTui.session.switchStreamingBlocked": "Während einer laufenden Antwort kann die Sitzung nicht gewechselt werden. Drücke zuerst Esc oder Ctrl-C.",
510524
- "blunTui.session.switchReplayBlocked": "Während der Wiedergabe des Sitzungsverlaufs kann die Sitzung nicht gewechselt werden.",
510525
- "blunTui.session.resumeFailed": "Sitzung {sessionId} konnte nicht fortgesetzt werden: {error}",
510526
- "blunTui.session.resumed": "Sitzung fortgesetzt ({sessionId}).",
510527
- "blunTui.session.replayFailed": "Der Sitzungsverlauf konnte nicht wiedergegeben werden: {error}",
510528
- "blunTui.session.createReplayBlocked": "Während der Wiedergabe des Sitzungsverlaufs kann keine neue Sitzung gestartet werden.",
510529
- "blunTui.session.createFailed": "Neue Sitzung konnte nicht gestartet werden: {error}",
510530
- "blunTui.session.postCreateFailed": "Die neue Sitzung konnte nicht eingerichtet werden: {error}",
510531
- "blunTui.session.started": "Neue Sitzung gestartet ({sessionId}).",
510532
- "blunTui.error": "Fehler: {message}",
510533
- "blunTui.login.title": "Bei BLUN anmelden",
510534
- "blunTui.login.hint": "Zum Abbrechen Ctrl-C drücken",
510535
- "blunTui.login.waiting": "Warten auf Autorisierung…",
510536
- "blunTui.detach.noShell": "Es wird kein Shell-Befehl ausgeführt.",
510537
- "blunTui.detach.shellStarting": "Der Befehl wird noch gestartet — versuche es erneut.",
510538
- "blunTui.detach.shellFinished": "Der Befehl ist bereits beendet.",
510539
- "blunTui.detach.moveFailed": "Verschieben in den Hintergrund fehlgeschlagen: {error}",
510540
- "blunTui.detach.movedTranscript": "In den Hintergrund verschoben.",
510541
- "blunTui.detach.movedView": "In den Hintergrund verschoben. Mit /tasks anzeigen.",
510542
- "blunTui.detach.noForeground": "Es wird keine Aufgabe im Vordergrund ausgeführt.",
510543
- "blunTui.detach.listFailed": "Aufgaben konnten nicht aufgelistet werden: {error}",
510544
- "blunTui.detach.taskFailed": "Aufgabe {taskId} konnte nicht in den Hintergrund verschoben werden: {error}",
510545
- "blunTui.detach.finished.one": "Aufgabe ist bereits beendet.",
510546
- "blunTui.detach.finished.other": "Aufgaben sind bereits beendet.",
510547
- "blunTui.detach.moved.one": "{count} Aufgabe in den Hintergrund verschoben.",
510548
- "blunTui.detach.moved.other": "{count} Aufgaben in den Hintergrund verschoben.",
510549
- "blunTui.detach.partial": "{detached} von {total} Aufgaben in den Hintergrund verschoben.",
510550
- "blunTui.detach.viewSuffix": "Mit /tasks anzeigen.",
510551
- "blunTui.startup.flagsFailed": "Startoptionen konnten nicht angewendet werden: {error}",
510552
- "blunTui.notification.approvalRequired": "BLUN-Genehmigung erforderlich",
510553
- "blunTui.notification.answerRequired": "BLUN benötigt deine Antwort",
510554
- "blunTui.telegram.fallbackDelivered": "Antwort automatisch nach Telegram zugestellt (Fallback).",
510555
- "blunTui.telegram.attachDisabled": "Telegram-Anbindung durch BLUN_TELEGRAM_ATTACH=off deaktiviert — Headless-Modus aktiv.",
510556
- "blunTui.telegram.noToken": "Telegram-Anbindung: kein Token erkannt — Headless-Modus aktiv.",
510557
- "blunTui.telegram.attached": "Telegram-Kanal angebunden (Lease-PID {pid}) — Nachrichten erscheinen in diesem Fenster.",
510558
- "blunTui.auto.status": "Auto: {label}",
510559
- "blunTui.activity.thinking": "{name} denkt…",
510560
- "blunTui.activity.working": "{name} arbeitet…",
510561
- "blunTui.activity.composing": "arbeitet...",
510562
- "blunTui.activity.tokens": "Token"
510563
- },
510564
- es: {
510565
- "blunTui.provider.modelAdded.one": "{providerName} · +{count} modelo.",
510566
- "blunTui.provider.modelAdded.other": "{providerName} · +{count} modelos.",
510567
- "blunTui.provider.refreshSkipped": "Se omitió la actualización de {provider}: {reason}",
510568
- "blunTui.warning": "Advertencia: {warning}",
510569
- "blunTui.startup.sessionNotFound": "No se encontró la sesión «{sessionId}».",
510570
- "blunTui.startup.sessionDifferentDirectory": "La sesión «{sessionId}» se creó en otro directorio de trabajo.",
510571
- "blunTui.startup.noSessionsToContinue": "No hay sesiones que reanudar en «{workDir}»; se iniciará una sesión nueva.",
510572
- "blunTui.startup.sessionNotInitialized": "No se pudo inicializar la sesión durante el arranque.",
510573
- "blunTui.input.replayBlocked": "No se puede enviar ninguna entrada mientras se reproduce el historial de la sesión.",
510574
- "blunTui.shell.noSession": "No hay ninguna sesión activa para el comando de shell.",
510575
- "blunTui.shell.runFailed": "El comando de shell falló: {error}",
510576
- "blunTui.shell.cancelFailed": "No se pudo cancelar el comando de shell: {error}",
510577
- "blunTui.channel.steerFailed": "No se pudo añadir el mensaje del canal a la respuesta en curso: {error}",
510578
- "blunTui.session.sendFailed": "No se pudo enviar: {error}",
510579
- "blunTui.media.imageUnsupported": "El modelo actual no admite entradas de imagen.",
510580
- "blunTui.media.videoUnsupported": "El modelo actual no admite entradas de vídeo.",
510581
- "blunTui.skill.failed": "El skill «{skillName}» falló: {error}",
510582
- "blunTui.pluginCommand.failed": "El comando «{command}» falló: {error}",
510583
- "blunTui.steer.failed": "No se pudo reorientar la respuesta: {error}",
510584
- "blunTui.session.otherWorkDir": "La sesión actual se encuentra en otro directorio de trabajo.",
510585
- "blunTui.session.resumeCommand": "Para reanudarla, ejecuta: {command}",
510586
- "blunTui.clipboard.commandCopied": "Comando copiado al portapapeles",
510587
- "blunTui.clipboard.commandCopyFailed": "No se pudo copiar el comando al portapapeles",
510588
- "blunTui.session.alreadyCurrent": "Esta sesión ya está activa.",
510589
- "blunTui.session.switchStreamingBlocked": "No se puede cambiar de sesión mientras se genera una respuesta. Pulsa primero Esc o Ctrl-C.",
510590
- "blunTui.session.switchReplayBlocked": "No se puede cambiar de sesión mientras se reproduce el historial.",
510591
- "blunTui.session.resumeFailed": "No se pudo reanudar la sesión {sessionId}: {error}",
510592
- "blunTui.session.resumed": "Sesión reanudada ({sessionId}).",
510593
- "blunTui.session.replayFailed": "No se pudo reproducir el historial de la sesión: {error}",
510594
- "blunTui.session.createReplayBlocked": "No se puede iniciar una sesión nueva mientras se reproduce el historial.",
510595
- "blunTui.session.createFailed": "No se pudo iniciar una sesión nueva: {error}",
510596
- "blunTui.session.postCreateFailed": "No se pudo configurar la sesión recién creada: {error}",
510597
- "blunTui.session.started": "Se inició una sesión nueva ({sessionId}).",
510598
- "blunTui.error": "Error: {message}",
510599
- "blunTui.login.title": "Iniciar sesión en BLUN",
510600
- "blunTui.login.hint": "Pulsa Ctrl-C para cancelar",
510601
- "blunTui.login.waiting": "Esperando autorización…",
510602
- "blunTui.detach.noShell": "No hay ningún comando de shell en ejecución.",
510603
- "blunTui.detach.shellStarting": "El comando todavía se está iniciando; inténtalo de nuevo.",
510604
- "blunTui.detach.shellFinished": "El comando ya ha finalizado.",
510605
- "blunTui.detach.moveFailed": "No se pudo mover a segundo plano: {error}",
510606
- "blunTui.detach.movedTranscript": "Se movió a segundo plano.",
510607
- "blunTui.detach.movedView": "Se movió a segundo plano. Consulta /tasks.",
510608
- "blunTui.detach.noForeground": "No hay ninguna tarea en ejecución en primer plano.",
510609
- "blunTui.detach.listFailed": "No se pudieron obtener las tareas: {error}",
510610
- "blunTui.detach.taskFailed": "No se pudo mover la tarea {taskId} a segundo plano: {error}",
510611
- "blunTui.detach.finished.one": "La tarea ya ha finalizado.",
510612
- "blunTui.detach.finished.other": "Las tareas ya han finalizado.",
510613
- "blunTui.detach.moved.one": "Se ha movido {count} tarea a segundo plano.",
510614
- "blunTui.detach.moved.other": "Se han movido {count} tareas a segundo plano.",
510615
- "blunTui.detach.partial": "Se han movido {detached} de {total} tareas a segundo plano.",
510616
- "blunTui.detach.viewSuffix": "Consulta /tasks.",
510617
- "blunTui.startup.flagsFailed": "No se pudieron aplicar las opciones de inicio: {error}",
510618
- "blunTui.notification.approvalRequired": "Se requiere aprobación de BLUN",
510619
- "blunTui.notification.answerRequired": "BLUN necesita tu respuesta",
510620
- "blunTui.telegram.fallbackDelivered": "La respuesta se envió automáticamente a Telegram (modo alternativo).",
510621
- "blunTui.telegram.attachDisabled": "Conexión con Telegram desactivada mediante BLUN_TELEGRAM_ATTACH=off — modo headless activo.",
510622
- "blunTui.telegram.noToken": "Conexión con Telegram: no se detectó ningún token — modo headless activo.",
510623
- "blunTui.telegram.attached": "Canal de Telegram conectado (PID de lease {pid}) — los mensajes aparecen en esta ventana.",
510624
- "blunTui.auto.status": "Automático: {label}",
510625
- "blunTui.activity.thinking": "{name} está pensando…",
510626
- "blunTui.activity.working": "{name} está trabajando…",
510627
- "blunTui.activity.composing": "trabajando...",
510628
- "blunTui.activity.tokens": "tokens"
510629
- },
510630
- fr: {
510631
- "blunTui.provider.modelAdded.one": "{providerName} · +{count} modèle.",
510632
- "blunTui.provider.modelAdded.other": "{providerName} · +{count} modèles.",
510633
- "blunTui.provider.refreshSkipped": "Actualisation de {provider} ignorée : {reason}",
510634
- "blunTui.warning": "Avertissement : {warning}",
510635
- "blunTui.startup.sessionNotFound": "Session « {sessionId} » introuvable.",
510636
- "blunTui.startup.sessionDifferentDirectory": "La session « {sessionId} » a été créée dans un autre répertoire de travail.",
510637
- "blunTui.startup.noSessionsToContinue": "Aucune session à reprendre dans « {workDir} » ; démarrage d’une nouvelle session.",
510638
- "blunTui.startup.sessionNotInitialized": "La session de démarrage n’a pas été initialisée.",
510639
- "blunTui.input.replayBlocked": "Impossible d’envoyer une saisie pendant la relecture de l’historique de la session.",
510640
- "blunTui.shell.noSession": "Aucune session active pour la commande shell.",
510641
- "blunTui.shell.runFailed": "Échec de la commande shell : {error}",
510642
- "blunTui.shell.cancelFailed": "Impossible d’annuler la commande shell : {error}",
510643
- "blunTui.channel.steerFailed": "Impossible d’ajouter le message du canal à la réponse en cours : {error}",
510644
- "blunTui.session.sendFailed": "Échec de l’envoi : {error}",
510645
- "blunTui.media.imageUnsupported": "Le modèle actuel ne prend pas en charge les images en entrée.",
510646
- "blunTui.media.videoUnsupported": "Le modèle actuel ne prend pas en charge les vidéos en entrée.",
510647
- "blunTui.skill.failed": "Échec du skill « {skillName} » : {error}",
510648
- "blunTui.pluginCommand.failed": "Échec de la commande « {command} » : {error}",
510649
- "blunTui.steer.failed": "Impossible de réorienter la réponse : {error}",
510650
- "blunTui.session.otherWorkDir": "La session actuelle se trouve dans un autre répertoire de travail.",
510651
- "blunTui.session.resumeCommand": "Pour la reprendre, exécutez : {command}",
510652
- "blunTui.clipboard.commandCopied": "Commande copiée dans le presse-papiers",
510653
- "blunTui.clipboard.commandCopyFailed": "Impossible de copier la commande dans le presse-papiers",
510654
- "blunTui.session.alreadyCurrent": "Cette session est déjà active.",
510655
- "blunTui.session.switchStreamingBlocked": "Impossible de changer de session pendant la génération d’une réponse. Appuyez d’abord sur Esc ou Ctrl-C.",
510656
- "blunTui.session.switchReplayBlocked": "Impossible de changer de session pendant la relecture de l’historique.",
510657
- "blunTui.session.resumeFailed": "Impossible de reprendre la session {sessionId} : {error}",
510658
- "blunTui.session.resumed": "Session reprise ({sessionId}).",
510659
- "blunTui.session.replayFailed": "Impossible de relire l’historique de la session : {error}",
510660
- "blunTui.session.createReplayBlocked": "Impossible de démarrer une nouvelle session pendant la relecture de l’historique.",
510661
- "blunTui.session.createFailed": "Impossible de démarrer une nouvelle session : {error}",
510662
- "blunTui.session.postCreateFailed": "Impossible de configurer la nouvelle session : {error}",
510663
- "blunTui.session.started": "Nouvelle session démarrée ({sessionId}).",
510664
- "blunTui.error": "Erreur : {message}",
510665
- "blunTui.login.title": "Se connecter à BLUN",
510666
- "blunTui.login.hint": "Appuyez sur Ctrl-C pour annuler",
510667
- "blunTui.login.waiting": "En attente de l’autorisation…",
510668
- "blunTui.detach.noShell": "Aucune commande shell en cours.",
510669
- "blunTui.detach.shellStarting": "La commande est encore en cours de démarrage — réessayez.",
510670
- "blunTui.detach.shellFinished": "La commande est déjà terminée.",
510671
- "blunTui.detach.moveFailed": "Impossible de passer la commande en arrière-plan : {error}",
510672
- "blunTui.detach.movedTranscript": "Commande passée en arrière-plan.",
510673
- "blunTui.detach.movedView": "Commande passée en arrière-plan. Consultez /tasks.",
510674
- "blunTui.detach.noForeground": "Aucune tâche en cours au premier plan.",
510675
- "blunTui.detach.listFailed": "Impossible de répertorier les tâches : {error}",
510676
- "blunTui.detach.taskFailed": "Impossible de passer la tâche {taskId} en arrière-plan : {error}",
510677
- "blunTui.detach.finished.one": "La tâche est déjà terminée.",
510678
- "blunTui.detach.finished.other": "Les tâches sont déjà terminées.",
510679
- "blunTui.detach.moved.one": "{count} tâche passée en arrière-plan.",
510680
- "blunTui.detach.moved.other": "{count} tâches passées en arrière-plan.",
510681
- "blunTui.detach.partial": "{detached} tâches sur {total} passées en arrière-plan.",
510682
- "blunTui.detach.viewSuffix": "Consultez /tasks.",
510683
- "blunTui.startup.flagsFailed": "Impossible d’appliquer les options de démarrage : {error}",
510684
- "blunTui.notification.approvalRequired": "Approbation BLUN requise",
510685
- "blunTui.notification.answerRequired": "BLUN attend votre réponse",
510686
- "blunTui.telegram.fallbackDelivered": "Réponse envoyée automatiquement sur Telegram (solution de secours).",
510687
- "blunTui.telegram.attachDisabled": "Connexion à Telegram désactivée via BLUN_TELEGRAM_ATTACH=off — mode headless actif.",
510688
- "blunTui.telegram.noToken": "Connexion à Telegram\xA0: aucun jeton détecté — mode headless actif.",
510689
- "blunTui.telegram.attached": "Canal Telegram connecté (PID de lease\xA0: {pid}) — les messages apparaissent dans cette fenêtre.",
510690
- "blunTui.auto.status": "Auto\xA0: {label}",
510691
- "blunTui.activity.thinking": "{name} réfléchit…",
510692
- "blunTui.activity.working": "{name} travaille…",
510693
- "blunTui.activity.composing": "travail en cours...",
510694
- "blunTui.activity.tokens": "jetons"
510695
- },
510696
- sv: {
510697
- "blunTui.provider.modelAdded.one": "{providerName} · +{count} modell.",
510698
- "blunTui.provider.modelAdded.other": "{providerName} · +{count} modeller.",
510699
- "blunTui.provider.refreshSkipped": "Uppdateringen av {provider} hoppades över: {reason}",
510700
- "blunTui.warning": "Varning: {warning}",
510701
- "blunTui.startup.sessionNotFound": "Sessionen ”{sessionId}” hittades inte.",
510702
- "blunTui.startup.sessionDifferentDirectory": "Sessionen ”{sessionId}” skapades i en annan arbetskatalog.",
510703
- "blunTui.startup.noSessionsToContinue": "Det finns inga sessioner att återuppta i ”{workDir}”; en ny session startas.",
510704
- "blunTui.startup.sessionNotInitialized": "Sessionen initierades inte vid start.",
510705
- "blunTui.input.replayBlocked": "Det går inte att skicka indata medan sessionshistoriken spelas upp.",
510706
- "blunTui.shell.noSession": "Det finns ingen aktiv session för skalkommandot.",
510707
- "blunTui.shell.runFailed": "Skalkommandot misslyckades: {error}",
510708
- "blunTui.shell.cancelFailed": "Det gick inte att avbryta skalkommandot: {error}",
510709
- "blunTui.channel.steerFailed": "Det gick inte att lägga till kanalmeddelandet i det pågående svaret: {error}",
510710
- "blunTui.session.sendFailed": "Det gick inte att skicka: {error}",
510711
- "blunTui.media.imageUnsupported": "Den aktuella modellen stöder inte bildindata.",
510712
- "blunTui.media.videoUnsupported": "Den aktuella modellen stöder inte videoindata.",
510713
- "blunTui.skill.failed": "Skill ”{skillName}” misslyckades: {error}",
510714
- "blunTui.pluginCommand.failed": "Kommandot ”{command}” misslyckades: {error}",
510715
- "blunTui.steer.failed": "Det gick inte att styra om svaret: {error}",
510716
- "blunTui.session.otherWorkDir": "Den aktuella sessionen finns i en annan arbetskatalog.",
510717
- "blunTui.session.resumeCommand": "Kör följande för att återuppta den: {command}",
510718
- "blunTui.clipboard.commandCopied": "Kommandot kopierades till urklipp",
510719
- "blunTui.clipboard.commandCopyFailed": "Det gick inte att kopiera kommandot till urklipp",
510720
- "blunTui.session.alreadyCurrent": "Den här sessionen är redan aktiv.",
510721
- "blunTui.session.switchStreamingBlocked": "Det går inte att byta session medan ett svar genereras. Tryck först på Esc eller Ctrl-C.",
510722
- "blunTui.session.switchReplayBlocked": "Det går inte att byta session medan historiken spelas upp.",
510723
- "blunTui.session.resumeFailed": "Det gick inte att återuppta sessionen {sessionId}: {error}",
510724
- "blunTui.session.resumed": "Sessionen återupptogs ({sessionId}).",
510725
- "blunTui.session.replayFailed": "Det gick inte att spela upp sessionshistoriken: {error}",
510726
- "blunTui.session.createReplayBlocked": "Det går inte att starta en ny session medan historiken spelas upp.",
510727
- "blunTui.session.createFailed": "Det gick inte att starta en ny session: {error}",
510728
- "blunTui.session.postCreateFailed": "Det gick inte att konfigurera den nya sessionen: {error}",
510729
- "blunTui.session.started": "En ny session startades ({sessionId}).",
510730
- "blunTui.error": "Fel: {message}",
510731
- "blunTui.login.title": "Logga in på BLUN",
510732
- "blunTui.login.hint": "Tryck på Ctrl-C för att avbryta",
510733
- "blunTui.login.waiting": "Väntar på auktorisering…",
510734
- "blunTui.detach.noShell": "Inget skalkommando körs.",
510735
- "blunTui.detach.shellStarting": "Kommandot håller fortfarande på att startas – försök igen.",
510736
- "blunTui.detach.shellFinished": "Kommandot är redan slutfört.",
510737
- "blunTui.detach.moveFailed": "Det gick inte att flytta kommandot till bakgrunden: {error}",
510738
- "blunTui.detach.movedTranscript": "Flyttades till bakgrunden.",
510739
- "blunTui.detach.movedView": "Flyttades till bakgrunden. Visa med /tasks.",
510740
- "blunTui.detach.noForeground": "Ingen uppgift körs i förgrunden.",
510741
- "blunTui.detach.listFailed": "Det gick inte att lista uppgifterna: {error}",
510742
- "blunTui.detach.taskFailed": "Det gick inte att flytta uppgiften {taskId} till bakgrunden: {error}",
510743
- "blunTui.detach.finished.one": "Uppgiften är redan slutförd.",
510744
- "blunTui.detach.finished.other": "Uppgifterna är redan slutförda.",
510745
- "blunTui.detach.moved.one": "{count} uppgift flyttades till bakgrunden.",
510746
- "blunTui.detach.moved.other": "{count} uppgifter flyttades till bakgrunden.",
510747
- "blunTui.detach.partial": "{detached} av {total} uppgifter flyttades till bakgrunden.",
510748
- "blunTui.detach.viewSuffix": "Visa med /tasks.",
510749
- "blunTui.startup.flagsFailed": "Det gick inte att tillämpa startalternativen: {error}",
510750
- "blunTui.notification.approvalRequired": "BLUN-godkännande krävs",
510751
- "blunTui.notification.answerRequired": "BLUN behöver ditt svar",
510752
- "blunTui.telegram.fallbackDelivered": "Svaret skickades automatiskt till Telegram (reservlösning).",
510753
- "blunTui.telegram.attachDisabled": "Telegram-anslutningen inaktiverades via BLUN_TELEGRAM_ATTACH=off — headless-läget är aktivt.",
510754
- "blunTui.telegram.noToken": "Telegram-anslutning: ingen token hittades — headless-läget är aktivt.",
510755
- "blunTui.telegram.attached": "Telegram-kanalen är ansluten (lease-PID {pid}) — meddelanden visas i det här fönstret.",
510756
- "blunTui.auto.status": "Automatiskt: {label}",
510757
- "blunTui.activity.thinking": "{name} tänker…",
510758
- "blunTui.activity.working": "{name} arbetar…",
510759
- "blunTui.activity.composing": "arbetar...",
510760
- "blunTui.activity.tokens": "token"
510761
- },
510762
- cs: {
510763
- "blunTui.provider.modelAdded.one": "{providerName} · +{count} model.",
510764
- "blunTui.provider.modelAdded.other": "{providerName} · nové modely: +{count}.",
510765
- "blunTui.provider.refreshSkipped": "Přeskočeno obnovení {provider}: {reason}",
510766
- "blunTui.warning": "Upozornění: {warning}",
510767
- "blunTui.startup.sessionNotFound": "Relace \"{sessionId}\" nebyla nalezena.",
510768
- "blunTui.startup.sessionDifferentDirectory": "Relace \"{sessionId}\" byla vytvořena v jiném adresáři.",
510769
- "blunTui.startup.noSessionsToContinue": "V adresáři \"{workDir}\" nejsou žádné relace, ve kterých by bylo možné pokračovat; spouští se nová relace.",
510770
- "blunTui.startup.sessionNotInitialized": "Relace při spuštění nebyla inicializována.",
510771
- "blunTui.input.replayBlocked": "Nelze odeslat vstup během přehrávání historie relace.",
510772
- "blunTui.shell.noSession": "Žádná aktivní relace pro příkaz shellu.",
510773
- "blunTui.shell.runFailed": "Příkaz shellu selhal: {error}",
510774
- "blunTui.shell.cancelFailed": "Selhalo zrušení příkazu shellu: {error}",
510775
- "blunTui.channel.steerFailed": "Předání zprávy z kanálu do probíhající odpovědi selhalo: {error}",
510776
- "blunTui.session.sendFailed": "Selhalo odeslání: {error}",
510777
- "blunTui.media.imageUnsupported": "Aktuální model nepodporuje vstup obrázku.",
510778
- "blunTui.media.videoUnsupported": "Aktuální model nepodporuje vstup videa.",
510779
- "blunTui.skill.failed": "Dovednost \"{skillName}\" selhala: {error}",
510780
- "blunTui.pluginCommand.failed": "Příkaz \"{command}\" selhal: {error}",
510781
- "blunTui.steer.failed": "Doplnění pokynu selhalo: {error}",
510782
- "blunTui.session.otherWorkDir": "Aktuální relace je v jiném pracovním adresáři.",
510783
- "blunTui.session.resumeCommand": "Chcete-li pokračovat, spusťte: {command}",
510784
- "blunTui.clipboard.commandCopied": "Příkaz zkopírován do schránky",
510785
- "blunTui.clipboard.commandCopyFailed": "Selhalo kopírování příkazu do schránky",
510786
- "blunTui.session.alreadyCurrent": "Již jste v této relaci.",
510787
- "blunTui.session.switchStreamingBlocked": "Nelze přepínat relace během streamování — nejdříve stiskněte Esc nebo Ctrl-C.",
510788
- "blunTui.session.switchReplayBlocked": "Nelze přepínat relace během přehrávání historie.",
510789
- "blunTui.session.resumeFailed": "Selhalo obnovení relace {sessionId}: {error}",
510790
- "blunTui.session.resumed": "Obnovena relace ({sessionId}).",
510791
- "blunTui.session.replayFailed": "Selhalo přehrávání historie relace: {error}",
510792
- "blunTui.session.createReplayBlocked": "Nelze spustit novou relaci během přehrávání historie.",
510793
- "blunTui.session.createFailed": "Selhalo spuštění nové relace: {error}",
510794
- "blunTui.session.postCreateFailed": "Selhalo nastavení po vytvoření: {error}",
510795
- "blunTui.session.started": "Spuštěna nová relace ({sessionId}).",
510796
- "blunTui.error": "Chyba: {message}",
510797
- "blunTui.login.title": "Přihlaste se do BLUN",
510798
- "blunTui.login.hint": "Stiskněte Ctrl-C pro zrušení",
510799
- "blunTui.login.waiting": "Čekání na autorizaci…",
510800
- "blunTui.detach.noShell": "Žádný příkaz shellu není spuštěn.",
510801
- "blunTui.detach.shellStarting": "Příkaz se stále spouští — zkuste znovu.",
510802
- "blunTui.detach.shellFinished": "Příkaz již skončil.",
510803
- "blunTui.detach.moveFailed": "Selhalo přesunutí na pozadí: {error}",
510804
- "blunTui.detach.movedTranscript": "Přesunuto na pozadí.",
510805
- "blunTui.detach.movedView": "Přesunuto na pozadí. Zobrazíte příkazem /tasks.",
510806
- "blunTui.detach.noForeground": "Žádný úkol na popředí není spuštěn.",
510807
- "blunTui.detach.listFailed": "Selhalo vypsání úkolů: {error}",
510808
- "blunTui.detach.taskFailed": "Přesunutí úlohy {taskId} na pozadí selhalo: {error}",
510809
- "blunTui.detach.finished.one": "Úkol již skončil.",
510810
- "blunTui.detach.finished.other": "Úkoly již skončily.",
510811
- "blunTui.detach.moved.one": "Přesunut {count} úkol na pozadí.",
510812
- "blunTui.detach.moved.other": "Úkoly přesunuté na pozadí: {count}.",
510813
- "blunTui.detach.partial": "Přesunuto {detached} z {total} úkolů na pozadí.",
510814
- "blunTui.detach.viewSuffix": "/tasks k zobrazení.",
510815
- "blunTui.startup.flagsFailed": "Nepodařilo se použít spouštěcí příznaky: {error}",
510816
- "blunTui.notification.approvalRequired": "Vyžadováno schválení BLUN",
510817
- "blunTui.notification.answerRequired": "BLUN potřebuje vaši odpověď",
510818
- "blunTui.telegram.fallbackDelivered": "Odpověď byla automaticky doručena do Telegramu (náhradním způsobem).",
510819
- "blunTui.telegram.attachDisabled": "Připojení Telegramu je zakázáno nastavením BLUN_TELEGRAM_ATTACH=off — aktivní je režim bez uživatelského rozhraní.",
510820
- "blunTui.telegram.noToken": "Připojení Telegramu: nebyl nalezen žádný token — aktivní je režim bez uživatelského rozhraní.",
510821
- "blunTui.telegram.attached": "Kanál Telegramu je připojen (PID držitele připojení {pid}) — zprávy se zobrazují v tomto okně.",
510822
- "blunTui.auto.status": "Automaticky: {label}",
510823
- "blunTui.activity.thinking": "{name} přemýšlí…",
510824
- "blunTui.activity.working": "{name} pracuje…",
510825
- "blunTui.activity.composing": "pracuje…",
510826
- "blunTui.activity.tokens": "Tokeny"
510827
- }
510828
- });
510829
- //#endregion
510830
511301
  //#region src/tui/blun-tui.ts
510831
511302
  function loadingTipKind(mode) {
510832
511303
  if (mode === "waiting" || mode === "tool") return "blun";
@@ -512001,6 +512472,21 @@ var BlunTUI = class {
512001
512472
  /** Pending delivery guard for the channel-origin turn currently running. */
512002
512473
  pendingChannelReplyGuard;
512003
512474
  /** See SessionEventHost.runChannelReplyFallback — called at turn end. */
512475
+ channelMediaDeliveries = /* @__PURE__ */ new Set();
512476
+ /** Deliver completed media at tool-result time so later queued work cannot hide it. */
512477
+ runChannelMediaFallback(output) {
512478
+ const guard = this.pendingChannelReplyGuard;
512479
+ const filePath = completedMediaLocalPath(output);
512480
+ if (guard === void 0 || filePath === void 0) return;
512481
+ const deliveryKey = `${guard.chatId}\0${filePath}`;
512482
+ if (this.channelMediaDeliveries.has(deliveryKey)) return;
512483
+ this.channelMediaDeliveries.add(deliveryKey);
512484
+ sendMediaReplyFallback(guard.chatId, filePath).then((sent) => {
512485
+ if (sent) return;
512486
+ this.channelMediaDeliveries.delete(deliveryKey);
512487
+ this.showError(uiText("blunTui.session.sendFailed", { error: "Telegram media" }));
512488
+ });
512489
+ }
512004
512490
  runChannelReplyFallback(reason) {
512005
512491
  const guard = this.pendingChannelReplyGuard;
512006
512492
  if (guard === void 0) return;