blun-king-cli 9.1.32 → 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 +1026 -514
  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:a7df37740da4f38cdb11b51de60021135cbc55aec8643f9501c9c946c3b67c0b
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
  };
@@ -399828,6 +400040,7 @@ registerUiCatalogFragment({
399828
400040
  "command.auto.description": "Toggle auto permission mode",
399829
400041
  "command.permission.description": "Select permission mode",
399830
400042
  "command.settings.description": "Open TUI settings",
400043
+ "command.outputStyle.description": "Choose the preferred way BLUN responds",
399831
400044
  "command.plan.description": "Toggle plan mode",
399832
400045
  "command.swarm.description": "Toggle swarm mode or run one task in swarm mode",
399833
400046
  "command.model.description": "Switch LLM model",
@@ -399884,6 +400097,7 @@ registerUiCatalogFragment({
399884
400097
  "command.auto.description": "Automatischen Berechtigungsmodus umschalten",
399885
400098
  "command.permission.description": "Berechtigungsmodus auswählen",
399886
400099
  "command.settings.description": "TUI-Einstellungen öffnen",
400100
+ "command.outputStyle.description": "Gewünschte Antwortweise für BLUN wählen",
399887
400101
  "command.plan.description": "Planmodus umschalten",
399888
400102
  "command.swarm.description": "Swarm-Modus umschalten oder eine Aufgabe im Swarm ausführen",
399889
400103
  "command.model.description": "LLM-Modell wechseln",
@@ -399940,6 +400154,7 @@ registerUiCatalogFragment({
399940
400154
  "command.auto.description": "Activar o desactivar el modo automático de permisos",
399941
400155
  "command.permission.description": "Seleccionar el modo de permisos",
399942
400156
  "command.settings.description": "Abrir los ajustes de la TUI",
400157
+ "command.outputStyle.description": "Elegir cómo debe responder BLUN",
399943
400158
  "command.plan.description": "Activar o desactivar el modo de planificación",
399944
400159
  "command.swarm.description": "Activar o desactivar el modo Swarm o ejecutar una tarea en ese modo",
399945
400160
  "command.model.description": "Cambiar el modelo LLM",
@@ -399996,6 +400211,7 @@ registerUiCatalogFragment({
399996
400211
  "command.auto.description": "Activer ou désactiver le mode d’autorisation automatique",
399997
400212
  "command.permission.description": "Choisir le mode d’autorisation",
399998
400213
  "command.settings.description": "Ouvrir les paramètres de la TUI",
400214
+ "command.outputStyle.description": "Choisir la manière dont BLUN doit répondre",
399999
400215
  "command.plan.description": "Activer ou désactiver le mode Plan",
400000
400216
  "command.swarm.description": "Activer ou désactiver le mode essaim, ou exécuter une tâche dans ce mode",
400001
400217
  "command.model.description": "Changer de modèle LLM",
@@ -400052,6 +400268,7 @@ registerUiCatalogFragment({
400052
400268
  "command.auto.description": "Slå på eller av automatiskt behörighetsläge",
400053
400269
  "command.permission.description": "Välj behörighetsläge",
400054
400270
  "command.settings.description": "Öppna TUI-inställningarna",
400271
+ "command.outputStyle.description": "Välj hur BLUN ska svara",
400055
400272
  "command.plan.description": "Slå på eller av planläget",
400056
400273
  "command.swarm.description": "Slå på eller av Swarm-läget eller kör en uppgift i Swarm-läge",
400057
400274
  "command.model.description": "Byt LLM-modell",
@@ -400108,6 +400325,7 @@ registerUiCatalogFragment({
400108
400325
  "command.auto.description": "Přepnout režim automatického oprávnění",
400109
400326
  "command.permission.description": "Vybrat režim oprávnění",
400110
400327
  "command.settings.description": "Otevřít nastavení TUI",
400328
+ "command.outputStyle.description": "Zvolit, jak má BLUN odpovídat",
400111
400329
  "command.plan.description": "Přepnout režim plánu",
400112
400330
  "command.swarm.description": "Přepnout režim swarm nebo spustit jednu úlohu v režimu swarm",
400113
400331
  "command.model.description": "Přepnout model LLM",
@@ -400391,6 +400609,13 @@ const BUILTIN_SLASH_COMMAND_DEFINITIONS = [
400391
400609
  priority: 100,
400392
400610
  availability: "always"
400393
400611
  },
400612
+ {
400613
+ name: "output-style",
400614
+ aliases: ["style"],
400615
+ descriptionKey: "command.outputStyle.description",
400616
+ priority: 100,
400617
+ availability: "always"
400618
+ },
400394
400619
  {
400395
400620
  name: "plan",
400396
400621
  aliases: [],
@@ -410941,12 +411166,30 @@ const STREAMING_ARGS_PREVIEW_MAX_CHARS = 64 * 1024;
410941
411166
  //#endregion
410942
411167
  //#region src/tui/utils/event-payload.copy.ts
410943
411168
  registerUiCatalogFragment({
410944
- en: { "eventPayload.providerFiltered": "Provider filtered the response before visible output (finishReason={finishReason}{raw})." },
410945
- de: { "eventPayload.providerFiltered": "Der Anbieter hat die Antwort vor der sichtbaren Ausgabe gefiltert (finishReason={finishReason}{raw})." },
410946
- es: { "eventPayload.providerFiltered": "El proveedor filtró la respuesta antes de que se mostrara la salida (finishReason={finishReason}{raw})." },
410947
- fr: { "eventPayload.providerFiltered": "Le fournisseur a filtré la réponse avant l’affichage de la sortie (finishReason={finishReason}{raw})." },
410948
- sv: { "eventPayload.providerFiltered": "Leverantören filtrerade svaret innan någon utdata visades (finishReason={finishReason}{raw})." },
410949
- 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
+ }
410950
411193
  });
410951
411194
  //#endregion
410952
411195
  //#region src/tui/utils/event-payload.ts
@@ -411019,6 +411262,7 @@ function formatErrorMessage$2(error) {
411019
411262
  return projectBlunIdentity(error instanceof Error ? error.message : String(error));
411020
411263
  }
411021
411264
  function formatErrorPayload(error) {
411265
+ if (error.code === "model.empty_response") return uiText("eventPayload.modelEmptyResponse");
411022
411266
  const filteredMessage = formatProviderFilteredMessage(error.details);
411023
411267
  if (filteredMessage !== void 0) return projectBlunIdentity(`[${error.code}] ${filteredMessage}`);
411024
411268
  return projectBlunIdentity(`[${error.code}] ${error.message}`);
@@ -411101,20 +411345,22 @@ registerUiCatalogFragment({
411101
411345
  "swarmPermission.god.description": "Tools and plan changes are approved automatically. BLUN may still ask you questions.",
411102
411346
  "swarmPermission.manual.label": "Start in Manual",
411103
411347
  "swarmPermission.manual.description": "Keep approvals on. BLUN may stop and wait for you during the swarm task.",
411104
- "settings.actionStyle.label": "Action style",
411105
- "settings.actionStyle.description": "Choose how BLUN acts and explains its work.",
411106
- "actionStyle.title": "Preferred action style",
411107
- "actionStyle.scope": "This setting changes how BLUN collaborates with you. Plan and permission rules still take priority.",
411348
+ "settings.actionStyle.label": "Output style",
411349
+ "settings.actionStyle.description": "Choose how BLUN responds and presents its work.",
411350
+ "actionStyle.title": "Preferred output style",
411351
+ "actionStyle.scope": "This setting changes how BLUN responds. Plan and permission rules still take priority.",
411108
411352
  "actionStyle.default.label": "Default",
411109
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.",
411110
411356
  "actionStyle.proactive.label": "Proactive",
411111
411357
  "actionStyle.proactive.description": "Acts immediately when the path is clear, minimizes interruptions, and asks only when necessary.",
411112
411358
  "actionStyle.explanatory.label": "Explanatory",
411113
411359
  "actionStyle.explanatory.description": "Explains implementation choices and relevant codebase patterns while working.",
411114
411360
  "actionStyle.learning.label": "Learning",
411115
411361
  "actionStyle.learning.description": "Pauses at useful points and invites you to write small pieces of code for hands-on practice.",
411116
- "actionStyle.saved": "Action style set to {style}.",
411117
- "actionStyle.saveFailed": "Could not save action style: {error}"
411362
+ "actionStyle.saved": "Output style set to {style}.",
411363
+ "actionStyle.saveFailed": "Could not save output style: {error}"
411118
411364
  },
411119
411365
  de: {
411120
411366
  "permission.title": "Berechtigungsmodus auswählen",
@@ -411175,20 +411421,22 @@ registerUiCatalogFragment({
411175
411421
  "swarmPermission.god.description": "Werkzeuge und Planwechsel werden automatisch freigegeben. BLUN kann dir weiterhin Fragen stellen.",
411176
411422
  "swarmPermission.manual.label": "Manuell starten",
411177
411423
  "swarmPermission.manual.description": "Freigaben beibehalten. BLUN kann den Swarm-Auftrag anhalten und auf dich warten.",
411178
- "settings.actionStyle.label": "Handlungsstil",
411179
- "settings.actionStyle.description": "Lege fest, wie BLUN handelt und seine Arbeit erklärt.",
411180
- "actionStyle.title": "Bevorzugter Handlungsstil",
411181
- "actionStyle.scope": "Diese Einstellung bestimmt, wie BLUN mit dir zusammenarbeitet. Plan- und Berechtigungsregeln haben weiterhin Vorrang.",
411424
+ "settings.actionStyle.label": "Ausgabestil",
411425
+ "settings.actionStyle.description": "Lege fest, wie BLUN antwortet und seine Arbeit darstellt.",
411426
+ "actionStyle.title": "Bevorzugter Ausgabestil",
411427
+ "actionStyle.scope": "Diese Einstellung bestimmt, wie BLUN antwortet. Plan- und Berechtigungsregeln haben weiterhin Vorrang.",
411182
411428
  "actionStyle.default.label": "Standard",
411183
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.",
411184
411432
  "actionStyle.proactive.label": "Proaktiv",
411185
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.",
411186
411434
  "actionStyle.explanatory.label": "Erklärend",
411187
411435
  "actionStyle.explanatory.description": "Erläutert während der Arbeit Implementierungsentscheidungen und relevante Muster im Codebestand.",
411188
411436
  "actionStyle.learning.label": "Lernorientiert",
411189
411437
  "actionStyle.learning.description": "Hält an sinnvollen Stellen inne und lädt dich dazu ein, kleine Programmteile selbst zu schreiben und praktisch zu üben.",
411190
- "actionStyle.saved": "Handlungsstil auf {style} gesetzt.",
411191
- "actionStyle.saveFailed": "Handlungsstil konnte nicht gespeichert werden: {error}"
411438
+ "actionStyle.saved": "Ausgabestil auf {style} gesetzt.",
411439
+ "actionStyle.saveFailed": "Ausgabestil konnte nicht gespeichert werden: {error}"
411192
411440
  },
411193
411441
  es: {
411194
411442
  "permission.title": "Seleccionar el modo de permisos",
@@ -411249,20 +411497,22 @@ registerUiCatalogFragment({
411249
411497
  "swarmPermission.god.description": "Las herramientas y los cambios del plan se aprueban automáticamente. BLUN todavía puede hacerte preguntas.",
411250
411498
  "swarmPermission.manual.label": "Iniciar en modo Manual",
411251
411499
  "swarmPermission.manual.description": "Mantener activadas las aprobaciones. BLUN puede detenerse y esperar tu intervención durante la tarea de Swarm.",
411252
- "settings.actionStyle.label": "Estilo de actuación",
411253
- "settings.actionStyle.description": "Elige cómo actúa BLUN y cómo explica su trabajo.",
411254
- "actionStyle.title": "Estilo de actuación preferido",
411255
- "actionStyle.scope": "Esta opción determina cómo colabora BLUN contigo. Las reglas del plan y los permisos siguen teniendo prioridad.",
411500
+ "settings.actionStyle.label": "Estilo de salida",
411501
+ "settings.actionStyle.description": "Elige cómo responde BLUN y cómo presenta su trabajo.",
411502
+ "actionStyle.title": "Estilo de salida preferido",
411503
+ "actionStyle.scope": "Esta opción determina cómo responde BLUN. Las reglas del plan y de permisos siguen teniendo prioridad.",
411256
411504
  "actionStyle.default.label": "Predeterminado",
411257
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.",
411258
411508
  "actionStyle.proactive.label": "Proactivo",
411259
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.",
411260
411510
  "actionStyle.explanatory.label": "Explicativo",
411261
411511
  "actionStyle.explanatory.description": "Explica mientras trabaja las decisiones de implementación y los patrones relevantes del código.",
411262
411512
  "actionStyle.learning.label": "Aprendizaje",
411263
411513
  "actionStyle.learning.description": "Se detiene en momentos útiles y te invita a escribir pequeños fragmentos de código para practicar.",
411264
- "actionStyle.saved": "Estilo de actuación establecido en {style}.",
411265
- "actionStyle.saveFailed": "No se ha podido guardar el estilo de actuación: {error}"
411514
+ "actionStyle.saved": "Estilo de salida establecido en {style}.",
411515
+ "actionStyle.saveFailed": "No se ha podido guardar el estilo de salida: {error}"
411266
411516
  },
411267
411517
  fr: {
411268
411518
  "permission.title": "Choisir le mode d’autorisation",
@@ -411323,20 +411573,22 @@ registerUiCatalogFragment({
411323
411573
  "swarmPermission.god.description": "Les outils et les changements de plan sont approuvés automatiquement. BLUN peut toujours vous poser des questions.",
411324
411574
  "swarmPermission.manual.label": "Lancer en mode Manuel",
411325
411575
  "swarmPermission.manual.description": "Conserver les approbations. BLUN peut s’arrêter et attendre votre réponse pendant la tâche en essaim.",
411326
- "settings.actionStyle.label": "Style d’action",
411327
- "settings.actionStyle.description": "Choisissez comment BLUN agit et explique son travail.",
411328
- "actionStyle.title": "Style d’action préféré",
411329
- "actionStyle.scope": "Ce réglage détermine la manière dont BLUN collabore avec vous. Les règles du plan et des autorisations restent prioritaires.",
411576
+ "settings.actionStyle.label": "Style de réponse",
411577
+ "settings.actionStyle.description": "Choisissez la manière dont BLUN répond et présente son travail.",
411578
+ "actionStyle.title": "Style de réponse préféré",
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.",
411330
411580
  "actionStyle.default.label": "Par défaut",
411331
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.",
411332
411584
  "actionStyle.proactive.label": "Proactif",
411333
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.",
411334
411586
  "actionStyle.explanatory.label": "Explicatif",
411335
411587
  "actionStyle.explanatory.description": "Explique ses choix d’implémentation et les éléments pertinents du code pendant son travail.",
411336
411588
  "actionStyle.learning.label": "Apprentissage",
411337
411589
  "actionStyle.learning.description": "S’arrête aux moments utiles et vous invite à écrire de petits extraits de code pour apprendre par la pratique.",
411338
- "actionStyle.saved": "Style d’action défini sur {style}.",
411339
- "actionStyle.saveFailed": "Impossible d’enregistrer le style d’action : {error}"
411590
+ "actionStyle.saved": "Style de réponse défini sur {style}.",
411591
+ "actionStyle.saveFailed": "Impossible d’enregistrer le style de réponse : {error}"
411340
411592
  },
411341
411593
  sv: {
411342
411594
  "permission.title": "Välj behörighetsläge",
@@ -411397,20 +411649,22 @@ registerUiCatalogFragment({
411397
411649
  "swarmPermission.god.description": "Verktyg och planändringar godkänns automatiskt. BLUN kan fortfarande ställa frågor.",
411398
411650
  "swarmPermission.manual.label": "Starta i manuellt läge",
411399
411651
  "swarmPermission.manual.description": "Behåll godkännanden aktiverade. BLUN kan stanna och vänta på dig under Swarm-uppgiften.",
411400
- "settings.actionStyle.label": "Handlingsstil",
411401
- "settings.actionStyle.description": "Välj hur BLUN agerar och förklarar sitt arbete.",
411402
- "actionStyle.title": "Önskad handlingsstil",
411403
- "actionStyle.scope": "Den här inställningen styr hur BLUN samarbetar med dig. Plan- och behörighetsregler har fortfarande företräde.",
411652
+ "settings.actionStyle.label": "Svarsstil",
411653
+ "settings.actionStyle.description": "Välj hur BLUN svarar och presenterar sitt arbete.",
411654
+ "actionStyle.title": "Önskad svarsstil",
411655
+ "actionStyle.scope": "Den här inställningen styr hur BLUN svarar. Plan- och behörighetsregler har fortfarande företräde.",
411404
411656
  "actionStyle.default.label": "Standard",
411405
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.",
411406
411660
  "actionStyle.proactive.label": "Proaktiv",
411407
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.",
411408
411662
  "actionStyle.explanatory.label": "Förklarande",
411409
411663
  "actionStyle.explanatory.description": "Förklarar implementeringsval och relevanta mönster i kodbasen under arbetets gång.",
411410
411664
  "actionStyle.learning.label": "Lärande",
411411
411665
  "actionStyle.learning.description": "Stannar upp vid lämpliga tillfällen och bjuder in dig att skriva små kodavsnitt för praktisk övning.",
411412
- "actionStyle.saved": "Handlingsstilen har ställts in på {style}.",
411413
- "actionStyle.saveFailed": "Det gick inte att spara handlingsstilen: {error}"
411666
+ "actionStyle.saved": "Svarsstilen har ställts in på {style}.",
411667
+ "actionStyle.saveFailed": "Det gick inte att spara svarsstilen: {error}"
411414
411668
  },
411415
411669
  cs: {
411416
411670
  "permission.title": "Vybrat režim oprávnění",
@@ -411471,20 +411725,22 @@ registerUiCatalogFragment({
411471
411725
  "swarmPermission.god.description": "Nástroje a změny plánu se schvalují automaticky. BLUN vám přesto může položit otázku.",
411472
411726
  "swarmPermission.manual.label": "Spustit v ručním režimu",
411473
411727
  "swarmPermission.manual.description": "Ponechte schválení zapnuto. BLUN se může během úlohy roje zastavit a čekat na vás.",
411474
- "settings.actionStyle.label": "Styl jednání",
411475
- "settings.actionStyle.description": "Zvolte, jak BLUN jedná a jak vysvětluje svou práci.",
411476
- "actionStyle.title": "Preferovaný styl jednání",
411477
- "actionStyle.scope": "Toto nastavení určuje, jak s vámi BLUN spolupracuje. Pravidla plánu a oprávnění mají i nadále přednost.",
411728
+ "settings.actionStyle.label": "Styl odpovědí",
411729
+ "settings.actionStyle.description": "Zvolte, jak BLUN odpovídá a jak prezentuje svou práci.",
411730
+ "actionStyle.title": "Preferovaný styl odpovědí",
411731
+ "actionStyle.scope": "Toto nastavení určuje, jak BLUN odpovídá. Pravidla plánu a oprávnění mají i nadále přednost.",
411478
411732
  "actionStyle.default.label": "Výchozí",
411479
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.",
411480
411736
  "actionStyle.proactive.label": "Proaktivní",
411481
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é.",
411482
411738
  "actionStyle.explanatory.label": "Vysvětlující",
411483
411739
  "actionStyle.explanatory.description": "Během práce vysvětluje implementační rozhodnutí a relevantní vzory v kódu.",
411484
411740
  "actionStyle.learning.label": "Výukový",
411485
411741
  "actionStyle.learning.description": "Na vhodných místech se zastaví a vyzve vás, abyste si pro praktické procvičení napsali malé části kódu.",
411486
- "actionStyle.saved": "Styl jednání byl nastaven na {style}.",
411487
- "actionStyle.saveFailed": "Styl jednání se nepodařilo uložit: {error}"
411742
+ "actionStyle.saved": "Styl odpovědí byl nastaven na {style}.",
411743
+ "actionStyle.saveFailed": "Styl odpovědí se nepodařilo uložit: {error}"
411488
411744
  }
411489
411745
  });
411490
411746
  //#endregion
@@ -412407,6 +412663,8 @@ registerUiCatalogFragment({
412407
412663
  "btw.error.send": "Failed to send /btw prompt: {error}",
412408
412664
  "btw.error.cancel": "Failed to cancel /btw: {error}",
412409
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.",
412410
412668
  "btw.turn.cancelled": "Interrupted by user",
412411
412669
  "btw.turn.filtered": "Provider safety policy blocked the response.",
412412
412670
  "btw.turn.ended": "BTW turn ended with reason: {reason}"
@@ -412416,6 +412674,8 @@ registerUiCatalogFragment({
412416
412674
  "btw.error.send": "Die /btw-Frage konnte nicht übermittelt werden: {error}",
412417
412675
  "btw.error.cancel": "/btw konnte nicht abgebrochen werden: {error}",
412418
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.",
412419
412679
  "btw.turn.cancelled": "Vom Benutzer unterbrochen",
412420
412680
  "btw.turn.filtered": "Die Sicherheitsrichtlinie des Anbieters hat die Antwort blockiert.",
412421
412681
  "btw.turn.ended": "Die BTW-Runde wurde mit folgendem Grund beendet: {reason}"
@@ -412425,6 +412685,8 @@ registerUiCatalogFragment({
412425
412685
  "btw.error.send": "No se pudo enviar la pregunta de /btw: {error}",
412426
412686
  "btw.error.cancel": "No se pudo cancelar /btw: {error}",
412427
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.",
412428
412690
  "btw.turn.cancelled": "Interrumpido por el usuario",
412429
412691
  "btw.turn.filtered": "La política de seguridad del proveedor bloqueó la respuesta.",
412430
412692
  "btw.turn.ended": "El turno de BTW terminó por este motivo: {reason}"
@@ -412434,6 +412696,8 @@ registerUiCatalogFragment({
412434
412696
  "btw.error.send": "Impossible d’envoyer la question /btw : {error}",
412435
412697
  "btw.error.cancel": "Impossible d’annuler /btw : {error}",
412436
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.",
412437
412701
  "btw.turn.cancelled": "Interrompu par l’utilisateur",
412438
412702
  "btw.turn.filtered": "La politique de sécurité du fournisseur a bloqué la réponse.",
412439
412703
  "btw.turn.ended": "Le tour BTW s’est terminé pour la raison suivante : {reason}"
@@ -412443,6 +412707,8 @@ registerUiCatalogFragment({
412443
412707
  "btw.error.send": "Det gick inte att skicka /btw-frågan: {error}",
412444
412708
  "btw.error.cancel": "Det gick inte att avbryta /btw: {error}",
412445
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.",
412446
412712
  "btw.turn.cancelled": "Avbröts av användaren",
412447
412713
  "btw.turn.filtered": "Leverantörens säkerhetspolicy blockerade svaret.",
412448
412714
  "btw.turn.ended": "BTW-rundan avslutades av följande orsak: {reason}"
@@ -412452,6 +412718,8 @@ registerUiCatalogFragment({
412452
412718
  "btw.error.send": "Odeslání dotazu /btw selhalo: {error}",
412453
412719
  "btw.error.cancel": "Nepodařilo se zrušit /btw: {error}",
412454
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.",
412455
412723
  "btw.turn.cancelled": "Přerušeno uživatelem",
412456
412724
  "btw.turn.filtered": "Bezpečnostní zásada poskytovatele zablokovala odpověď.",
412457
412725
  "btw.turn.ended": "Kolo BTW skončilo z důvodu: {reason}"
@@ -413272,6 +413540,7 @@ var EffortSelectorComponent = class extends Container {
413272
413540
  //#region src/tui/components/dialogs/action-style-selector.ts
413273
413541
  const ACTION_STYLES = [
413274
413542
  "default",
413543
+ "concise",
413275
413544
  "proactive",
413276
413545
  "explanatory",
413277
413546
  "learning"
@@ -417523,7 +417792,7 @@ function showPermissionPicker(host) {
417523
417792
  }
417524
417793
  }));
417525
417794
  }
417526
- function showActionStylePicker(host) {
417795
+ function showOutputStylePicker(host) {
417527
417796
  host.mountEditorReplacement(new ActionStyleSelectorComponent({
417528
417797
  currentValue: host.state.appState.actionStyle ?? "default",
417529
417798
  onSelect: (value) => {
@@ -417627,7 +417896,7 @@ function handleSettingsSelection(host, value) {
417627
417896
  handleEffortCommand(host, "");
417628
417897
  return;
417629
417898
  case "action-style":
417630
- showActionStylePicker(host);
417899
+ showOutputStylePicker(host);
417631
417900
  return;
417632
417901
  case "permission":
417633
417902
  showPermissionPicker(host);
@@ -425477,6 +425746,9 @@ var CompactionComponent = class extends Container {
425477
425746
  tokensAfter;
425478
425747
  estimatedInputTokens;
425479
425748
  attempt = 1;
425749
+ stage = 1;
425750
+ stageCount;
425751
+ estimatedProgressPercent;
425480
425752
  constructor(ui, instruction, tip, showRunning = true) {
425481
425753
  super();
425482
425754
  this.showRunning = showRunning;
@@ -425498,11 +425770,12 @@ var CompactionComponent = class extends Container {
425498
425770
  if (!this.showRunning && !this.done && !this.canceled && !this.failed) return [];
425499
425771
  return super.render(width);
425500
425772
  }
425501
- markDone(tokensBefore, tokensAfter) {
425773
+ markDone(tokensBefore, tokensAfter, stageCount) {
425502
425774
  if (this.done || this.canceled || this.failed) return;
425503
425775
  this.done = true;
425504
425776
  this.tokensBefore = tokensBefore;
425505
425777
  this.tokensAfter = tokensAfter;
425778
+ this.stageCount = stageCount;
425506
425779
  this.stopTicking();
425507
425780
  this.statusText.setText(this.buildStatusLine());
425508
425781
  this.ui?.requestRender();
@@ -425529,6 +425802,8 @@ var CompactionComponent = class extends Container {
425529
425802
  if (progress.estimatedInputTokens !== void 0) this.estimatedInputTokens = progress.estimatedInputTokens;
425530
425803
  if (progress.attempt !== this.attempt) this.attemptStartedAtMs = Date.now();
425531
425804
  this.attempt = progress.attempt;
425805
+ this.stage = progress.stage ?? this.stage;
425806
+ this.estimatedProgressPercent = progress.estimatedProgressPercent;
425532
425807
  this.statusText.setText(this.buildStatusLine());
425533
425808
  if (this.showRunning) this.ui?.requestRender();
425534
425809
  }
@@ -425539,16 +425814,21 @@ var CompactionComponent = class extends Container {
425539
425814
  return Math.max(0, Math.round((Date.now() - this.startedAtMs) / 1e3));
425540
425815
  }
425541
425816
  buildStatusLine() {
425542
- 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", {
425543
- before: this.tokensBefore.toLocaleString(getCurrentUiLocale()),
425544
- after: this.tokensAfter.toLocaleString(getCurrentUiLocale())
425545
- })) : ""}`;
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
+ }
425546
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")}`)}`;
425547
425826
  if (this.canceled) return `${currentTheme.fg("warning", STATUS_BULLET)}${currentTheme.boldFg("warning", uiText("compaction.canceled"))}`;
425548
- 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);
425549
425829
  const filled = Math.round(percent / 100 * BAR_WIDTH);
425550
425830
  const bar = `[${"█".repeat(filled)}${"░".repeat(BAR_WIDTH - filled)}]`;
425551
- const runningText = formatCompactionRunningText(this.estimatedInputTokens);
425831
+ const runningText = `${formatCompactionRunningText(this.estimatedInputTokens)} · ${String(this.stage)}/?`;
425552
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}`) : ""}`;
425553
425833
  }
425554
425834
  startTicking() {
@@ -492031,6 +492311,9 @@ async function handleBuiltInSlashCommand(host, name, args) {
492031
492311
  case "effort":
492032
492312
  await handleEffortCommand(host, args);
492033
492313
  return;
492314
+ case "output-style":
492315
+ showOutputStylePicker(host);
492316
+ return;
492034
492317
  case "permission":
492035
492318
  showPermissionPicker(host);
492036
492319
  return;
@@ -498091,6 +498374,406 @@ registerUiCatalogFragment({
498091
498374
  }
498092
498375
  });
498093
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
498094
498777
  //#region src/tui/components/panes/btw-panel.ts
498095
498778
  const MIN_COLLAPSED_PANEL_LINES = 3;
498096
498779
  var BtwPanelComponent = class {
@@ -498101,6 +498784,8 @@ var BtwPanelComponent = class {
498101
498784
  followTail = true;
498102
498785
  scrollTop = 0;
498103
498786
  maxScrollTop = 0;
498787
+ spinnerFrame = 0;
498788
+ liveStatusTimer;
498104
498789
  constructor(options) {
498105
498790
  this.options = options;
498106
498791
  }
@@ -498112,10 +498797,12 @@ var BtwPanelComponent = class {
498112
498797
  this.transientNotices.length = 0;
498113
498798
  this.turns.push({
498114
498799
  prompt: normalized,
498800
+ startedAtMs: Date.now(),
498115
498801
  answer: "",
498116
498802
  thinking: "",
498117
498803
  phase: "running"
498118
498804
  });
498805
+ this.startLiveStatusTimer();
498119
498806
  this.options.onPrompt(normalized);
498120
498807
  }
498121
498808
  addTransientNotice(message) {
@@ -498132,31 +498819,48 @@ var BtwPanelComponent = class {
498132
498819
  if (turn === void 0) return;
498133
498820
  turn.thinking += delta;
498134
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
+ }
498135
498832
  markDone(resultSummary) {
498136
498833
  const turn = this.currentTurn();
498137
498834
  if (turn === void 0) return;
498138
498835
  if (turn.answer.trim().length === 0 && resultSummary !== void 0) turn.answer = resultSummary;
498139
498836
  this.transientNotices.length = 0;
498140
498837
  turn.phase = "done";
498838
+ this.stopLiveStatusTimer();
498141
498839
  }
498142
498840
  markFailed(error) {
498143
498841
  const turn = this.currentTurn();
498144
498842
  if (turn === void 0 || turn.phase !== "running") {
498145
498843
  this.turns.push({
498146
498844
  prompt: "",
498845
+ startedAtMs: Date.now(),
498147
498846
  answer: "",
498148
498847
  thinking: "",
498149
498848
  error,
498150
498849
  phase: "failed"
498151
498850
  });
498152
498851
  this.transientNotices.length = 0;
498852
+ this.stopLiveStatusTimer();
498153
498853
  return;
498154
498854
  }
498155
498855
  turn.error = error;
498156
498856
  this.transientNotices.length = 0;
498157
498857
  turn.phase = "failed";
498858
+ this.stopLiveStatusTimer();
498158
498859
  }
498159
498860
  invalidate() {}
498861
+ dispose() {
498862
+ this.stopLiveStatusTimer();
498863
+ }
498160
498864
  render(width) {
498161
498865
  const safeWidth = Math.max(4, width);
498162
498866
  const contentWidth = Math.max(1, safeWidth - 4);
@@ -498229,7 +498933,8 @@ var BtwPanelComponent = class {
498229
498933
  const thinkingLines = new Text(chalk.hex(currentTheme.palette.textDim)(thinking), 0, 0).render(width);
498230
498934
  const visibleThinking = thinkingLines.length > 2 ? thinkingLines.slice(thinkingLines.length - 2) : thinkingLines;
498231
498935
  lines.push(...visibleThinking);
498232
- } 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));
498233
498938
  if (turn.error !== void 0) {
498234
498939
  const error = chalk.hex(currentTheme.palette.error)(turn.error);
498235
498940
  lines.push(...new Text(error, 0, 0).render(width));
@@ -498246,6 +498951,26 @@ var BtwPanelComponent = class {
498246
498951
  currentTurn() {
498247
498952
  return this.turns.at(-1);
498248
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
+ }
498249
498974
  isRunning() {
498250
498975
  return this.currentTurn()?.phase === "running";
498251
498976
  }
@@ -498312,6 +499037,7 @@ function formatHookResultBody(event) {
498312
499037
  }
498313
499038
  //#endregion
498314
499039
  //#region src/tui/controllers/btw-panel.ts
499040
+ const BTW_INACTIVITY_TIMEOUT_MS = 12e4;
498315
499041
  var BtwPanelController = class {
498316
499042
  host;
498317
499043
  active;
@@ -498325,13 +499051,16 @@ var BtwPanelController = class {
498325
499051
  markdownTheme: createMarkdownTheme(),
498326
499052
  canUseScrollKeys: () => this.host.state.editor.getText().length === 0,
498327
499053
  terminalRows: () => this.host.state.terminal.rows,
499054
+ requestRender: () => this.host.state.ui.requestRender(),
498328
499055
  onPrompt: (prompt) => {
498329
- this.promptAgent(agentId, prompt, panel);
499056
+ this.promptPanel(panel, prompt);
498330
499057
  }
498331
499058
  });
498332
499059
  this.active = {
498333
499060
  agentId,
498334
- panel
499061
+ panel,
499062
+ prompt: "",
499063
+ retryCount: 0
498335
499064
  };
498336
499065
  this.panelsByAgentId.set(agentId, panel);
498337
499066
  this.mount(panel);
@@ -498340,6 +499069,8 @@ var BtwPanelController = class {
498340
499069
  clear() {
498341
499070
  const active = this.active;
498342
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);
498343
499074
  this.active = void 0;
498344
499075
  this.panelsByAgentId.clear();
498345
499076
  this.host.state.btwPanelContainer.clear();
@@ -498382,19 +499113,23 @@ var BtwPanelController = class {
498382
499113
  if (panel === void 0) return false;
498383
499114
  switch (event.type) {
498384
499115
  case "assistant.delta":
499116
+ this.clearInactivityTimer(this.activeForAgent(event.agentId));
498385
499117
  panel.appendAnswer(event.delta);
498386
499118
  this.host.state.ui.requestRender();
498387
499119
  return true;
498388
499120
  case "thinking.delta":
498389
499121
  panel.appendThinking(event.delta);
499122
+ this.armInactivityTimer(this.activeForAgent(event.agentId));
498390
499123
  this.host.state.ui.requestRender();
498391
499124
  return true;
498392
499125
  case "hook.result":
499126
+ this.clearInactivityTimer(this.activeForAgent(event.agentId));
498393
499127
  panel.appendAnswer(formatHookResultPlain(event));
498394
499128
  this.host.state.ui.requestRender();
498395
499129
  return true;
498396
499130
  case "turn.ended":
498397
- if (event.reason === "completed") panel.markDone();
499131
+ this.clearInactivityTimer(this.activeForAgent(event.agentId));
499132
+ if (event.reason === "completed") panel.markDone(uiText("eventPayload.modelEmptyResponse"));
498398
499133
  else panel.markFailed(formatBtwTurnEnd(event));
498399
499134
  this.host.state.ui.requestRender();
498400
499135
  return true;
@@ -498411,7 +499146,9 @@ var BtwPanelController = class {
498411
499146
  }
498412
499147
  close(panel) {
498413
499148
  if (!this.host.state.btwPanelContainer.children.includes(panel)) return;
499149
+ this.clearInactivityTimer(this.active?.panel === panel ? this.active : void 0);
498414
499150
  this.unregister(panel);
499151
+ panel.dispose();
498415
499152
  this.host.state.btwPanelContainer.clear();
498416
499153
  this.host.state.editor.connectedAbove = false;
498417
499154
  this.host.state.ui.setFocus(this.host.state.editor);
@@ -498426,6 +499163,14 @@ var BtwPanelController = class {
498426
499163
  active.panel.addTransientNotice(uiText("btw.busy"));
498427
499164
  this.host.state.ui.requestRender();
498428
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
+ }
498429
499174
  promptAgent(agentId, prompt, panel) {
498430
499175
  const session = this.host.session;
498431
499176
  if (session === void 0) {
@@ -498434,16 +499179,76 @@ var BtwPanelController = class {
498434
499179
  return;
498435
499180
  }
498436
499181
  this.withInteractiveAgent(agentId, () => session.prompt(prompt)).catch((error) => {
499182
+ if (this.panelsByAgentId.get(agentId) !== panel) return;
499183
+ this.clearInactivityTimer(this.activeForAgent(agentId));
498437
499184
  panel.markFailed(uiText("btw.error.send", { error: formatErrorMessage$2(error) }));
498438
499185
  this.host.state.ui.requestRender();
498439
499186
  });
498440
499187
  }
498441
499188
  async cancelAgent(agentId) {
498442
499189
  const session = this.host.session;
498443
- if (session === void 0) return;
498444
- await this.withInteractiveAgent(agentId, () => session.cancel()).catch((error) => {
498445
- this.host.showError(uiText("btw.error.cancel", { error: formatErrorMessage$2(error) }));
498446
- });
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
+ }
498447
499252
  }
498448
499253
  shouldCancelOnUnmount(panel) {
498449
499254
  return panel.isRunning() || panel.isEmpty();
@@ -498491,6 +499296,7 @@ var ChannelQueueDeadlineController = class {
498491
499296
  if (this.deadlineTimer === void 0 && !this.interruptionRequested) this.scheduleDeadline(CHANNEL_QUEUE_DEADLINE_MS);
498492
499297
  }
498493
499298
  requestDeliveryAtSafePoint() {
499299
+ if (this.retainReleaseWhileDeliveryIsInFlight()) return;
498494
499300
  if (this.host.hasWaitingWork() && !this.host.canDeliverWork()) {
498495
499301
  this.requestInterruption();
498496
499302
  return;
@@ -498498,12 +499304,18 @@ var ChannelQueueDeadlineController = class {
498498
499304
  this.requestDelivery();
498499
499305
  }
498500
499306
  requestDeliveryNow() {
499307
+ if (this.retainReleaseWhileDeliveryIsInFlight()) return;
498501
499308
  if (this.host.hasWaitingWork() && !this.host.canDeliverWork()) {
498502
499309
  this.requestInterruption();
498503
499310
  return;
498504
499311
  }
498505
499312
  this.requestDelivery();
498506
499313
  }
499314
+ retainReleaseWhileDeliveryIsInFlight() {
499315
+ if (this.disposed || !this.deliveryInFlight || !this.host.hasWaitingWork()) return false;
499316
+ this.pendingReleaseCount += 1;
499317
+ return true;
499318
+ }
498507
499319
  dispose() {
498508
499320
  this.disposed = true;
498509
499321
  this.pendingReleaseCount = 0;
@@ -502987,6 +503799,7 @@ var SessionEventHandler = class {
502987
503799
  synthetic: event.synthetic
502988
503800
  };
502989
503801
  const matchedCall = streamingUI.completeToolResult(event.toolCallId, resultData);
503802
+ if (matchedCall?.name === "GetMedia" && event.isError !== true) this.host.runChannelMediaFallback(event.output);
502990
503803
  this.subAgentEventHandler.handleAgentSwarmToolResult(event.toolCallId, resultData, event.isError === true);
502991
503804
  if (matchedCall !== void 0 && matchedCall.name === "TodoList" && !event.isError) {
502992
503805
  const rawTodos = matchedCall.args.todos;
@@ -503375,7 +504188,7 @@ var SessionEventHandler = class {
503375
504188
  }
503376
504189
  handleCompactionEnd(event) {
503377
504190
  this.host.setAppState({ contextTokens: event.projectedContextTokens ?? event.result.tokensAfter });
503378
- this.host.streamingUI.endCompaction(event.result.tokensBefore, event.result.tokensAfter);
504191
+ this.host.streamingUI.endCompaction(event.result.tokensBefore, event.result.tokensAfter, event.stageCount);
503379
504192
  this.lastCompactionInstruction = void 0;
503380
504193
  this.finishCompaction();
503381
504194
  }
@@ -506335,7 +507148,7 @@ var StreamingUIController = class {
506335
507148
  this._activeCompactionBlock.markDone();
506336
507149
  this._activeCompactionBlock = void 0;
506337
507150
  }
506338
- const block = new CompactionComponent(state.ui, instruction, currentWorkingTip()?.text, false);
507151
+ const block = new CompactionComponent(state.ui, instruction, currentWorkingTip()?.text, true);
506339
507152
  this._activeCompactionBlock = block;
506340
507153
  state.transcriptContainer.addChild(block);
506341
507154
  state.ui.requestRender();
@@ -506347,11 +507160,11 @@ var StreamingUIController = class {
506347
507160
  block.setProgress(progress);
506348
507161
  this.host.state.ui.requestRender();
506349
507162
  }
506350
- endCompaction(tokensBefore, tokensAfter) {
507163
+ endCompaction(tokensBefore, tokensAfter, stageCount) {
506351
507164
  this.host.state.footer.finishCompaction();
506352
507165
  const block = this._activeCompactionBlock;
506353
507166
  if (block === void 0) return;
506354
- block.markDone(tokensBefore, tokensAfter);
507167
+ block.markDone(tokensBefore, tokensAfter, stageCount);
506355
507168
  this._activeCompactionBlock = void 0;
506356
507169
  this._cancelledCompactionBlock = void 0;
506357
507170
  this.host.state.ui.requestRender();
@@ -509330,6 +510143,10 @@ function channelDir() {
509330
510143
  function outboxPath() {
509331
510144
  return join$4(channelDir(), "outbox.jsonl");
509332
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
+ }
509333
510150
  /** Host-owned channel setting from process env or the channel .env. */
509334
510151
  function channelSetting(name) {
509335
510152
  const configured = process.env[name]?.trim();
@@ -509372,6 +510189,49 @@ function outboxGrewForChat(marker, chatId) {
509372
510189
  return false;
509373
510190
  }
509374
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
+ }
509375
510235
  /** Telegram group/supergroup ids are negative; DMs are the positive user id. */
509376
510236
  function isGroupChat(chatId) {
509377
510237
  return chatId.startsWith("-");
@@ -509444,6 +510304,40 @@ async function sendReplyFallback(chatId, text, contextOnly = false) {
509444
510304
  return false;
509445
510305
  }
509446
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
+ }
509447
510341
  //#endregion
509448
510342
  //#region src/tui/utils/dead-terminal.ts
509449
510343
  /**
@@ -510404,406 +511298,6 @@ function turnsToTrim(turns, maxTurns, hysteresis) {
510404
511298
  return toRemove;
510405
511299
  }
510406
511300
  //#endregion
510407
- //#region src/tui/blun-tui.copy.ts
510408
- registerUiCatalogFragment({
510409
- en: {
510410
- "blunTui.provider.modelAdded.one": "{providerName} · +{count} model.",
510411
- "blunTui.provider.modelAdded.other": "{providerName} · +{count} models.",
510412
- "blunTui.provider.refreshSkipped": "Skipped refreshing {provider}: {reason}",
510413
- "blunTui.warning": "Warning: {warning}",
510414
- "blunTui.startup.sessionNotFound": "Session \"{sessionId}\" not found.",
510415
- "blunTui.startup.sessionDifferentDirectory": "Session \"{sessionId}\" was created under a different directory.",
510416
- "blunTui.startup.noSessionsToContinue": "No sessions to continue under \"{workDir}\"; starting a fresh session.",
510417
- "blunTui.startup.sessionNotInitialized": "Startup session was not initialized.",
510418
- "blunTui.input.replayBlocked": "Cannot send input while session history is replaying.",
510419
- "blunTui.shell.noSession": "No active session for shell command.",
510420
- "blunTui.shell.runFailed": "Shell command failed: {error}",
510421
- "blunTui.shell.cancelFailed": "Failed to cancel shell command: {error}",
510422
- "blunTui.channel.steerFailed": "Failed to steer channel message: {error}",
510423
- "blunTui.session.sendFailed": "Failed to send: {error}",
510424
- "blunTui.media.imageUnsupported": "Current model does not support image input.",
510425
- "blunTui.media.videoUnsupported": "Current model does not support video input.",
510426
- "blunTui.skill.failed": "Skill \"{skillName}\" failed: {error}",
510427
- "blunTui.pluginCommand.failed": "Command \"{command}\" failed: {error}",
510428
- "blunTui.steer.failed": "Failed to steer: {error}",
510429
- "blunTui.session.otherWorkDir": "Current session is in a different working directory.",
510430
- "blunTui.session.resumeCommand": "To resume, run: {command}",
510431
- "blunTui.clipboard.commandCopied": "Command copied to clipboard",
510432
- "blunTui.clipboard.commandCopyFailed": "Failed to copy command to clipboard",
510433
- "blunTui.session.alreadyCurrent": "Already on this session.",
510434
- "blunTui.session.switchStreamingBlocked": "Cannot switch sessions while streaming — press Esc or Ctrl-C first.",
510435
- "blunTui.session.switchReplayBlocked": "Cannot switch sessions while history is replaying.",
510436
- "blunTui.session.resumeFailed": "Failed to resume session {sessionId}: {error}",
510437
- "blunTui.session.resumed": "Resumed session ({sessionId}).",
510438
- "blunTui.session.replayFailed": "Failed to replay session history: {error}",
510439
- "blunTui.session.createReplayBlocked": "Cannot start a new session while history is replaying.",
510440
- "blunTui.session.createFailed": "Failed to start a new session: {error}",
510441
- "blunTui.session.postCreateFailed": "Post-create setup failed: {error}",
510442
- "blunTui.session.started": "Started a new session ({sessionId}).",
510443
- "blunTui.error": "Error: {message}",
510444
- "blunTui.login.title": "Sign in to BLUN",
510445
- "blunTui.login.hint": "Press Ctrl-C to cancel",
510446
- "blunTui.login.waiting": "Waiting for authorization…",
510447
- "blunTui.detach.noShell": "No shell command running.",
510448
- "blunTui.detach.shellStarting": "Command is still starting — try again.",
510449
- "blunTui.detach.shellFinished": "Command already finished.",
510450
- "blunTui.detach.moveFailed": "Failed to move to background: {error}",
510451
- "blunTui.detach.movedTranscript": "Moved to background.",
510452
- "blunTui.detach.movedView": "Moved to background. /tasks to view.",
510453
- "blunTui.detach.noForeground": "No foreground task running.",
510454
- "blunTui.detach.listFailed": "Failed to list tasks: {error}",
510455
- "blunTui.detach.taskFailed": "Failed to detach {taskId}: {error}",
510456
- "blunTui.detach.finished.one": "Task already finished.",
510457
- "blunTui.detach.finished.other": "Tasks already finished.",
510458
- "blunTui.detach.moved.one": "Moved {count} task to background.",
510459
- "blunTui.detach.moved.other": "Moved {count} tasks to background.",
510460
- "blunTui.detach.partial": "Moved {detached} of {total} tasks to background.",
510461
- "blunTui.detach.viewSuffix": "/tasks to view.",
510462
- "blunTui.startup.flagsFailed": "Failed to apply startup flags: {error}",
510463
- "blunTui.notification.approvalRequired": "BLUN approval required",
510464
- "blunTui.notification.answerRequired": "BLUN needs your answer",
510465
- "blunTui.telegram.fallbackDelivered": "Reply delivered to Telegram automatically (fallback).",
510466
- "blunTui.telegram.attachDisabled": "Telegram attachment disabled by BLUN_TELEGRAM_ATTACH=off — headless mode active.",
510467
- "blunTui.telegram.noToken": "Telegram attachment: no token detected — headless mode active.",
510468
- "blunTui.telegram.attached": "Telegram channel attached (lease PID {pid}) — messages appear in this window.",
510469
- "blunTui.auto.status": "Auto: {label}",
510470
- "blunTui.activity.thinking": "{name} is thinking…",
510471
- "blunTui.activity.working": "{name} is working…",
510472
- "blunTui.activity.composing": "working...",
510473
- "blunTui.activity.tokens": "Tokens"
510474
- },
510475
- de: {
510476
- "blunTui.provider.modelAdded.one": "{providerName} · +{count} Modell.",
510477
- "blunTui.provider.modelAdded.other": "{providerName} · +{count} Modelle.",
510478
- "blunTui.provider.refreshSkipped": "Aktualisierung von {provider} übersprungen: {reason}",
510479
- "blunTui.warning": "Warnung: {warning}",
510480
- "blunTui.startup.sessionNotFound": "Sitzung „{sessionId}“ wurde nicht gefunden.",
510481
- "blunTui.startup.sessionDifferentDirectory": "Sitzung „{sessionId}“ wurde in einem anderen Arbeitsverzeichnis erstellt.",
510482
- "blunTui.startup.noSessionsToContinue": "Unter „{workDir}“ gibt es keine Sitzung zum Fortsetzen; eine neue Sitzung wird gestartet.",
510483
- "blunTui.startup.sessionNotInitialized": "Die Sitzung konnte beim Start nicht initialisiert werden.",
510484
- "blunTui.input.replayBlocked": "Während der Wiedergabe des Sitzungsverlaufs können keine Eingaben gesendet werden.",
510485
- "blunTui.shell.noSession": "Keine aktive Sitzung für den Shell-Befehl.",
510486
- "blunTui.shell.runFailed": "Shell-Befehl fehlgeschlagen: {error}",
510487
- "blunTui.shell.cancelFailed": "Shell-Befehl konnte nicht abgebrochen werden: {error}",
510488
- "blunTui.channel.steerFailed": "Die Kanalnachricht konnte nicht zur laufenden Antwort hinzugefügt werden: {error}",
510489
- "blunTui.session.sendFailed": "Senden fehlgeschlagen: {error}",
510490
- "blunTui.media.imageUnsupported": "Das aktuelle Modell unterstützt keine Bildeingaben.",
510491
- "blunTui.media.videoUnsupported": "Das aktuelle Modell unterstützt keine Videoeingaben.",
510492
- "blunTui.skill.failed": "Skill „{skillName}“ fehlgeschlagen: {error}",
510493
- "blunTui.pluginCommand.failed": "Befehl „{command}“ fehlgeschlagen: {error}",
510494
- "blunTui.steer.failed": "Nachsteuern fehlgeschlagen: {error}",
510495
- "blunTui.session.otherWorkDir": "Die aktuelle Sitzung befindet sich in einem anderen Arbeitsverzeichnis.",
510496
- "blunTui.session.resumeCommand": "Zum Fortsetzen ausführen: {command}",
510497
- "blunTui.clipboard.commandCopied": "Befehl in die Zwischenablage kopiert",
510498
- "blunTui.clipboard.commandCopyFailed": "Befehl konnte nicht in die Zwischenablage kopiert werden",
510499
- "blunTui.session.alreadyCurrent": "Diese Sitzung ist bereits aktiv.",
510500
- "blunTui.session.switchStreamingBlocked": "Während einer laufenden Antwort kann die Sitzung nicht gewechselt werden. Drücke zuerst Esc oder Ctrl-C.",
510501
- "blunTui.session.switchReplayBlocked": "Während der Wiedergabe des Sitzungsverlaufs kann die Sitzung nicht gewechselt werden.",
510502
- "blunTui.session.resumeFailed": "Sitzung {sessionId} konnte nicht fortgesetzt werden: {error}",
510503
- "blunTui.session.resumed": "Sitzung fortgesetzt ({sessionId}).",
510504
- "blunTui.session.replayFailed": "Der Sitzungsverlauf konnte nicht wiedergegeben werden: {error}",
510505
- "blunTui.session.createReplayBlocked": "Während der Wiedergabe des Sitzungsverlaufs kann keine neue Sitzung gestartet werden.",
510506
- "blunTui.session.createFailed": "Neue Sitzung konnte nicht gestartet werden: {error}",
510507
- "blunTui.session.postCreateFailed": "Die neue Sitzung konnte nicht eingerichtet werden: {error}",
510508
- "blunTui.session.started": "Neue Sitzung gestartet ({sessionId}).",
510509
- "blunTui.error": "Fehler: {message}",
510510
- "blunTui.login.title": "Bei BLUN anmelden",
510511
- "blunTui.login.hint": "Zum Abbrechen Ctrl-C drücken",
510512
- "blunTui.login.waiting": "Warten auf Autorisierung…",
510513
- "blunTui.detach.noShell": "Es wird kein Shell-Befehl ausgeführt.",
510514
- "blunTui.detach.shellStarting": "Der Befehl wird noch gestartet — versuche es erneut.",
510515
- "blunTui.detach.shellFinished": "Der Befehl ist bereits beendet.",
510516
- "blunTui.detach.moveFailed": "Verschieben in den Hintergrund fehlgeschlagen: {error}",
510517
- "blunTui.detach.movedTranscript": "In den Hintergrund verschoben.",
510518
- "blunTui.detach.movedView": "In den Hintergrund verschoben. Mit /tasks anzeigen.",
510519
- "blunTui.detach.noForeground": "Es wird keine Aufgabe im Vordergrund ausgeführt.",
510520
- "blunTui.detach.listFailed": "Aufgaben konnten nicht aufgelistet werden: {error}",
510521
- "blunTui.detach.taskFailed": "Aufgabe {taskId} konnte nicht in den Hintergrund verschoben werden: {error}",
510522
- "blunTui.detach.finished.one": "Aufgabe ist bereits beendet.",
510523
- "blunTui.detach.finished.other": "Aufgaben sind bereits beendet.",
510524
- "blunTui.detach.moved.one": "{count} Aufgabe in den Hintergrund verschoben.",
510525
- "blunTui.detach.moved.other": "{count} Aufgaben in den Hintergrund verschoben.",
510526
- "blunTui.detach.partial": "{detached} von {total} Aufgaben in den Hintergrund verschoben.",
510527
- "blunTui.detach.viewSuffix": "Mit /tasks anzeigen.",
510528
- "blunTui.startup.flagsFailed": "Startoptionen konnten nicht angewendet werden: {error}",
510529
- "blunTui.notification.approvalRequired": "BLUN-Genehmigung erforderlich",
510530
- "blunTui.notification.answerRequired": "BLUN benötigt deine Antwort",
510531
- "blunTui.telegram.fallbackDelivered": "Antwort automatisch nach Telegram zugestellt (Fallback).",
510532
- "blunTui.telegram.attachDisabled": "Telegram-Anbindung durch BLUN_TELEGRAM_ATTACH=off deaktiviert — Headless-Modus aktiv.",
510533
- "blunTui.telegram.noToken": "Telegram-Anbindung: kein Token erkannt — Headless-Modus aktiv.",
510534
- "blunTui.telegram.attached": "Telegram-Kanal angebunden (Lease-PID {pid}) — Nachrichten erscheinen in diesem Fenster.",
510535
- "blunTui.auto.status": "Auto: {label}",
510536
- "blunTui.activity.thinking": "{name} denkt…",
510537
- "blunTui.activity.working": "{name} arbeitet…",
510538
- "blunTui.activity.composing": "arbeitet...",
510539
- "blunTui.activity.tokens": "Token"
510540
- },
510541
- es: {
510542
- "blunTui.provider.modelAdded.one": "{providerName} · +{count} modelo.",
510543
- "blunTui.provider.modelAdded.other": "{providerName} · +{count} modelos.",
510544
- "blunTui.provider.refreshSkipped": "Se omitió la actualización de {provider}: {reason}",
510545
- "blunTui.warning": "Advertencia: {warning}",
510546
- "blunTui.startup.sessionNotFound": "No se encontró la sesión «{sessionId}».",
510547
- "blunTui.startup.sessionDifferentDirectory": "La sesión «{sessionId}» se creó en otro directorio de trabajo.",
510548
- "blunTui.startup.noSessionsToContinue": "No hay sesiones que reanudar en «{workDir}»; se iniciará una sesión nueva.",
510549
- "blunTui.startup.sessionNotInitialized": "No se pudo inicializar la sesión durante el arranque.",
510550
- "blunTui.input.replayBlocked": "No se puede enviar ninguna entrada mientras se reproduce el historial de la sesión.",
510551
- "blunTui.shell.noSession": "No hay ninguna sesión activa para el comando de shell.",
510552
- "blunTui.shell.runFailed": "El comando de shell falló: {error}",
510553
- "blunTui.shell.cancelFailed": "No se pudo cancelar el comando de shell: {error}",
510554
- "blunTui.channel.steerFailed": "No se pudo añadir el mensaje del canal a la respuesta en curso: {error}",
510555
- "blunTui.session.sendFailed": "No se pudo enviar: {error}",
510556
- "blunTui.media.imageUnsupported": "El modelo actual no admite entradas de imagen.",
510557
- "blunTui.media.videoUnsupported": "El modelo actual no admite entradas de vídeo.",
510558
- "blunTui.skill.failed": "El skill «{skillName}» falló: {error}",
510559
- "blunTui.pluginCommand.failed": "El comando «{command}» falló: {error}",
510560
- "blunTui.steer.failed": "No se pudo reorientar la respuesta: {error}",
510561
- "blunTui.session.otherWorkDir": "La sesión actual se encuentra en otro directorio de trabajo.",
510562
- "blunTui.session.resumeCommand": "Para reanudarla, ejecuta: {command}",
510563
- "blunTui.clipboard.commandCopied": "Comando copiado al portapapeles",
510564
- "blunTui.clipboard.commandCopyFailed": "No se pudo copiar el comando al portapapeles",
510565
- "blunTui.session.alreadyCurrent": "Esta sesión ya está activa.",
510566
- "blunTui.session.switchStreamingBlocked": "No se puede cambiar de sesión mientras se genera una respuesta. Pulsa primero Esc o Ctrl-C.",
510567
- "blunTui.session.switchReplayBlocked": "No se puede cambiar de sesión mientras se reproduce el historial.",
510568
- "blunTui.session.resumeFailed": "No se pudo reanudar la sesión {sessionId}: {error}",
510569
- "blunTui.session.resumed": "Sesión reanudada ({sessionId}).",
510570
- "blunTui.session.replayFailed": "No se pudo reproducir el historial de la sesión: {error}",
510571
- "blunTui.session.createReplayBlocked": "No se puede iniciar una sesión nueva mientras se reproduce el historial.",
510572
- "blunTui.session.createFailed": "No se pudo iniciar una sesión nueva: {error}",
510573
- "blunTui.session.postCreateFailed": "No se pudo configurar la sesión recién creada: {error}",
510574
- "blunTui.session.started": "Se inició una sesión nueva ({sessionId}).",
510575
- "blunTui.error": "Error: {message}",
510576
- "blunTui.login.title": "Iniciar sesión en BLUN",
510577
- "blunTui.login.hint": "Pulsa Ctrl-C para cancelar",
510578
- "blunTui.login.waiting": "Esperando autorización…",
510579
- "blunTui.detach.noShell": "No hay ningún comando de shell en ejecución.",
510580
- "blunTui.detach.shellStarting": "El comando todavía se está iniciando; inténtalo de nuevo.",
510581
- "blunTui.detach.shellFinished": "El comando ya ha finalizado.",
510582
- "blunTui.detach.moveFailed": "No se pudo mover a segundo plano: {error}",
510583
- "blunTui.detach.movedTranscript": "Se movió a segundo plano.",
510584
- "blunTui.detach.movedView": "Se movió a segundo plano. Consulta /tasks.",
510585
- "blunTui.detach.noForeground": "No hay ninguna tarea en ejecución en primer plano.",
510586
- "blunTui.detach.listFailed": "No se pudieron obtener las tareas: {error}",
510587
- "blunTui.detach.taskFailed": "No se pudo mover la tarea {taskId} a segundo plano: {error}",
510588
- "blunTui.detach.finished.one": "La tarea ya ha finalizado.",
510589
- "blunTui.detach.finished.other": "Las tareas ya han finalizado.",
510590
- "blunTui.detach.moved.one": "Se ha movido {count} tarea a segundo plano.",
510591
- "blunTui.detach.moved.other": "Se han movido {count} tareas a segundo plano.",
510592
- "blunTui.detach.partial": "Se han movido {detached} de {total} tareas a segundo plano.",
510593
- "blunTui.detach.viewSuffix": "Consulta /tasks.",
510594
- "blunTui.startup.flagsFailed": "No se pudieron aplicar las opciones de inicio: {error}",
510595
- "blunTui.notification.approvalRequired": "Se requiere aprobación de BLUN",
510596
- "blunTui.notification.answerRequired": "BLUN necesita tu respuesta",
510597
- "blunTui.telegram.fallbackDelivered": "La respuesta se envió automáticamente a Telegram (modo alternativo).",
510598
- "blunTui.telegram.attachDisabled": "Conexión con Telegram desactivada mediante BLUN_TELEGRAM_ATTACH=off — modo headless activo.",
510599
- "blunTui.telegram.noToken": "Conexión con Telegram: no se detectó ningún token — modo headless activo.",
510600
- "blunTui.telegram.attached": "Canal de Telegram conectado (PID de lease {pid}) — los mensajes aparecen en esta ventana.",
510601
- "blunTui.auto.status": "Automático: {label}",
510602
- "blunTui.activity.thinking": "{name} está pensando…",
510603
- "blunTui.activity.working": "{name} está trabajando…",
510604
- "blunTui.activity.composing": "trabajando...",
510605
- "blunTui.activity.tokens": "tokens"
510606
- },
510607
- fr: {
510608
- "blunTui.provider.modelAdded.one": "{providerName} · +{count} modèle.",
510609
- "blunTui.provider.modelAdded.other": "{providerName} · +{count} modèles.",
510610
- "blunTui.provider.refreshSkipped": "Actualisation de {provider} ignorée : {reason}",
510611
- "blunTui.warning": "Avertissement : {warning}",
510612
- "blunTui.startup.sessionNotFound": "Session « {sessionId} » introuvable.",
510613
- "blunTui.startup.sessionDifferentDirectory": "La session « {sessionId} » a été créée dans un autre répertoire de travail.",
510614
- "blunTui.startup.noSessionsToContinue": "Aucune session à reprendre dans « {workDir} » ; démarrage d’une nouvelle session.",
510615
- "blunTui.startup.sessionNotInitialized": "La session de démarrage n’a pas été initialisée.",
510616
- "blunTui.input.replayBlocked": "Impossible d’envoyer une saisie pendant la relecture de l’historique de la session.",
510617
- "blunTui.shell.noSession": "Aucune session active pour la commande shell.",
510618
- "blunTui.shell.runFailed": "Échec de la commande shell : {error}",
510619
- "blunTui.shell.cancelFailed": "Impossible d’annuler la commande shell : {error}",
510620
- "blunTui.channel.steerFailed": "Impossible d’ajouter le message du canal à la réponse en cours : {error}",
510621
- "blunTui.session.sendFailed": "Échec de l’envoi : {error}",
510622
- "blunTui.media.imageUnsupported": "Le modèle actuel ne prend pas en charge les images en entrée.",
510623
- "blunTui.media.videoUnsupported": "Le modèle actuel ne prend pas en charge les vidéos en entrée.",
510624
- "blunTui.skill.failed": "Échec du skill « {skillName} » : {error}",
510625
- "blunTui.pluginCommand.failed": "Échec de la commande « {command} » : {error}",
510626
- "blunTui.steer.failed": "Impossible de réorienter la réponse : {error}",
510627
- "blunTui.session.otherWorkDir": "La session actuelle se trouve dans un autre répertoire de travail.",
510628
- "blunTui.session.resumeCommand": "Pour la reprendre, exécutez : {command}",
510629
- "blunTui.clipboard.commandCopied": "Commande copiée dans le presse-papiers",
510630
- "blunTui.clipboard.commandCopyFailed": "Impossible de copier la commande dans le presse-papiers",
510631
- "blunTui.session.alreadyCurrent": "Cette session est déjà active.",
510632
- "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.",
510633
- "blunTui.session.switchReplayBlocked": "Impossible de changer de session pendant la relecture de l’historique.",
510634
- "blunTui.session.resumeFailed": "Impossible de reprendre la session {sessionId} : {error}",
510635
- "blunTui.session.resumed": "Session reprise ({sessionId}).",
510636
- "blunTui.session.replayFailed": "Impossible de relire l’historique de la session : {error}",
510637
- "blunTui.session.createReplayBlocked": "Impossible de démarrer une nouvelle session pendant la relecture de l’historique.",
510638
- "blunTui.session.createFailed": "Impossible de démarrer une nouvelle session : {error}",
510639
- "blunTui.session.postCreateFailed": "Impossible de configurer la nouvelle session : {error}",
510640
- "blunTui.session.started": "Nouvelle session démarrée ({sessionId}).",
510641
- "blunTui.error": "Erreur : {message}",
510642
- "blunTui.login.title": "Se connecter à BLUN",
510643
- "blunTui.login.hint": "Appuyez sur Ctrl-C pour annuler",
510644
- "blunTui.login.waiting": "En attente de l’autorisation…",
510645
- "blunTui.detach.noShell": "Aucune commande shell en cours.",
510646
- "blunTui.detach.shellStarting": "La commande est encore en cours de démarrage — réessayez.",
510647
- "blunTui.detach.shellFinished": "La commande est déjà terminée.",
510648
- "blunTui.detach.moveFailed": "Impossible de passer la commande en arrière-plan : {error}",
510649
- "blunTui.detach.movedTranscript": "Commande passée en arrière-plan.",
510650
- "blunTui.detach.movedView": "Commande passée en arrière-plan. Consultez /tasks.",
510651
- "blunTui.detach.noForeground": "Aucune tâche en cours au premier plan.",
510652
- "blunTui.detach.listFailed": "Impossible de répertorier les tâches : {error}",
510653
- "blunTui.detach.taskFailed": "Impossible de passer la tâche {taskId} en arrière-plan : {error}",
510654
- "blunTui.detach.finished.one": "La tâche est déjà terminée.",
510655
- "blunTui.detach.finished.other": "Les tâches sont déjà terminées.",
510656
- "blunTui.detach.moved.one": "{count} tâche passée en arrière-plan.",
510657
- "blunTui.detach.moved.other": "{count} tâches passées en arrière-plan.",
510658
- "blunTui.detach.partial": "{detached} tâches sur {total} passées en arrière-plan.",
510659
- "blunTui.detach.viewSuffix": "Consultez /tasks.",
510660
- "blunTui.startup.flagsFailed": "Impossible d’appliquer les options de démarrage : {error}",
510661
- "blunTui.notification.approvalRequired": "Approbation BLUN requise",
510662
- "blunTui.notification.answerRequired": "BLUN attend votre réponse",
510663
- "blunTui.telegram.fallbackDelivered": "Réponse envoyée automatiquement sur Telegram (solution de secours).",
510664
- "blunTui.telegram.attachDisabled": "Connexion à Telegram désactivée via BLUN_TELEGRAM_ATTACH=off — mode headless actif.",
510665
- "blunTui.telegram.noToken": "Connexion à Telegram\xA0: aucun jeton détecté — mode headless actif.",
510666
- "blunTui.telegram.attached": "Canal Telegram connecté (PID de lease\xA0: {pid}) — les messages apparaissent dans cette fenêtre.",
510667
- "blunTui.auto.status": "Auto\xA0: {label}",
510668
- "blunTui.activity.thinking": "{name} réfléchit…",
510669
- "blunTui.activity.working": "{name} travaille…",
510670
- "blunTui.activity.composing": "travail en cours...",
510671
- "blunTui.activity.tokens": "jetons"
510672
- },
510673
- sv: {
510674
- "blunTui.provider.modelAdded.one": "{providerName} · +{count} modell.",
510675
- "blunTui.provider.modelAdded.other": "{providerName} · +{count} modeller.",
510676
- "blunTui.provider.refreshSkipped": "Uppdateringen av {provider} hoppades över: {reason}",
510677
- "blunTui.warning": "Varning: {warning}",
510678
- "blunTui.startup.sessionNotFound": "Sessionen ”{sessionId}” hittades inte.",
510679
- "blunTui.startup.sessionDifferentDirectory": "Sessionen ”{sessionId}” skapades i en annan arbetskatalog.",
510680
- "blunTui.startup.noSessionsToContinue": "Det finns inga sessioner att återuppta i ”{workDir}”; en ny session startas.",
510681
- "blunTui.startup.sessionNotInitialized": "Sessionen initierades inte vid start.",
510682
- "blunTui.input.replayBlocked": "Det går inte att skicka indata medan sessionshistoriken spelas upp.",
510683
- "blunTui.shell.noSession": "Det finns ingen aktiv session för skalkommandot.",
510684
- "blunTui.shell.runFailed": "Skalkommandot misslyckades: {error}",
510685
- "blunTui.shell.cancelFailed": "Det gick inte att avbryta skalkommandot: {error}",
510686
- "blunTui.channel.steerFailed": "Det gick inte att lägga till kanalmeddelandet i det pågående svaret: {error}",
510687
- "blunTui.session.sendFailed": "Det gick inte att skicka: {error}",
510688
- "blunTui.media.imageUnsupported": "Den aktuella modellen stöder inte bildindata.",
510689
- "blunTui.media.videoUnsupported": "Den aktuella modellen stöder inte videoindata.",
510690
- "blunTui.skill.failed": "Skill ”{skillName}” misslyckades: {error}",
510691
- "blunTui.pluginCommand.failed": "Kommandot ”{command}” misslyckades: {error}",
510692
- "blunTui.steer.failed": "Det gick inte att styra om svaret: {error}",
510693
- "blunTui.session.otherWorkDir": "Den aktuella sessionen finns i en annan arbetskatalog.",
510694
- "blunTui.session.resumeCommand": "Kör följande för att återuppta den: {command}",
510695
- "blunTui.clipboard.commandCopied": "Kommandot kopierades till urklipp",
510696
- "blunTui.clipboard.commandCopyFailed": "Det gick inte att kopiera kommandot till urklipp",
510697
- "blunTui.session.alreadyCurrent": "Den här sessionen är redan aktiv.",
510698
- "blunTui.session.switchStreamingBlocked": "Det går inte att byta session medan ett svar genereras. Tryck först på Esc eller Ctrl-C.",
510699
- "blunTui.session.switchReplayBlocked": "Det går inte att byta session medan historiken spelas upp.",
510700
- "blunTui.session.resumeFailed": "Det gick inte att återuppta sessionen {sessionId}: {error}",
510701
- "blunTui.session.resumed": "Sessionen återupptogs ({sessionId}).",
510702
- "blunTui.session.replayFailed": "Det gick inte att spela upp sessionshistoriken: {error}",
510703
- "blunTui.session.createReplayBlocked": "Det går inte att starta en ny session medan historiken spelas upp.",
510704
- "blunTui.session.createFailed": "Det gick inte att starta en ny session: {error}",
510705
- "blunTui.session.postCreateFailed": "Det gick inte att konfigurera den nya sessionen: {error}",
510706
- "blunTui.session.started": "En ny session startades ({sessionId}).",
510707
- "blunTui.error": "Fel: {message}",
510708
- "blunTui.login.title": "Logga in på BLUN",
510709
- "blunTui.login.hint": "Tryck på Ctrl-C för att avbryta",
510710
- "blunTui.login.waiting": "Väntar på auktorisering…",
510711
- "blunTui.detach.noShell": "Inget skalkommando körs.",
510712
- "blunTui.detach.shellStarting": "Kommandot håller fortfarande på att startas – försök igen.",
510713
- "blunTui.detach.shellFinished": "Kommandot är redan slutfört.",
510714
- "blunTui.detach.moveFailed": "Det gick inte att flytta kommandot till bakgrunden: {error}",
510715
- "blunTui.detach.movedTranscript": "Flyttades till bakgrunden.",
510716
- "blunTui.detach.movedView": "Flyttades till bakgrunden. Visa med /tasks.",
510717
- "blunTui.detach.noForeground": "Ingen uppgift körs i förgrunden.",
510718
- "blunTui.detach.listFailed": "Det gick inte att lista uppgifterna: {error}",
510719
- "blunTui.detach.taskFailed": "Det gick inte att flytta uppgiften {taskId} till bakgrunden: {error}",
510720
- "blunTui.detach.finished.one": "Uppgiften är redan slutförd.",
510721
- "blunTui.detach.finished.other": "Uppgifterna är redan slutförda.",
510722
- "blunTui.detach.moved.one": "{count} uppgift flyttades till bakgrunden.",
510723
- "blunTui.detach.moved.other": "{count} uppgifter flyttades till bakgrunden.",
510724
- "blunTui.detach.partial": "{detached} av {total} uppgifter flyttades till bakgrunden.",
510725
- "blunTui.detach.viewSuffix": "Visa med /tasks.",
510726
- "blunTui.startup.flagsFailed": "Det gick inte att tillämpa startalternativen: {error}",
510727
- "blunTui.notification.approvalRequired": "BLUN-godkännande krävs",
510728
- "blunTui.notification.answerRequired": "BLUN behöver ditt svar",
510729
- "blunTui.telegram.fallbackDelivered": "Svaret skickades automatiskt till Telegram (reservlösning).",
510730
- "blunTui.telegram.attachDisabled": "Telegram-anslutningen inaktiverades via BLUN_TELEGRAM_ATTACH=off — headless-läget är aktivt.",
510731
- "blunTui.telegram.noToken": "Telegram-anslutning: ingen token hittades — headless-läget är aktivt.",
510732
- "blunTui.telegram.attached": "Telegram-kanalen är ansluten (lease-PID {pid}) — meddelanden visas i det här fönstret.",
510733
- "blunTui.auto.status": "Automatiskt: {label}",
510734
- "blunTui.activity.thinking": "{name} tänker…",
510735
- "blunTui.activity.working": "{name} arbetar…",
510736
- "blunTui.activity.composing": "arbetar...",
510737
- "blunTui.activity.tokens": "token"
510738
- },
510739
- cs: {
510740
- "blunTui.provider.modelAdded.one": "{providerName} · +{count} model.",
510741
- "blunTui.provider.modelAdded.other": "{providerName} · nové modely: +{count}.",
510742
- "blunTui.provider.refreshSkipped": "Přeskočeno obnovení {provider}: {reason}",
510743
- "blunTui.warning": "Upozornění: {warning}",
510744
- "blunTui.startup.sessionNotFound": "Relace \"{sessionId}\" nebyla nalezena.",
510745
- "blunTui.startup.sessionDifferentDirectory": "Relace \"{sessionId}\" byla vytvořena v jiném adresáři.",
510746
- "blunTui.startup.noSessionsToContinue": "V adresáři \"{workDir}\" nejsou žádné relace, ve kterých by bylo možné pokračovat; spouští se nová relace.",
510747
- "blunTui.startup.sessionNotInitialized": "Relace při spuštění nebyla inicializována.",
510748
- "blunTui.input.replayBlocked": "Nelze odeslat vstup během přehrávání historie relace.",
510749
- "blunTui.shell.noSession": "Žádná aktivní relace pro příkaz shellu.",
510750
- "blunTui.shell.runFailed": "Příkaz shellu selhal: {error}",
510751
- "blunTui.shell.cancelFailed": "Selhalo zrušení příkazu shellu: {error}",
510752
- "blunTui.channel.steerFailed": "Předání zprávy z kanálu do probíhající odpovědi selhalo: {error}",
510753
- "blunTui.session.sendFailed": "Selhalo odeslání: {error}",
510754
- "blunTui.media.imageUnsupported": "Aktuální model nepodporuje vstup obrázku.",
510755
- "blunTui.media.videoUnsupported": "Aktuální model nepodporuje vstup videa.",
510756
- "blunTui.skill.failed": "Dovednost \"{skillName}\" selhala: {error}",
510757
- "blunTui.pluginCommand.failed": "Příkaz \"{command}\" selhal: {error}",
510758
- "blunTui.steer.failed": "Doplnění pokynu selhalo: {error}",
510759
- "blunTui.session.otherWorkDir": "Aktuální relace je v jiném pracovním adresáři.",
510760
- "blunTui.session.resumeCommand": "Chcete-li pokračovat, spusťte: {command}",
510761
- "blunTui.clipboard.commandCopied": "Příkaz zkopírován do schránky",
510762
- "blunTui.clipboard.commandCopyFailed": "Selhalo kopírování příkazu do schránky",
510763
- "blunTui.session.alreadyCurrent": "Již jste v této relaci.",
510764
- "blunTui.session.switchStreamingBlocked": "Nelze přepínat relace během streamování — nejdříve stiskněte Esc nebo Ctrl-C.",
510765
- "blunTui.session.switchReplayBlocked": "Nelze přepínat relace během přehrávání historie.",
510766
- "blunTui.session.resumeFailed": "Selhalo obnovení relace {sessionId}: {error}",
510767
- "blunTui.session.resumed": "Obnovena relace ({sessionId}).",
510768
- "blunTui.session.replayFailed": "Selhalo přehrávání historie relace: {error}",
510769
- "blunTui.session.createReplayBlocked": "Nelze spustit novou relaci během přehrávání historie.",
510770
- "blunTui.session.createFailed": "Selhalo spuštění nové relace: {error}",
510771
- "blunTui.session.postCreateFailed": "Selhalo nastavení po vytvoření: {error}",
510772
- "blunTui.session.started": "Spuštěna nová relace ({sessionId}).",
510773
- "blunTui.error": "Chyba: {message}",
510774
- "blunTui.login.title": "Přihlaste se do BLUN",
510775
- "blunTui.login.hint": "Stiskněte Ctrl-C pro zrušení",
510776
- "blunTui.login.waiting": "Čekání na autorizaci…",
510777
- "blunTui.detach.noShell": "Žádný příkaz shellu není spuštěn.",
510778
- "blunTui.detach.shellStarting": "Příkaz se stále spouští — zkuste znovu.",
510779
- "blunTui.detach.shellFinished": "Příkaz již skončil.",
510780
- "blunTui.detach.moveFailed": "Selhalo přesunutí na pozadí: {error}",
510781
- "blunTui.detach.movedTranscript": "Přesunuto na pozadí.",
510782
- "blunTui.detach.movedView": "Přesunuto na pozadí. Zobrazíte příkazem /tasks.",
510783
- "blunTui.detach.noForeground": "Žádný úkol na popředí není spuštěn.",
510784
- "blunTui.detach.listFailed": "Selhalo vypsání úkolů: {error}",
510785
- "blunTui.detach.taskFailed": "Přesunutí úlohy {taskId} na pozadí selhalo: {error}",
510786
- "blunTui.detach.finished.one": "Úkol již skončil.",
510787
- "blunTui.detach.finished.other": "Úkoly již skončily.",
510788
- "blunTui.detach.moved.one": "Přesunut {count} úkol na pozadí.",
510789
- "blunTui.detach.moved.other": "Úkoly přesunuté na pozadí: {count}.",
510790
- "blunTui.detach.partial": "Přesunuto {detached} z {total} úkolů na pozadí.",
510791
- "blunTui.detach.viewSuffix": "/tasks k zobrazení.",
510792
- "blunTui.startup.flagsFailed": "Nepodařilo se použít spouštěcí příznaky: {error}",
510793
- "blunTui.notification.approvalRequired": "Vyžadováno schválení BLUN",
510794
- "blunTui.notification.answerRequired": "BLUN potřebuje vaši odpověď",
510795
- "blunTui.telegram.fallbackDelivered": "Odpověď byla automaticky doručena do Telegramu (náhradním způsobem).",
510796
- "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í.",
510797
- "blunTui.telegram.noToken": "Připojení Telegramu: nebyl nalezen žádný token — aktivní je režim bez uživatelského rozhraní.",
510798
- "blunTui.telegram.attached": "Kanál Telegramu je připojen (PID držitele připojení {pid}) — zprávy se zobrazují v tomto okně.",
510799
- "blunTui.auto.status": "Automaticky: {label}",
510800
- "blunTui.activity.thinking": "{name} přemýšlí…",
510801
- "blunTui.activity.working": "{name} pracuje…",
510802
- "blunTui.activity.composing": "pracuje…",
510803
- "blunTui.activity.tokens": "Tokeny"
510804
- }
510805
- });
510806
- //#endregion
510807
511301
  //#region src/tui/blun-tui.ts
510808
511302
  function loadingTipKind(mode) {
510809
511303
  if (mode === "waiting" || mode === "tool") return "blun";
@@ -511649,7 +512143,8 @@ var BlunTUI = class {
511649
512143
  this.state.queuedMessages = this.state.queuedMessages.slice(1);
511650
512144
  const turnId = this.streamingUI.getTurnContext().turnId;
511651
512145
  let transcriptRendered = item.channelTranscriptRendered === true;
511652
- if (!transcriptRendered) {
512146
+ const renderTranscript = () => {
512147
+ if (transcriptRendered) return;
511653
512148
  this.appendTranscriptEntry({
511654
512149
  id: nextTranscriptId(),
511655
512150
  kind: "user",
@@ -511659,8 +512154,9 @@ var BlunTUI = class {
511659
512154
  origin: item.origin
511660
512155
  });
511661
512156
  transcriptRendered = true;
511662
- }
512157
+ };
511663
512158
  if (item.channelContextOnly === true) {
512159
+ renderTranscript();
511664
512160
  item.channelAcknowledge?.();
511665
512161
  if (this.queueFlushBatchRemaining > 0) this.queueFlushBatchRemaining -= 1;
511666
512162
  this.syncChannelQueueDeadline();
@@ -511690,6 +512186,7 @@ var BlunTUI = class {
511690
512186
  stepStarted: false,
511691
512187
  turnEnded: false,
511692
512188
  onCommit: () => {
512189
+ renderTranscript();
511693
512190
  item.channelAcknowledge?.();
511694
512191
  if (this.queueFlushBatchRemaining > 0) this.queueFlushBatchRemaining -= 1;
511695
512192
  },
@@ -511975,6 +512472,21 @@ var BlunTUI = class {
511975
512472
  /** Pending delivery guard for the channel-origin turn currently running. */
511976
512473
  pendingChannelReplyGuard;
511977
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
+ }
511978
512490
  runChannelReplyFallback(reason) {
511979
512491
  const guard = this.pendingChannelReplyGuard;
511980
512492
  if (guard === void 0) return;