comfyui-mcp 0.52.67 → 0.52.69

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.
@@ -16381,7 +16381,7 @@ CHECKED FOR YOU: the graph read this message prescribes was just run, and it ` +
16381
16381
  // and refuse to report freed:true when a device is still pinned.
16382
16382
  return annotateFreeVramAck(ctx, res);
16383
16383
  }),
16384
- def("panel_show_media", "Display one or more images or videos directly in the panel chat. Use this whenever the user asks to SEE or SHOW a file — a disk path you composited/downloaded/generated (absolute path on the orchestrator host) OR a ComfyUI output ref ({ filename, subfolder?, type? }). Items are rendered as media cards in the agent chat area; supply optional captions. Max 8 items per call. A path item OVER the 20 MB inline cap that is not under any directory ComfyUI serves can still be shown by passing stage:true on that item — the orchestrator COPIES it into <output>/_panel_staged (an opt-in, persistent disk write; 512 MB per-file and 2 GB total caps) and displays the copy by reference. NEVER describe an image with emoji or text placeholders — call this tool instead.", {
16384
+ def("panel_show_media", "Display one or more images, videos or AUDIO files directly in the panel chat. Use this whenever the user asks to SEE, SHOW, PLAY or HEAR a file — a disk path you composited/downloaded/generated (absolute path on the orchestrator host) OR a ComfyUI output ref ({ filename, subfolder?, type? }). Items are rendered as media cards in the agent chat area — audio gets a real player (.wav/.mp3/.flac/.ogg/.oga/.opus/.m4a/.aac), so a generated TTS or voice take is played back with this tool, not described; supply optional captions. You are never sent the audio yourself and cannot hear it. Max 8 items per call. A path item OVER the 20 MB inline cap that is not under any directory ComfyUI serves can still be shown by passing stage:true on that item — the orchestrator COPIES it into <output>/_panel_staged (an opt-in, persistent disk write; 512 MB per-file and 2 GB total caps) and displays the copy by reference. NEVER describe an image with emoji or text placeholders — call this tool instead.", {
16385
16385
  items: z
16386
16386
  .array(z.object({
16387
16387
  // #1968 — OPTIONAL in the shape, required by normalizeShowMediaItem
@@ -16472,14 +16472,97 @@ CHECKED FOR YOU: the graph read this message prescribes was just run, and it ` +
16472
16472
  ".m4v": "video/x-m4v",
16473
16473
  ".avi": "video/x-msvideo",
16474
16474
  };
16475
- // #2011family classification for the /view probe only. The path
16476
- // gate below still refuses audio; a /view ref is already forwarded
16477
- // unclassified, and without this set `take.wav` serving `image/png`
16478
- // is silently blessed. Keys match upload_image action:"audio" /
16479
- // image-management AUDIO_MIME the orchestrator's own audio set,
16480
- // not the panel renderer's (which also lists .opus/.oga).
16481
- const AUDIO_EXTS = new Set([".wav", ".mp3", ".flac", ".ogg", ".m4a", ".aac"]);
16475
+ // #1572audio was refused outright ("unsupported file type \".wav\""),
16476
+ // so a TTS/voice-clone session could not play a generated take back to
16477
+ // the user at all. Nothing else was missing: the panel has shipped a
16478
+ // real `<audio controls>` card since #710 (paintAudio, its own
16479
+ // AUDIO_EXTENSIONS allowlist, an "audible" disclosure in the reply, and
16480
+ // media persistence under mkind:"audio"), and composeShowMediaReply
16481
+ // already routes kind:"audio" to it and skips the video storyboard for
16482
+ // it. THIS gate was the only closed door.
16483
+ //
16484
+ // The set is the panel renderer's own AUDIO_EXTENSIONS, verbatim —
16485
+ // the gate's job is to refuse what the card cannot present, so the
16486
+ // card's allowlist is the authoritative answer. It is a superset of
16487
+ // what this codebase already calls audio elsewhere (upload_image
16488
+ // action:"audio" and services/image-management's AUDIO_MIME:
16489
+ // .wav/.mp3/.flac/.ogg/.m4a/.aac) and of what ComfyUI's own save nodes
16490
+ // actually write (comfy_api AudioSaveHelper._FORMATS is
16491
+ // {flac, mp3, opus} and it names the file `…_00001.<format>`, so
16492
+ // SaveAudio/SaveAudioMP3/SaveAudioOpus/SaveAudioAdvanced emit exactly
16493
+ // .flac/.mp3/.opus; .wav is what VHS and the TTS packs write).
16494
+ //
16495
+ // Deliberately NARROWER than the orchestrator's own AUDIO_MIME_BY_EXT
16496
+ // (audio-attachment.ts), which additionally lists webm/weba/wave/m4b/
16497
+ // mpga/mp2: `.webm` is ALREADY in VIDEO_EXTS above and claiming it as
16498
+ // audio here would demote an existing video card, and the rest are not
16499
+ // in the panel's renderer set — admitting them would pass this gate only
16500
+ // to reach a link card, i.e. widen the allowlist without widening what
16501
+ // can be shown.
16502
+ const AUDIO_EXTS = new Set([
16503
+ ".wav",
16504
+ ".mp3",
16505
+ ".flac",
16506
+ ".ogg",
16507
+ ".oga",
16508
+ ".opus",
16509
+ ".m4a",
16510
+ ".aac",
16511
+ ]);
16512
+ // MEASURED in Chromium against real ffmpeg-encoded files served as
16513
+ // `data:` URLs, not derived from a table — the same failure #811 fixed
16514
+ // for video bites harder here, because a data URL's declared type is
16515
+ // the ONLY type the browser gets and a wrong one is refused before any
16516
+ // decoder runs ("MEDIA_ELEMENT_ERROR: Unable to load URL due to content
16517
+ // type"). Two entries would have been wrong if copied from elsewhere in
16518
+ // this codebase:
16519
+ // - `.opus` → `audio/opus` (what audio-attachment.ts uses, and the
16520
+ // natural guess) is REFUSED by Chromium; `audio/ogg` decodes. That
16521
+ // matters because .opus is a core ComfyUI SaveAudio output.
16522
+ // - `.flac` → `audio/x-flac` is REFUSED; `audio/flac` decodes.
16523
+ const AUDIO_MIME = {
16524
+ ".wav": "audio/wav",
16525
+ ".mp3": "audio/mpeg",
16526
+ ".flac": "audio/flac",
16527
+ ".ogg": "audio/ogg",
16528
+ ".oga": "audio/ogg",
16529
+ ".opus": "audio/ogg",
16530
+ ".m4a": "audio/mp4",
16531
+ ".aac": "audio/aac",
16532
+ };
16482
16533
  const MAX_BYTES = 20 * 1024 * 1024; // 20 MB
