blun-king-cli 9.1.47 → 9.1.48

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 (4) hide show
  1. package/LIESMICH.txt +11 -1
  2. package/README.md +12 -1
  3. package/blun.mjs +393 -64
  4. package/package.json +1 -1
package/LIESMICH.txt CHANGED
@@ -9,7 +9,7 @@ Installation
9
9
  ------------
10
10
  Die geprüfte Version exakt global installieren:
11
11
 
12
- npm install -g blun-king-cli@9.1.47
12
+ npm install -g blun-king-cli@9.1.48
13
13
 
14
14
  Start
15
15
  -----
@@ -31,6 +31,16 @@ Die Grenzwerte lassen sich vor dem Start über diese Umgebungsvariablen ändern:
31
31
  BLUN_TURN_WATCHDOG_IDLE_MINUTES
32
32
  BLUN_TURN_WATCHDOG_MAX_FAILED_REPETITIONS
33
33
 
34
+ Befehle und Loops während eines laufenden Zugs
35
+ ------------------------------------------------
36
+ Slash-Befehle, die einen freien Agenten benötigen, werden während eines
37
+ laufenden Zugs in ihrer Eingabereihenfolge vorgemerkt und danach ausgeführt.
38
+ Status-, Stopp- und andere sichere Steuerbefehle bleiben sofort verfügbar.
39
+
40
+ Ein neuer /loop ersetzt den bisherigen Loop derselben Sitzung. Dabei bleibt
41
+ genau ein gespeicherter Loop aktiv. /loop stop, /loop pause und /loop status
42
+ wirken auch dann sofort, wenn der Agent gerade arbeitet.
43
+
34
44
  Nachweisbare Arbeitsabläufe
35
45
  ---------------------------
36
46
 
package/README.md CHANGED
@@ -9,7 +9,7 @@ Voraussetzung ist Node.js 24.15 oder neuer. Die geprüfte Version wird exakt
9
9
  installiert:
10
10
 
11
11
  ```powershell
12
- npm install -g blun-king-cli@9.1.47
12
+ npm install -g blun-king-cli@9.1.48
13
13
  ```
14
14
 
15
15
  ## Reproduzierbares Staging und Packen
@@ -45,6 +45,17 @@ Die Grenzwerte lassen sich vor dem Start mit
45
45
  `BLUN_TURN_WATCHDOG_IDLE_MINUTES` und
46
46
  `BLUN_TURN_WATCHDOG_MAX_FAILED_REPETITIONS` ändern.
47
47
 
48
+ ## Befehle und Loops während eines laufenden Zugs
49
+
50
+ Slash-Befehle, die einen freien Agenten benötigen, werden während eines
51
+ laufenden Zugs in ihrer Eingabereihenfolge vorgemerkt und danach ausgeführt.
52
+ Status-, Stopp- und andere sichere Steuerbefehle bleiben sofort verfügbar.
53
+
54
+ Ein neuer `/loop` ersetzt den bisherigen Loop derselben Sitzung. Dabei bleibt
55
+ genau ein gespeicherter Loop aktiv; der neue Auftrag, das neue Intervall und
56
+ der neue Startzeitpunkt gelten vollständig. `/loop stop`, `/loop pause` und
57
+ `/loop status` wirken auch dann sofort, wenn der Agent gerade arbeitet.
58
+
48
59
  Beim ersten Start werden das Telegram-Plugin und die mitgelieferten Skills
49
60
  eingerichtet. Die Anmeldung erfolgt anschließend in der
50
61
  Konsole mit `/login` über den BLUN-OAuth-Server. Das Paket erzeugt keine
package/blun.mjs CHANGED
@@ -75066,10 +75066,6 @@ function estimateCompactionStageCount(initialInputTokens, safeRequestLimitTokens
75066
75066
  if (safeRequestLimitTokens <= 0) return currentStage;
75067
75067
  return Math.max(currentStage, Math.ceil(initialInputTokens / safeRequestLimitTokens));
75068
75068
  }
