blun-king-cli 9.1.48 → 9.1.50

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.50
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.50
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
 
@@ -30152,7 +30172,8 @@ function parseEnvBudget(raw) {
30152
30172
  */
30153
30173
  function computeCompletionBudgetCap(args) {
30154
30174
  const maxCtx = args.capability?.max_context_tokens ?? 0;
30155
- const cap = args.budget.hardCap ?? (maxCtx > 0 ? maxCtx : args.budget.fallback ?? DEFAULT_UNKNOWN_CONTEXT_FALLBACK);
30175
+ const fallback = args.budget.fallback ?? DEFAULT_UNKNOWN_CONTEXT_FALLBACK;
30176
+ const cap = args.budget.hardCap ?? (maxCtx > 0 ? Math.min(maxCtx, fallback) : fallback);
30156
30177
  return Math.max(MIN_FLOOR, cap);
30157
30178
  }
30158
30179
  /**
@@ -30177,7 +30198,7 @@ function applyCompletionBudgetWithDetails(args) {
30177
30198
  });
30178
30199
  if (args.retry !== void 0) cap = Math.max(args.retry.minimumCompletionTokens, Math.ceil(cap * args.retry.multiplier));
30179
30200
  const maxContextTokens = args.capability?.max_context_tokens;
30180
- if (args.usedContextTokens !== void 0 && maxContextTokens !== void 0 && maxContextTokens > 0) cap = Math.max(MIN_FLOOR, Math.min(cap, maxContextTokens - args.usedContextTokens));
30201
+ if (args.usedContextTokens !== void 0 && maxContextTokens !== void 0 && maxContextTokens > 0) cap = Math.max(MIN_FLOOR, Math.min(cap, maxContextTokens - args.usedContextTokens - COMPLETION_CONTEXT_SAFETY_MARGIN));
30181
30202
  return {
30182
30203
  provider: args.provider.withMaxCompletionTokens(cap, {
30183
30204
  usedContextTokens: args.usedContextTokens,
@@ -30186,11 +30207,12 @@ function applyCompletionBudgetWithDetails(args) {
30186
30207
  maxCompletionTokens: cap
30187
30208
  };
30188
30209
  }
30189
- var MIN_FLOOR, DEFAULT_UNKNOWN_CONTEXT_FALLBACK, MIN_THINKING_COMPLETION_TOKENS;
30210
+ var MIN_FLOOR, DEFAULT_UNKNOWN_CONTEXT_FALLBACK, MIN_THINKING_COMPLETION_TOKENS, COMPLETION_CONTEXT_SAFETY_MARGIN;
30190
30211
  var init_completion_budget = __esmMin((() => {
30191
30212
  MIN_FLOOR = 1;
30192
30213
  DEFAULT_UNKNOWN_CONTEXT_FALLBACK = 32e3;
30193
30214
  MIN_THINKING_COMPLETION_TOKENS = 1024;
30215
+ COMPLETION_CONTEXT_SAFETY_MARGIN = 1e4;
30194
30216
  }));
30195
30217
  //#endregion
30196
30218
  //#region ../../packages/agent-core/src/loop/retry.ts
@@ -261995,7 +262017,11 @@ var init_blun_media$1 = __esmMin((() => {
261995
262017
  PromptSchema = string().trim().min(1).max(2e4);
261996
262018
  MediaIdSchema = string().trim().min(1).max(200).regex(/^[A-Za-z0-9_-]+$/);
261997
262019
  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.") });
262020
+ GenerateVideoInputSchema = object({
262021
+ prompt: PromptSchema.describe("A complete visual description of the video or motion to generate."),
262022
+ image_id: MediaIdSchema.optional().describe("An optional completed PNG or JPEG media job id to animate into the video."),
262023
+ path: string().trim().min(1).optional().describe("An optional path to a local PNG or JPEG file to upload and animate. Relative paths resolve against the working directory.")
262024
+ }).refine((args) => !(args.image_id && args.path), { message: "Provide either image_id or path, not both." });
261999
262025
  GenerateSpeechInputSchema = object({ input: PromptSchema.describe("The exact text to synthesize as speech.") });
262000
262026
  GetMediaInputSchema = object({ id: MediaIdSchema.describe("The media job id returned by a generation tool.") });
262001
262027
  UnderstandImageInputSchema = object({
@@ -262069,14 +262095,82 @@ var init_blun_media$1 = __esmMin((() => {
262069
262095
  }
262070
262096
  };
262071
262097
  GenerateVideoTool = class extends MediaGenerationTool {
262098
+ kaos;
262099
+ workspace;
262072
262100
  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.";
262101
+ description = "Generate a new video from text, animate an existing completed image job, or animate a local PNG/JPEG file with BLUN media models. Pass image_id for an existing media job or path for a local or Telegram-provided image. Do not use this for understanding an existing video. This starts a billed asynchronous media job.";
262074
262102
  parameters = toInputJsonSchema(GenerateVideoInputSchema);
262103
+ constructor(provider, kaos, workspace) {
262104
+ super(provider);
262105
+ this.kaos = kaos;
262106
+ this.workspace = workspace;
262107
+ }
262075
262108
  subject(args) {
262076
- return args.prompt;
262109
+ return args.path ? `${args.path}: ${args.prompt}` : args.image_id ? `${args.image_id}: ${args.prompt}` : args.prompt;
262110
+ }
262111
+ resolveExecution(args) {
262112
+ if (!args.path) return super.resolveExecution(args);
262113
+ const path = resolvePathAccessPath(args.path, {
262114
+ kaos: this.kaos,
262115
+ workspace: this.workspace,
262116
+ operation: "read"
262117
+ });
262118
+ return {
262119
+ accesses: ToolAccesses.readFile(path),
262120
+ description: `${this.name}: ${args.path}`,
262121
+ display: {
262122
+ kind: "file_io",
262123
+ operation: "read",
262124
+ path
262125
+ },
262126
+ approvalRule: literalRulePattern(this.name, path),
262127
+ matchesRule: (ruleArgs) => matchesPathRuleSubject(ruleArgs, path, {
262128
+ cwd: this.workspace.workspaceDir,
262129
+ pathClass: this.kaos.pathClass(),
262130
+ homeDir: this.kaos.gethome()
262131
+ }),
262132
+ execute: (ctx) => this.executionFromPath(args, path, ctx)
262133
+ };
262134
+ }
262135
+ async executionFromPath(args, safePath, ctx) {
262136
+ emitMediaProgress(ctx, this.name, "uploading");
262137
+ try {
262138
+ const fileType = detectFileType(safePath, await this.kaos.readBytes(safePath, 512), "media");
262139
+ if (fileType.mimeType !== "image/png" && fileType.mimeType !== "image/jpeg") return {
262140
+ isError: true,
262141
+ output: "Image-to-video accepts only PNG or JPEG files."
262142
+ };
262143
+ const data = await this.kaos.readBytes(safePath);
262144
+ if (data.byteLength === 0) return {
262145
+ isError: true,
262146
+ output: `"${args.path}" is empty.`
262147
+ };
262148
+ const options = {
262149
+ signal: ctx.signal,
262150
+ toolCallId: ctx.toolCallId
262151
+ };
262152
+ const upload = await this.provider.uploadMedia(data, fileType.mimeType, options);
262153
+ emitMediaProgress(ctx, this.name, "submitting", { sourceId: upload.id });
262154
+ const job = await this.provider.generateVideo(args.prompt, options, upload.id);
262155
+ emitMediaProgress(ctx, this.name, job.status, {
262156
+ id: job.id,
262157
+ sourceId: upload.id,
262158
+ ...job.progress
262159
+ });
262160
+ return {
262161
+ output: `Media job ${job.id} accepted with status ${job.status}. ${this.requestNote}`,
262162
+ isError: false
262163
+ };
262164
+ } catch (error) {
262165
+ emitMediaProgress(ctx, this.name, "failed", { error: errorMessage$10(error) });
262166
+ return {
262167
+ isError: true,
262168
+ output: `Media request failed: ${errorMessage$10(error)}`
262169
+ };
262170
+ }
262077
262171
  }
262078
262172
  submit(args, options) {
262079
- return this.provider.generateVideo(args.prompt, options);
262173
+ return this.provider.generateVideo(args.prompt, options, args.image_id);
262080
262174
  }
262081
262175
  };
262082
262176
  GenerateSpeechTool = class extends MediaGenerationTool {
@@ -262741,7 +262835,7 @@ var init_tool$1 = __esmMin((() => {
262741
262835
  toolServices?.webSearcher && new WebSearchTool(toolServices.webSearcher),
262742
262836
  toolServices?.urlFetcher && new FetchURLTool(toolServices.urlFetcher),
262743
262837
  toolServices?.media && new GenerateImageTool(toolServices.media),
262744
- toolServices?.media && new GenerateVideoTool(toolServices.media),
262838
+ toolServices?.media && new GenerateVideoTool(toolServices.media, kaos, workspace),
262745
262839
  toolServices?.media && new GenerateSpeechTool(toolServices.media),
262746
262840
  toolServices?.media && new UnderstandImageTool(toolServices.media, kaos, workspace),
262747
262841
  toolServices?.media && new UnderstandVideoTool(toolServices.media),
@@ -310814,6 +310908,14 @@ function mediaPayloadText(payload, keys) {
310814
310908
  if (typeof value === "string" && value.trim() !== "") return value.trim();
310815
310909
  }
310816
310910
  }
310911
+ function mediaPayloadNumberArray(payload, keys) {
310912
+ for (const key of keys) {
310913
+ const value = payload[key];
310914
+ if (!Array.isArray(value)) continue;
310915
+ const samples = value.map((entry) => typeof entry === "number" ? entry : Number(entry)).filter((entry) => Number.isFinite(entry));
310916
+ if (samples.length > 0) return samples.slice(0, 512);
310917
+ }
310918
+ }
310817
310919
  function mediaProgressFromPayload(payload) {
310818
310920
  const rawPercent = mediaPayloadNumber(payload, ["percent", "progress_percent", "progressPercent", "progress"]);
310819
310921
  const percent = rawPercent === void 0 ? void 0 : rawPercent <= 1 ? rawPercent * 100 : rawPercent;
@@ -310830,7 +310932,16 @@ function mediaProgressFromPayload(payload) {
310830
310932
  generatedSeconds: mediaPayloadNumber(payload, ["generated_seconds", "generatedSeconds", "audio_seconds", "audioSeconds"]),
310831
310933
  queuePosition: mediaPayloadNumber(payload, ["queue_position", "queuePosition"]),
310832
310934
  elapsedSeconds: mediaPayloadNumber(payload, ["elapsed_seconds", "elapsedSeconds"]),
310833
- remainingSeconds: mediaPayloadNumber(payload, ["remaining_seconds", "remainingSeconds", "eta_seconds", "etaSeconds"])
310935
+ remainingSeconds: mediaPayloadNumber(payload, ["remaining_seconds", "remainingSeconds", "eta_seconds", "etaSeconds"]),
310936
+ durationSeconds: mediaPayloadNumber(payload, ["duration_seconds", "durationSeconds"]),
310937
+ width: mediaPayloadNumber(payload, ["width", "width_px", "widthPx"]),
310938
+ height: mediaPayloadNumber(payload, ["height", "height_px", "heightPx"]),
310939
+ previewUrl: mediaPayloadText(payload, ["preview_url", "previewUrl", "thumbnail_url", "thumbnailUrl"]),
310940
+ previewDataBase64: mediaPayloadText(payload, ["preview_base64", "previewBase64", "thumbnail_base64", "thumbnailBase64"]),
310941
+ previewMimeType: mediaPayloadText(payload, ["preview_mime_type", "previewMimeType", "thumbnail_mime_type", "thumbnailMimeType"]),
310942
+ sampleUrl: mediaPayloadText(payload, ["sample_url", "sampleUrl", "audio_preview_url", "audioPreviewUrl"]),
310943
+ localPath: mediaPayloadText(payload, ["local_path", "localPath"]),
310944
+ waveform: mediaPayloadNumberArray(payload, ["waveform", "waveform_samples", "waveformSamples"])
310834
310945
  };
310835
310946
  return Object.fromEntries(Object.entries(progress).filter(([, value]) => value !== void 0));
310836
310947
  }
@@ -310884,8 +310995,10 @@ var init_blun_media = __esmMin((() => {
310884
310995
  generateImage(prompt, options) {
310885
310996
  return this.submit("/images/generations", { prompt }, options);
310886
310997
  }
310887
- generateVideo(prompt, options) {
310888
- return this.submit("/videos/generations", { prompt }, options);
310998
+ generateVideo(prompt, options, imageId) {
310999
+ const body = { prompt };
311000
+ if (imageId !== void 0 && imageId.length > 0) body["image_id"] = imageId;
311001
+ return this.submit("/videos/generations", body, options);
310889
311002
  }
310890
311003
  generateSpeech(input, options) {
310891
311004
  return this.submit("/audio/speech", { input }, options);
@@ -409996,6 +410109,16 @@ function trimPartialClosingFences(tokens) {
409996
410109
  if (!marker || !lastLine || lastLine.length >= marker.length || lastLine !== marker[0]?.repeat(lastLine.length)) return;
409997
410110
  token.text = token.text.slice(0, -lastLine.length).replace(/\n$/, "");
409998
410111
  }
410112
+ function terminalHyperlinkTarget(value) {
410113
+ const candidate = value.trim();
410114
+ if (/^https?:\/\//iu.test(candidate) || /^file:\/\//iu.test(candidate)) try {
410115
+ const parsed = new URL(candidate);
410116
+ if (["http:", "https:", "file:"].includes(parsed.protocol)) return parsed.href;
410117
+ } catch {
410118
+ return;
410119
+ }
410120
+ if (/^[A-Za-z]:[\\/]/u.test(candidate)) return pathToFileURL(candidate).href;
410121
+ }
409999
410122
  const markdownParser = new q();
410000
410123
  markdownParser.setOptions({ tokenizer: new StrictStrikethroughTokenizer() });
410001
410124
  var Markdown = class {
@@ -410249,7 +410372,11 @@ var Markdown = class {
410249
410372
  break;
410250
410373
  }
410251
410374
  case "codespan":
410252
- result += this.theme.code(token.text) + stylePrefix;
410375
+ {
410376
+ const styledCode = this.theme.code(token.text);
410377
+ const linkTarget = terminalHyperlinkTarget(token.text);
410378
+ result += (linkTarget !== void 0 && getCapabilities().hyperlinks ? hyperlink(styledCode, linkTarget) : styledCode) + stylePrefix;
410379
+ }
410253
410380
  break;
410254
410381
  case "link": {
410255
410382
  const linkText = this.renderInlineTokens(token.tokens || [], resolvedStyleContext);
@@ -498004,10 +498131,19 @@ var ActivityPaneComponent = class extends Container {
498004
498131
  registerUiCatalogFragment({
498005
498132
  en: {
498006
498133
  "media.activity.jobs": "{count} media jobs active",
498134
+ "media.activity.showJobs": "ctrl+o: show jobs",
498135
+ "media.activity.collapse": "ctrl+o: collapse",
498136
+ "media.activity.chain": "Production chain",
498007
498137
  "media.detail.queue": "Position {position}",
498008
498138
  "media.detail.segment": "Segment {current}/{total}",
498009
498139
  "media.detail.audio": "{seconds}s audio",
498010
498140
  "media.detail.remaining": "about {duration} remaining",
498141
+ "media.detail.preview": "Preview",
498142
+ "media.detail.playSample": "Play sample",
498143
+ "media.detail.waveform": "Waveform",
498144
+ "media.detail.open": "Open",
498145
+ "media.detail.duration": "Duration {duration}",
498146
+ "media.detail.resolution": "{width}x{height}",
498011
498147
  "media.kind.media": "Media",
498012
498148
  "media.kind.image": "Image",
498013
498149
  "media.kind.video": "Video",
@@ -498038,10 +498174,19 @@ registerUiCatalogFragment({
498038
498174
  },
498039
498175
  de: {
498040
498176
  "media.activity.jobs": "{count} Medienaufträge aktiv",
498177
+ "media.activity.showJobs": "ctrl+o: Aufträge öffnen",
498178
+ "media.activity.collapse": "ctrl+o: einklappen",
498179
+ "media.activity.chain": "Produktionskette",
498041
498180
  "media.detail.queue": "Platz {position}",
498042
498181
  "media.detail.segment": "Abschnitt {current}/{total}",
498043
498182
  "media.detail.audio": "{seconds}s Audio",
498044
498183
  "media.detail.remaining": "noch etwa {duration}",
498184
+ "media.detail.preview": "Vorschau",
498185
+ "media.detail.playSample": "Probehören",
498186
+ "media.detail.waveform": "Wellenform",
498187
+ "media.detail.open": "Öffnen",
498188
+ "media.detail.duration": "Dauer {duration}",
498189
+ "media.detail.resolution": "{width}x{height}",
498045
498190
  "media.kind.media": "Medien",
498046
498191
  "media.kind.image": "Bild",
498047
498192
  "media.kind.video": "Video",
@@ -498084,6 +498229,82 @@ function mediaActivityIsTerminal(value) {
498084
498229
  "blocked"
498085
498230
  ].includes(mediaActivityStatusKey(value));
498086
498231
  }
498232
+ function shouldSuppressMediaPollTranscript(toolName, isError, output) {
498233
+ if (toolName !== "GetMedia" || isError === true) return false;
498234
+ const text = typeof output === "string" ? output : JSON.stringify(output);
498235
+ const match = /Media job\s+\S+\s+status:\s*([a-z0-9_-]+)/i.exec(text);
498236
+ return match !== null && !mediaActivityIsTerminal(match[1]);
498237
+ }
498238
+ function mediaActivityIsQueued(job) {
498239
+ return job.queuePosition !== void 0 || [
498240
+ "accepted",
498241
+ "pending",
498242
+ "queued",
498243
+ "submitting",
498244
+ "uploading"
498245
+ ].includes(mediaActivityStatusKey(job.phase));
498246
+ }
498247
+ function mediaActivityShouldShowPercent(job) {
498248
+ return !mediaActivityIsQueued(job) && job.percent !== void 0 && Number.isFinite(job.percent);
498249
+ }
498250
+ function mediaActivityCanonicalPhase(value) {
498251
+ const phase = mediaActivityStatusKey(value);
498252
+ if (["accepted", "pending", "submitting", "uploading"].includes(phase)) return "queued";
498253
+ if (["running", "in-progress"].includes(phase)) return "processing";
498254
+ if (phase === "face-detection") return "face-tracking";
498255
+ return phase;
498256
+ }
498257
+ function mediaActivityPhaseSequence(mediaKind) {
498258
+ switch (mediaActivityStatusKey(mediaKind)) {
498259
+ case "image": return ["queued", "generating", "upscaling", "saving"];
498260
+ case "video": return ["queued", "generating", "rendering", "saving"];
498261
+ case "voice": return ["queued", "processing", "saving"];
498262
+ case "dubbing": return ["queued", "audio-analysis", "rendering", "saving"];
498263
+ case "lipsync": return ["queued", "audio-analysis", "face-tracking", "lip-sync-running", "rendering", "exporting", "saving"];
498264
+ case "image-analysis":
498265
+ case "video-analysis": return ["queued", "analyzing"];
498266
+ default: return ["queued", "processing", "saving"];
498267
+ }
498268
+ }
498269
+ function mediaActivityChainStates(job) {
498270
+ const sequence = mediaActivityPhaseSequence(job.mediaKind);
498271
+ const normalize = (value) => {
498272
+ const phase = mediaActivityCanonicalPhase(value);
498273
+ if (phase === "processing" && !sequence.includes(phase)) return sequence[1] ?? phase;
498274
+ return phase;
498275
+ };
498276
+ const current = normalize(job.phaseLabel ?? job.phase);
498277
+ const currentIndex = sequence.indexOf(current);
498278
+ const history = new Set((job.phaseHistory ?? []).map(normalize));
498279
+ return sequence.map((phase, index) => ({
498280
+ phase,
498281
+ state: phase === current ? "active" : history.has(phase) || currentIndex > index ? "done" : "pending"
498282
+ }));
498283
+ }
498284
+ function renderMediaWaveform(samples, width) {
498285
+ if (!Array.isArray(samples) || samples.length === 0 || width <= 0) return "";
498286
+ const bars = "▁▂▃▄▅▆▇█";
498287
+ const safeWidth = Math.max(1, Math.floor(width));
498288
+ const max = Math.max(...samples.map((sample) => Math.abs(sample)), 1e-9);
498289
+ let output = "";
498290
+ for (let index = 0; index < safeWidth; index++) {
498291
+ const sampleIndex = Math.min(samples.length - 1, Math.floor(index * samples.length / safeWidth));
498292
+ const normalized = Math.min(1, Math.abs(samples[sampleIndex]) / max);
498293
+ output += bars[Math.min(bars.length - 1, Math.floor(normalized * bars.length))];
498294
+ }
498295
+ return output;
498296
+ }
498297
+ function mediaActivityOpenUrl(job) {
498298
+ const candidate = [job.sampleUrl, job.previewUrl, job.localPath].find((value) => typeof value === "string" && value.trim().length > 0)?.trim();
498299
+ if (candidate === void 0) return;
498300
+ if (/^https?:\/\//iu.test(candidate) || /^file:\/\//iu.test(candidate)) try {
498301
+ const parsed = new URL(candidate);
498302
+ if (["http:", "https:", "file:"].includes(parsed.protocol)) return parsed.href;
498303
+ } catch {
498304
+ return;
498305
+ }
498306
+ if (/^[A-Za-z]:[\\/]/u.test(candidate) || candidate.startsWith("/")) return pathToFileURL(candidate).href;
498307
+ }
498087
498308
  const MEDIA_ACTIVITY_LABELS = {
498088
498309
  "media.kind": /* @__PURE__ */ new Set([
498089
498310
  "media",
@@ -498150,11 +498371,16 @@ var MediaActivityStore = class {
498150
498371
  return;
498151
498372
  }
498152
498373
  const mediaKind = event.mediaKind === "media" && previous?.mediaKind !== void 0 ? previous.mediaKind : event.mediaKind ?? previous?.mediaKind ?? "media";
498374
+ const phase = mediaActivityStatusKey(event.phase ?? previous?.phase ?? "processing");
498375
+ const phaseHistory = [...previous?.phaseHistory ?? []];
498376
+ if (phaseHistory.at(-1) !== phase) phaseHistory.push(phase);
498153
498377
  this.jobs.set(key, {
498154
498378
  ...previous,
498155
498379
  ...event,
498156
498380
  id: id ?? previous?.id,
498157
498381
  mediaKind,
498382
+ phase,
498383
+ phaseHistory,
498158
498384
  toolCallId,
498159
498385
  startedAt: previous?.startedAt ?? Date.now(),
498160
498386
  updatedAt: Date.now()
@@ -498169,13 +498395,16 @@ var MediaActivityStore = class {
498169
498395
  };
498170
498396
  var MediaActivityComponent = class {
498171
498397
  jobs;
498172
- constructor(jobs) {
498398
+ expanded;
498399
+ previewImages = /* @__PURE__ */ new Map();
498400
+ constructor(jobs, expanded = false) {
498173
498401
  this.jobs = jobs;
498402
+ this.expanded = expanded;
498174
498403
  }
498175
- invalidate() {}
498176
- render(width) {
498177
- if (this.jobs.length === 0) return [];
498178
- const job = this.jobs[0];
498404
+ invalidate() {
498405
+ for (const image of this.previewImages.values()) image.invalidate();
498406
+ }
498407
+ jobLine(job, width, includeBar) {
498179
498408
  const kind = mediaActivityLabel("media.kind", job.mediaKind, String(job.mediaKind ?? "Media"));
498180
498409
  const rawPhase = job.phaseLabel ?? job.phase ?? "processing";
498181
498410
  const phase = sanitizeTerminalText(mediaActivityLabel("media.phase", rawPhase, String(rawPhase)));
@@ -498189,18 +498418,83 @@ var MediaActivityComponent = class {
498189
498418
  if (job.currentFrame !== void 0 && job.totalFrames !== void 0) details.push(`Frame ${String(job.currentFrame)}/${String(job.totalFrames)}`);
498190
498419
  if (job.fps !== void 0) details.push(`${String(Math.round(job.fps * 10) / 10)} FPS`);
498191
498420
  if (job.generatedSeconds !== void 0) details.push(mediaUiText("media.detail.audio", { seconds: Math.round(job.generatedSeconds) }));
498421
+ if (job.durationSeconds !== void 0) details.push(mediaUiText("media.detail.duration", { duration: formatMediaDuration(job.durationSeconds) }));
498422
+ if (job.width > 0 && job.height > 0) details.push(mediaUiText("media.detail.resolution", {
498423
+ width: Math.round(job.width),
498424
+ height: Math.round(job.height)
498425
+ }));
498192
498426
  const elapsed = job.elapsedSeconds ?? (Date.now() - job.startedAt) / 1e3;
498193
498427
  const elapsedText = formatMediaDuration(elapsed);
498194
498428
  if (elapsedText !== void 0) details.push(elapsedText);
498195
498429
  if (job.remainingSeconds !== void 0) details.push(mediaUiText("media.detail.remaining", { duration: formatMediaDuration(job.remainingSeconds) }));
498196
498430
  let progress = "";
498197
- if (job.percent !== void 0 && Number.isFinite(job.percent)) {
498431
+ if (mediaActivityShouldShowPercent(job)) {
498198
498432
  const percent = Math.min(100, Math.max(0, job.percent));
498199
- progress = ` · ${renderProgressBar(percent / 100, 12)} · ${String(Math.round(percent))} %`;
498433
+ progress = includeBar ? ` · ${renderProgressBar(percent / 100, 12)} · ${String(Math.round(percent))} %` : ` · ${String(Math.round(percent))} %`;
498434
+ }
498435
+ const line = `${currentTheme.boldFg("primary", "●")} ${currentTheme.boldFg("primary", kind)} · ${phase}${details.length === 0 ? "" : ` · ${details.join(" · ")}`}${progress}`;
498436
+ return truncateToWidth(line, Math.max(1, width), "…");
498437
+ }
498438
+ chainLine(job, width) {
498439
+ const chain = mediaActivityChainStates(job).map(({ phase, state }) => {
498440
+ const symbol = state === "done" ? "[x]" : state === "active" ? "[>]" : "[ ]";
498441
+ const label = mediaActivityLabel("media.phase", phase, phase);
498442
+ const text = `${symbol} ${label}`;
498443
+ if (state === "done") return currentTheme.fg("success", text);
498444
+ if (state === "active") return currentTheme.boldFg("primary", text);
498445
+ return currentTheme.fg("textDim", text);
498446
+ });
498447
+ return truncateToWidth(` ${currentTheme.fg("textDim", mediaUiText("media.activity.chain"))} · ${chain.join(currentTheme.fg("textDim", " · "))}`, Math.max(1, width), "…");
498448
+ }
498449
+ previewImage(job) {
498450
+ 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;
498451
+ const mimeType = typeof job.previewMimeType === "string" ? job.previewMimeType.toLowerCase() : "";
498452
+ if (!["image/png", "image/jpeg", "image/webp", "image/gif"].includes(mimeType)) return;
498453
+ const key = `${job.id ?? job.toolCallId}:${mimeType}:${job.previewDataBase64.length}`;
498454
+ let image = this.previewImages.get(key);
498455
+ if (image === void 0) {
498456
+ image = new Image(job.previewDataBase64, mimeType, { fallbackColor: (text) => currentTheme.fg("textDim", text) }, {
498457
+ maxHeightCells: 5,
498458
+ maxWidthCells: 24,
498459
+ filename: mediaUiText("media.detail.preview")
498460
+ });
498461
+ this.previewImages.set(key, image);
498462
+ }
498463
+ return image;
498464
+ }
498465
+ extraLines(job, width) {
498466
+ const lines = [this.chainLine(job, width)];
498467
+ if (Array.isArray(job.waveform) && job.waveform.length > 0) {
498468
+ const label = `${mediaUiText("media.detail.waveform")} · `;
498469
+ const waveform = renderMediaWaveform(job.waveform, Math.max(1, width - visibleWidth(label) - 2));
498470
+ lines.push(truncateToWidth(` ${label}${waveform}`, Math.max(1, width), "…"));
498471
+ }
498472
+ const openUrl = mediaActivityOpenUrl(job);
498473
+ if (openUrl !== void 0) {
498474
+ const key = job.mediaKind === "voice" ? "media.detail.playSample" : job.localPath !== void 0 ? "media.detail.open" : "media.detail.preview";
498475
+ lines.push(truncateToWidth(` ${hyperlink(mediaUiText(key), openUrl)}`, Math.max(1, width), "…"));
498476
+ }
498477
+ const image = this.previewImage(job);
498478
+ if (image !== void 0) lines.push(...image.render(Math.max(1, width - 2)).map((line) => ` ${line}`));
498479
+ return lines;
498480
+ }
498481
+ render(width) {
498482
+ if (this.jobs.length === 0) return [];
498483
+ const safeWidth = Math.max(1, width);
498484
+ const hint = mediaUiText(this.expanded ? "media.activity.collapse" : "media.activity.showJobs");
498485
+ if (this.jobs.length > 1 && !this.expanded) {
498486
+ const summary = `${currentTheme.boldFg("primary", mediaUiText("media.activity.jobs", { count: this.jobs.length }))} · ${currentTheme.fg("textDim", hint)}`;
498487
+ return [truncateToWidth(summary, safeWidth, "…")];
498488
+ }
498489
+ const lines = [];
498490
+ if (this.jobs.length === 1) lines.push(truncateToWidth(`${this.jobLine(this.jobs[0], safeWidth, true)} · ${currentTheme.fg("textDim", hint)}`, safeWidth, "…"));
498491
+ else lines.push(truncateToWidth(`${currentTheme.boldFg("primary", mediaUiText("media.activity.jobs", { count: this.jobs.length }))} · ${currentTheme.fg("textDim", hint)}`, safeWidth, "…"));
498492
+ if (!this.expanded) return lines;
498493
+ for (const [index, job] of this.jobs.entries()) {
498494
+ if (this.jobs.length > 1) lines.push(truncateToWidth(` ${String(index + 1)}. ${this.jobLine(job, Math.max(1, safeWidth - 3), false)}`, safeWidth, "…"));
498495
+ lines.push(...this.extraLines(job, safeWidth));
498200
498496
  }
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), "…")];
498497
+ return lines;
498204
498498
  }
498205
498499
  };
498206
498500
  //#endregion
@@ -504753,6 +505047,8 @@ var SessionEventHandler = class {
504753
505047
  is_error: event.isError,
504754
505048
  synthetic: event.synthetic
504755
505049
  };
505050
+ const activeCall = streamingUI.getActiveToolCall(event.toolCallId);
505051
+ if (shouldSuppressMediaPollTranscript(activeCall?.name, event.isError, event.output)) streamingUI.suppressToolCallTranscript(event.toolCallId);
504756
505052
  const matchedCall = streamingUI.completeToolResult(event.toolCallId, resultData);
504757
505053
  if (matchedCall !== void 0 && isMediaToolName(matchedCall.name) && event.isError === true) this.host.updateMediaActivity(event.toolCallId, {
504758
505054
  mediaKind: mediaKindForToolName(matchedCall.name),
@@ -506069,6 +506365,7 @@ var SessionReplayRenderer = class {
506069
506365
  };
506070
506366
  call.result = result;
506071
506367
  this.applyStepContext(context);
506368
+ if (shouldSuppressMediaPollTranscript(call.name, message.isError, result.output)) this.host.streamingUI.suppressToolCallTranscript(toolCallId);
506072
506369
  this.host.streamingUI.onToolCallEnd(toolCallId, result);
506073
506370
  this.host.streamingUI.removeActiveToolCall(toolCallId);
506074
506371
  context.completedToolCallIds.add(toolCallId);
@@ -507683,6 +507980,18 @@ var StreamingUIController = class {
507683
507980
  removeToolComponent(id) {
507684
507981
  this._pendingToolComponents.delete(id);
507685
507982
  }
507983
+ suppressToolCallTranscript(id) {
507984
+ const { state } = this.host;
507985
+ const component = this._pendingToolComponents.get(id);
507986
+ this._pendingToolComponents.delete(id);
507987
+ const transcriptEntryIndex = state.transcriptEntries.findIndex((entry) => entry.toolCallData?.id === id);
507988
+ if (transcriptEntryIndex >= 0) state.transcriptEntries.splice(transcriptEntryIndex, 1);
507989
+ if (component !== void 0) {
507990
+ const childIndex = state.transcriptContainer.children.indexOf(component);
507991
+ if (childIndex >= 0) state.transcriptContainer.children.splice(childIndex, 1);
507992
+ }
507993
+ state.ui.requestRender();
507994
+ }
507686
507995
  hasPendingAgentGroup() {
507687
507996
  return this._pendingAgentGroup !== null;
507688
507997
  }
@@ -512378,6 +512687,7 @@ var BlunTUI = class {
512378
512687
  currentLoadingTip = void 0;
512379
512688
  mediaActivityStore = new MediaActivityStore();
512380
512689
  mediaActivityTickTimer;
512690
+ mediaActivityExpanded = false;
512381
512691
  lastHistoryContent;
512382
512692
  shellOutputStreams = /* @__PURE__ */ new Map();
512383
512693
  streamingUI;
@@ -514507,9 +514817,13 @@ var BlunTUI = class {
514507
514817
  }
514508
514818
  updateMediaActivity(toolCallId, event) {
514509
514819
  this.mediaActivityStore.update(toolCallId, event);
514510
- if (this.mediaActivityStore.active().length > 0) {
514820
+ const mediaActivities = this.mediaActivityStore.active();
514821
+ if (mediaActivities.length > 0) {
514511
514822
  if (this.mediaActivityTickTimer === void 0) this.mediaActivityTickTimer = setInterval(() => this.state.ui.requestRender(), 1e3);
514512
- } else this.stopMediaActivityTicking();
514823
+ } else {
514824
+ this.mediaActivityExpanded = false;
514825
+ this.stopMediaActivityTicking();
514826
+ }
514513
514827
  this.lastActivityMode = void 0;
514514
514828
  this.updateActivityPane();
514515
514829
  }
@@ -514524,10 +514838,10 @@ var BlunTUI = class {
514524
514838
  if (mediaActivities.length > 0) {
514525
514839
  this.stopActivitySpinner();
514526
514840
  this.syncAgentSwarmActivitySpinner(void 0);
514527
- this.lastActivityMode = `media:${mediaActivities.map((job) => `${job.id ?? job.toolCallId}:${job.updatedAt}`).join(",")}`;
514841
+ this.lastActivityMode = `media:${this.mediaActivityExpanded ? "expanded" : "collapsed"}:${mediaActivities.map((job) => `${job.id ?? job.toolCallId}:${job.updatedAt}`).join(",")}`;
514528
514842
  this.state.activityContainer.clear();
514529
514843
  this.state.activityContainer.addChild(new Spacer(1));
514530
- this.state.activityContainer.addChild(new MediaActivityComponent(mediaActivities));
514844
+ this.state.activityContainer.addChild(new MediaActivityComponent(mediaActivities, this.mediaActivityExpanded));
514531
514845
  this.state.ui.requestRender();
514532
514846
  return;
514533
514847
  }
@@ -514623,6 +514937,10 @@ var BlunTUI = class {
514623
514937
  }));
514624
514938
  }
514625
514939
  toggleToolOutputExpansion() {
514940
+ if (this.mediaActivityStore.active().length > 0) {
514941
+ this.toggleMediaActivityExpansion();
514942
+ return;
514943
+ }
514626
514944
  this.state.toolOutputExpanded = !this.state.toolOutputExpanded;
514627
514945
  const children = this.state.transcriptContainer.children;
514628
514946
  const boundaries = [];
@@ -514635,6 +514953,13 @@ var BlunTUI = class {
514635
514953
  }
514636
514954
  this.state.ui.requestRender(true);
514637
514955
  }
514956
+ toggleMediaActivityExpansion() {
514957
+ if (this.mediaActivityStore.active().length === 0) return;
514958
+ this.mediaActivityExpanded = !this.mediaActivityExpanded;
514959
+ this.lastActivityMode = void 0;
514960
+ this.updateActivityPane();
514961
+ this.state.ui.requestRender(true);
514962
+ }
514638
514963
  toggleTodoPanelExpansion() {
514639
514964
  this.state.todoPanel.toggleExpanded();
514640
514965
  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.50",
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": {