16534
+ // Capability refusal must be a preflight, before the loop can perform
16535
+ // the opt-in stage:true filesystem write. A mixed batch such as a
16536
+ // staged image followed by audio must not leave a persistent copy when
16537
+ // a proven-old panel will refuse the audio item. The same preflight also
16538
+ // keeps local audio out of the known headless clients: they have no
16539
+ // audio painter, and #2018's honest accounting cannot make an absent
16540
+ // player render the item.
16541
+ const panelCompatibility = {
16542
+ reading: tabPanelVersionReading(ctx),
16543
+ advertisedKinds: tabAdvertisedShowMediaKinds(ctx),
16544
+ };
16545
+ const knownAudioItems = [];
16546
+ const knownAudioPaths = [];
16547
+ for (const item of items) {
16548
+ const src = item.source;
16549
+ const filename = "path" in src ? src.path : src.filename;
16550
+ if (AUDIO_EXTS.has(extname(filename).toLowerCase())) {
16551
+ knownAudioItems.push({ kind: "audio", filename });
16552
+ if ("path" in src)
16553
+ knownAudioPaths.push(filename);
16554
+ }
16555
+ }
16556
+ if (knownAudioItems.length > 0) {
16557
+ const blocked = showMediaItemsPanelCannotPaint(knownAudioItems, panelCompatibility);
16558
+ if (blocked.length > 0) {
16559
+ return fail(formatShowMediaKindUnsupported(blocked));
16560
+ }
16561
+ if (knownAudioPaths.length > 0 && resolveClientKind(ctx).kind === "headless") {
16562
+ return fail("panel_show_media cannot send audio to this headless client: it has no audio player. " +
16563
+ "Use an interactive ComfyUI browser panel tab to play the audio instead.");
16564
+ }
16565
+ }
16483
16566
  const resolved = [];
16484
16567
  /** Oversized items that took the /view reference route instead (#648). */
16485
16568
  const forwarded = [];
@@ -16537,8 +16620,16 @@ CHECKED FOR YOU: the graph read this message prescribes was just run, and it ` +
16537
16620
  mime = VIDEO_MIME[ext];
16538
16621
  kind = "video";
16539
16622
  }
16623
+ else if (AUDIO_EXTS.has(ext)) {
16624
+ mime = AUDIO_MIME[ext];
16625
+ kind = "audio";
16626
+ }
16540
16627
  else {
16541
- return fail("unsupported file type \"" + ext + "\" (allowed: " + [...IMAGE_EXTS, ...VIDEO_EXTS].join(", ") + "): " + p);
16628
+ // Still an explicit ALLOWLIST, and still the whole gate: anything
16629
+ // not named in one of the three sets above is refused here, before
16630
+ // its bytes are read. Widening it by three more extensions is not
16631
+ // a licence to sniff or to fall back to a denylist.
16632
+ return fail("unsupported file type \"" + ext + "\" (allowed: " + [...IMAGE_EXTS, ...VIDEO_EXTS, ...AUDIO_EXTS].join(", ") + "): " + p);
16542
16633
  }
16543
16634
  if (stat.size > MAX_BYTES) {
16544
16635
  // Too big to INLINE is not the same as too big to SHOW. When the
@@ -16696,8 +16787,7 @@ CHECKED FOR YOU: the graph read this message prescribes was just run, and it ` +
16696
16787
  // panel that omitted the field is never told to update. Image/video
16697
16788
  // skip the check entirely.
16698
16789
  const blocked = showMediaItemsPanelCannotPaint(resolved, {
16699
- reading: tabPanelVersionReading(ctx),
16700
- advertisedKinds: tabAdvertisedShowMediaKinds(ctx),
16790
+ ...panelCompatibility,
16701
16791
  });
16702
16792
  if (blocked.length > 0) {
16703
16793
  return fail(formatShowMediaKindUnsupported(blocked));