75069
- function estimateCompactionProgressPercent(stage, estimatedStageCount) {
75070
- if (estimatedStageCount <= 0) return 0;
75071
- return Math.min(99, Math.floor(stage / estimatedStageCount * 100));
75072
- }
75073
75069
  function estimateCompactionWindowUsagePercent(requestTokens, maxContextTokens) {
75074
75070
  if (maxContextTokens <= 0) return void 0;
75075
75071
  return Math.min(100, Math.max(0, Math.ceil(requestTokens / maxContextTokens * 100)));
@@ -75554,7 +75550,6 @@ var init_full = __esmMin((() => {
75554
75550
  charsReceived = 0;
75555
75551
  const stage = hierarchicalPassCount + 1;
75556
75552
  const estimatedStageCount = estimateCompactionStageCount(initialCompactionRequestTokens, safeCompactionRequestLimit, stage);
75557
- const estimatedProgressPercent = estimateCompactionProgressPercent(stage, estimatedStageCount);
75558
75553
  const windowUsagePercent = estimateCompactionWindowUsagePercent(estimatedCompactionRequestTokens, compactionRequestLimit);
75559
75554
  this.agent.log.info("compaction stage request", {
75560
75555
  requestPurpose: "compaction",
@@ -75578,7 +75573,6 @@ var init_full = __esmMin((() => {
75578
75573
  stage,
75579
75574
  estimatedStageCount,
75580
75575
  estimatedInputTokens: estimatedCompactionRequestTokens,
75581
- estimatedProgressPercent,
75582
75576
  ...windowUsagePercent === void 0 ? {} : { windowUsagePercent },
75583
75577
  charsReceived,
75584
75578
  attempt: attemptCount,
@@ -77688,12 +77682,29 @@ var init_session_loop = __esmMin((() => {
77688
77682
  }
77689
77683
  createLoop(input) {
77690
77684
  if (process.env["BLUN_DISABLE_CRON"] === "1") throw new BlunError(ErrorCodes.LOOP_DISABLED, "Recurring loops are disabled");
77691
- if (this.currentTask() !== void 0) throw new BlunError(ErrorCodes.LOOP_ALREADY_EXISTS, "A recurring loop already exists");
77692
- if (this.cron.store.list().length >= 50) throw new BlunError(ErrorCodes.LOOP_LIMIT_REACHED, "Session cron task limit reached");
77685
+ const existing = this.currentTask();
77686
+ if (existing === void 0 && this.cron.store.list().length >= 50) throw new BlunError(ErrorCodes.LOOP_LIMIT_REACHED, "Session cron task limit reached");
77693
77687
  const parsed = parseLoopInterval(input.interval);
77694
77688
  const prompt = input.prompt.trim();
77695
77689
  if (prompt.length === 0) throw new BlunError(ErrorCodes.LOOP_PROMPT_EMPTY, "Loop prompt cannot be empty");
77696
77690
  if (Buffer.byteLength(prompt, "utf8") > MAX_LOOP_PROMPT_BYTES) throw new BlunError(ErrorCodes.LOOP_PROMPT_TOO_LONG, `Loop prompt cannot exceed ${String(MAX_LOOP_PROMPT_BYTES)} UTF-8 bytes`);
77691
+ if (existing !== void 0) {
77692
+ const now = this.cron.clocks.wallNow();
77693
+ const task = this.requireUpdatedTask(existing.id, {
77694
+ cron: parsed.cron,
77695
+ prompt,
77696
+ recurring: true,
77697
+ owner: "session-loop",
77698
+ loopInterval: parsed.interval,
77699
+ paused: false,
77700
+ autoExpire: false,
77701
+ createdAt: now,
77702
+ scheduleFromAt: now,
77703
+ lastFiredAt: void 0
77704
+ });
77705
+ this.cron.emitScheduled(task);
77706
+ return this.snapshot(task);
77707
+ }
77697
77708
  const task = this.cron.addTask({
77698
77709
  cron: parsed.cron,
77699
77710
  prompt,
@@ -261933,6 +261944,45 @@ function contentPartFor(mimeType, url) {
261933
261944
  function errorMessage$10(error) {
261934
261945
  return error instanceof Error ? error.message : String(error);
261935
261946
  }
261947
+ const MEDIA_PROGRESS_CUSTOM_KIND = "blun.media.progress";
261948
+ function mediaKindForToolName(name) {
261949
+ switch (name) {
261950
+ case "GenerateImage": return "image";
261951
+ case "GenerateVideo": return "video";
261952
+ case "GenerateSpeech": return "voice";
261953
+ case "UnderstandImage": return "image-analysis";
261954
+ case "UnderstandVideo": return "video-analysis";
261955
+ case "DubVideo": return "dubbing";
261956
+ case "LipSyncMedia": return "lipsync";
261957
+ case "GetMedia": return "media";
261958
+ default: return "media";
261959
+ }
261960
+ }
261961
+ function isMediaToolName(name) {
261962
+ return [
261963
+ "GenerateImage",
261964
+ "GenerateVideo",
261965
+ "GenerateSpeech",
261966
+ "UnderstandImage",
261967
+ "UnderstandVideo",
261968
+ "DubVideo",
261969
+ "LipSyncMedia",
261970
+ "GetMedia"
261971
+ ].includes(name);
261972
+ }
261973
+ function emitMediaProgress(ctx, toolName, phase, data = {}) {
261974
+ ctx.onUpdate({
261975
+ kind: "custom",
261976
+ customKind: MEDIA_PROGRESS_CUSTOM_KIND,
261977
+ customData: {
261978
+ toolName,
261979
+ mediaKind: data.mediaKind ?? mediaKindForToolName(toolName),
261980
+ phase,
261981
+ observedAt: Date.now(),
261982
+ ...data
261983
+ }
261984
+ });
261985
+ }
261936
261986
  var PromptSchema, MediaIdSchema, GenerateImageInputSchema, GenerateVideoInputSchema, GenerateSpeechInputSchema, GetMediaInputSchema, UnderstandImageInputSchema, UnderstandVideoInputSchema, DubVideoInputSchema, LipSyncMediaInputSchema, REQUEST_NOTE, LIPSYNC_REQUEST_NOTE, MediaGenerationTool, GenerateImageTool, GenerateVideoTool, GenerateSpeechTool, UnderstandImageTool, UnderstandVideoTool, DubVideoTool, LipSyncMediaTool, GetMediaTool;
261937
261987
  var init_blun_media$1 = __esmMin((() => {
261938
261988
  init_zod$1();
@@ -261984,16 +262034,22 @@ var init_blun_media$1 = __esmMin((() => {
261984
262034
  };
261985
262035
  }
261986
262036
  async execution(args, ctx) {
262037
+ emitMediaProgress(ctx, this.name, "submitting");
261987
262038
  try {
261988
262039
  const job = await this.submit(args, {
261989
262040
  signal: ctx.signal,
261990
262041
  toolCallId: ctx.toolCallId
261991
262042
  });
262043
+ emitMediaProgress(ctx, this.name, job.status, {
262044
+ id: job.id,
262045
+ ...job.progress
262046
+ });
261992
262047
  return {
261993
262048
  output: `Media job ${job.id} accepted with status ${job.status}. ${this.requestNote}`,
261994
262049
  isError: false
261995
262050
  };
261996
262051
  } catch (error) {
262052
+ emitMediaProgress(ctx, this.name, "failed", { error: errorMessage$10(error) });
261997
262053
  return {
261998
262054
  isError: true,
261999
262055
  output: `Media request failed: ${errorMessage$10(error)}`
@@ -262070,6 +262126,7 @@ var init_blun_media$1 = __esmMin((() => {
262070
262126
  };
262071
262127
  }
262072
262128
  async execution(args, safePath, ctx) {
262129
+ emitMediaProgress(ctx, "UnderstandImage", "uploading");
262073
262130
  try {
262074
262131
  const fileType = detectFileType(safePath, await this.kaos.readBytes(safePath, 512), "media");
262075
262132
  if (fileType.mimeType !== "image/png" && fileType.mimeType !== "image/jpeg") return {
@@ -262086,12 +262143,18 @@ var init_blun_media$1 = __esmMin((() => {
262086
262143
  toolCallId: ctx.toolCallId
262087
262144
  };
262088
262145
  const upload = await this.provider.uploadMedia(data, fileType.mimeType, options);
262146
+ emitMediaProgress(ctx, "UnderstandImage", "processing");
262089
262147
  const job = await this.provider.understandImage(upload.id, args.prompt, options);
262148
+ emitMediaProgress(ctx, "UnderstandImage", job.status, {
262149
+ id: job.id,
262150
+ ...job.progress
262151
+ });
262090
262152
  return {
262091
262153
  output: `Media job ${job.id} accepted with status ${job.status}. ${REQUEST_NOTE}`,
262092
262154
  isError: false
262093
262155
  };
262094
262156
  } catch (error) {
262157
+ emitMediaProgress(ctx, "UnderstandImage", "failed", { error: errorMessage$10(error) });
262095
262158
  return {
262096
262159
  isError: true,
262097
262160
  output: `Image understanding failed: ${errorMessage$10(error)}`
@@ -262153,6 +262216,7 @@ var init_blun_media$1 = __esmMin((() => {
262153
262216
  };
262154
262217
  }
262155
262218
  async execution(args, ctx) {
262219
+ emitMediaProgress(ctx, "GetMedia", "checking", { id: args.id });
262156
262220
  try {
262157
262221
  const result = await this.provider.getMedia(args.id, {
262158
262222
  signal: ctx.signal,
@@ -262166,15 +262230,22 @@ var init_blun_media$1 = __esmMin((() => {
262166
262230
  ].includes(result.status.trim().toLowerCase());
262167
262231
  const source = result.sourceId === void 0 ? "" : ` Source job ${result.sourceId}${result.sourceStatus === void 0 ? "" : ` status: ${result.sourceStatus}`}.`;
262168
262232
  const retryable = result.retryable === void 0 ? "" : ` Retryable: ${result.retryable ? "yes" : "no"}.`;
262233
+ emitMediaProgress(ctx, "GetMedia", result.status, {
262234
+ id: result.id,
262235
+ ...result.progress
262236
+ });
262169
262237
  return {
262170
262238
  output: `Media job ${result.id} status: ${result.status}.${source}${retryable}`,
262171
262239
  isError: terminalError
262172
262240
  };
262173
262241
  }
262174
- if (result.kind === "text") return {
262175
- output: result.text,
262176
- isError: false
262177
- };
262242
+ if (result.kind === "text") {
262243
+ emitMediaProgress(ctx, "GetMedia", "complete", { id: result.id });
262244
+ return {
262245
+ output: result.text,
262246
+ isError: false
262247
+ };
262248
+ }
262178
262249
  const url = `data:${result.mimeType};base64,${Buffer.from(result.data).toString("base64")}`;
262179
262250
  const mediaPart = contentPartFor(result.mimeType, url);
262180
262251
  if (mediaPart === void 0) return {
@@ -262187,6 +262258,11 @@ var init_blun_media$1 = __esmMin((() => {
262187
262258
  });
262188
262259
  const localPath = join(this.outputDir, `${safeMediaId(result.id)}.${extensionForMediaType(result.mimeType)}`);
262189
262260
  await writeFile(localPath, result.data, { mode: 384 });
262261
+ emitMediaProgress(ctx, "GetMedia", "complete", {
262262
+ id: result.id,
262263
+ mimeType: result.mimeType,
262264
+ localPath
262265
+ });
262190
262266
  return {
262191
262267
  output: [{
262192
262268
  type: "text",
@@ -262195,6 +262271,10 @@ var init_blun_media$1 = __esmMin((() => {
262195
262271
  isError: false
262196
262272
  };
262197
262273
  } catch (error) {
262274
+ emitMediaProgress(ctx, "GetMedia", "failed", {
262275
+ id: args.id,
262276
+ error: errorMessage$10(error)
262277
+ });
262198
262278
  return {
262199
262279
  isError: true,
262200
262280
  output: `Media lookup failed: ${errorMessage$10(error)}`
@@ -310721,13 +310801,47 @@ async function parseBlockedStatus(response, id) {
310721
310801
  ...typeof retryable === "boolean" ? { retryable } : {}
310722
310802
  };
310723
310803
  }
310804
+ function mediaPayloadNumber(payload, keys) {
310805
+ for (const key of keys) {
310806
+ const value = payload[key];
310807
+ const parsed = typeof value === "number" ? value : typeof value === "string" && value.trim() !== "" ? Number(value) : NaN;
310808
+ if (Number.isFinite(parsed) && parsed >= 0) return parsed;
310809
+ }
310810
+ }
310811
+ function mediaPayloadText(payload, keys) {
310812
+ for (const key of keys) {
310813
+ const value = payload[key];
310814
+ if (typeof value === "string" && value.trim() !== "") return value.trim();
310815
+ }
310816
+ }
310817
+ function mediaProgressFromPayload(payload) {
310818
+ const rawPercent = mediaPayloadNumber(payload, ["percent", "progress_percent", "progressPercent", "progress"]);
310819
+ const percent = rawPercent === void 0 ? void 0 : rawPercent <= 1 ? rawPercent * 100 : rawPercent;
310820
+ const progress = {
310821
+ phaseLabel: mediaPayloadText(payload, ["phase", "stage", "step_name", "stepName"]),
310822
+ percent: percent === void 0 ? void 0 : Math.min(100, Math.max(0, percent)),
310823
+ currentFrame: mediaPayloadNumber(payload, ["current_frame", "currentFrame", "frames_completed", "framesCompleted"]),
310824
+ totalFrames: mediaPayloadNumber(payload, ["total_frames", "totalFrames", "frame_count", "frameCount"]),
310825
+ fps: mediaPayloadNumber(payload, ["fps", "frames_per_second", "framesPerSecond"]),
310826
+ currentStep: mediaPayloadNumber(payload, ["current_step", "currentStep", "step"]),
310827
+ totalSteps: mediaPayloadNumber(payload, ["total_steps", "totalSteps", "steps"]),
310828
+ currentSegment: mediaPayloadNumber(payload, ["current_segment", "currentSegment", "segment"]),
310829
+ totalSegments: mediaPayloadNumber(payload, ["total_segments", "totalSegments", "segments"]),
310830
+ generatedSeconds: mediaPayloadNumber(payload, ["generated_seconds", "generatedSeconds", "audio_seconds", "audioSeconds"]),
310831
+ queuePosition: mediaPayloadNumber(payload, ["queue_position", "queuePosition"]),
310832
+ elapsedSeconds: mediaPayloadNumber(payload, ["elapsed_seconds", "elapsedSeconds"]),
310833
+ remainingSeconds: mediaPayloadNumber(payload, ["remaining_seconds", "remainingSeconds", "eta_seconds", "etaSeconds"])
310834
+ };
310835
+ return Object.fromEntries(Object.entries(progress).filter(([, value]) => value !== void 0));
310836
+ }
310724
310837
  function parseJob(payload) {
310725
310838
  const id = payload["id"];
310726
310839
  const status = payload["status"];
310727
310840
  if (typeof id !== "string" || id.length === 0 || typeof status !== "string" || status.length === 0) throw new Error("Media request returned an invalid job response.");
310728
310841
  return {
310729
310842
  id,
310730
- status
310843
+ status,
310844
+ progress: mediaProgressFromPayload(payload)
310731
310845
  };
310732
310846
  }
310733
310847
  function parseStatus(payload, expectedId) {
@@ -310737,7 +310851,8 @@ function parseStatus(payload, expectedId) {
310737
310851
  return {
310738
310852
  kind: "status",
310739
310853
  id,
310740
- status
310854
+ status,
310855
+ progress: mediaProgressFromPayload(payload)
310741
310856
  };
310742
310857
  }
310743
310858
  async function assertSuccess(response, operation) {
@@ -401449,6 +401564,16 @@ function slashCommandBusyReason(options) {
401449
401564
  if (options.isStreaming) return "streaming";
401450
401565
  if (options.isCompacting) return "compacting";
401451
401566
  }
401567
+ function shouldQueueBusySlashCommand(input, options) {
401568
+ const parsed = parseSlashInput(input);
401569
+ if (parsed === null) return false;
401570
+ if (slashCommandBusyReason(options) === void 0) return false;
401571
+ const normalizedName = normalizeLegacyEffortCommandName(parsed.name);
401572
+ const command = findBuiltInSlashCommand(normalizedName);
401573
+ if (command === void 0) return true;
401574
+ if (!isExperimentalFlagEnabled(command.experimentalFlag)) return false;
401575
+ return resolveSlashCommandAvailability(command, parsed.args) === "idle-only";
401576
+ }
401452
401577
  function slashBusyMessage(commandName, reason) {
401453
401578
  if (reason === "streaming") return uiText("resolve.busy.streaming", { command: commandName });
401454
401579
  return uiText("resolve.busy.compacting", { command: commandName });
@@ -426149,27 +426274,7 @@ registerUiCatalogFragment({
426149
426274
  }
426150
426275
  });
426151
426276
  const TICK_INTERVAL = 200;
426152
- const COMPACTION_TOKENS_PER_SECOND = 4250;
426153
- const MIN_ESTIMATED_DURATION_MS = 1e3;
426154
- const ACTIVE_PERCENT_LIMIT = 97;
426155
426277
  const BAR_WIDTH = 12;
426156
- const LOAD_PHASE_SHARE = .72;
426157
- const TAIL_PHASE_SHARE = .25;
426158
- function estimateCompactionDurationMs(inputTokens) {
426159
- if (inputTokens === void 0 || !Number.isFinite(inputTokens) || inputTokens <= 0) return;
426160
- return Math.max(MIN_ESTIMATED_DURATION_MS, Math.round(inputTokens / COMPACTION_TOKENS_PER_SECOND * 1e3));
426161
- }
426162
- function estimateCompactionPercent(elapsedMs, inputTokens) {
426163
- const durationMs = estimateCompactionDurationMs(inputTokens);
426164
- if (durationMs === void 0) return 0;
426165
- const safeElapsedMs = Math.max(0, elapsedMs);
426166
- let estimate = Math.min(1, safeElapsedMs / durationMs) * LOAD_PHASE_SHARE;
426167
- if (safeElapsedMs > durationMs) {
426168
- const tailFraction = 1 - Math.exp(-(safeElapsedMs - durationMs) / durationMs);
426169
- estimate += tailFraction * TAIL_PHASE_SHARE;
426170
- }
426171
- return Math.min(ACTIVE_PERCENT_LIMIT, Math.max(0, Math.round(estimate * 100)));
426172
- }
426173
426278
  var SingleLineText = class {
426174
426279
  text;
426175
426280
  constructor(text) {
@@ -426240,7 +426345,6 @@ var CompactionComponent = class extends Container {
426240
426345
  stage = 1;
426241
426346
  estimatedStageCount;
426242
426347
  stageCount;
426243
- estimatedProgressPercent;
426244
426348
  windowUsagePercent;
426245
426349
  constructor(ui, instruction, tip, showRunning = true) {
426246
426350
  super();
@@ -426297,7 +426401,6 @@ var CompactionComponent = class extends Container {
426297
426401
  this.attempt = progress.attempt;
426298
426402
  this.stage = progress.stage ?? this.stage;
426299
426403
  this.estimatedStageCount = progress.estimatedStageCount;
426300
- this.estimatedProgressPercent = progress.estimatedProgressPercent;
426301
426404
  this.windowUsagePercent = progress.windowUsagePercent;
426302
426405
  this.statusText.setText(this.buildStatusLine());
426303
426406
  if (this.showRunning) this.ui?.requestRender();
@@ -426319,21 +426422,16 @@ var CompactionComponent = class extends Container {
426319
426422
  }
426320
426423
  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")}`)}`;
426321
426424
  if (this.canceled) return `${currentTheme.fg("warning", STATUS_BULLET)}${currentTheme.boldFg("warning", uiText("compaction.canceled"))}`;
426322
- const elapsedMs = Math.max(0, Date.now() - this.attemptStartedAtMs);
426323
- const percent = this.estimatedProgressPercent ?? estimateCompactionPercent(elapsedMs, this.estimatedInputTokens);
426324
- const filled = Math.round(percent / 100 * BAR_WIDTH);
426325
- const bar = `[${"█".repeat(filled)}${"░".repeat(BAR_WIDTH - filled)}]`;
426326
426425
  const stageText = formatCompactionStageText({
426327
426426
  stage: this.stage,
426328
426427
  estimatedStageCount: this.estimatedStageCount,
426329
- estimatedProgressPercent: this.estimatedProgressPercent,
426330
426428
  windowUsagePercent: this.windowUsagePercent,
426331
426429
  estimatedInputTokens: this.estimatedInputTokens,
426332
426430
  charsReceived: 0,
426333
426431
  attempt: this.attempt
426334
426432
  });
426335
426433
  const runningText = `${formatCompactionRunningText(this.estimatedInputTokens)}${stageText.length === 0 ? "" : ` · ${stageText}`}`;
426336
- return `${currentTheme.fg("primary", bar)} ${currentTheme.boldFg("primary", runningText)}${this.windowUsagePercent === void 0 ? 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}`) : ""}`;
426434
+ return `${currentTheme.fg("primary", STATUS_BULLET)}${currentTheme.boldFg("primary", runningText)}${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}`) : ""}`;
426337
426435
  }
426338
426436
  startTicking() {
426339
426437
  this.timer = setInterval(() => {
@@ -427842,24 +427940,12 @@ var AgentSwarmProgressComponent = class {
427842
427940
  if (snapshot.phase === "cancelled" && snapshot.ticks <= 0) return renderCancelledUnstartedCell(member, width, this.colors);
427843
427941
  if (!layout.renderText) return this.renderCompactCell(member, snapshot, layout.barCells, nowMs);
427844
427942
  if (snapshot.phase === "queued" && snapshot.ticks <= 0) return renderQueuedCell(member, width, this.colors);
427845
- const estimate = this.progressEstimator.estimate({
427846
- memberKey: member.id,
427847
- phase: snapshot.phase,
427848
- capacityTicks: layout.barCells * PROGRESS_LEVELS.length,
427849
- nowMs
427850
- });
427851
- const prefix = `${chalk.hex(this.colors.primary)(member.id)} ${progressBar(estimate.displayTicks, snapshot.phase, layout.barCells, this.colors, snapshot.phaseElapsedMs, cancelledProgressColor(member, snapshot.phase, this.colors))} `;
427943
+ const prefix = `${chalk.hex(this.colors.primary)(member.id)} ${chalk.hex(phaseColor(snapshot.phase, this.colors))(phaseLabel(snapshot.phase))} · `;
427852
427944
  return prefix + renderCellLabel(member, snapshot, Math.max(1, width - visibleWidth(prefix)), this.colors);
427853
427945
  }
427854
427946
  renderCompactCell(member, snapshot, barCells, nowMs) {
427855
427947
  const estimatePhase = snapshot.phase === "pending" ? "queued" : snapshot.phase;
427856
- const estimate = this.progressEstimator.estimate({
427857
- memberKey: member.id,
427858
- phase: estimatePhase,
427859
- capacityTicks: barCells * PROGRESS_LEVELS.length,
427860
- nowMs
427861
- });
427862
- return `${chalk.hex(this.colors.primary)(member.id)} ${progressBar(estimate.displayTicks, estimatePhase, barCells, this.colors, snapshot.phaseElapsedMs, cancelledProgressColor(member, snapshot.phase, this.colors))}${compactTerminalMark(member, snapshot.phase, this.colors)}`;
427948
+ return `${chalk.hex(this.colors.primary)(member.id)} ${chalk.hex(phaseColor(estimatePhase, this.colors))(phaseLabel(estimatePhase))}${compactTerminalMark(member, snapshot.phase, this.colors)}`;
427863
427949
  }
427864
427950
  findMemberForSubagent(agentId, swarmIndex) {
427865
427951
  const existing = this.findMemberByAgentId(agentId);
@@ -497915,6 +498001,208 @@ var ActivityPaneComponent = class extends Container {
497915
498001
  return lines;
497916
498002
  }
497917
498003
  };
498004
+ registerUiCatalogFragment({
498005
+ en: {
498006
+ "media.activity.jobs": "{count} media jobs active",
498007
+ "media.detail.queue": "Position {position}",
498008
+ "media.detail.segment": "Segment {current}/{total}",
498009
+ "media.detail.audio": "{seconds}s audio",
498010
+ "media.detail.remaining": "about {duration} remaining",
498011
+ "media.kind.media": "Media",
498012
+ "media.kind.image": "Image",
498013
+ "media.kind.video": "Video",
498014
+ "media.kind.voice": "Voice",
498015
+ "media.kind.image-analysis": "Image analysis",
498016
+ "media.kind.video-analysis": "Video analysis",
498017
+ "media.kind.dubbing": "Dubbing",
498018
+ "media.kind.lipsync": "Lip sync",
498019
+ "media.phase.submitting": "Sending request",
498020
+ "media.phase.uploading": "Sending request",
498021
+ "media.phase.accepted": "Queued",
498022
+ "media.phase.pending": "Queued",
498023
+ "media.phase.queued": "Queued",
498024
+ "media.phase.running": "Processing",
498025
+ "media.phase.in-progress": "Processing",
498026
+ "media.phase.generating": "Generating",
498027
+ "media.phase.rendering": "Rendering",
498028
+ "media.phase.processing": "Processing",
498029
+ "media.phase.checking": "Checking status",
498030
+ "media.phase.audio-analysis": "Audio analysis",
498031
+ "media.phase.face-detection": "Face detection",
498032
+ "media.phase.face-tracking": "Face tracking",
498033
+ "media.phase.lip-sync-running": "Lip sync running",
498034
+ "media.phase.exporting": "Exporting",
498035
+ "media.phase.saving": "Saving",
498036
+ "media.phase.upscaling": "Upscaling",
498037
+ "media.phase.analyzing": "Analyzing"
498038
+ },
498039
+ de: {
498040
+ "media.activity.jobs": "{count} Medienaufträge aktiv",
498041
+ "media.detail.queue": "Platz {position}",
498042
+ "media.detail.segment": "Abschnitt {current}/{total}",
498043
+ "media.detail.audio": "{seconds}s Audio",
498044
+ "media.detail.remaining": "noch etwa {duration}",
498045
+ "media.kind.media": "Medien",
498046
+ "media.kind.image": "Bild",
498047
+ "media.kind.video": "Video",
498048
+ "media.kind.voice": "Stimme",
498049
+ "media.kind.image-analysis": "Bildanalyse",
498050
+ "media.kind.video-analysis": "Videoanalyse",
498051
+ "media.kind.dubbing": "Vertonung",
498052
+ "media.kind.lipsync": "Lip-Sync",
498053
+ "media.phase.submitting": "Auftrag wird gesendet",
498054
+ "media.phase.uploading": "Auftrag wird gesendet",
498055
+ "media.phase.accepted": "Eingereiht",
498056
+ "media.phase.pending": "Eingereiht",
498057
+ "media.phase.queued": "Eingereiht",
498058
+ "media.phase.running": "Wird verarbeitet",
498059
+ "media.phase.in-progress": "Wird verarbeitet",
498060
+ "media.phase.generating": "Wird erzeugt",
498061
+ "media.phase.rendering": "Wird gerendert",
498062
+ "media.phase.processing": "Wird verarbeitet",
498063
+ "media.phase.checking": "Status wird geprüft",
498064
+ "media.phase.audio-analysis": "Audioanalyse",
498065
+ "media.phase.face-detection": "Gesichtserkennung",
498066
+ "media.phase.face-tracking": "Gesicht wird verfolgt",
498067
+ "media.phase.lip-sync-running": "Lip-Sync läuft",
498068
+ "media.phase.exporting": "Wird exportiert",
498069
+ "media.phase.saving": "Wird gespeichert",
498070
+ "media.phase.upscaling": "Wird hochskaliert",
498071
+ "media.phase.analyzing": "Wird analysiert"
498072
+ }
498073
+ });
498074
+ function mediaActivityStatusKey(value) {
498075
+ return String(value ?? "processing").trim().toLowerCase().replaceAll(/[^a-z0-9]+/g, "-");
498076
+ }
498077
+ function mediaActivityIsTerminal(value) {
498078
+ return [
498079
+ "complete",
498080
+ "completed",
498081
+ "ready",
498082
+ "failed",
498083
+ "expired",
498084
+ "blocked"
498085
+ ].includes(mediaActivityStatusKey(value));
498086
+ }
498087
+ const MEDIA_ACTIVITY_LABELS = {
498088
+ "media.kind": /* @__PURE__ */ new Set([
498089
+ "media",
498090
+ "image",
498091
+ "video",
498092
+ "voice",
498093
+ "image-analysis",
498094
+ "video-analysis",
498095
+ "dubbing",
498096
+ "lipsync"
498097
+ ]),
498098
+ "media.phase": /* @__PURE__ */ new Set([
498099
+ "submitting",
498100
+ "uploading",
498101
+ "accepted",
498102
+ "pending",
498103
+ "queued",
498104
+ "running",
498105
+ "in-progress",
498106
+ "generating",
498107
+ "rendering",
498108
+ "processing",
498109
+ "checking",
498110
+ "audio-analysis",
498111
+ "face-detection",
498112
+ "face-tracking",
498113
+ "lip-sync-running",
498114
+ "exporting",
498115
+ "saving",
498116
+ "upscaling",
498117
+ "analyzing"
498118
+ ])
498119
+ };
498120
+ function mediaUiText(key, params = {}) {
498121
+ return uiTextFor(getCurrentUiLocale() === "de" ? "de" : "en", key, params);
498122
+ }
498123
+ function mediaActivityLabel(prefix, value, fallback) {
498124
+ const normalized = mediaActivityStatusKey(value);
498125
+ if (!MEDIA_ACTIVITY_LABELS[prefix]?.has(normalized)) return fallback;
498126
+ return mediaUiText(`${prefix}.${normalized}`);
498127
+ }
498128
+ function formatMediaDuration(seconds) {
498129
+ if (!Number.isFinite(seconds) || seconds < 0) return;
498130
+ const whole = Math.round(seconds);
498131
+ const minutes = Math.floor(whole / 60);
498132
+ const rest = whole % 60;
498133
+ return minutes > 0 ? `${String(minutes)}:${String(rest).padStart(2, "0")}` : `${String(rest)}s`;
498134
+ }
498135
+ var MediaActivityStore = class {
498136
+ jobs = /* @__PURE__ */ new Map();
498137
+ update(toolCallId, event) {
498138
+ if (event === null || typeof event !== "object") return;
498139
+ const id = typeof event.id === "string" && event.id.length > 0 ? event.id : void 0;
498140
+ let existingKey = id;
498141
+ if (existingKey === void 0 || !this.jobs.has(existingKey)) for (const [key, job] of this.jobs) if (job.toolCallId === toolCallId || id !== void 0 && job.id === id) {
498142
+ existingKey = key;
498143
+ break;
498144
+ }
498145
+ const key = id ?? existingKey ?? toolCallId;
498146
+ const previous = existingKey === void 0 ? void 0 : this.jobs.get(existingKey);
498147
+ if (existingKey !== void 0 && existingKey !== key) this.jobs.delete(existingKey);
498148
+ if (mediaActivityIsTerminal(event.phase)) {
498149
+ this.jobs.delete(key);
498150
+ return;
498151
+ }
498152
+ const mediaKind = event.mediaKind === "media" && previous?.mediaKind !== void 0 ? previous.mediaKind : event.mediaKind ?? previous?.mediaKind ?? "media";
498153
+ this.jobs.set(key, {
498154
+ ...previous,
498155
+ ...event,
498156
+ id: id ?? previous?.id,
498157
+ mediaKind,
498158
+ toolCallId,
498159
+ startedAt: previous?.startedAt ?? Date.now(),
498160
+ updatedAt: Date.now()
498161
+ });
498162
+ }
498163
+ active() {
498164
+ return [...this.jobs.values()].filter((job) => !mediaActivityIsTerminal(job.phase)).sort((a, b) => a.startedAt - b.startedAt);
498165
+ }
498166
+ clear() {
498167
+ this.jobs.clear();
498168
+ }
498169
+ };
498170
+ var MediaActivityComponent = class {
498171
+ jobs;
498172
+ constructor(jobs) {
498173
+ this.jobs = jobs;
498174
+ }
498175
+ invalidate() {}
498176
+ render(width) {
498177
+ if (this.jobs.length === 0) return [];
498178
+ const job = this.jobs[0];
498179
+ const kind = mediaActivityLabel("media.kind", job.mediaKind, String(job.mediaKind ?? "Media"));
498180
+ const rawPhase = job.phaseLabel ?? job.phase ?? "processing";
498181
+ const phase = sanitizeTerminalText(mediaActivityLabel("media.phase", rawPhase, String(rawPhase)));
498182
+ const details = [];
498183
+ if (job.queuePosition !== void 0) details.push(mediaUiText("media.detail.queue", { position: job.queuePosition }));
498184
+ if (job.currentStep !== void 0 && job.totalSteps !== void 0) details.push(`${String(job.currentStep)}/${String(job.totalSteps)}`);
498185
+ if (job.currentSegment !== void 0 && job.totalSegments !== void 0) details.push(mediaUiText("media.detail.segment", {
498186
+ current: job.currentSegment,
498187
+ total: job.totalSegments
498188
+ }));
498189
+ if (job.currentFrame !== void 0 && job.totalFrames !== void 0) details.push(`Frame ${String(job.currentFrame)}/${String(job.totalFrames)}`);
498190
+ if (job.fps !== void 0) details.push(`${String(Math.round(job.fps * 10) / 10)} FPS`);
498191
+ if (job.generatedSeconds !== void 0) details.push(mediaUiText("media.detail.audio", { seconds: Math.round(job.generatedSeconds) }));
498192
+ const elapsed = job.elapsedSeconds ?? (Date.now() - job.startedAt) / 1e3;
498193
+ const elapsedText = formatMediaDuration(elapsed);
498194
+ if (elapsedText !== void 0) details.push(elapsedText);
498195
+ if (job.remainingSeconds !== void 0) details.push(mediaUiText("media.detail.remaining", { duration: formatMediaDuration(job.remainingSeconds) }));
498196
+ let progress = "";
498197
+ if (job.percent !== void 0 && Number.isFinite(job.percent)) {
498198
+ const percent = Math.min(100, Math.max(0, job.percent));
498199
+ progress = ` · ${renderProgressBar(percent / 100, 12)} · ${String(Math.round(percent))} %`;
498200
+ }
498201
+ const summary = this.jobs.length > 1 ? ` · ${mediaUiText("media.activity.jobs", { count: this.jobs.length })}` : "";
498202
+ const line = `${currentTheme.boldFg("primary", kind)} · ${phase}${details.length === 0 ? "" : ` · ${details.join(" · ")}`}${progress}${summary}`;
498203
+ return [truncateToWidth(line, Math.max(1, width), "…")];
498204
+ }
498205
+ };
497918
498206
  //#endregion
497919
498207
  //#region src/tui/components/panes/queue-pane.copy.ts
497920
498208
  registerUiCatalogFragment({
@@ -504441,6 +504729,10 @@ var SessionEventHandler = class {
504441
504729
  streamingUI.scheduleFlush();
504442
504730
  }
504443
504731
  handleToolProgress(event) {
504732
+ if (event.update.kind === "custom" && event.update.customKind === MEDIA_PROGRESS_CUSTOM_KIND) {
504733
+ this.host.updateMediaActivity(event.toolCallId, event.update.customData);
504734
+ return;
504735
+ }
504444
504736
  const text = event.update.text;
504445
504737
  if (text === void 0 || text.length === 0) return;
504446
504738
  const tc = this.host.streamingUI.getToolComponent(event.toolCallId);
@@ -504462,6 +504754,10 @@ var SessionEventHandler = class {
504462
504754
  synthetic: event.synthetic
504463
504755
  };
504464
504756
  const matchedCall = streamingUI.completeToolResult(event.toolCallId, resultData);
504757
+ if (matchedCall !== void 0 && isMediaToolName(matchedCall.name) && event.isError === true) this.host.updateMediaActivity(event.toolCallId, {
504758
+ mediaKind: mediaKindForToolName(matchedCall.name),
504759
+ phase: "failed"
504760
+ });
504465
504761
  if (matchedCall?.name === "GetMedia" && event.isError !== true) this.host.runChannelMediaFallback(event.output);
504466
504762
  this.subAgentEventHandler.handleAgentSwarmToolResult(event.toolCallId, resultData, event.isError === true);
504467
504763
  if (matchedCall !== void 0 && matchedCall.name === "TodoList" && !event.isError) {
@@ -506827,24 +507123,21 @@ function formatContextStatus(usage, tokens, maxTokens, colors, width, now = /* @
506827
507123
  return truncateToWidth(current, available, "…");
506828
507124
  }
506829
507125
  function formatCompactionStatus(elapsedMs, estimatedInputTokens, progress, colors, width, now = /* @__PURE__ */ new Date()) {
506830
- const percent = progress?.estimatedProgressPercent ?? estimateCompactionPercent(elapsedMs, estimatedInputTokens);
506831
- const filled = Math.max(1, Math.ceil((1 - percent / 100) * CONTEXT_BAR_WIDTH));
506832
- const bar = chalk.hex(colors.primary)(`${"█".repeat(filled)}${"░".repeat(CONTEXT_BAR_WIDTH - filled)}`);
506833
507126
  const label = formatCompactionRunningText(estimatedInputTokens);
506834
507127
  const stageText = progress === void 0 ? "" : formatCompactionStageText(progress);
506835
507128
  const compactStageText = progress?.stage === void 0 || progress.estimatedStageCount === void 0 ? "" : `${String(progress.stage)}/~${String(progress.estimatedStageCount)}${progress.windowUsagePercent === void 0 ? "" : ` · ${String(progress.windowUsagePercent)}%`}`;
506836
- const estimate = progress?.windowUsagePercent === void 0 ? `~${String(percent)} %` : "";
507129
+ const elapsed = `${String(Math.max(0, Math.round(elapsedMs / 1e3)))}s`;
506837
507130
  const clock = chalk.hex(colors.textDim)(` · ${clockHhmm(now)}`);
506838
507131
  const variants = [
506839
- `${label}${stageText.length === 0 ? "" : ` · ${stageText}`} ${bar}${estimate.length === 0 ? "" : ` ${estimate}`}${clock}`,
507132
+ `${label}${stageText.length === 0 ? "" : ` · ${stageText}`} · ${elapsed}${clock}`,
506840
507133
  `${label}${stageText.length === 0 ? "" : ` · ${stageText}`}`,
506841
507134
  compactStageText,
506842
- `${bar}${estimate.length === 0 ? "" : ` ${estimate}`}`,
506843
- stageText.length === 0 ? estimate : stageText
507135
+ elapsed,
507136
+ stageText
506844
507137
  ];
506845
507138
  const available = Math.max(0, width);
506846
507139
  for (const variant of variants) if (visibleWidth(variant) <= available) return variant;
506847
- return truncateToWidth(estimate, available, "…");
507140
+ return truncateToWidth(elapsed, available, "…");
506848
507141
  }
506849
507142
  function contextMetrics(state) {
506850
507143
  const tokens = Number.isFinite(state.contextTokens) ? Math.max(0, state.contextTokens) : 0;
@@ -512083,6 +512376,8 @@ var BlunTUI = class {
512083
512376
  startupGoalPromptedSessionId;
512084
512377
  lastActivityMode;
512085
512378
  currentLoadingTip = void 0;
512379
+ mediaActivityStore = new MediaActivityStore();
512380
+ mediaActivityTickTimer;
512086
512381
  lastHistoryContent;
512087
512382
  shellOutputStreams = /* @__PURE__ */ new Map();
512088
512383
  streamingUI;
@@ -512594,6 +512889,7 @@ var BlunTUI = class {
512594
512889
  this.tasksBrowserController.close();
512595
512890
  this.btwPanelController.clear();
512596
512891
  this.stopActivitySpinner();
512892
+ this.stopMediaActivityTicking();
512597
512893
  this.disposeEditorReplacement();
512598
512894
  this.streamingUI.disposeActiveCompactionBlock();
512599
512895
  this.streamingUI.resetToolUi();
@@ -512742,6 +513038,13 @@ var BlunTUI = class {
512742
513038
  return;
512743
513039
  }
512744
513040
  if (text.trimStart().startsWith("/")) {
513041
+ if (shouldQueueBusySlashCommand(text, {
513042
+ isStreaming: this.state.appState.streamingPhase !== "idle",
513043
+ isCompacting: this.state.appState.isCompacting
513044
+ })) {
513045
+ this.enqueueSlashCommand(text);
513046
+ return;
513047
+ }
512745
513048
  this.runSlashCommand(text);
512746
513049
  return;
512747
513050
  }
@@ -513749,6 +514052,8 @@ var BlunTUI = class {
513749
514052
  this.setAppState({ mcpServersSummary: null });
513750
514053
  this.streamingUI.setStep(0);
513751
514054
  this.streamingUI.resetLiveText();
514055
+ this.mediaActivityStore.clear();
514056
+ this.stopMediaActivityTicking();
513752
514057
  this.updateQueueDisplay();
513753
514058
  }
513754
514059
  async showResumeOtherWorkDirHint(session) {
@@ -514200,8 +514505,32 @@ var BlunTUI = class {
514200
514505
  this.state.ui.requestRender();
514201
514506
  return this.showLoginProgressSpinner(uiText("blunTui.login.waiting"));
514202
514507
  }
514508
+ updateMediaActivity(toolCallId, event) {
514509
+ this.mediaActivityStore.update(toolCallId, event);
514510
+ if (this.mediaActivityStore.active().length > 0) {
514511
+ if (this.mediaActivityTickTimer === void 0) this.mediaActivityTickTimer = setInterval(() => this.state.ui.requestRender(), 1e3);
514512
+ } else this.stopMediaActivityTicking();
514513
+ this.lastActivityMode = void 0;
514514
+ this.updateActivityPane();
514515
+ }
514516
+ stopMediaActivityTicking() {
514517
+ if (this.mediaActivityTickTimer === void 0) return;
514518
+ clearInterval(this.mediaActivityTickTimer);
514519
+ this.mediaActivityTickTimer = void 0;
514520
+ }
514203
514521
  updateActivityPane() {
514204
514522
  const effectiveMode = this.resolveActivityPaneMode();
514523
+ const mediaActivities = effectiveMode === "hidden" ? [] : this.mediaActivityStore.active();
514524
+ if (mediaActivities.length > 0) {
514525
+ this.stopActivitySpinner();
514526
+ this.syncAgentSwarmActivitySpinner(void 0);
514527
+ this.lastActivityMode = `media:${mediaActivities.map((job) => `${job.id ?? job.toolCallId}:${job.updatedAt}`).join(",")}`;
514528
+ this.state.activityContainer.clear();
514529
+ this.state.activityContainer.addChild(new Spacer(1));
514530
+ this.state.activityContainer.addChild(new MediaActivityComponent(mediaActivities));
514531
+ this.state.ui.requestRender();
514532
+ return;
514533
+ }
514205
514534
  const tipKind = loadingTipKind(effectiveMode);
514206
514535
  if (effectiveMode === "idle" || effectiveMode === "session" || effectiveMode === "hidden") this.currentLoadingTip = void 0;
514207
514536
  else if (tipKind !== void 0 && (this.currentLoadingTip === void 0 || this.currentLoadingTip.kind !== tipKind)) this.currentLoadingTip = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "9.1.47",
3
+ "version": "9.1.48",
4
4
  "description": "BLUN CLI - your own AI agent with a Telegram channel. Get it done. With BLUN.",
5
5
  "license": "MIT",
6
6
  "bin": {