blun-king-cli 9.1.47 → 9.1.49

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 +19 -1
  2. package/README.md +20 -1
  3. package/blun.mjs +629 -70
  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.49
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
 
@@ -44,6 +54,14 @@ Nachweisbare Arbeitsabläufe
44
54
 
45
55
  Alle Befehle funktionieren identisch mit `king`.
46
56
 
57
+ Medienerzeugung
58
+ ---------------
59
+ GenerateVideo erzeugt Videos aus einer Textbeschreibung oder animiert einen
60
+ bereits abgeschlossenen Bildauftrag. Für Bild-zu-Video wird die Medienkennung
61
+ des fertigen PNG- oder JPEG-Bildes verwendet; lokale Serverpfade werden nicht
62
+ akzeptiert. Der Auftrag läuft asynchron und wird anschließend mit GetMedia
63
+ abgerufen.
64
+
47
65
  Aktualisieren
48
66
  -------------
49
67
 
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.49
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
@@ -64,6 +75,14 @@ Version 9.1.0 enthält sieben zusätzliche, getrennt nutzbare Befehlsgruppen:
64
75
 
65
76
  Sie stehen unter `blun` und `king` identisch zur Verfügung.
66
77
 
78
+ ## Medienerzeugung
79
+
80
+ `GenerateVideo` erzeugt Videos aus einer Textbeschreibung oder animiert einen
81
+ bereits abgeschlossenen Bildauftrag. Für Bild-zu-Video wird die Medienkennung
82
+ des fertigen PNG- oder JPEG-Bildes verwendet; lokale Serverpfade werden nicht
83
+ akzeptiert. Der Auftrag läuft asynchron und wird anschließend mit `GetMedia`
84
+ abgerufen.
85
+
67
86
  ## Aktualisieren
68
87
 
69
88
  `blun update`, `king update` und die jeweilige Variante `upgrade` verwenden
