blun-king-cli 9.1.48 → 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.
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.48
12
+ npm install -g blun-king-cli@9.1.49
13
13
 
14
14
  Start
15
15
  -----
@@ -54,6 +54,14 @@ Nachweisbare Arbeitsabläufe
54
54
 
55
55
  Alle Befehle funktionieren identisch mit `king`.
56
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
+
57
65
  Aktualisieren
58
66
  -------------
59
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.48
12
+ npm install -g blun-king-cli@9.1.49
13
13
  ```
14
14
 
15
15
  ## Reproduzierbares Staging und Packen
@@ -75,6 +75,14 @@ Version 9.1.0 enthält sieben zusätzliche, getrennt nutzbare Befehlsgruppen:
75
75
 
76
76
  Sie stehen unter `blun` und `king` identisch zur Verfügung.
77
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
+
78
86
  ## Aktualisieren
79
87
 
80
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
 
@@ -261995,7 +262015,10 @@ var init_blun_media$1 = __esmMin((() => {
261995
262015
  PromptSchema = string().trim().min(1).max(2e4);
261996
262016
  MediaIdSchema = string().trim().min(1).max(200).regex(/^[A-Za-z0-9_-]+$/);
261997
262017
  GenerateImageInputSchema = object({ prompt: PromptSchema.describe("A complete visual description of the image to generate.") });
261998
- 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
+ });
261999
262022
  GenerateSpeechInputSchema = object({ input: PromptSchema.describe("The exact text to synthesize as speech.") });
262000
262023
  GetMediaInputSchema = object({ id: MediaIdSchema.describe("The media job id returned by a generation tool.") });
262001
262024
  UnderstandImageInputSchema = object({
@@ -262070,13 +262093,13 @@ var init_blun_media$1 = __esmMin((() => {
262070
262093
  };
262071
262094
  GenerateVideoTool = class extends MediaGenerationTool {
262072
262095
  name = "GenerateVideo";
262073
- 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.";
262074
262097
  parameters = toInputJsonSchema(GenerateVideoInputSchema);
262075
262098
  subject(args) {
262076
- return args.prompt;
262099
+ return args.image_id ? `${args.image_id}: ${args.prompt}` : args.prompt;
262077
262100
  }
262078
262101
  submit(args, options) {
262079
- return this.provider.generateVideo(args.prompt, options);
262102
+ return this.provider.generateVideo(args.prompt, options, args.image_id);
262080
262103
  }
262081
262104
  };
262082
262105
  GenerateSpeechTool = class extends MediaGenerationTool {
@@ -310814,6 +310837,14 @@ function mediaPayloadText(payload, keys) {
310814
310837
  if (typeof value === "string" && value.trim() !== "") return value.trim();
310815
310838
  }
310816
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
+ }
310817
310848
  function mediaProgressFromPayload(payload) {
310818
310849
  const rawPercent = mediaPayloadNumber(payload, ["percent", "progress_percent", "progressPercent", "progress"]);
310819
310850
  const percent = rawPercent === void 0 ? void 0 : rawPercent <= 1 ? rawPercent * 100 : rawPercent;
@@ -310830,7 +310861,16 @@ function mediaProgressFromPayload(payload) {
310830
310861
  generatedSeconds: mediaPayloadNumber(payload, ["generated_seconds", "generatedSeconds", "audio_seconds", "audioSeconds"]),
310831
310862
  queuePosition: mediaPayloadNumber(payload, ["queue_position", "queuePosition"]),
310832
310863
  elapsedSeconds: mediaPayloadNumber(payload, ["elapsed_seconds", "elapsedSeconds"]),
310833
- remainingSeconds: mediaPayloadNumber(payload, ["remaining_seconds", "remainingSeconds", "eta_seconds", "etaSeconds"])
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"])
310834
310874
  };
310835
310875
  return Object.fromEntries(Object.entries(progress).filter(([, value]) => value !== void 0));
310836
310876
  }
@@ -310884,8 +310924,10 @@ var init_blun_media = __esmMin((() => {
310884
310924
  generateImage(prompt, options) {
310885
310925
  return this.submit("/images/generations", { prompt }, options);
310886
310926
  }
310887
- generateVideo(prompt, options) {
310888
- 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);
310889
310931
  }
310890
310932
  generateSpeech(input, options) {
310891
310933
  return this.submit("/audio/speech", { input }, options);
@@ -498004,10 +498046,19 @@ var ActivityPaneComponent = class extends Container {
498004
498046
  registerUiCatalogFragment({
498005
498047
  en: {
498006
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",
498007
498052
  "media.detail.queue": "Position {position}",
498008
498053
  "media.detail.segment": "Segment {current}/{total}",
498009
498054
  "media.detail.audio": "{seconds}s audio",
498010
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}",
498011
498062
  "media.kind.media": "Media",
498012
498063
  "media.kind.image": "Image",
498013
498064
  "media.kind.video": "Video",
@@ -498038,10 +498089,19 @@ registerUiCatalogFragment({
498038
498089
  },
498039
498090
  de: {
498040
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",
498041
498095
  "media.detail.queue": "Platz {position}",
498042
498096
  "media.detail.segment": "Abschnitt {current}/{total}",
498043
498097
  "media.detail.audio": "{seconds}s Audio",
498044
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}",
498045
498105
  "media.kind.media": "Medien",
498046
498106
  "media.kind.image": "Bild",
498047
498107
  "media.kind.video": "Video",
@@ -498084,6 +498144,75 @@ function mediaActivityIsTerminal(value) {
498084
498144
  "blocked"
498085
498145
  ].includes(mediaActivityStatusKey(value));
498086
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
+ }
498087
498216
  const MEDIA_ACTIVITY_LABELS = {
498088
498217
  "media.kind": /* @__PURE__ */ new Set([
498089
498218
  "media",
@@ -498150,11 +498279,16 @@ var MediaActivityStore = class {
498150
498279
  return;
498151
498280
  }
498152
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);
498153
498285
  this.jobs.set(key, {
498154
498286
  ...previous,
498155
498287
  ...event,
498156
498288
  id: id ?? previous?.id,
498157
498289
  mediaKind,
498290
+ phase,
498291
+ phaseHistory,
498158
498292
  toolCallId,
498159
498293
  startedAt: previous?.startedAt ?? Date.now(),
498160
498294
  updatedAt: Date.now()
@@ -498169,13 +498303,16 @@ var MediaActivityStore = class {
498169
498303
  };
498170
498304
  var MediaActivityComponent = class {
498171
498305
  jobs;
498172
- constructor(jobs) {
498306
+ expanded;
498307
+ previewImages = /* @__PURE__ */ new Map();
498308
+ constructor(jobs, expanded = false) {
498173
498309
  this.jobs = jobs;
498310
+ this.expanded = expanded;
498174
498311
  }
498175
- invalidate() {}
498176
- render(width) {
498177
- if (this.jobs.length === 0) return [];
498178
- const job = this.jobs[0];
498312
+ invalidate() {
498313
+ for (const image of this.previewImages.values()) image.invalidate();
498314
+ }
498315
+ jobLine(job, width, includeBar) {
498179
498316
  const kind = mediaActivityLabel("media.kind", job.mediaKind, String(job.mediaKind ?? "Media"));
498180
498317
  const rawPhase = job.phaseLabel ?? job.phase ?? "processing";
498181
498318
  const phase = sanitizeTerminalText(mediaActivityLabel("media.phase", rawPhase, String(rawPhase)));
@@ -498189,18 +498326,80 @@ var MediaActivityComponent = class {
498189
498326
  if (job.currentFrame !== void 0 && job.totalFrames !== void 0) details.push(`Frame ${String(job.currentFrame)}/${String(job.totalFrames)}`);
498190
498327
  if (job.fps !== void 0) details.push(`${String(Math.round(job.fps * 10) / 10)} FPS`);
498191
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
+ }));
498192
498334
  const elapsed = job.elapsedSeconds ?? (Date.now() - job.startedAt) / 1e3;
498193
498335
  const elapsedText = formatMediaDuration(elapsed);
498194
498336
  if (elapsedText !== void 0) details.push(elapsedText);
498195
498337
  if (job.remainingSeconds !== void 0) details.push(mediaUiText("media.detail.remaining", { duration: formatMediaDuration(job.remainingSeconds) }));
498196
498338
  let progress = "";
498197
- if (job.percent !== void 0 && Number.isFinite(job.percent)) {
498339
+ if (mediaActivityShouldShowPercent(job)) {
498198
498340
  const percent = Math.min(100, Math.max(0, job.percent));
498199
- progress = ` · ${renderProgressBar(percent / 100, 12)} · ${String(Math.round(percent))} %`;
498341
+ progress = includeBar ? ` · ${renderProgressBar(percent / 100, 12)} · ${String(Math.round(percent))} %` : ` · ${String(Math.round(percent))} %`;
498200
498342
  }
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), "…")];
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;
498204
498403
  }
498205
498404
  };
498206
498405
  //#endregion
@@ -504753,6 +504952,8 @@ var SessionEventHandler = class {
504753
504952
  is_error: event.isError,
504754
504953
  synthetic: event.synthetic
504755
504954
  };
504955
+ const activeCall = streamingUI.getActiveToolCall(event.toolCallId);
504956
+ if (shouldSuppressMediaPollTranscript(activeCall?.name, event.isError, event.output)) streamingUI.suppressToolCallTranscript(event.toolCallId);
504756
504957
  const matchedCall = streamingUI.completeToolResult(event.toolCallId, resultData);
504757
504958
  if (matchedCall !== void 0 && isMediaToolName(matchedCall.name) && event.isError === true) this.host.updateMediaActivity(event.toolCallId, {
504758
504959
  mediaKind: mediaKindForToolName(matchedCall.name),
@@ -506069,6 +506270,7 @@ var SessionReplayRenderer = class {
506069
506270
  };
506070
506271
  call.result = result;
506071
506272
  this.applyStepContext(context);
506273
+ if (shouldSuppressMediaPollTranscript(call.name, message.isError, result.output)) this.host.streamingUI.suppressToolCallTranscript(toolCallId);
506072
506274
  this.host.streamingUI.onToolCallEnd(toolCallId, result);
506073
506275
  this.host.streamingUI.removeActiveToolCall(toolCallId);
506074
506276
  context.completedToolCallIds.add(toolCallId);
@@ -507683,6 +507885,18 @@ var StreamingUIController = class {
507683
507885
  removeToolComponent(id) {
507684
507886
  this._pendingToolComponents.delete(id);
507685
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
+ }
507686
507900
  hasPendingAgentGroup() {
507687
507901
  return this._pendingAgentGroup !== null;
507688
507902
  }
@@ -512378,6 +512592,7 @@ var BlunTUI = class {
512378
512592
  currentLoadingTip = void 0;
512379
512593
  mediaActivityStore = new MediaActivityStore();
512380
512594
  mediaActivityTickTimer;
512595
+ mediaActivityExpanded = false;
512381
512596
  lastHistoryContent;
512382
512597
  shellOutputStreams = /* @__PURE__ */ new Map();
512383
512598
  streamingUI;
@@ -514507,9 +514722,13 @@ var BlunTUI = class {
514507
514722
  }
514508
514723
  updateMediaActivity(toolCallId, event) {
514509
514724
  this.mediaActivityStore.update(toolCallId, event);
514510
- if (this.mediaActivityStore.active().length > 0) {
514725
+ const mediaActivities = this.mediaActivityStore.active();
514726
+ if (mediaActivities.length > 0) {
514511
514727
  if (this.mediaActivityTickTimer === void 0) this.mediaActivityTickTimer = setInterval(() => this.state.ui.requestRender(), 1e3);
514512
- } else this.stopMediaActivityTicking();
514728
+ } else {
514729
+ this.mediaActivityExpanded = false;
514730
+ this.stopMediaActivityTicking();
514731
+ }
514513
514732
  this.lastActivityMode = void 0;
514514
514733
  this.updateActivityPane();
514515
514734
  }
@@ -514524,10 +514743,10 @@ var BlunTUI = class {
514524
514743
  if (mediaActivities.length > 0) {
514525
514744
  this.stopActivitySpinner();
514526
514745
  this.syncAgentSwarmActivitySpinner(void 0);
514527
- this.lastActivityMode = `media:${mediaActivities.map((job) => `${job.id ?? job.toolCallId}:${job.updatedAt}`).join(",")}`;
514746
+ this.lastActivityMode = `media:${this.mediaActivityExpanded ? "expanded" : "collapsed"}:${mediaActivities.map((job) => `${job.id ?? job.toolCallId}:${job.updatedAt}`).join(",")}`;
514528
514747
  this.state.activityContainer.clear();
514529
514748
  this.state.activityContainer.addChild(new Spacer(1));
514530
- this.state.activityContainer.addChild(new MediaActivityComponent(mediaActivities));
514749
+ this.state.activityContainer.addChild(new MediaActivityComponent(mediaActivities, this.mediaActivityExpanded));
514531
514750
  this.state.ui.requestRender();
514532
514751
  return;
514533
514752
  }
@@ -514623,6 +514842,10 @@ var BlunTUI = class {
514623
514842
  }));
514624
514843
  }
514625
514844
  toggleToolOutputExpansion() {
514845
+ if (this.mediaActivityStore.active().length > 0) {
514846
+ this.toggleMediaActivityExpansion();
514847
+ return;
514848
+ }
514626
514849
  this.state.toolOutputExpanded = !this.state.toolOutputExpanded;
514627
514850
  const children = this.state.transcriptContainer.children;
514628
514851
  const boundaries = [];
@@ -514635,6 +514858,13 @@ var BlunTUI = class {
514635
514858
  }
514636
514859
  this.state.ui.requestRender(true);
514637
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
+ }
514638
514868
  toggleTodoPanelExpansion() {
514639
514869
  this.state.todoPanel.toggleExpanded();
514640
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.48",
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": {