@volter-ai-dev/supercode-ui 0.1.21 → 0.1.23

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/composer.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/composer.jsx
2
- import { useEffect, useRef, useState } from "preact/hooks";
2
+ import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "preact/hooks";
3
3
 
4
4
  // core.mjs
5
5
  var HARNESS_NAMES = Object.freeze({
@@ -129,7 +129,8 @@ function UiIcon({ name, size = 16, class: className = "" }) {
129
129
  }
130
130
 
131
131
  // src/context.jsx
132
- import { jsx as jsx2, jsxs as jsxs2 } from "preact/jsx-runtime";
132
+ import { useEffect, useRef, useState } from "preact/hooks";
133
+ import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "preact/jsx-runtime";
133
134
  var MAX_CONTEXT_ITEMS = 32;
134
135
  var MAX_IMAGE_ITEMS = 4;
135
136
  var MAX_IMAGE_BYTES = 5 * 1024 * 1024;
@@ -268,17 +269,18 @@ function ContinuationBar({ state, adapter, labels = DEFAULT_LABELS }) {
268
269
  }
269
270
  function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`, pendingStatus = null, restoreDraft = null, onPending, onDraftRestored }) {
270
271
  const remembered = composerMemory.get(memoryKey) ?? { draft: state.savedDraft, context: [], images: [], queue: [] };
271
- const [draft, setDraft] = useState(remembered.draft);
272
- const [context, setContext] = useState(remembered.context ?? []);
273
- const [images, setImages] = useState(remembered.images ?? []);
274
- const [queue, setQueue] = useState((remembered.queue ?? []).map((item) => ({ ...item, context: item.context ?? [], images: item.images ?? [] })));
275
- const [dispatching, setDispatching] = useState(false);
276
- const [picking, setPicking] = useState(false);
277
- const [pickerError, setPickerError] = useState(null);
278
- const textarea = useRef(null);
272
+ const [draft, setDraft] = useState2(remembered.draft);
273
+ const [context, setContext] = useState2(remembered.context ?? []);
274
+ const [images, setImages] = useState2(remembered.images ?? []);
275
+ const [queue, setQueue] = useState2((remembered.queue ?? []).map((item) => ({ ...item, context: item.context ?? [], images: item.images ?? [] })));
276
+ const [dispatching, setDispatching] = useState2(false);
277
+ const [picking, setPicking] = useState2(false);
278
+ const [dragging, setDragging] = useState2(false);
279
+ const [pickerError, setPickerError] = useState2(null);
280
+ const textarea = useRef2(null);
279
281
  useAutosizeTextarea(textarea, draft);
280
282
  const remember = (nextDraft, nextContext, nextImages, nextQueue) => boundedSet(composerMemory, memoryKey, { draft: nextDraft, context: nextContext, images: nextImages, queue: nextQueue });
281
- useEffect(() => {
283
+ useEffect2(() => {
282
284
  remember(draft, context, images, queue);
283
285
  }, [draft, context, images, memoryKey, queue]);
284
286
  const updateQueue = (update) => setQueue((items) => {
@@ -288,7 +290,7 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
288
290
  });
289
291
  const queueBlocked = state.busy || pendingStatus !== null || dispatching;
290
292
  const queuesNewMessage = state.busy || pendingStatus === "sending" || pendingStatus === "failed" || dispatching;
291
- useEffect(() => {
293
+ useEffect2(() => {
292
294
  if (!queueBlocked && state.canSend && queue.length) {
293
295
  const [next, ...rest] = queue;
294
296
  setDispatching(true);
@@ -298,13 +300,13 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
298
300
  adapter.onIntent({ action: "send", text: next.text, ...next.context.length ? { context: next.context } : {}, ...next.images.length ? { images: next.images } : {} });
299
301
  }
300
302
  }, [adapter, draft, memoryKey, onPending, queue, queueBlocked, state.canSend]);
301
- useEffect(() => {
303
+ useEffect2(() => {
302
304
  if (pendingStatus !== null || state.busy) setDispatching(false);
303
305
  }, [pendingStatus, state.busy]);
304
- useEffect(() => {
306
+ useEffect2(() => {
305
307
  textarea.current?.focus({ preventScroll: true });
306
308
  }, [memoryKey]);
307
- useEffect(() => {
309
+ useEffect2(() => {
308
310
  if (!restoreDraft) return;
309
311
  setDraft(restoreDraft.text);
310
312
  const restoredContext = normalizeContext(restoreDraft.context);
@@ -315,7 +317,7 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
315
317
  textarea.current?.focus({ preventScroll: true });
316
318
  onDraftRestored?.(restoreDraft.id);
317
319
  }, [onDraftRestored, restoreDraft?.id]);
318
- useEffect(() => {
320
+ useEffect2(() => {
319
321
  const timer = setTimeout(() => adapter.onIntent({ action: "draft", text: draft }), 250);
320
322
  return () => clearTimeout(timer);
321
323
  }, [adapter, draft]);
@@ -339,13 +341,17 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
339
341
  });
340
342
  }).catch((error) => setPickerError(error instanceof Error ? error.message : "Could not attach context.")).finally(() => setPicking(false));
341
343
  };
342
- const pasteImages = (event) => {
343
- const files = Array.from(event.clipboardData?.files ?? []).filter((file) => file.type.startsWith("image/"));
344
- if (!files.length) return;
345
- event.preventDefault();
344
+ const addImageFiles = (value, source) => {
345
+ const allFiles = Array.from(value ?? []);
346
+ if (!allFiles.length) return false;
347
+ const files = allFiles.filter((file) => file.type.startsWith("image/"));
348
+ if (files.length !== allFiles.length) {
349
+ setPickerError("Drop or paste PNG, JPEG, GIF, or WebP images.");
350
+ return true;
351
+ }
346
352
  if (images.length + files.length > MAX_IMAGE_ITEMS) {
347
353
  setPickerError(`Attach at most ${MAX_IMAGE_ITEMS} images.`);
348
- return;
354
+ return true;
349
355
  }
350
356
  setPicking(true);
351
357
  setPickerError(null);
@@ -353,11 +359,19 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
353
359
  const next = mergeImages(current, picked);
354
360
  remember(draft, context, next, queue);
355
361
  return next;
356
- }), (error) => setPickerError(error instanceof Error ? error.message : "Could not paste image.")).finally(() => setPicking(false));
362
+ }), (error) => setPickerError(error instanceof Error ? error.message : `Could not ${source} image.`)).finally(() => setPicking(false));
363
+ return true;
364
+ };
365
+ const pasteImages = (event) => {
366
+ if (addImageFiles(event.clipboardData?.files, "paste")) event.preventDefault();
367
+ };
368
+ const dropImages = (event) => {
369
+ setDragging(false);
370
+ if (addImageFiles(event.dataTransfer?.files, "drop")) event.preventDefault();
357
371
  };
358
372
  const send = () => {
359
373
  const text = draft.trim();
360
- if (!text) return;
374
+ if (!text && !images.length) return;
361
375
  const message = { text, context, images };
362
376
  if (queuesNewMessage) updateQueue((items) => [...items, message]);
363
377
  else if (state.canSend) {
@@ -378,7 +392,7 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
378
392
  ] }),
379
393
  queue.map((item, index) => /* @__PURE__ */ jsxs3("span", { children: [
380
394
  /* @__PURE__ */ jsxs3("span", { children: [
381
- item.text,
395
+ item.text || "Image attachment",
382
396
  item.context.length + item.images.length ? /* @__PURE__ */ jsxs3("small", { children: [
383
397
  item.context.length + item.images.length,
384
398
  " attached"
@@ -398,7 +412,16 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
398
412
  return next;
399
413
  }) }),
400
414
  pickerError ? /* @__PURE__ */ jsx3("small", { class: "scui-context-error", role: "alert", children: pickerError }) : null,
401
- /* @__PURE__ */ jsxs3("div", { class: "scui-envelope", children: [
415
+ /* @__PURE__ */ jsxs3("div", { class: `scui-envelope${dragging ? " scui-drop-target" : ""}`, onDragEnter: (event) => {
416
+ if (Array.from(event.dataTransfer?.types ?? []).includes("Files")) {
417
+ event.preventDefault();
418
+ setDragging(true);
419
+ }
420
+ }, onDragOver: (event) => {
421
+ if (Array.from(event.dataTransfer?.types ?? []).includes("Files")) event.preventDefault();
422
+ }, onDragLeave: (event) => {
423
+ if (!event.currentTarget.contains(event.relatedTarget)) setDragging(false);
424
+ }, onDrop: dropImages, children: [
402
425
  adapter.pickContext && state.mode === "control" ? /* @__PURE__ */ jsx3("button", { class: "scui-attach", type: "button", "aria-label": "Attach files or images", disabled: picking || context.length >= MAX_CONTEXT_ITEMS && images.length >= MAX_IMAGE_ITEMS, onClick: pickContext, children: picking ? /* @__PURE__ */ jsx3("i", { class: "scui-control-spinner" }) : /* @__PURE__ */ jsx3(UiIcon, { name: "attach", size: 17 }) }) : null,
403
426
  /* @__PURE__ */ jsx3("textarea", { ref: textarea, rows: 1, "aria-label": `Message ${harnessDisplayName(state.harness) || "agent"}`, placeholder: state.startup !== "ready" ? "Connecting\u2026" : pendingStatus === "failed" ? "Retry or edit the unsent message\u2026" : pendingStatus === "editing" ? "Edit and resend\u2026" : queueBlocked ? "Queue a follow-up\u2026" : labels.askAgent, value: draft, disabled: state.mode !== "control" && !state.canSend, onPaste: pasteImages, onInput: (event) => {
404
427
  const value = event.currentTarget.value;
@@ -412,7 +435,7 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
412
435
  } }),
413
436
  /* @__PURE__ */ jsxs3("span", { children: [
414
437
  state.busy ? /* @__PURE__ */ jsx3("button", { class: "scui-stop", type: "button", "aria-label": "Stop agent", disabled: !state.canInterrupt, onClick: () => adapter.onIntent({ action: "interrupt" }), children: /* @__PURE__ */ jsx3(UiIcon, { name: "stop", size: 15 }) }) : null,
415
- /* @__PURE__ */ jsx3("button", { class: "scui-send", type: "button", "aria-label": queuesNewMessage ? "Queue message" : "Send message", disabled: !draft.trim() || !queuesNewMessage && !state.canSend, onClick: send, children: /* @__PURE__ */ jsx3(UiIcon, { name: queuesNewMessage ? "plus" : "send", size: 17 }) })
438
+ /* @__PURE__ */ jsx3("button", { class: "scui-send", type: "button", "aria-label": queuesNewMessage ? "Queue message" : "Send message", disabled: !draft.trim() && !images.length || !queuesNewMessage && !state.canSend, onClick: send, children: /* @__PURE__ */ jsx3(UiIcon, { name: queuesNewMessage ? "plus" : "send", size: 17 }) })
416
439
  ] })
417
440
  ] })
418
441
  ] });
package/conversation.d.ts CHANGED
@@ -15,7 +15,9 @@ export type {
15
15
  export {
16
16
  ActivityGroup,
17
17
  Conversation,
18
+ ImageViewer,
18
19
  LoadingStatus,
20
+ MessageImages,
19
21
  RequestCard,
20
22
  SessionDetails,
21
23
  TaskPlan,
package/conversation.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  // src/conversation.jsx
2
- import { Fragment as Fragment2 } from "preact";
3
- import { useEffect as useEffect2, useId, useLayoutEffect, useRef as useRef2, useState } from "preact/hooks";
2
+ import { Fragment as Fragment3 } from "preact";
3
+ import { useEffect as useEffect3, useId, useLayoutEffect, useRef as useRef3, useState as useState2 } from "preact/hooks";
4
4
 
5
5
  // core.mjs
6
6
  var HARNESS_NAMES = Object.freeze({
@@ -622,18 +622,108 @@ function UiIcon({ name, size = 16, class: className = "" }) {
622
622
  }
623
623
 
624
624
  // src/context.jsx
625
- import { jsx as jsx3, jsxs as jsxs2 } from "preact/jsx-runtime";
625
+ import { useEffect as useEffect2, useRef as useRef2, useState } from "preact/hooks";
626
+ import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs2 } from "preact/jsx-runtime";
626
627
  var MAX_IMAGE_BYTES = 5 * 1024 * 1024;
627
- function MessageImages({ items }) {
628
+ function imageFilename(label) {
629
+ const value = label.trim().replace(/[\\/:*?"<>|]+/g, "-");
630
+ return value || "image";
631
+ }
632
+ function ImageViewer({ items, index, adapter, onChange, onClose }) {
633
+ const dialog = useRef2(null);
634
+ const reset = useRef2(null);
635
+ const [copyState, setCopyState2] = useState("idle");
636
+ const item = items[index];
637
+ const remote = item?.url?.startsWith("http://") || item?.url?.startsWith("https://");
638
+ useEffect2(() => {
639
+ if (!dialog.current?.open) dialog.current?.showModal();
640
+ return () => clearTimeout(reset.current);
641
+ }, []);
642
+ useEffect2(() => {
643
+ clearTimeout(reset.current);
644
+ setCopyState2("idle");
645
+ }, [index]);
646
+ if (!item?.url) return null;
647
+ const move = (amount) => onChange((index + amount + items.length) % items.length);
648
+ const copy = async () => {
649
+ try {
650
+ await adapter.copyText(item.url);
651
+ setCopyState2("copied");
652
+ } catch {
653
+ setCopyState2("failed");
654
+ }
655
+ clearTimeout(reset.current);
656
+ reset.current = setTimeout(() => setCopyState2("idle"), 1500);
657
+ };
658
+ const close = () => dialog.current?.close();
659
+ return /* @__PURE__ */ jsxs2(
660
+ "dialog",
661
+ {
662
+ ref: dialog,
663
+ class: "scui-image-viewer",
664
+ "aria-label": `Image preview: ${item.label}`,
665
+ onClose,
666
+ onClick: (event) => {
667
+ if (event.target === event.currentTarget) close();
668
+ },
669
+ onKeyDown: (event) => {
670
+ if (items.length < 2 || !["ArrowLeft", "ArrowRight"].includes(event.key)) return;
671
+ event.preventDefault();
672
+ move(event.key === "ArrowLeft" ? -1 : 1);
673
+ },
674
+ children: [
675
+ /* @__PURE__ */ jsxs2("header", { children: [
676
+ /* @__PURE__ */ jsxs2("span", { children: [
677
+ /* @__PURE__ */ jsx3("strong", { children: item.label }),
678
+ items.length > 1 ? /* @__PURE__ */ jsxs2("small", { children: [
679
+ index + 1,
680
+ " of ",
681
+ items.length
682
+ ] }) : null
683
+ ] }),
684
+ /* @__PURE__ */ jsxs2("nav", { "aria-label": "Image actions", children: [
685
+ remote && adapter?.copyText ? /* @__PURE__ */ jsx3("button", { type: "button", "aria-label": copyState === "copied" ? "Image link copied" : copyState === "failed" ? "Could not copy image link" : "Copy image link", title: "Copy image link", "data-status": copyState, onClick: copy, children: /* @__PURE__ */ jsx3(UiIcon, { name: copyState === "copied" ? "check" : "copy", size: 16 }) }) : null,
686
+ remote ? /* @__PURE__ */ jsx3("a", { href: item.url, target: "_blank", rel: "noreferrer", "aria-label": "Open original image", title: "Open original image", children: /* @__PURE__ */ jsx3(UiIcon, { name: "image", size: 16 }) }) : /* @__PURE__ */ jsx3("a", { href: item.url, download: imageFilename(item.label), "aria-label": "Download image", title: "Download image", children: /* @__PURE__ */ jsx3(UiIcon, { name: "down", size: 16 }) }),
687
+ /* @__PURE__ */ jsx3("button", { type: "button", "aria-label": "Close image preview", title: "Close", onClick: close, children: /* @__PURE__ */ jsx3(UiIcon, { name: "close", size: 17 }) })
688
+ ] })
689
+ ] }),
690
+ /* @__PURE__ */ jsxs2("figure", { children: [
691
+ /* @__PURE__ */ jsx3("img", { src: item.url, alt: item.label }),
692
+ items.length > 1 ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
693
+ /* @__PURE__ */ jsx3("button", { type: "button", class: "scui-image-previous", "aria-label": "Previous image", onClick: () => move(-1), children: /* @__PURE__ */ jsx3(UiIcon, { name: "chevron", size: 19 }) }),
694
+ /* @__PURE__ */ jsx3("button", { type: "button", class: "scui-image-next", "aria-label": "Next image", onClick: () => move(1), children: /* @__PURE__ */ jsx3(UiIcon, { name: "chevron", size: 19 }) })
695
+ ] }) : null
696
+ ] })
697
+ ]
698
+ }
699
+ );
700
+ }
701
+ function MessageImages({ items, adapter }) {
702
+ const [active, setActive] = useState(null);
703
+ const opener = useRef2(null);
628
704
  if (!items?.length) return null;
629
- return /* @__PURE__ */ jsx3("div", { class: "scui-message-images", children: items.map((item, index) => item.url ? /* @__PURE__ */ jsx3("img", { src: item.url, alt: item.label }, item.id ?? `${item.label}:${index}`) : /* @__PURE__ */ jsxs2("span", { children: [
630
- /* @__PURE__ */ jsx3(UiIcon, { name: "image", size: 14 }),
631
- item.label
632
- ] }, item.id ?? `${item.label}:${index}`)) });
705
+ const viewable = items.filter((item) => item.url);
706
+ const close = () => {
707
+ setActive(null);
708
+ requestAnimationFrame(() => opener.current?.focus({ preventScroll: true }));
709
+ };
710
+ return /* @__PURE__ */ jsxs2(Fragment2, { children: [
711
+ /* @__PURE__ */ jsx3("div", { class: "scui-message-images", "aria-label": "Message images", children: items.map((item, index) => item.url ? /* @__PURE__ */ jsx3("button", { type: "button", "aria-label": `View image ${item.label}`, onClick: (event) => {
712
+ opener.current = event.currentTarget;
713
+ setActive(viewable.indexOf(item));
714
+ }, children: /* @__PURE__ */ jsx3("img", { src: item.url, alt: "" }) }, item.id ?? `${item.label}:${index}`) : /* @__PURE__ */ jsxs2("span", { "data-unavailable": "true", children: [
715
+ /* @__PURE__ */ jsx3(UiIcon, { name: "image", size: 14 }),
716
+ /* @__PURE__ */ jsxs2("span", { children: [
717
+ /* @__PURE__ */ jsx3("strong", { children: item.label }),
718
+ /* @__PURE__ */ jsx3("small", { children: "Preview unavailable" })
719
+ ] })
720
+ ] }, item.id ?? `${item.label}:${index}`)) }),
721
+ active !== null && viewable[active] ? /* @__PURE__ */ jsx3(ImageViewer, { items: viewable, index: active, adapter, onChange: setActive, onClose: close }) : null
722
+ ] });
633
723
  }
634
724
 
635
725
  // src/conversation.jsx
636
- import { Fragment as Fragment3, jsx as jsx4, jsxs as jsxs3 } from "preact/jsx-runtime";
726
+ import { Fragment as Fragment4, jsx as jsx4, jsxs as jsxs3 } from "preact/jsx-runtime";
637
727
  function LoadingStatus({ state, compact = false }) {
638
728
  const copy = {
639
729
  connecting: ["Connecting to coding agents", "Checking installed harnesses and capabilities.", 0],
@@ -682,9 +772,9 @@ function ContextDisclosure({ context }) {
682
772
  ] });
683
773
  }
684
774
  function MessageMeta({ entry, adapter }) {
685
- const [copyState, setCopyState2] = useState("idle");
686
- const reset = useRef2(null);
687
- useEffect2(() => () => clearTimeout(reset.current), []);
775
+ const [copyState, setCopyState2] = useState2("idle");
776
+ const reset = useRef3(null);
777
+ useEffect3(() => () => clearTimeout(reset.current), []);
688
778
  const date = entry.ts === null ? null : new Date(entry.ts);
689
779
  const validDate = date && Number.isFinite(date.valueOf()) ? date : null;
690
780
  if (!validDate && (!adapter?.copyText || !entry.text)) return null;
@@ -705,33 +795,33 @@ function MessageMeta({ entry, adapter }) {
705
795
  ] });
706
796
  }
707
797
  var TOOL_ICONS = {
708
- read: () => /* @__PURE__ */ jsxs3(Fragment3, { children: [
798
+ read: () => /* @__PURE__ */ jsxs3(Fragment4, { children: [
709
799
  /* @__PURE__ */ jsx4("path", { d: "M5 2.75h7.25L15 5.5v7.75A1.75 1.75 0 0 1 13.25 15h-8.5A1.75 1.75 0 0 1 3 13.25v-8.5A2 2 0 0 1 5 2.75Z" }),
710
800
  /* @__PURE__ */ jsx4("path", { d: "M12 2.9v3h2.85M6 9h6M6 12h4" })
711
801
  ] }),
712
- search: () => /* @__PURE__ */ jsxs3(Fragment3, { children: [
802
+ search: () => /* @__PURE__ */ jsxs3(Fragment4, { children: [
713
803
  /* @__PURE__ */ jsx4("circle", { cx: "8", cy: "8", r: "4.5" }),
714
804
  /* @__PURE__ */ jsx4("path", { d: "m11.5 11.5 3 3" })
715
805
  ] }),
716
- edit: () => /* @__PURE__ */ jsxs3(Fragment3, { children: [
806
+ edit: () => /* @__PURE__ */ jsxs3(Fragment4, { children: [
717
807
  /* @__PURE__ */ jsx4("path", { d: "m11.75 3.25 3 3-8.5 8.5-3.75.75.75-3.75 8.5-8.5Z" }),
718
808
  /* @__PURE__ */ jsx4("path", { d: "m10 5 3 3" })
719
809
  ] }),
720
- command: () => /* @__PURE__ */ jsx4(Fragment3, { children: /* @__PURE__ */ jsx4("path", { d: "m3 5 3 3-3 3M8 12h6" }) }),
721
- test: () => /* @__PURE__ */ jsxs3(Fragment3, { children: [
810
+ command: () => /* @__PURE__ */ jsx4(Fragment4, { children: /* @__PURE__ */ jsx4("path", { d: "m3 5 3 3-3 3M8 12h6" }) }),
811
+ test: () => /* @__PURE__ */ jsxs3(Fragment4, { children: [
722
812
  /* @__PURE__ */ jsx4("path", { d: "M6 2.5v3L3 12a2 2 0 0 0 1.8 3h8.4a2 2 0 0 0 1.8-3l-3-6.5v-3M5 9h8" }),
723
813
  /* @__PURE__ */ jsx4("path", { d: "M5 2.5h8" })
724
814
  ] }),
725
- web: () => /* @__PURE__ */ jsxs3(Fragment3, { children: [
815
+ web: () => /* @__PURE__ */ jsxs3(Fragment4, { children: [
726
816
  /* @__PURE__ */ jsx4("circle", { cx: "9", cy: "9", r: "6.5" }),
727
817
  /* @__PURE__ */ jsx4("path", { d: "M2.75 9h12.5M9 2.5c2 1.8 3 4 3 6.5s-1 4.7-3 6.5c-2-1.8-3-4-3-6.5s1-4.7 3-6.5Z" })
728
818
  ] }),
729
- agent: () => /* @__PURE__ */ jsxs3(Fragment3, { children: [
819
+ agent: () => /* @__PURE__ */ jsxs3(Fragment4, { children: [
730
820
  /* @__PURE__ */ jsx4("circle", { cx: "9", cy: "6", r: "2.5" }),
731
821
  /* @__PURE__ */ jsx4("path", { d: "M4 15c.4-3 2-4.5 5-4.5s4.6 1.5 5 4.5" })
732
822
  ] }),
733
- plan: () => /* @__PURE__ */ jsx4(Fragment3, { children: /* @__PURE__ */ jsx4("path", { d: "m3 5 1 1 2-2M3 9l1 1 2-2M3 13l1 1 2-2M8 5h7M8 9h7M8 13h7" }) }),
734
- other: () => /* @__PURE__ */ jsxs3(Fragment3, { children: [
823
+ plan: () => /* @__PURE__ */ jsx4(Fragment4, { children: /* @__PURE__ */ jsx4("path", { d: "m3 5 1 1 2-2M3 9l1 1 2-2M3 13l1 1 2-2M8 5h7M8 9h7M8 13h7" }) }),
824
+ other: () => /* @__PURE__ */ jsxs3(Fragment4, { children: [
735
825
  /* @__PURE__ */ jsx4("path", { d: "M9 2.5v3M9 12.5v3M2.5 9h3M12.5 9h3" }),
736
826
  /* @__PURE__ */ jsx4("circle", { cx: "9", cy: "9", r: "3.5" })
737
827
  ] })
@@ -797,9 +887,9 @@ function ToolMetrics({ presentation }) {
797
887
  }
798
888
  function PendingElapsed({ now }) {
799
889
  const clock = now ?? Date.now;
800
- const started = useRef2(clock());
801
- const [elapsed, setElapsed] = useState(0);
802
- useEffect2(() => {
890
+ const started = useRef3(clock());
891
+ const [elapsed, setElapsed] = useState2(0);
892
+ useEffect3(() => {
803
893
  const timer = setInterval(() => setElapsed(Math.max(0, clock() - started.current)), 1e3);
804
894
  return () => clearInterval(timer);
805
895
  }, [clock]);
@@ -840,7 +930,7 @@ function SearchPreview({ presentation }) {
840
930
  ] }) : null,
841
931
  lines.length ? /* @__PURE__ */ jsx4("ol", { children: lines.map((line, index) => {
842
932
  const match = /^(.*?):(\d+)(?::(\d+))?:(.*)$/.exec(line);
843
- return /* @__PURE__ */ jsx4("li", { children: match ? /* @__PURE__ */ jsxs3(Fragment3, { children: [
933
+ return /* @__PURE__ */ jsx4("li", { children: match ? /* @__PURE__ */ jsxs3(Fragment4, { children: [
844
934
  /* @__PURE__ */ jsx4("code", { children: match[1] }),
845
935
  /* @__PURE__ */ jsxs3("small", { children: [
846
936
  match[2],
@@ -873,9 +963,9 @@ function ToolPreview({ presentation, entry }) {
873
963
  return presentation.preview ? /* @__PURE__ */ jsx4("pre", { class: "scui-tool-output", "data-error": failed, children: stripAnsi(presentation.preview) }) : null;
874
964
  }
875
965
  function ToolActions({ presentation, adapter }) {
876
- const [copied, setCopied] = useState(false);
877
- const reset = useRef2(null);
878
- useEffect2(() => () => clearTimeout(reset.current), []);
966
+ const [copied, setCopied] = useState2(false);
967
+ const reset = useRef3(null);
968
+ useEffect3(() => () => clearTimeout(reset.current), []);
879
969
  if (!adapter?.copyText) return null;
880
970
  const action = presentation.command ? ["Copy command", presentation.command] : presentation.path ? ["Copy path", presentation.path] : presentation.url ? ["Copy URL", presentation.url] : presentation.query ? ["Copy query", presentation.query] : null;
881
971
  const copy = async () => {
@@ -922,7 +1012,7 @@ function TranscriptEntry({ entry, state, adapter }) {
922
1012
  }
923
1013
  if (entry.role === "notice" || entry.role === "system") return /* @__PURE__ */ jsx4("div", { class: "scui-notice", "data-code": entry.code, children: entry.text });
924
1014
  return /* @__PURE__ */ jsxs3("article", { class: "scui-message", "data-role": entry.role, "aria-label": `${entry.role === "user" ? "Your" : "Assistant"} message`, children: [
925
- /* @__PURE__ */ jsx4(MessageImages, { items: entry.images }),
1015
+ /* @__PURE__ */ jsx4(MessageImages, { items: entry.images, adapter }),
926
1016
  /* @__PURE__ */ jsx4(Markdown, { value: entry.text, copyText: adapter?.copyText }),
927
1017
  /* @__PURE__ */ jsx4(ContextDisclosure, { context: entry.context }),
928
1018
  entry.truncated ? /* @__PURE__ */ jsx4("small", { class: "scui-truncated", children: "Entry truncated by host \xB7 load native session for the complete value" }) : null,
@@ -931,14 +1021,14 @@ function TranscriptEntry({ entry, state, adapter }) {
931
1021
  }
932
1022
  function ToolRow({ entry, workspace, open = false, adapter }) {
933
1023
  const presentation = entry.presentation ?? createToolPresentation(entry);
934
- const [expanded, setExpanded] = useState(open || entry.status === "pending");
935
- useEffect2(() => {
1024
+ const [expanded, setExpanded] = useState2(open || entry.status === "pending");
1025
+ useEffect3(() => {
936
1026
  if (entry.status === "pending") setExpanded(true);
937
1027
  }, [entry.status]);
938
1028
  const target = compactToolTarget(presentation.target, workspace);
939
1029
  const hasDetail = Boolean(entry.arguments || entry.resultText || presentation.preview || presentation.fields.length);
940
1030
  const category = presentation.category ?? toolCategory(entry);
941
- const summary = /* @__PURE__ */ jsxs3(Fragment3, { children: [
1031
+ const summary = /* @__PURE__ */ jsxs3(Fragment4, { children: [
942
1032
  /* @__PURE__ */ jsx4(ToolIcon, { category }),
943
1033
  /* @__PURE__ */ jsx4("strong", { children: toolAction2(entry, category, presentation) }),
944
1034
  target ? /* @__PURE__ */ jsx4("code", { class: "scui-tool-target", title: presentation.target, children: target }) : null,
@@ -966,9 +1056,9 @@ function ToolRow({ entry, workspace, open = false, adapter }) {
966
1056
  function ActivityGroup({ entries, state, adapter }) {
967
1057
  if (entries.length === 1) return /* @__PURE__ */ jsx4("section", { class: "scui-activity", "data-single": "true", children: /* @__PURE__ */ jsx4(ToolRow, { entry: entries[0], workspace: state.workspace, adapter }) });
968
1058
  const active = entries.some((entry) => entry.status === "pending");
969
- const [open, setOpen] = useState(active);
1059
+ const [open, setOpen] = useState2(active);
970
1060
  const id = useId();
971
- useEffect2(() => {
1061
+ useEffect3(() => {
972
1062
  if (active) setOpen(true);
973
1063
  }, [active]);
974
1064
  return /* @__PURE__ */ jsxs3("section", { class: "scui-activity", children: [
@@ -1044,9 +1134,9 @@ function SessionDetails({ semantics }) {
1044
1134
  }
1045
1135
  var conversationMemory = /* @__PURE__ */ new Map();
1046
1136
  function ConversationAnnouncements({ state }) {
1047
- const previousBusy = useRef2(state.busy);
1048
- const [announcement, setAnnouncement] = useState("");
1049
- useEffect2(() => {
1137
+ const previousBusy = useRef3(state.busy);
1138
+ const [announcement, setAnnouncement] = useState2("");
1139
+ useEffect3(() => {
1050
1140
  if (previousBusy.current && !state.busy && !state.error) {
1051
1141
  setAnnouncement(`${harnessDisplayName(state.harness) || "Coding agent"} finished working`);
1052
1142
  }
@@ -1055,13 +1145,13 @@ function ConversationAnnouncements({ state }) {
1055
1145
  return /* @__PURE__ */ jsx4("span", { class: "scui-sr-only", role: "status", "aria-live": "polite", "aria-atomic": "true", children: announcement });
1056
1146
  }
1057
1147
  function Conversation({ state, adapter, components = {}, slots = {}, memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`, pending = null, unreadAfterMessages = null }) {
1058
- const scroller = useRef2(null);
1148
+ const scroller = useRef3(null);
1059
1149
  const remembered = conversationMemory.get(memoryKey) ?? { top: null, atBottom: true };
1060
- const [atBottom, setAtBottom] = useState(remembered.atBottom);
1061
- const earlierAnchor = useRef2(null);
1062
- const restored = useRef2(false);
1150
+ const [atBottom, setAtBottom] = useState2(remembered.atBottom);
1151
+ const earlierAnchor = useRef3(null);
1152
+ const restored = useRef3(false);
1063
1153
  const blocks = groupConversation(state.transcript);
1064
- const unreadBoundary = useRef2(Number.isSafeInteger(unreadAfterMessages) && unreadAfterMessages >= 0 ? unreadAfterMessages : null);
1154
+ const unreadBoundary = useRef3(Number.isSafeInteger(unreadAfterMessages) && unreadAfterMessages >= 0 ? unreadAfterMessages : null);
1065
1155
  const unreadBlock = unreadBoundary.current === null ? -1 : blocks.findIndex((block) => {
1066
1156
  const entries = block.kind === "activity" ? block.entries : [block.entry];
1067
1157
  return entries.some((entry) => Number.isSafeInteger(entry.messageIndex) && entry.messageIndex > unreadBoundary.current);
@@ -1120,12 +1210,12 @@ function Conversation({ state, adapter, components = {}, slots = {}, memoryKey =
1120
1210
  }, children: "Load earlier messages" }) : null,
1121
1211
  !blocks.length && state.startup !== "ready" ? /* @__PURE__ */ jsx4(LoadingStatus, { state }) : null,
1122
1212
  !blocks.length && state.startup === "ready" ? Empty ? /* @__PURE__ */ jsx4(Empty, { state, adapter, value: null }) : /* @__PURE__ */ jsx4("div", { class: "scui-empty", children: state.error ?? (state.harness ? `${harnessDisplayName(state.harness)} is listening. Say something.` : "No transcript yet.") }) : null,
1123
- blocks.map((block, index) => /* @__PURE__ */ jsxs3(Fragment2, { children: [
1213
+ blocks.map((block, index) => /* @__PURE__ */ jsxs3(Fragment3, { children: [
1124
1214
  index === unreadBlock ? /* @__PURE__ */ jsx4("div", { class: "scui-unread-divider", role: "separator", "aria-label": "New messages", children: /* @__PURE__ */ jsx4("span", { children: "New" }) }) : null,
1125
1215
  block.kind === "activity" ? /* @__PURE__ */ jsx4(Group, { value: block.entries, entries: block.entries, state, adapter }) : /* @__PURE__ */ jsx4(Entry, { value: block.entry, entry: block.entry, state, adapter })
1126
1216
  ] }, block.id)),
1127
1217
  pendingMessage ? /* @__PURE__ */ jsxs3("article", { class: "scui-message scui-pending", "data-role": "user", "data-status": pendingMessage.status, "aria-label": "Your pending message", children: [
1128
- /* @__PURE__ */ jsx4(MessageImages, { items: pendingMessage.images }),
1218
+ /* @__PURE__ */ jsx4(MessageImages, { items: pendingMessage.images, adapter }),
1129
1219
  /* @__PURE__ */ jsx4(Markdown, { value: pendingMessage.text, copyText: adapter?.copyText }),
1130
1220
  /* @__PURE__ */ jsx4(ContextDisclosure, { context: pendingMessage.context }),
1131
1221
  /* @__PURE__ */ jsxs3("footer", { children: [
@@ -1157,7 +1247,9 @@ function Conversation({ state, adapter, components = {}, slots = {}, memoryKey =
1157
1247
  export {
1158
1248
  ActivityGroup,
1159
1249
  Conversation,
1250
+ ImageViewer,
1160
1251
  LoadingStatus,
1252
+ MessageImages,
1161
1253
  RequestCard,
1162
1254
  SessionDetails,
1163
1255
  TaskPlan,