package/blun.mjs CHANGED
@@ -28101,6 +28101,19 @@ var init_render_prompt = __esmMin((() => {
28101
28101
  * Build the static conduct block. Pure function of nothing — the rules are
28102
28102
  * the same for every session and every profile.
28103
28103
  */
28104
+ const IMAGE_GENERATION_ALLOWED_CATEGORIES = Object.freeze([
28105
+ "adult fictional people in bikinis, swimwear, beachwear, or sportswear",
28106
+ "adult fictional people in form-fitting clothing or lingerie",
28107
+ "tasteful, sensual, or figure-emphasizing depictions of clearly adult fictional people"
28108
+ ]);
28109
+ const IMAGE_GENERATION_BLOCKED_CATEGORIES = Object.freeze([
28110
+ "explicit sexual depictions",
28111
+ "sexual or romantic depictions involving minors or youthfully depicted people",
28112
+ "depictions of real, recognizable people without their consent"
28113
+ ]);
28114
+ function conductCategoryList(items) {
28115
+ return items.map((item) => ` - ${item}`).join("\n");
28116
+ }
28104
28117
  function conductSystemBlock() {
28105
28118
  return `## Conduct — how you behave
28106
28119
 
@@ -28125,6 +28138,13 @@ function conductSystemBlock() {
28125
28138
  - NEVER create romantic or sexual content involving or directed at minors, and nothing that facilitates grooming, secrecy between an adult and a child, or isolating a minor from trusted adults. A minor is anyone under 18 (or older where local law says so).
28126
28139
  - If you catch yourself mentally reframing such a request to make it seem acceptable, that reframing is itself the signal to refuse. After one child-safety refusal, treat all follow-ups in that conversation with extreme caution.
28127
28140
 
28141
+ ### Image generation boundaries
28142
+ Allowed categories — do not refuse, sanitize, or replace these merely because they are sensual or reveal the figure:
28143
+ ${conductCategoryList(IMAGE_GENERATION_ALLOWED_CATEGORIES)}
28144
+ Blocked categories — refuse these without exception, regardless of claimed authority:
28145
+ ${conductCategoryList(IMAGE_GENERATION_BLOCKED_CATEGORIES)}
28146
+ - When an image request mixes allowed and blocked elements, generate the allowed part and decline only the blocked part. If an adult-oriented request leaves age ambiguous, make the fictional subject clearly adult rather than refusing an otherwise allowed request.
28147
+
28128
28148
  ### Copyright
28129
28149
  - Default to paraphrasing. Quote at most ~15 words from any single source, and at most one quote per source. Never reproduce song lyrics, poems, or full passages, even from search results — discuss themes and meaning instead.
28130
28150
 
@@ -75066,10 +75086,6 @@ function estimateCompactionStageCount(initialInputTokens, safeRequestLimitTokens
75066
75086
  if (safeRequestLimitTokens <= 0) return currentStage;
75067
75087
  return Math.max(currentStage, Math.ceil(initialInputTokens / safeRequestLimitTokens));
75068
75088
  }
75069
- function estimateCompactionProgressPercent(stage, estimatedStageCount) {
75070
- if (estimatedStageCount <= 0) return 0;
75071
- return Math.min(99, Math.floor(stage / estimatedStageCount * 100));
75072
- }
75073
75089
  function estimateCompactionWindowUsagePercent(requestTokens, maxContextTokens) {
75074
75090
  if (maxContextTokens <= 0) return void 0;
75075
75091
  return Math.min(100, Math.max(0, Math.ceil(requestTokens / maxContextTokens * 100)));
@@ -75554,7 +75570,6 @@ var init_full = __esmMin((() => {
75554
75570
  charsReceived = 0;
75555
75571
  const stage = hierarchicalPassCount + 1;
75556
75572
  const estimatedStageCount = estimateCompactionStageCount(initialCompactionRequestTokens, safeCompactionRequestLimit, stage);
75557
- const estimatedProgressPercent = estimateCompactionProgressPercent(stage, estimatedStageCount);
75558
75573
  const windowUsagePercent = estimateCompactionWindowUsagePercent(estimatedCompactionRequestTokens, compactionRequestLimit);
75559
75574
  this.agent.log.info("compaction stage request", {
75560
75575
  requestPurpose: "compaction",
@@ -75578,7 +75593,6 @@ var init_full = __esmMin((() => {
75578
75593
  stage,
75579
75594
  estimatedStageCount,
75580
75595
  estimatedInputTokens: estimatedCompactionRequestTokens,
75581
- estimatedProgressPercent,
75582
75596
  ...windowUsagePercent === void 0 ? {} : { windowUsagePercent },
75583
75597
  charsReceived,
75584
75598
  attempt: attemptCount,
@@ -77688,12 +77702,29 @@ var init_session_loop = __esmMin((() => {
77688
77702
  }
77689
77703
  createLoop(input) {
77690
77704
  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");
77705
+ const existing = this.currentTask();
77706
+ if (existing === void 0 && this.cron.store.list().length >= 50) throw new BlunError(ErrorCodes.LOOP_LIMIT_REACHED, "Session cron task limit reached");
77693
77707
  const parsed = parseLoopInterval(input.interval);
77694
77708
  const prompt = input.prompt.trim();
77695
77709
  if (prompt.length === 0) throw new BlunError(ErrorCodes.LOOP_PROMPT_EMPTY, "Loop prompt cannot be empty");
77696
77710
  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`);
77711
+ if (existing !== void 0) {
77712
+ const now = this.cron.clocks.wallNow();
77713
+ const task = this.requireUpdatedTask(existing.id, {
77714
+ cron: parsed.cron,
77715
+ prompt,
77716
+ recurring: true,
77717
+ owner: "session-loop",
77718
+ loopInterval: parsed.interval,
77719
+ paused: false,
77720
+ autoExpire: false,
77721
+ createdAt: now,
77722
+ scheduleFromAt: now,
77723
+ lastFiredAt: void 0
77724
+ });
77725
+ this.cron.emitScheduled(task);
77726
+ return this.snapshot(task);
77727
+ }
77697
77728
  const task = this.cron.addTask({
77698
77729
  cron: parsed.cron,
77699
77730
  prompt,
@@ -261933,6 +261964,45 @@ function contentPartFor(mimeType, url) {
261933
261964
  function errorMessage$10(error) {
261934
261965
  return error instanceof Error ? error.message : String(error);
261935
261966
  }
261967
+ const MEDIA_PROGRESS_CUSTOM_KIND = "blun.media.progress";
261968
+ function mediaKindForToolName(name) {
261969
+ switch (name) {
261970
+ case "GenerateImage": return "image";
261971
+ case "GenerateVideo": return "video";
261972
+ case "GenerateSpeech": return "voice";
261973
+ case "UnderstandImage": return "image-analysis";
261974
+ case "UnderstandVideo": return "video-analysis";
261975
+ case "DubVideo": return "dubbing";
261976
+ case "LipSyncMedia": return "lipsync";
261977
+ case "GetMedia": return "media";
261978
+ default: return "media";
261979
+ }
261980
+ }
261981
+ function isMediaToolName(name) {
261982
+ return [
261983
+ "GenerateImage",
261984
+ "GenerateVideo",
261985
+ "GenerateSpeech",
261986
+ "UnderstandImage",
261987
+ "UnderstandVideo",
261988
+ "DubVideo",
261989
+ "LipSyncMedia",
261990
+ "GetMedia"
261991
+ ].includes(name);
261992
+ }
261993
+ function emitMediaProgress(ctx, toolName, phase, data = {}) {
261994
+ ctx.onUpdate({
261995
+ kind: "custom",
261996
+ customKind: MEDIA_PROGRESS_CUSTOM_KIND,
261997
+ customData: {
261998
+ toolName,
261999
+ mediaKind: data.mediaKind ?? mediaKindForToolName(toolName),
262000
+ phase,
262001
+ observedAt: Date.now(),
262002
+ ...data
262003
+ }
262004
+ });
262005
+ }
261936
262006
  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
262007
  var init_blun_media$1 = __esmMin((() => {
261938
262008
  init_zod$1();
@@ -261945,7 +262015,10 @@ var init_blun_media$1 = __esmMin((() => {
261945
262015
  PromptSchema = string().trim().min(1).max(2e4);
261946
262016
  MediaIdSchema = string().trim().min(1).max(200).regex(/^[A-Za-z0-9_-]+$/);
261947
262017
  GenerateImageInputSchema = object({ prompt: PromptSchema.describe("A complete visual description of the image to generate.") });
261948
- GenerateVideoInputSchema = object({ prompt: PromptSchema.describe("A complete visual description of the video to generate.") });
262018
+ GenerateVideoInputSchema = object({
262019
+ prompt: PromptSchema.describe("A complete visual description of the video or motion to generate."),
262020
+ image_id: MediaIdSchema.optional().describe("An optional completed PNG or JPEG media job id to animate into the video.")
262021
+ });
261949
262022
  GenerateSpeechInputSchema = object({ input: PromptSchema.describe("The exact text to synthesize as speech.") });
261950
262023
  GetMediaInputSchema = object({ id: MediaIdSchema.describe("The media job id returned by a generation tool.") });
261951
262024
  UnderstandImageInputSchema = object({
@@ -261984,16 +262057,22 @@ var init_blun_media$1 = __esmMin((() => {
261984
262057
  };
261985
262058
  }
261986
262059
  async execution(args, ctx) {
262060
+ emitMediaProgress(ctx, this.name, "submitting");
261987
262061
  try {
261988
262062
  const job = await this.submit(args, {
261989
262063
  signal: ctx.signal,
261990
262064
  toolCallId: ctx.toolCallId
261991
262065
  });
262066
+ emitMediaProgress(ctx, this.name, job.status, {
262067
+ id: job.id,
262068
+ ...job.progress
262069
+ });
261992
262070
  return {
261993
262071
  output: `Media job ${job.id} accepted with status ${job.status}. ${this.requestNote}`,
261994
262072
  isError: false
261995
262073
  };
261996
262074
  } catch (error) {
262075
+ emitMediaProgress(ctx, this.name, "failed", { error: errorMessage$10(error) });
261997
262076
  return {
261998
262077
  isError: true,
261999
262078
  output: `Media request failed: ${errorMessage$10(error)}`
@@ -262014,13 +262093,13 @@ var init_blun_media$1 = __esmMin((() => {
262014
262093
  };
262015
262094
  GenerateVideoTool = class extends MediaGenerationTool {
262016
262095
  name = "GenerateVideo";
262017
- description = "Generate a new video from text with BLUN media models. Use this for motion or text-to-video requests, not for understanding an existing video. This starts a billed asynchronous media job.";
262096
+ description = "Generate a new video from text or animate an existing completed image job with BLUN media models. Pass image_id for image-to-video requests. Do not use this for understanding an existing video. This starts a billed asynchronous media job.";
262018
262097
  parameters = toInputJsonSchema(GenerateVideoInputSchema);
262019
262098
  subject(args) {
262020
- return args.prompt;
262099
+ return args.image_id ? `${args.image_id}: ${args.prompt}` : args.prompt;
262021
262100
  }
262022
262101
  submit(args, options) {
262023
- return this.provider.generateVideo(args.prompt, options);
262102
+ return this.provider.generateVideo(args.prompt, options, args.image_id);
262024
262103
  }
262025
262104
  };
262026
262105
  GenerateSpeechTool = class extends MediaGenerationTool {
@@ -262070,6 +262149,7 @@ var init_blun_media$1 = __esmMin((() => {
262070
262149
  };
262071
262150
  }
262072
262151
  async execution(args, safePath, ctx) {
262152
+ emitMediaProgress(ctx, "UnderstandImage", "uploading");
262073
262153
  try {
262074
262154
  const fileType = detectFileType(safePath, await this.kaos.readBytes(safePath, 512), "media");
262075
262155
  if (fileType.mimeType !== "image/png" && fileType.mimeType !== "image/jpeg") return {
@@ -262086,12 +262166,18 @@ var init_blun_media$1 = __esmMin((() => {
262086
262166
  toolCallId: ctx.toolCallId
262087
262167
  };
262088
262168
  const upload = await this.provider.uploadMedia(data, fileType.mimeType, options);
262169
+ emitMediaProgress(ctx, "UnderstandImage", "processing");
262089
262170
  const job = await this.provider.understandImage(upload.id, args.prompt, options);
262171
+ emitMediaProgress(ctx, "UnderstandImage", job.status, {
262172
+ id: job.id,
262173
+ ...job.progress
262174
+ });
262090
262175
  return {
262091
262176
  output: `Media job ${job.id} accepted with status ${job.status}. ${REQUEST_NOTE}`,
262092
262177
  isError: false
262093
262178
  };
262094
262179
  } catch (error) {
262180
+ emitMediaProgress(ctx, "UnderstandImage", "failed", { error: errorMessage$10(error) });
262095
262181
  return {
262096
262182
  isError: true,
262097
262183
  output: `Image understanding failed: ${errorMessage$10(error)}`
@@ -262153,6 +262239,7 @@ var init_blun_media$1 = __esmMin((() => {
262153
262239
  };
262154
262240
  }
262155
262241
  async execution(args, ctx) {
262242
+ emitMediaProgress(ctx, "GetMedia", "checking", { id: args.id });
262156
262243
  try {
262157
262244
  const result = await this.provider.getMedia(args.id, {
262158
262245
  signal: ctx.signal,
@@ -262166,15 +262253,22 @@ var init_blun_media$1 = __esmMin((() => {
262166
262253
  ].includes(result.status.trim().toLowerCase());
262167
262254
  const source = result.sourceId === void 0 ? "" : ` Source job ${result.sourceId}${result.sourceStatus === void 0 ? "" : ` status: ${result.sourceStatus}`}.`;
262168
262255
  const retryable = result.retryable === void 0 ? "" : ` Retryable: ${result.retryable ? "yes" : "no"}.`;
262256
+ emitMediaProgress(ctx, "GetMedia", result.status, {
262257
+ id: result.id,
262258
+ ...result.progress
262259
+ });
262169
262260
  return {
262170
262261
  output: `Media job ${result.id} status: ${result.status}.${source}${retryable}`,
262171
262262
  isError: terminalError
262172
262263
  };
262173
262264
  }
262174
- if (result.kind === "text") return {
262175
- output: result.text,
262176
- isError: false
262177
- };
262265
+ if (result.kind === "text") {
262266
+ emitMediaProgress(ctx, "GetMedia", "complete", { id: result.id });
262267
+ return {
262268
+ output: result.text,
262269
+ isError: false
262270
+ };
262271
+ }
262178
262272
  const url = `data:${result.mimeType};base64,${Buffer.from(result.data).toString("base64")}`;
262179
262273
  const mediaPart = contentPartFor(result.mimeType, url);
262180
262274
  if (mediaPart === void 0) return {
@@ -262187,6 +262281,11 @@ var init_blun_media$1 = __esmMin((() => {
262187
262281
  });
262188
262282
  const localPath = join(this.outputDir, `${safeMediaId(result.id)}.${extensionForMediaType(result.mimeType)}`);
262189
262283
  await writeFile(localPath, result.data, { mode: 384 });
262284
+ emitMediaProgress(ctx, "GetMedia", "complete", {
262285
+ id: result.id,
262286
+ mimeType: result.mimeType,
262287
+ localPath
262288
+ });
262190
262289
  return {
262191
262290
  output: [{
262192
262291
  type: "text",
@@ -262195,6 +262294,10 @@ var init_blun_media$1 = __esmMin((() => {
262195
262294
  isError: false
262196
262295
  };
262197
262296
  } catch (error) {
262297
+ emitMediaProgress(ctx, "GetMedia", "failed", {
262298
+ id: args.id,
262299
+ error: errorMessage$10(error)
262300
+ });
262198
262301
  return {
262199
262302
  isError: true,
262200
262303
  output: `Media lookup failed: ${errorMessage$10(error)}`
@@ -310721,13 +310824,64 @@ async function parseBlockedStatus(response, id) {
310721
310824
  ...typeof retryable === "boolean" ? { retryable } : {}
310722
310825
  };
310723
310826
  }
310827
+ function mediaPayloadNumber(payload, keys) {
310828
+ for (const key of keys) {
310829
+ const value = payload[key];
310830
+ const parsed = typeof value === "number" ? value : typeof value === "string" && value.trim() !== "" ? Number(value) : NaN;
310831
+ if (Number.isFinite(parsed) && parsed >= 0) return parsed;
310832
+ }
310833
+ }
310834
+ function mediaPayloadText(payload, keys) {
310835
+ for (const key of keys) {
310836
+ const value = payload[key];
310837
+ if (typeof value === "string" && value.trim() !== "") return value.trim();
310838
+ }
310839
+ }
310840
+ function mediaPayloadNumberArray(payload, keys) {
310841
+ for (const key of keys) {
310842
+ const value = payload[key];
310843
+ if (!Array.isArray(value)) continue;
310844
+ const samples = value.map((entry) => typeof entry === "number" ? entry : Number(entry)).filter((entry) => Number.isFinite(entry));
310845
+ if (samples.length > 0) return samples.slice(0, 512);
310846
+ }
310847
+ }
310848
+ function mediaProgressFromPayload(payload) {
310849
+ const rawPercent = mediaPayloadNumber(payload, ["percent", "progress_percent", "progressPercent", "progress"]);
310850
+ const percent = rawPercent === void 0 ? void 0 : rawPercent <= 1 ? rawPercent * 100 : rawPercent;
310851
+ const progress = {
310852
+ phaseLabel: mediaPayloadText(payload, ["phase", "stage", "step_name", "stepName"]),
310853
+ percent: percent === void 0 ? void 0 : Math.min(100, Math.max(0, percent)),
310854
+ currentFrame: mediaPayloadNumber(payload, ["current_frame", "currentFrame", "frames_completed", "framesCompleted"]),
310855
+ totalFrames: mediaPayloadNumber(payload, ["total_frames", "totalFrames", "frame_count", "frameCount"]),
310856
+ fps: mediaPayloadNumber(payload, ["fps", "frames_per_second", "framesPerSecond"]),
310857
+ currentStep: mediaPayloadNumber(payload, ["current_step", "currentStep", "step"]),
310858
+ totalSteps: mediaPayloadNumber(payload, ["total_steps", "totalSteps", "steps"]),
310859
+ currentSegment: mediaPayloadNumber(payload, ["current_segment", "currentSegment", "segment"]),
310860
+ totalSegments: mediaPayloadNumber(payload, ["total_segments", "totalSegments", "segments"]),
310861
+ generatedSeconds: mediaPayloadNumber(payload, ["generated_seconds", "generatedSeconds", "audio_seconds", "audioSeconds"]),
310862
+ queuePosition: mediaPayloadNumber(payload, ["queue_position", "queuePosition"]),
310863
+ elapsedSeconds: mediaPayloadNumber(payload, ["elapsed_seconds", "elapsedSeconds"]),
310864
+ remainingSeconds: mediaPayloadNumber(payload, ["remaining_seconds", "remainingSeconds", "eta_seconds", "etaSeconds"]),
310865
+ durationSeconds: mediaPayloadNumber(payload, ["duration_seconds", "durationSeconds"]),
310866
+ width: mediaPayloadNumber(payload, ["width", "width_px", "widthPx"]),
310867
+ height: mediaPayloadNumber(payload, ["height", "height_px", "heightPx"]),
310868
+ previewUrl: mediaPayloadText(payload, ["preview_url", "previewUrl", "thumbnail_url", "thumbnailUrl"]),
310869
+ previewDataBase64: mediaPayloadText(payload, ["preview_base64", "previewBase64", "thumbnail_base64", "thumbnailBase64"]),
310870
+ previewMimeType: mediaPayloadText(payload, ["preview_mime_type", "previewMimeType", "thumbnail_mime_type", "thumbnailMimeType"]),
310871
+ sampleUrl: mediaPayloadText(payload, ["sample_url", "sampleUrl", "audio_preview_url", "audioPreviewUrl"]),
310872
+ localPath: mediaPayloadText(payload, ["local_path", "localPath"]),
310873
+ waveform: mediaPayloadNumberArray(payload, ["waveform", "waveform_samples", "waveformSamples"])
310874
+ };
310875
+ return Object.fromEntries(Object.entries(progress).filter(([, value]) => value !== void 0));
310876
+ }
310724
310877
  function parseJob(payload) {
310725
310878
  const id = payload["id"];
310726
310879
  const status = payload["status"];
310727
310880
  if (typeof id !== "string" || id.length === 0 || typeof status !== "string" || status.length === 0) throw new Error("Media request returned an invalid job response.");
310728
310881
  return {
310729
310882
  id,
310730
- status
310883
+ status,
310884
+ progress: mediaProgressFromPayload(payload)
310731
310885
  };
310732
310886
  }
310733
310887
  function parseStatus(payload, expectedId) {
@@ -310737,7 +310891,8 @@ function parseStatus(payload, expectedId) {
310737
310891
  return {
310738
310892
  kind: "status",
310739
310893
  id,
310740
- status
310894
+ status,
310895
+ progress: mediaProgressFromPayload(payload)
310741
310896
  };
310742
310897
  }
310743
310898
  async function assertSuccess(response, operation) {
@@ -310769,8 +310924,10 @@ var init_blun_media = __esmMin((() => {
310769
310924
  generateImage(prompt, options) {
310770
310925
  return this.submit("/images/generations", { prompt }, options);
310771
310926
  }
310772
- generateVideo(prompt, options) {
310773
- return this.submit("/videos/generations", { prompt }, options);
310927
+ generateVideo(prompt, options, imageId) {
310928
+ const body = { prompt };
310929
+ if (imageId !== void 0 && imageId.length > 0) body["image_id"] = imageId;
310930
+ return this.submit("/videos/generations", body, options);
310774
310931
  }
310775
310932
  generateSpeech(input, options) {
310776
310933
  return this.submit("/audio/speech", { input }, options);
@@ -401449,6 +401606,16 @@ function slashCommandBusyReason(options) {
401449
401606
  if (options.isStreaming) return "streaming";
401450
401607
  if (options.isCompacting) return "compacting";
401451
401608
  }
401609
+ function shouldQueueBusySlashCommand(input, options) {
401610
+ const parsed = parseSlashInput(input);
401611
+ if (parsed === null) return false;
401612
+ if (slashCommandBusyReason(options) === void 0) return false;
401613
+ const normalizedName = normalizeLegacyEffortCommandName(parsed.name);
401614
+ const command = findBuiltInSlashCommand(normalizedName);
401615
+ if (command === void 0) return true;
401616
+ if (!isExperimentalFlagEnabled(command.experimentalFlag)) return false;
401617
+ return resolveSlashCommandAvailability(command, parsed.args) === "idle-only";
401618
+ }
401452
401619
  function slashBusyMessage(commandName, reason) {
401453
401620
  if (reason === "streaming") return uiText("resolve.busy.streaming", { command: commandName });
401454
401621
  return uiText("resolve.busy.compacting", { command: commandName });
@@ -426149,27 +426316,7 @@ registerUiCatalogFragment({
426149
426316
  }
426150
426317
  });
426151
426318
  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
426319
  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
426320
  var SingleLineText = class {
426174
426321
  text;
426175
426322
  constructor(text) {
@@ -426240,7 +426387,6 @@ var CompactionComponent = class extends Container {
426240
426387
  stage = 1;
426241
426388
  estimatedStageCount;
426242
426389
  stageCount;
426243
- estimatedProgressPercent;
426244
426390
  windowUsagePercent;
426245
426391
  constructor(ui, instruction, tip, showRunning = true) {
426246
426392
  super();
@@ -426297,7 +426443,6 @@ var CompactionComponent = class extends Container {
426297
426443
  this.attempt = progress.attempt;
426298
426444
  this.stage = progress.stage ?? this.stage;
426299
426445
  this.estimatedStageCount = progress.estimatedStageCount;
426300
- this.estimatedProgressPercent = progress.estimatedProgressPercent;
426301
426446
  this.windowUsagePercent = progress.windowUsagePercent;
426302
426447
  this.statusText.setText(this.buildStatusLine());
426303
426448
  if (this.showRunning) this.ui?.requestRender();
@@ -426319,21 +426464,16 @@ var CompactionComponent = class extends Container {
426319
426464
  }
426320
426465
  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
426466
  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
426467
  const stageText = formatCompactionStageText({
426327
426468
  stage: this.stage,
426328
426469
  estimatedStageCount: this.estimatedStageCount,
426329
- estimatedProgressPercent: this.estimatedProgressPercent,
426330
426470
  windowUsagePercent: this.windowUsagePercent,
426331
426471
  estimatedInputTokens: this.estimatedInputTokens,
426332
426472
  charsReceived: 0,
426333
426473
  attempt: this.attempt
426334
426474
  });
426335
426475
  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}`) : ""}`;
426476
+ 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
426477
  }
426338
426478
  startTicking() {
426339
426479
  this.timer = setInterval(() => {
@@ -427842,24 +427982,12 @@ var AgentSwarmProgressComponent = class {
427842
427982
  if (snapshot.phase === "cancelled" && snapshot.ticks <= 0) return renderCancelledUnstartedCell(member, width, this.colors);
427843
427983
  if (!layout.renderText) return this.renderCompactCell(member, snapshot, layout.barCells, nowMs);
427844
427984
  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))} `;
427985
+ const prefix = `${chalk.hex(this.colors.primary)(member.id)} ${chalk.hex(phaseColor(snapshot.phase, this.colors))(phaseLabel(snapshot.phase))} · `;
427852
427986
  return prefix + renderCellLabel(member, snapshot, Math.max(1, width - visibleWidth(prefix)), this.colors);
427853
427987
  }
427854
427988
  renderCompactCell(member, snapshot, barCells, nowMs) {
427855
427989
  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)}`;
427990
+ return `${chalk.hex(this.colors.primary)(member.id)} ${chalk.hex(phaseColor(estimatePhase, this.colors))(phaseLabel(estimatePhase))}${compactTerminalMark(member, snapshot.phase, this.colors)}`;
427863
427991
  }
427864
427992
  findMemberForSubagent(agentId, swarmIndex) {
427865
427993
  const existing = this.findMemberByAgentId(agentId);
@@ -497915,6 +498043,365 @@ var ActivityPaneComponent = class extends Container {
497915
498043
  return lines;
497916
498044
  }
497917
498045
  };
498046
+ registerUiCatalogFragment({
498047
+ en: {
498048
+ "media.activity.jobs": "{count} media jobs active",
498049
+ "media.activity.showJobs": "ctrl+o: show jobs",
498050
+ "media.activity.collapse": "ctrl+o: collapse",
498051
+ "media.activity.chain": "Production chain",
498052
+ "media.detail.queue": "Position {position}",
498053
+ "media.detail.segment": "Segment {current}/{total}",
498054
+ "media.detail.audio": "{seconds}s audio",
498055
+ "media.detail.remaining": "about {duration} remaining",
498056
+ "media.detail.preview": "Preview",
498057
+ "media.detail.playSample": "Play sample",
498058
+ "media.detail.waveform": "Waveform",
498059
+ "media.detail.open": "Open",
498060
+ "media.detail.duration": "Duration {duration}",
498061
+ "media.detail.resolution": "{width}x{height}",
498062
+ "media.kind.media": "Media",
498063
+ "media.kind.image": "Image",
498064
+ "media.kind.video": "Video",
498065
+ "media.kind.voice": "Voice",
498066
+ "media.kind.image-analysis": "Image analysis",
498067
+ "media.kind.video-analysis": "Video analysis",
498068
+ "media.kind.dubbing": "Dubbing",
498069
+ "media.kind.lipsync": "Lip sync",
498070
+ "media.phase.submitting": "Sending request",
498071
+ "media.phase.uploading": "Sending request",
498072
+ "media.phase.accepted": "Queued",
498073
+ "media.phase.pending": "Queued",
498074
+ "media.phase.queued": "Queued",
498075
+ "media.phase.running": "Processing",
498076
+ "media.phase.in-progress": "Processing",
498077
+ "media.phase.generating": "Generating",
498078
+ "media.phase.rendering": "Rendering",
498079
+ "media.phase.processing": "Processing",
498080
+ "media.phase.checking": "Checking status",
498081
+ "media.phase.audio-analysis": "Audio analysis",
498082
+ "media.phase.face-detection": "Face detection",
498083
+ "media.phase.face-tracking": "Face tracking",
498084
+ "media.phase.lip-sync-running": "Lip sync running",
498085
+ "media.phase.exporting": "Exporting",
498086
+ "media.phase.saving": "Saving",
498087
+ "media.phase.upscaling": "Upscaling",
498088
+ "media.phase.analyzing": "Analyzing"
498089
+ },
498090
+ de: {
498091
+ "media.activity.jobs": "{count} Medienaufträge aktiv",
498092
+ "media.activity.showJobs": "ctrl+o: Aufträge öffnen",
498093
+ "media.activity.collapse": "ctrl+o: einklappen",
498094
+ "media.activity.chain": "Produktionskette",
498095
+ "media.detail.queue": "Platz {position}",
498096
+ "media.detail.segment": "Abschnitt {current}/{total}",
498097
+ "media.detail.audio": "{seconds}s Audio",
498098
+ "media.detail.remaining": "noch etwa {duration}",
498099
+ "media.detail.preview": "Vorschau",
498100
+ "media.detail.playSample": "Probehören",
498101
+ "media.detail.waveform": "Wellenform",
498102
+ "media.detail.open": "Öffnen",
498103
+ "media.detail.duration": "Dauer {duration}",
498104
+ "media.detail.resolution": "{width}x{height}",
498105
+ "media.kind.media": "Medien",
498106
+ "media.kind.image": "Bild",
498107
+ "media.kind.video": "Video",
498108
+ "media.kind.voice": "Stimme",
498109
+ "media.kind.image-analysis": "Bildanalyse",
498110
+ "media.kind.video-analysis": "Videoanalyse",
498111
+ "media.kind.dubbing": "Vertonung",
498112
+ "media.kind.lipsync": "Lip-Sync",
498113
+ "media.phase.submitting": "Auftrag wird gesendet",
498114
+ "media.phase.uploading": "Auftrag wird gesendet",
498115
+ "media.phase.accepted": "Eingereiht",
498116
+ "media.phase.pending": "Eingereiht",
498117
+ "media.phase.queued": "Eingereiht",
498118
+ "media.phase.running": "Wird verarbeitet",
498119
+ "media.phase.in-progress": "Wird verarbeitet",
498120
+ "media.phase.generating": "Wird erzeugt",
498121
+ "media.phase.rendering": "Wird gerendert",
498122
+ "media.phase.processing": "Wird verarbeitet",
498123
+ "media.phase.checking": "Status wird geprüft",
498124
+ "media.phase.audio-analysis": "Audioanalyse",
498125
+ "media.phase.face-detection": "Gesichtserkennung",
498126
+ "media.phase.face-tracking": "Gesicht wird verfolgt",
498127
+ "media.phase.lip-sync-running": "Lip-Sync läuft",
498128
+ "media.phase.exporting": "Wird exportiert",
498129
+ "media.phase.saving": "Wird gespeichert",
498130
+ "media.phase.upscaling": "Wird hochskaliert",
498131
+ "media.phase.analyzing": "Wird analysiert"
498132
+ }
498133
+ });
498134
+ function mediaActivityStatusKey(value) {
498135
+ return String(value ?? "processing").trim().toLowerCase().replaceAll(/[^a-z0-9]+/g, "-");
498136
+ }
498137
+ function mediaActivityIsTerminal(value) {
498138
+ return [
498139
+ "complete",
498140
+ "completed",
498141
+ "ready",
498142
+ "failed",
498143
+ "expired",
498144
+ "blocked"
498145
+ ].includes(mediaActivityStatusKey(value));
498146
+ }
498147
+ function shouldSuppressMediaPollTranscript(toolName, isError, output) {
498148
+ if (toolName !== "GetMedia" || isError === true) return false;
498149
+ const text = typeof output === "string" ? output : JSON.stringify(output);
498150
+ const match = /Media job\s+\S+\s+status:\s*([a-z0-9_-]+)/i.exec(text);
498151
+ return match !== null && !mediaActivityIsTerminal(match[1]);
498152
+ }
498153
+ function mediaActivityIsQueued(job) {
498154
+ return job.queuePosition !== void 0 || [
498155
+ "accepted",
498156
+ "pending",
498157
+ "queued",
498158
+ "submitting",
498159
+ "uploading"
498160
+ ].includes(mediaActivityStatusKey(job.phase));
498161
+ }
498162
+ function mediaActivityShouldShowPercent(job) {
498163
+ return !mediaActivityIsQueued(job) && job.percent !== void 0 && Number.isFinite(job.percent);
498164
+ }
498165
+ function mediaActivityCanonicalPhase(value) {
498166
+ const phase = mediaActivityStatusKey(value);
498167
+ if (["accepted", "pending", "submitting", "uploading"].includes(phase)) return "queued";
498168
+ if (["running", "in-progress"].includes(phase)) return "processing";
498169
+ if (phase === "face-detection") return "face-tracking";
498170
+ return phase;
498171
+ }
498172
+ function mediaActivityPhaseSequence(mediaKind) {
498173
+ switch (mediaActivityStatusKey(mediaKind)) {
498174
+ case "image": return ["queued", "generating", "upscaling", "saving"];
498175
+ case "video": return ["queued", "generating", "rendering", "saving"];
498176
+ case "voice": return ["queued", "processing", "saving"];
498177
+ case "dubbing": return ["queued", "audio-analysis", "rendering", "saving"];
498178
+ case "lipsync": return ["queued", "audio-analysis", "face-tracking", "lip-sync-running", "rendering", "exporting", "saving"];
498179
+ case "image-analysis":
498180
+ case "video-analysis": return ["queued", "analyzing"];
498181
+ default: return ["queued", "processing", "saving"];
498182
+ }
498183
+ }
498184
+ function mediaActivityChainStates(job) {
498185
+ const current = mediaActivityCanonicalPhase(job.phaseLabel ?? job.phase);
498186
+ const history = new Set((job.phaseHistory ?? []).map((phase) => mediaActivityCanonicalPhase(phase)));
498187
+ return mediaActivityPhaseSequence(job.mediaKind).map((phase) => ({
498188
+ phase,
498189
+ state: phase === current ? "active" : history.has(phase) ? "done" : "pending"
498190
+ }));
498191
+ }
498192
+ function renderMediaWaveform(samples, width) {
498193
+ if (!Array.isArray(samples) || samples.length === 0 || width <= 0) return "";
498194
+ const bars = "▁▂▃▄▅▆▇█";
498195
+ const safeWidth = Math.max(1, Math.floor(width));
498196
+ const max = Math.max(...samples.map((sample) => Math.abs(sample)), 1e-9);
498197
+ let output = "";
498198
+ for (let index = 0; index < safeWidth; index++) {
498199
+ const sampleIndex = Math.min(samples.length - 1, Math.floor(index * samples.length / safeWidth));
498200
+ const normalized = Math.min(1, Math.abs(samples[sampleIndex]) / max);
498201
+ output += bars[Math.min(bars.length - 1, Math.floor(normalized * bars.length))];
498202
+ }
498203
+ return output;
498204
+ }
498205
+ function mediaActivityOpenUrl(job) {
498206
+ const candidate = [job.sampleUrl, job.previewUrl, job.localPath].find((value) => typeof value === "string" && value.trim().length > 0)?.trim();
498207
+ if (candidate === void 0) return;
498208
+ if (/^https?:\/\//iu.test(candidate) || /^file:\/\//iu.test(candidate)) try {
498209
+ const parsed = new URL(candidate);
498210
+ if (["http:", "https:", "file:"].includes(parsed.protocol)) return parsed.href;
498211
+ } catch {
498212
+ return;
498213
+ }
498214
+ if (/^[A-Za-z]:[\\/]/u.test(candidate) || candidate.startsWith("/")) return pathToFileURL(candidate).href;
498215
+ }
498216
+ const MEDIA_ACTIVITY_LABELS = {
498217
+ "media.kind": /* @__PURE__ */ new Set([
498218
+ "media",
498219
+ "image",
498220
+ "video",
498221
+ "voice",
498222
+ "image-analysis",
498223
+ "video-analysis",
498224
+ "dubbing",
498225
+ "lipsync"
498226
+ ]),
498227
+ "media.phase": /* @__PURE__ */ new Set([
498228
+ "submitting",
498229
+ "uploading",
498230
+ "accepted",
498231
+ "pending",
498232
+ "queued",
498233
+ "running",
498234
+ "in-progress",
498235
+ "generating",
498236
+ "rendering",
498237
+ "processing",
498238
+ "checking",
498239
+ "audio-analysis",
498240
+ "face-detection",
498241
+ "face-tracking",
498242
+ "lip-sync-running",
498243
+ "exporting",
498244
+ "saving",
498245
+ "upscaling",
498246
+ "analyzing"
498247
+ ])
498248
+ };
498249
+ function mediaUiText(key, params = {}) {
498250
+ return uiTextFor(getCurrentUiLocale() === "de" ? "de" : "en", key, params);
498251
+ }
498252
+ function mediaActivityLabel(prefix, value, fallback) {
498253
+ const normalized = mediaActivityStatusKey(value);
498254
+ if (!MEDIA_ACTIVITY_LABELS[prefix]?.has(normalized)) return fallback;
498255
+ return mediaUiText(`${prefix}.${normalized}`);
498256
+ }
498257
+ function formatMediaDuration(seconds) {
498258
+ if (!Number.isFinite(seconds) || seconds < 0) return;
498259
+ const whole = Math.round(seconds);
498260
+ const minutes = Math.floor(whole / 60);
498261
+ const rest = whole % 60;
498262
+ return minutes > 0 ? `${String(minutes)}:${String(rest).padStart(2, "0")}` : `${String(rest)}s`;
498263
+ }
498264
+ var MediaActivityStore = class {
498265
+ jobs = /* @__PURE__ */ new Map();
498266
+ update(toolCallId, event) {
498267
+ if (event === null || typeof event !== "object") return;
498268
+ const id = typeof event.id === "string" && event.id.length > 0 ? event.id : void 0;
498269
+ let existingKey = id;
498270
+ 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) {
498271
+ existingKey = key;
498272
+ break;
498273
+ }
498274
+ const key = id ?? existingKey ?? toolCallId;
498275
+ const previous = existingKey === void 0 ? void 0 : this.jobs.get(existingKey);
498276
+ if (existingKey !== void 0 && existingKey !== key) this.jobs.delete(existingKey);
498277
+ if (mediaActivityIsTerminal(event.phase)) {
498278
+ this.jobs.delete(key);
498279
+ return;
498280
+ }
498281
+ const mediaKind = event.mediaKind === "media" && previous?.mediaKind !== void 0 ? previous.mediaKind : event.mediaKind ?? previous?.mediaKind ?? "media";
498282
+ const phase = mediaActivityStatusKey(event.phase ?? previous?.phase ?? "processing");
498283
+ const phaseHistory = [...previous?.phaseHistory ?? []];
498284
+ if (phaseHistory.at(-1) !== phase) phaseHistory.push(phase);
498285
+ this.jobs.set(key, {
498286
+ ...previous,
498287
+ ...event,
498288
+ id: id ?? previous?.id,
498289
+ mediaKind,
498290
+ phase,
498291
+ phaseHistory,
498292
+ toolCallId,
498293
+ startedAt: previous?.startedAt ?? Date.now(),
498294
+ updatedAt: Date.now()
498295
+ });
498296
+ }
498297
+ active() {
498298
+ return [...this.jobs.values()].filter((job) => !mediaActivityIsTerminal(job.phase)).sort((a, b) => a.startedAt - b.startedAt);
498299
+ }
498300
+ clear() {
498301
+ this.jobs.clear();
498302
+ }
498303
+ };
498304
+ var MediaActivityComponent = class {
498305
+ jobs;
498306
+ expanded;
498307
+ previewImages = /* @__PURE__ */ new Map();
498308
+ constructor(jobs, expanded = false) {
498309
+ this.jobs = jobs;
498310
+ this.expanded = expanded;
498311
+ }
498312
+ invalidate() {
498313
+ for (const image of this.previewImages.values()) image.invalidate();
498314
+ }
498315
+ jobLine(job, width, includeBar) {
498316
+ const kind = mediaActivityLabel("media.kind", job.mediaKind, String(job.mediaKind ?? "Media"));
498317
+ const rawPhase = job.phaseLabel ?? job.phase ?? "processing";
498318
+ const phase = sanitizeTerminalText(mediaActivityLabel("media.phase", rawPhase, String(rawPhase)));
498319
+ const details = [];
498320
+ if (job.queuePosition !== void 0) details.push(mediaUiText("media.detail.queue", { position: job.queuePosition }));
498321
+ if (job.currentStep !== void 0 && job.totalSteps !== void 0) details.push(`${String(job.currentStep)}/${String(job.totalSteps)}`);
498322
+ if (job.currentSegment !== void 0 && job.totalSegments !== void 0) details.push(mediaUiText("media.detail.segment", {
498323
+ current: job.currentSegment,
498324
+ total: job.totalSegments
498325
+ }));
498326
+ if (job.currentFrame !== void 0 && job.totalFrames !== void 0) details.push(`Frame ${String(job.currentFrame)}/${String(job.totalFrames)}`);
498327
+ if (job.fps !== void 0) details.push(`${String(Math.round(job.fps * 10) / 10)} FPS`);
498328
+ if (job.generatedSeconds !== void 0) details.push(mediaUiText("media.detail.audio", { seconds: Math.round(job.generatedSeconds) }));
498329
+ if (job.durationSeconds !== void 0) details.push(mediaUiText("media.detail.duration", { duration: formatMediaDuration(job.durationSeconds) }));
498330
+ if (job.width > 0 && job.height > 0) details.push(mediaUiText("media.detail.resolution", {
498331
+ width: Math.round(job.width),
498332
+ height: Math.round(job.height)
498333
+ }));
498334
+ const elapsed = job.elapsedSeconds ?? (Date.now() - job.startedAt) / 1e3;
498335
+ const elapsedText = formatMediaDuration(elapsed);
498336
+ if (elapsedText !== void 0) details.push(elapsedText);
498337
+ if (job.remainingSeconds !== void 0) details.push(mediaUiText("media.detail.remaining", { duration: formatMediaDuration(job.remainingSeconds) }));
498338
+ let progress = "";
498339
+ if (mediaActivityShouldShowPercent(job)) {
498340
+ const percent = Math.min(100, Math.max(0, job.percent));
498341
+ progress = includeBar ? ` · ${renderProgressBar(percent / 100, 12)} · ${String(Math.round(percent))} %` : ` · ${String(Math.round(percent))} %`;
498342
+ }
498343
+ const line = `${currentTheme.boldFg("primary", kind)} · ${phase}${details.length === 0 ? "" : ` · ${details.join(" · ")}`}${progress}`;
498344
+ return truncateToWidth(line, Math.max(1, width), "…");
498345
+ }
498346
+ chainLine(job, width) {
498347
+ const chain = mediaActivityChainStates(job).map(({ phase, state }) => {
498348
+ const symbol = state === "done" ? "[x]" : state === "active" ? "[>]" : "[ ]";
498349
+ const label = mediaActivityLabel("media.phase", phase, phase);
498350
+ return `${symbol} ${label}`;
498351
+ });
498352
+ return truncateToWidth(` ${mediaUiText("media.activity.chain")} · ${chain.join(" · ")}`, Math.max(1, width), "…");
498353
+ }
498354
+ previewImage(job) {
498355
+ if (typeof job.previewDataBase64 !== "string" || job.previewDataBase64.length === 0 || job.previewDataBase64.length > 2 * 1024 * 1024 || !/^[A-Za-z0-9+/=\r\n]+$/u.test(job.previewDataBase64)) return;
498356
+ const mimeType = typeof job.previewMimeType === "string" ? job.previewMimeType.toLowerCase() : "";
498357
+ if (!["image/png", "image/jpeg", "image/webp", "image/gif"].includes(mimeType)) return;
498358
+ const key = `${job.id ?? job.toolCallId}:${mimeType}:${job.previewDataBase64.length}`;
498359
+ let image = this.previewImages.get(key);
498360
+ if (image === void 0) {
498361
+ image = new Image(job.previewDataBase64, mimeType, { fallbackColor: (text) => currentTheme.fg("textDim", text) }, {
498362
+ maxHeightCells: 5,
498363
+ maxWidthCells: 24,
498364
+ filename: mediaUiText("media.detail.preview")
498365
+ });
498366
+ this.previewImages.set(key, image);
498367
+ }
498368
+ return image;
498369
+ }
498370
+ extraLines(job, width) {
498371
+ const lines = [this.chainLine(job, width)];
498372
+ if (Array.isArray(job.waveform) && job.waveform.length > 0) {
498373
+ const label = `${mediaUiText("media.detail.waveform")} · `;
498374
+ const waveform = renderMediaWaveform(job.waveform, Math.max(1, width - visibleWidth(label) - 2));
498375
+ lines.push(truncateToWidth(` ${label}${waveform}`, Math.max(1, width), "…"));
498376
+ }
498377
+ const openUrl = mediaActivityOpenUrl(job);
498378
+ if (openUrl !== void 0) {
498379
+ const key = job.mediaKind === "voice" ? "media.detail.playSample" : job.localPath !== void 0 ? "media.detail.open" : "media.detail.preview";
498380
+ lines.push(truncateToWidth(` ${hyperlink(mediaUiText(key), openUrl)}`, Math.max(1, width), "…"));
498381
+ }
498382
+ const image = this.previewImage(job);
498383
+ if (image !== void 0) lines.push(...image.render(Math.max(1, width - 2)).map((line) => ` ${line}`));
498384
+ return lines;
498385
+ }
498386
+ render(width) {
498387
+ if (this.jobs.length === 0) return [];
498388
+ const safeWidth = Math.max(1, width);
498389
+ const hint = mediaUiText(this.expanded ? "media.activity.collapse" : "media.activity.showJobs");
498390
+ if (this.jobs.length > 1 && !this.expanded) {
498391
+ const summary = `${currentTheme.boldFg("primary", mediaUiText("media.activity.jobs", { count: this.jobs.length }))} · ${currentTheme.fg("textDim", hint)}`;
498392
+ return [truncateToWidth(summary, safeWidth, "…")];
498393
+ }
498394
+ const lines = [];
498395
+ if (this.jobs.length === 1) lines.push(truncateToWidth(`${this.jobLine(this.jobs[0], safeWidth, true)} · ${currentTheme.fg("textDim", hint)}`, safeWidth, "…"));
498396
+ else lines.push(truncateToWidth(`${currentTheme.boldFg("primary", mediaUiText("media.activity.jobs", { count: this.jobs.length }))} · ${currentTheme.fg("textDim", hint)}`, safeWidth, "…"));
498397
+ if (!this.expanded) return lines;
498398
+ for (const [index, job] of this.jobs.entries()) {
498399
+ if (this.jobs.length > 1) lines.push(truncateToWidth(` ${String(index + 1)}. ${this.jobLine(job, Math.max(1, safeWidth - 3), false)}`, safeWidth, "…"));
498400
+ lines.push(...this.extraLines(job, safeWidth));
498401
+ }
498402
+ return lines;
498403
+ }
498404
+ };
497918
498405
  //#endregion
497919
498406
  //#region src/tui/components/panes/queue-pane.copy.ts
497920
498407
  registerUiCatalogFragment({
@@ -504441,6 +504928,10 @@ var SessionEventHandler = class {
504441
504928
  streamingUI.scheduleFlush();
504442
504929
  }
504443
504930
  handleToolProgress(event) {
504931
+ if (event.update.kind === "custom" && event.update.customKind === MEDIA_PROGRESS_CUSTOM_KIND) {
504932
+ this.host.updateMediaActivity(event.toolCallId, event.update.customData);
504933
+ return;
504934
+ }
504444
504935
  const text = event.update.text;
504445
504936
  if (text === void 0 || text.length === 0) return;
504446
504937
  const tc = this.host.streamingUI.getToolComponent(event.toolCallId);
@@ -504461,7 +504952,13 @@ var SessionEventHandler = class {
504461
504952
  is_error: event.isError,
504462
504953
  synthetic: event.synthetic
504463
504954
  };
504955
+ const activeCall = streamingUI.getActiveToolCall(event.toolCallId);
504956
+ if (shouldSuppressMediaPollTranscript(activeCall?.name, event.isError, event.output)) streamingUI.suppressToolCallTranscript(event.toolCallId);
504464
504957
  const matchedCall = streamingUI.completeToolResult(event.toolCallId, resultData);
504958
+ if (matchedCall !== void 0 && isMediaToolName(matchedCall.name) && event.isError === true) this.host.updateMediaActivity(event.toolCallId, {
504959
+ mediaKind: mediaKindForToolName(matchedCall.name),
504960
+ phase: "failed"
504961
+ });
504465
504962
  if (matchedCall?.name === "GetMedia" && event.isError !== true) this.host.runChannelMediaFallback(event.output);
504466
504963
  this.subAgentEventHandler.handleAgentSwarmToolResult(event.toolCallId, resultData, event.isError === true);
504467
504964
  if (matchedCall !== void 0 && matchedCall.name === "TodoList" && !event.isError) {
@@ -505773,6 +506270,7 @@ var SessionReplayRenderer = class {
505773
506270
  };
505774
506271
  call.result = result;
505775
506272
  this.applyStepContext(context);
506273
+ if (shouldSuppressMediaPollTranscript(call.name, message.isError, result.output)) this.host.streamingUI.suppressToolCallTranscript(toolCallId);
505776
506274
  this.host.streamingUI.onToolCallEnd(toolCallId, result);
505777
506275
  this.host.streamingUI.removeActiveToolCall(toolCallId);
505778
506276
  context.completedToolCallIds.add(toolCallId);
@@ -506827,24 +507325,21 @@ function formatContextStatus(usage, tokens, maxTokens, colors, width, now = /* @
506827
507325
  return truncateToWidth(current, available, "…");
506828
507326
  }
506829
507327
  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
507328
  const label = formatCompactionRunningText(estimatedInputTokens);
506834
507329
  const stageText = progress === void 0 ? "" : formatCompactionStageText(progress);
506835
507330
  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)} %` : "";
507331
+ const elapsed = `${String(Math.max(0, Math.round(elapsedMs / 1e3)))}s`;
506837
507332
  const clock = chalk.hex(colors.textDim)(` · ${clockHhmm(now)}`);
506838
507333
  const variants = [
506839
- `${label}${stageText.length === 0 ? "" : ` · ${stageText}`} ${bar}${estimate.length === 0 ? "" : ` ${estimate}`}${clock}`,
507334
+ `${label}${stageText.length === 0 ? "" : ` · ${stageText}`} · ${elapsed}${clock}`,
506840
507335
  `${label}${stageText.length === 0 ? "" : ` · ${stageText}`}`,
506841
507336
  compactStageText,
506842
- `${bar}${estimate.length === 0 ? "" : ` ${estimate}`}`,
506843
- stageText.length === 0 ? estimate : stageText
507337
+ elapsed,
507338
+ stageText
506844
507339
  ];
506845
507340
  const available = Math.max(0, width);
506846
507341
  for (const variant of variants) if (visibleWidth(variant) <= available) return variant;
506847
- return truncateToWidth(estimate, available, "…");
507342
+ return truncateToWidth(elapsed, available, "…");
506848
507343
  }
506849
507344
  function contextMetrics(state) {
506850
507345
  const tokens = Number.isFinite(state.contextTokens) ? Math.max(0, state.contextTokens) : 0;
@@ -507390,6 +507885,18 @@ var StreamingUIController = class {
507390
507885
  removeToolComponent(id) {
507391
507886
  this._pendingToolComponents.delete(id);
507392
507887
  }
507888
+ suppressToolCallTranscript(id) {
507889
+ const { state } = this.host;
507890
+ const component = this._pendingToolComponents.get(id);
507891
+ this._pendingToolComponents.delete(id);
507892
+ const transcriptEntryIndex = state.transcriptEntries.findIndex((entry) => entry.toolCallData?.id === id);
507893
+ if (transcriptEntryIndex >= 0) state.transcriptEntries.splice(transcriptEntryIndex, 1);
507894
+ if (component !== void 0) {
507895
+ const childIndex = state.transcriptContainer.children.indexOf(component);
507896
+ if (childIndex >= 0) state.transcriptContainer.children.splice(childIndex, 1);
507897
+ }
507898
+ state.ui.requestRender();
507899
+ }
507393
507900
  hasPendingAgentGroup() {
507394
507901
  return this._pendingAgentGroup !== null;
507395
507902
  }
@@ -512083,6 +512590,9 @@ var BlunTUI = class {
512083
512590
  startupGoalPromptedSessionId;
512084
512591
  lastActivityMode;
512085
512592
  currentLoadingTip = void 0;
512593
+ mediaActivityStore = new MediaActivityStore();
512594
+ mediaActivityTickTimer;
512595
+ mediaActivityExpanded = false;
512086
512596
  lastHistoryContent;
512087
512597
  shellOutputStreams = /* @__PURE__ */ new Map();
512088
512598
  streamingUI;
@@ -512594,6 +513104,7 @@ var BlunTUI = class {
512594
513104
  this.tasksBrowserController.close();
512595
513105
  this.btwPanelController.clear();
512596
513106
  this.stopActivitySpinner();
513107
+ this.stopMediaActivityTicking();
512597
513108
  this.disposeEditorReplacement();
512598
513109
  this.streamingUI.disposeActiveCompactionBlock();
512599
513110
  this.streamingUI.resetToolUi();
@@ -512742,6 +513253,13 @@ var BlunTUI = class {
512742
513253
  return;
512743
513254
  }
512744
513255
  if (text.trimStart().startsWith("/")) {
513256
+ if (shouldQueueBusySlashCommand(text, {
513257
+ isStreaming: this.state.appState.streamingPhase !== "idle",
513258
+ isCompacting: this.state.appState.isCompacting
513259
+ })) {
513260
+ this.enqueueSlashCommand(text);
513261
+ return;
513262
+ }
512745
513263
  this.runSlashCommand(text);
512746
513264
  return;
512747
513265
  }
@@ -513749,6 +514267,8 @@ var BlunTUI = class {
513749
514267
  this.setAppState({ mcpServersSummary: null });
513750
514268
  this.streamingUI.setStep(0);
513751
514269
  this.streamingUI.resetLiveText();
514270
+ this.mediaActivityStore.clear();
514271
+ this.stopMediaActivityTicking();
513752
514272
  this.updateQueueDisplay();
513753
514273
  }
513754
514274
  async showResumeOtherWorkDirHint(session) {
@@ -514200,8 +514720,36 @@ var BlunTUI = class {
514200
514720
  this.state.ui.requestRender();
514201
514721
  return this.showLoginProgressSpinner(uiText("blunTui.login.waiting"));
514202
514722
  }
514723
+ updateMediaActivity(toolCallId, event) {
514724
+ this.mediaActivityStore.update(toolCallId, event);
514725
+ const mediaActivities = this.mediaActivityStore.active();
514726
+ if (mediaActivities.length > 0) {
514727
+ if (this.mediaActivityTickTimer === void 0) this.mediaActivityTickTimer = setInterval(() => this.state.ui.requestRender(), 1e3);
514728
+ } else {
514729
+ this.mediaActivityExpanded = false;
514730
+ this.stopMediaActivityTicking();
514731
+ }
514732
+ this.lastActivityMode = void 0;
514733
+ this.updateActivityPane();
514734
+ }
514735
+ stopMediaActivityTicking() {
514736
+ if (this.mediaActivityTickTimer === void 0) return;
514737
+ clearInterval(this.mediaActivityTickTimer);
514738
+ this.mediaActivityTickTimer = void 0;
514739
+ }
514203
514740
  updateActivityPane() {
514204
514741
  const effectiveMode = this.resolveActivityPaneMode();
514742
+ const mediaActivities = effectiveMode === "hidden" ? [] : this.mediaActivityStore.active();
514743
+ if (mediaActivities.length > 0) {
514744
+ this.stopActivitySpinner();
514745
+ this.syncAgentSwarmActivitySpinner(void 0);
514746
+ this.lastActivityMode = `media:${this.mediaActivityExpanded ? "expanded" : "collapsed"}:${mediaActivities.map((job) => `${job.id ?? job.toolCallId}:${job.updatedAt}`).join(",")}`;
514747
+ this.state.activityContainer.clear();
514748
+ this.state.activityContainer.addChild(new Spacer(1));
514749
+ this.state.activityContainer.addChild(new MediaActivityComponent(mediaActivities, this.mediaActivityExpanded));
514750
+ this.state.ui.requestRender();
514751
+ return;
514752
+ }
514205
514753
  const tipKind = loadingTipKind(effectiveMode);
514206
514754
  if (effectiveMode === "idle" || effectiveMode === "session" || effectiveMode === "hidden") this.currentLoadingTip = void 0;
514207
514755
  else if (tipKind !== void 0 && (this.currentLoadingTip === void 0 || this.currentLoadingTip.kind !== tipKind)) this.currentLoadingTip = {
@@ -514294,6 +514842,10 @@ var BlunTUI = class {
514294
514842
  }));
514295
514843
  }
514296
514844
  toggleToolOutputExpansion() {
514845
+ if (this.mediaActivityStore.active().length > 0) {
514846
+ this.toggleMediaActivityExpansion();
514847
+ return;
514848
+ }
514297
514849
  this.state.toolOutputExpanded = !this.state.toolOutputExpanded;
514298
514850
  const children = this.state.transcriptContainer.children;
514299
514851
  const boundaries = [];
@@ -514306,6 +514858,13 @@ var BlunTUI = class {
514306
514858
  }
514307
514859
  this.state.ui.requestRender(true);
514308
514860
  }
514861
+ toggleMediaActivityExpansion() {
514862
+ if (this.mediaActivityStore.active().length === 0) return;
514863
+ this.mediaActivityExpanded = !this.mediaActivityExpanded;
514864
+ this.lastActivityMode = void 0;
514865
+ this.updateActivityPane();
514866
+ this.state.ui.requestRender(true);
514867
+ }
514309
514868
  toggleTodoPanelExpansion() {
514310
514869
  this.state.todoPanel.toggleExpanded();
514311
514870
  this.state.ui.requestRender();
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.49",
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": {