@volter-ai-dev/supercode-ui 0.1.12 → 0.1.14

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/messenger.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/messenger.jsx
2
- import { useEffect as useEffect4, useMemo as useMemo2, useRef as useRef3, useState as useState4 } from "preact/hooks";
2
+ import { useEffect as useEffect6, useId as useId2, useMemo as useMemo2, useRef as useRef5, useState as useState4 } from "preact/hooks";
3
3
 
4
4
  // core.mjs
5
5
  var HARNESS_NAMES = Object.freeze({
@@ -292,7 +292,8 @@ function readTranscript(value) {
292
292
  role: item.role,
293
293
  text: item.text,
294
294
  ts: nullableNumber(item.ts),
295
- truncated: item.truncated === true
295
+ truncated: item.truncated === true,
296
+ ...Number.isSafeInteger(item.messageIndex) && item.messageIndex > 0 ? { messageIndex: item.messageIndex } : {}
296
297
  };
297
298
  for (const key of ["label", "arguments", "resultText", "code"]) {
298
299
  if (typeof item[key] === "string") entry[key] = item[key];
@@ -463,7 +464,7 @@ function normalizeUiState(value) {
463
464
  savedDraft: string(raw.savedDraft),
464
465
  attention: Array.isArray(raw.attention) ? raw.attention.flatMap((candidate) => {
465
466
  const item = record(candidate);
466
- return item && typeof item.key === "string" && ["unseen", "finished", "failed"].includes(item.kind) ? [{ key: item.key, kind: item.kind, ...typeof item.preview === "string" ? { preview: item.preview } : {} }] : [];
467
+ return item && typeof item.key === "string" && ["unseen", "finished", "failed"].includes(item.kind) ? [{ key: item.key, kind: item.kind, ...typeof item.preview === "string" ? { preview: item.preview } : {}, ...Number.isSafeInteger(item.afterMessages) && item.afterMessages >= 0 ? { afterMessages: item.afterMessages } : {} }] : [];
467
468
  }) : [],
468
469
  sessions: readSessions(raw.sessions),
469
470
  attached: readAttached(raw.attached),
@@ -545,7 +546,7 @@ function canContinueHere(state) {
545
546
  }
546
547
  function operationLabel(operation) {
547
548
  if (!operation) return "";
548
- const labels = { discover: "Refreshing chats\u2026", observe: "Loading recent messages\u2026", attach: "Opening chat\u2026", resume: "Continuing here\u2026", branch: "Starting continuation\u2026", reduce: "Reducing context and verifying reversibility\u2026", terminal: "Preparing terminal handoff\u2026", export: "Exporting losslessly\u2026", refresh: "Retrying\u2026" };
549
+ const labels = { discover: "Refreshing chats\u2026", observe: "Loading recent messages\u2026", attach: "Opening chat\u2026", start: "Starting new chat\u2026", resume: "Continuing here\u2026", join: "Joining live session\u2026", detach: "Detaching to read-only\u2026", branch: "Starting continuation\u2026", reduce: "Reducing context and verifying reversibility\u2026", terminal: "Preparing terminal handoff\u2026", export: "Exporting losslessly\u2026", interrupt: "Stopping agent\u2026", respond: "Sending response\u2026", loadEarlier: "Loading earlier messages\u2026", loadSessions: "Loading more chats\u2026", refresh: "Retrying\u2026" };
549
550
  return labels[operation] ?? `${operation.replaceAll("_", " ")}\u2026`;
550
551
  }
551
552
  function terminalCommand(handoff) {
@@ -567,8 +568,65 @@ function boundedSet(map, key, value) {
567
568
  while (map.size > MEMORY_LIMIT) map.delete(map.keys().next().value);
568
569
  }
569
570
 
571
+ // src/icon.jsx
572
+ import { Fragment, jsx, jsxs } from "preact/jsx-runtime";
573
+ var ICONS = {
574
+ back: () => /* @__PURE__ */ jsx("path", { d: "m11.5 4.5-4.5 4.5 4.5 4.5" }),
575
+ check: () => /* @__PURE__ */ jsx("path", { d: "m4 9 3.25 3.25L14 5.5" }),
576
+ chevron: () => /* @__PURE__ */ jsx("path", { d: "m7 4.5 4.5 4.5L7 13.5" }),
577
+ close: () => /* @__PURE__ */ jsxs(Fragment, { children: [
578
+ /* @__PURE__ */ jsx("path", { d: "m4.75 4.75 8.5 8.5" }),
579
+ /* @__PURE__ */ jsx("path", { d: "m13.25 4.75-8.5 8.5" })
580
+ ] }),
581
+ copy: () => /* @__PURE__ */ jsxs(Fragment, { children: [
582
+ /* @__PURE__ */ jsx("rect", { x: "5", y: "5", width: "8", height: "8", rx: "1.5" }),
583
+ /* @__PURE__ */ jsx("path", { d: "M3 10.5V4.25C3 3.56 3.56 3 4.25 3h6.25" })
584
+ ] }),
585
+ down: () => /* @__PURE__ */ jsxs(Fragment, { children: [
586
+ /* @__PURE__ */ jsx("path", { d: "M9 3.5v10" }),
587
+ /* @__PURE__ */ jsx("path", { d: "m4.75 9.5 4.25 4 4.25-4" })
588
+ ] }),
589
+ menu: () => /* @__PURE__ */ jsxs(Fragment, { children: [
590
+ /* @__PURE__ */ jsx("circle", { cx: "4", cy: "9", r: "1.25", fill: "currentColor", stroke: "none" }),
591
+ /* @__PURE__ */ jsx("circle", { cx: "9", cy: "9", r: "1.25", fill: "currentColor", stroke: "none" }),
592
+ /* @__PURE__ */ jsx("circle", { cx: "14", cy: "9", r: "1.25", fill: "currentColor", stroke: "none" })
593
+ ] }),
594
+ plus: () => /* @__PURE__ */ jsxs(Fragment, { children: [
595
+ /* @__PURE__ */ jsx("path", { d: "M9 3.5v11" }),
596
+ /* @__PURE__ */ jsx("path", { d: "M3.5 9h11" })
597
+ ] }),
598
+ search: () => /* @__PURE__ */ jsxs(Fragment, { children: [
599
+ /* @__PURE__ */ jsx("circle", { cx: "7.75", cy: "7.75", r: "4.25" }),
600
+ /* @__PURE__ */ jsx("path", { d: "m11 11 3.5 3.5" })
601
+ ] }),
602
+ send: () => /* @__PURE__ */ jsxs(Fragment, { children: [
603
+ /* @__PURE__ */ jsx("path", { d: "M9 14.5v-11" }),
604
+ /* @__PURE__ */ jsx("path", { d: "m4.75 7.75 4.25-4.25 4.25 4.25" })
605
+ ] }),
606
+ stop: () => /* @__PURE__ */ jsx("rect", { x: "4.5", y: "4.5", width: "9", height: "9", rx: "1.5", fill: "currentColor", stroke: "none" })
607
+ };
608
+ function UiIcon({ name, size = 16, class: className = "" }) {
609
+ const Glyph = ICONS[name];
610
+ if (!Glyph) return null;
611
+ return /* @__PURE__ */ jsx("svg", { class: `scui-icon ${className}`, style: { "--scui-icon-size": `${size}px` }, viewBox: "0 0 18 18", fill: "none", stroke: "currentColor", "stroke-width": "1.5", "stroke-linecap": "round", "stroke-linejoin": "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx(Glyph, {}) });
612
+ }
613
+
614
+ // src/textarea.js
615
+ import { useLayoutEffect } from "preact/hooks";
616
+ function useAutosizeTextarea(ref, value) {
617
+ useLayoutEffect(() => {
618
+ const element = ref.current;
619
+ if (!element) return;
620
+ element.style.height = "auto";
621
+ const maxHeight = Number.parseFloat(getComputedStyle(element).maxHeight) || 150;
622
+ const height = Math.min(element.scrollHeight, maxHeight);
623
+ element.style.height = `${height}px`;
624
+ element.style.overflowY = element.scrollHeight > maxHeight ? "auto" : "hidden";
625
+ }, [ref, value]);
626
+ }
627
+
570
628
  // src/composer.jsx
571
- import { jsx, jsxs } from "preact/jsx-runtime";
629
+ import { jsx as jsx2, jsxs as jsxs2 } from "preact/jsx-runtime";
572
630
  var composerMemory = /* @__PURE__ */ new Map();
573
631
  function ContinuationBar({ state, adapter, labels = DEFAULT_LABELS }) {
574
632
  if (state.mode !== "mirror" || state.canSend) return null;
@@ -578,38 +636,56 @@ function ContinuationBar({ state, adapter, labels = DEFAULT_LABELS }) {
578
636
  const resume = canContinueHere(state);
579
637
  const join = state.canAttach;
580
638
  const branch = state.canBranch;
581
- if (!resume && !join && !branch) return /* @__PURE__ */ jsx("div", { class: "scui-continuation", children: /* @__PURE__ */ jsxs("span", { children: [
582
- /* @__PURE__ */ jsx("strong", { children: activeElsewhere ? "Active elsewhere" : "Read-only" }),
583
- /* @__PURE__ */ jsx("small", { children: "This session cannot be continued by an available harness." })
639
+ if (!resume && !join && !branch) return /* @__PURE__ */ jsx2("div", { class: "scui-continuation", children: /* @__PURE__ */ jsxs2("span", { children: [
640
+ /* @__PURE__ */ jsx2("strong", { children: activeElsewhere ? "Active elsewhere" : "Read-only" }),
641
+ /* @__PURE__ */ jsx2("small", { children: "This session cannot be continued by an available harness." })
584
642
  ] }) });
585
- return /* @__PURE__ */ jsxs("div", { class: "scui-continuation", children: [
586
- /* @__PURE__ */ jsxs("span", { children: [
587
- /* @__PURE__ */ jsx("strong", { children: activeElsewhere ? "Active elsewhere" : "Read-only" }),
588
- /* @__PURE__ */ jsx("small", { children: join ? "Join the proven live runtime without taking it over." : resume ? `Resume this ${harnessDisplayName(state.harness)} session here.` : "Start an independent continuation." })
643
+ return /* @__PURE__ */ jsxs2("div", { class: "scui-continuation", children: [
644
+ /* @__PURE__ */ jsxs2("span", { children: [
645
+ /* @__PURE__ */ jsx2("strong", { children: activeElsewhere ? "Active elsewhere" : "Read-only" }),
646
+ /* @__PURE__ */ jsx2("small", { children: join ? "Join the proven live runtime without taking it over." : resume ? `Resume this ${harnessDisplayName(state.harness)} session here.` : "Start an independent continuation." })
589
647
  ] }),
590
- /* @__PURE__ */ jsx("button", { type: "button", disabled: Boolean(state.operation), onClick: () => adapter.onIntent(join ? { action: "join" } : resume ? { action: "resume" } : { action: "branch" }), children: join ? labels.joinLive : resume ? labels.continueHere : labels.forkHere })
648
+ /* @__PURE__ */ jsx2("button", { type: "button", disabled: Boolean(state.operation), onClick: () => adapter.onIntent(join ? { action: "join" } : resume ? { action: "resume" } : { action: "branch" }), children: join ? labels.joinLive : resume ? labels.continueHere : labels.forkHere })
591
649
  ] });
592
650
  }
593
- function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`, onPending }) {
651
+ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`, pendingStatus = null, restoreDraft = null, onPending, onDraftRestored }) {
594
652
  const remembered = composerMemory.get(memoryKey) ?? { draft: state.savedDraft, queue: [] };
595
653
  const [draft, setDraft] = useState(remembered.draft);
596
654
  const [queue, setQueue] = useState(remembered.queue);
655
+ const [dispatching, setDispatching] = useState(false);
597
656
  const textarea = useRef(null);
657
+ useAutosizeTextarea(textarea, draft);
598
658
  const remember = (nextDraft, nextQueue) => boundedSet(composerMemory, memoryKey, { draft: nextDraft, queue: nextQueue });
599
659
  const updateQueue = (update) => setQueue((items) => {
600
660
  const next = update(items);
601
661
  remember(draft, next);
602
662
  return next;
603
663
  });
664
+ const queueBlocked = state.busy || pendingStatus !== null || dispatching;
665
+ const queuesNewMessage = state.busy || pendingStatus === "sending" || pendingStatus === "failed" || dispatching;
604
666
  useEffect(() => {
605
- if (!state.busy && state.canSend && queue.length) {
667
+ if (!queueBlocked && state.canSend && queue.length) {
606
668
  const [next, ...rest] = queue;
669
+ setDispatching(true);
607
670
  setQueue(rest);
608
671
  remember(draft, rest);
609
672
  onPending?.(next);
610
673
  adapter.onIntent({ action: "send", text: next });
611
674
  }
612
- }, [adapter, draft, memoryKey, onPending, queue, state.busy, state.canSend]);
675
+ }, [adapter, draft, memoryKey, onPending, queue, queueBlocked, state.canSend]);
676
+ useEffect(() => {
677
+ if (pendingStatus !== null || state.busy) setDispatching(false);
678
+ }, [pendingStatus, state.busy]);
679
+ useEffect(() => {
680
+ textarea.current?.focus({ preventScroll: true });
681
+ }, [memoryKey]);
682
+ useEffect(() => {
683
+ if (!restoreDraft) return;
684
+ setDraft(restoreDraft.text);
685
+ remember(restoreDraft.text, queue);
686
+ textarea.current?.focus({ preventScroll: true });
687
+ onDraftRestored?.(restoreDraft.id);
688
+ }, [onDraftRestored, restoreDraft?.id]);
613
689
  useEffect(() => {
614
690
  const timer = setTimeout(() => adapter.onIntent({ action: "draft", text: draft }), 250);
615
691
  return () => clearTimeout(timer);
@@ -617,27 +693,28 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
617
693
  const send = () => {
618
694
  const text = draft.trim();
619
695
  if (!text) return;
620
- if (state.busy) updateQueue((items) => [...items, text]);
696
+ if (queuesNewMessage) updateQueue((items) => [...items, text]);
621
697
  else if (state.canSend) {
698
+ if (onPending) setDispatching(true);
622
699
  onPending?.(text);
623
700
  adapter.onIntent({ action: "send", text });
624
701
  } else return;
625
702
  setDraft("");
626
- remember("", state.busy ? [...queue, text] : queue);
703
+ remember("", queuesNewMessage ? [...queue, text] : queue);
627
704
  };
628
- return /* @__PURE__ */ jsxs("div", { class: "scui-compose", children: [
629
- queue.length ? /* @__PURE__ */ jsxs("div", { class: "scui-queue", children: [
630
- /* @__PURE__ */ jsxs("strong", { children: [
705
+ return /* @__PURE__ */ jsxs2("div", { class: "scui-compose", children: [
706
+ queue.length ? /* @__PURE__ */ jsxs2("div", { class: "scui-queue", children: [
707
+ /* @__PURE__ */ jsxs2("strong", { children: [
631
708
  queue.length,
632
709
  " queued"
633
710
  ] }),
634
- queue.map((item, index) => /* @__PURE__ */ jsxs("span", { children: [
711
+ queue.map((item, index) => /* @__PURE__ */ jsxs2("span", { children: [
635
712
  item,
636
- /* @__PURE__ */ jsx("button", { type: "button", "aria-label": `Remove queued message ${index + 1}`, onClick: () => updateQueue((items) => items.filter((_, itemIndex) => itemIndex !== index)), children: "\xD7" })
713
+ /* @__PURE__ */ jsx2("button", { type: "button", "aria-label": `Remove queued message ${index + 1}`, onClick: () => updateQueue((items) => items.filter((_, itemIndex) => itemIndex !== index)), children: /* @__PURE__ */ jsx2(UiIcon, { name: "close", size: 13 }) })
637
714
  ] }, `${index}:${item}`))
638
715
  ] }) : null,
639
- /* @__PURE__ */ jsxs("div", { class: "scui-envelope", children: [
640
- /* @__PURE__ */ jsx("textarea", { ref: textarea, rows: 1, "aria-label": `Message ${harnessDisplayName(state.harness) || "agent"}`, placeholder: state.startup !== "ready" ? "Connecting\u2026" : state.busy ? "Queue a follow-up\u2026" : labels.askAgent, value: draft, disabled: state.mode !== "control" && !state.canSend, onInput: (event) => {
716
+ /* @__PURE__ */ jsxs2("div", { class: "scui-envelope", children: [
717
+ /* @__PURE__ */ jsx2("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, onInput: (event) => {
641
718
  const value = event.currentTarget.value;
642
719
  setDraft(value);
643
720
  remember(value, queue);
@@ -647,35 +724,113 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
647
724
  send();
648
725
  }
649
726
  } }),
650
- /* @__PURE__ */ jsxs("span", { children: [
651
- state.busy ? /* @__PURE__ */ jsx("button", { class: "scui-stop", type: "button", "aria-label": "Stop agent", disabled: !state.canInterrupt, onClick: () => adapter.onIntent({ action: "interrupt" }), children: "\u25A0" }) : null,
652
- /* @__PURE__ */ jsx("button", { class: "scui-send", type: "button", "aria-label": state.busy ? "Queue message" : "Send message", disabled: !draft.trim() || !state.busy && !state.canSend, onClick: send, children: state.busy ? "+" : "\u2191" })
727
+ /* @__PURE__ */ jsxs2("span", { children: [
728
+ state.busy ? /* @__PURE__ */ jsx2("button", { class: "scui-stop", type: "button", "aria-label": "Stop agent", disabled: !state.canInterrupt, onClick: () => adapter.onIntent({ action: "interrupt" }), children: /* @__PURE__ */ jsx2(UiIcon, { name: "stop", size: 15 }) }) : null,
729
+ /* @__PURE__ */ jsx2("button", { class: "scui-send", type: "button", "aria-label": queuesNewMessage ? "Queue message" : "Send message", disabled: !draft.trim() || !queuesNewMessage && !state.canSend, onClick: send, children: /* @__PURE__ */ jsx2(UiIcon, { name: queuesNewMessage ? "plus" : "send", size: 17 }) })
653
730
  ] })
654
731
  ] })
655
732
  ] });
656
733
  }
657
734
 
658
735
  // src/conversation.jsx
659
- import { useEffect as useEffect2, useId, useLayoutEffect, useRef as useRef2, useState as useState2 } from "preact/hooks";
736
+ import { Fragment as Fragment2 } from "preact";
737
+ import { useEffect as useEffect3, useId, useLayoutEffect as useLayoutEffect2, useRef as useRef3, useState as useState2 } from "preact/hooks";
660
738
 
661
739
  // src/markdown.jsx
662
740
  import MarkdownIt from "markdown-it";
663
- import { useMemo } from "preact/hooks";
664
- import { jsx as jsx2 } from "preact/jsx-runtime";
741
+ import { useEffect as useEffect2, useMemo, useRef as useRef2 } from "preact/hooks";
742
+ import { jsx as jsx3 } from "preact/jsx-runtime";
665
743
  var markdown = new MarkdownIt({ html: false, linkify: true, breaks: false });
744
+ var LANGUAGE_LABELS = {
745
+ bash: "Shell",
746
+ css: "CSS",
747
+ html: "HTML",
748
+ js: "JavaScript",
749
+ javascript: "JavaScript",
750
+ jsx: "JSX",
751
+ md: "Markdown",
752
+ markdown: "Markdown",
753
+ py: "Python",
754
+ python: "Python",
755
+ rs: "Rust",
756
+ rust: "Rust",
757
+ sh: "Shell",
758
+ shell: "Shell",
759
+ ts: "TypeScript",
760
+ tsx: "TSX",
761
+ yaml: "YAML",
762
+ yml: "YAML",
763
+ json: "JSON",
764
+ jsonc: "JSONC",
765
+ sql: "SQL",
766
+ xml: "XML"
767
+ };
768
+ var COPY_ICON = '<svg viewBox="0 0 18 18" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="5" y="5" width="8" height="8" rx="1.5"></rect><path d="M3 10.5V4.25C3 3.56 3.56 3 4.25 3h6.25"></path></svg>';
769
+ function languageLabel(info) {
770
+ const language = info.trim().split(/\s+/, 1)[0]?.toLocaleLowerCase() ?? "";
771
+ if (!language) return "Code";
772
+ return LANGUAGE_LABELS[language] ?? language.toLocaleUpperCase();
773
+ }
774
+ function frameCode(render, tokens, index, options, env, self) {
775
+ const label = markdown.utils.escapeHtml(languageLabel(tokens[index]?.info ?? ""));
776
+ return `<div class="scui-code-block"><div class="scui-code-head"><span>${label}</span><button class="scui-code-copy" type="button" aria-label="Copy code" title="Copy code">${COPY_ICON}<span>Copy</span></button></div>${render(tokens, index, options, env, self)}</div>`;
777
+ }
778
+ for (const kind of ["fence", "code_block"]) {
779
+ const render = markdown.renderer.rules[kind];
780
+ markdown.renderer.rules[kind] = (tokens, index, options, env, self) => frameCode(render, tokens, index, options, env, self);
781
+ }
666
782
  var defaultLinkOpen = markdown.renderer.rules.link_open;
667
783
  markdown.renderer.rules.link_open = (tokens, index, options, env, self) => {
668
784
  tokens[index]?.attrSet("target", "_blank");
669
785
  tokens[index]?.attrSet("rel", "noreferrer noopener");
670
786
  return defaultLinkOpen ? defaultLinkOpen(tokens, index, options, env, self) : self.renderToken(tokens, index, options);
671
787
  };
672
- function Markdown({ value }) {
788
+ function setCopyState(button, status) {
789
+ const labels = {
790
+ idle: ["Copy code", "Copy"],
791
+ copied: ["Code copied", "Copied"],
792
+ failed: ["Copy failed \xB7 retry", "Retry"]
793
+ };
794
+ const [label, visible] = labels[status];
795
+ button.dataset.status = status;
796
+ button.setAttribute("aria-label", label);
797
+ button.setAttribute("title", label);
798
+ const text = button.querySelector("span");
799
+ if (text) text.textContent = visible;
800
+ }
801
+ function Markdown({ value, copyText }) {
673
802
  const html = useMemo(() => markdown.render(value), [value]);
674
- return /* @__PURE__ */ jsx2("div", { class: "scui-markdown", dangerouslySetInnerHTML: { __html: html } });
803
+ const resets = useRef2(/* @__PURE__ */ new Map());
804
+ useEffect2(() => () => {
805
+ for (const timer of resets.current.values()) clearTimeout(timer);
806
+ resets.current.clear();
807
+ }, []);
808
+ const copyCode = async (event) => {
809
+ const button = event.target.closest?.(".scui-code-copy");
810
+ if (!button || !event.currentTarget.contains(button) || !copyText) return;
811
+ const code = button.closest(".scui-code-block")?.querySelector("pre code");
812
+ if (!code) return;
813
+ button.disabled = true;
814
+ try {
815
+ await copyText(code.textContent ?? "");
816
+ setCopyState(button, "copied");
817
+ } catch {
818
+ setCopyState(button, "failed");
819
+ } finally {
820
+ button.disabled = false;
821
+ }
822
+ clearTimeout(resets.current.get(button));
823
+ const timer = setTimeout(() => {
824
+ if (button.isConnected) setCopyState(button, "idle");
825
+ resets.current.delete(button);
826
+ }, 1500);
827
+ resets.current.set(button, timer);
828
+ };
829
+ return /* @__PURE__ */ jsx3("div", { class: "scui-markdown", "data-copyable": Boolean(copyText), onClick: copyCode, dangerouslySetInnerHTML: { __html: html } });
675
830
  }
676
831
 
677
832
  // src/conversation.jsx
678
- import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "preact/jsx-runtime";
833
+ import { Fragment as Fragment3, jsx as jsx4, jsxs as jsxs3 } from "preact/jsx-runtime";
679
834
  function LoadingStatus({ state, compact = false }) {
680
835
  const copy = {
681
836
  connecting: ["Connecting to coding agents", "Checking installed harnesses and capabilities.", 0],
@@ -683,81 +838,104 @@ function LoadingStatus({ state, compact = false }) {
683
838
  discovering: ["Loading recent sessions", "Scanning native session stores without loading full transcripts.", 2],
684
839
  ready: ["Ready", "Coding sessions are up to date.", 3]
685
840
  }[state.startup];
686
- return /* @__PURE__ */ jsxs2("div", { class: `scui-loading${compact ? " scui-loading-compact" : ""}`, role: "status", "aria-busy": state.startup !== "ready", children: [
687
- /* @__PURE__ */ jsx3("span", { class: "scui-orbit", "aria-hidden": "true", children: /* @__PURE__ */ jsx3("i", {}) }),
688
- /* @__PURE__ */ jsxs2("span", { class: "scui-loading-copy", children: [
689
- /* @__PURE__ */ jsx3("strong", { children: copy[0] }),
690
- /* @__PURE__ */ jsx3("small", { children: copy[1] })
841
+ return /* @__PURE__ */ jsxs3("div", { class: `scui-loading${compact ? " scui-loading-compact" : ""}`, role: "status", "aria-busy": state.startup !== "ready", children: [
842
+ /* @__PURE__ */ jsx4("span", { class: "scui-orbit", "aria-hidden": "true", children: /* @__PURE__ */ jsx4("i", {}) }),
843
+ /* @__PURE__ */ jsxs3("span", { class: "scui-loading-copy", children: [
844
+ /* @__PURE__ */ jsx4("strong", { children: copy[0] }),
845
+ /* @__PURE__ */ jsx4("small", { children: copy[1] })
691
846
  ] }),
692
- /* @__PURE__ */ jsx3("span", { class: "scui-progress", "aria-hidden": "true", children: [1, 2, 3].map((step) => /* @__PURE__ */ jsx3("i", { "data-progress": step <= copy[2] ? "done" : step === copy[2] + 1 ? "current" : "waiting" }, step)) })
847
+ /* @__PURE__ */ jsx4("span", { class: "scui-progress", "aria-hidden": "true", children: [1, 2, 3].map((step) => /* @__PURE__ */ jsx4("i", { "data-progress": step <= copy[2] ? "done" : step === copy[2] + 1 ? "current" : "waiting" }, step)) })
693
848
  ] });
694
849
  }
695
850
  function RequestCard({ entry, adapter, canRespond }) {
696
851
  const request = entry.request;
697
852
  if (!request) return null;
698
853
  if (request.status === "responded") {
699
- return /* @__PURE__ */ jsxs2("div", { class: "scui-request-done", children: [
854
+ return /* @__PURE__ */ jsxs3("div", { class: "scui-request-done", children: [
700
855
  "\u2713 Request answered \xB7 ",
701
856
  request.resolution?.name ?? request.requestKind
702
857
  ] });
703
858
  }
704
- return /* @__PURE__ */ jsxs2("section", { class: "scui-request", "aria-label": `${request.requestKind} needs input`, children: [
705
- /* @__PURE__ */ jsx3("strong", { children: "Agent needs input" }),
706
- /* @__PURE__ */ jsx3(Markdown, { value: request.payloadText || entry.text }),
707
- /* @__PURE__ */ jsxs2("div", { class: "scui-request-actions", children: [
708
- request.options.map((option) => /* @__PURE__ */ jsx3("button", { type: "button", disabled: !canRespond, onClick: () => adapter.onIntent({ action: "respond", requestId: request.requestId, optionId: option.optionId }), children: option.name }, option.optionId)),
709
- request.cancellable ? /* @__PURE__ */ jsx3("button", { type: "button", disabled: !canRespond, onClick: () => adapter.onIntent({ action: "respond", requestId: request.requestId, optionId: null }), children: "Cancel" }) : null
859
+ return /* @__PURE__ */ jsxs3("section", { class: "scui-request", role: "alert", "aria-label": `${request.requestKind} needs input`, children: [
860
+ /* @__PURE__ */ jsx4("strong", { children: "Agent needs input" }),
861
+ /* @__PURE__ */ jsx4(Markdown, { value: request.payloadText || entry.text, copyText: adapter?.copyText }),
862
+ /* @__PURE__ */ jsxs3("div", { class: "scui-request-actions", children: [
863
+ request.options.map((option) => /* @__PURE__ */ jsx4("button", { type: "button", disabled: !canRespond, onClick: () => adapter.onIntent({ action: "respond", requestId: request.requestId, optionId: option.optionId }), children: option.name }, option.optionId)),
864
+ request.cancellable ? /* @__PURE__ */ jsx4("button", { type: "button", disabled: !canRespond, onClick: () => adapter.onIntent({ action: "respond", requestId: request.requestId, optionId: null }), children: "Cancel" }) : null
710
865
  ] })
711
866
  ] });
712
867
  }
713
868
  function ContextDisclosure({ context }) {
714
869
  if (!context?.length) return null;
715
- return /* @__PURE__ */ jsxs2("details", { class: "scui-context", children: [
716
- /* @__PURE__ */ jsxs2("summary", { children: [
870
+ return /* @__PURE__ */ jsxs3("details", { class: "scui-context", children: [
871
+ /* @__PURE__ */ jsxs3("summary", { children: [
717
872
  "Context \xB7 ",
718
873
  context.length
719
874
  ] }),
720
- /* @__PURE__ */ jsx3("div", { children: context.map((item, index) => /* @__PURE__ */ jsxs2("p", { children: [
721
- /* @__PURE__ */ jsx3("strong", { children: item.label }),
722
- /* @__PURE__ */ jsx3("span", { children: item.detail })
875
+ /* @__PURE__ */ jsx4("div", { children: context.map((item, index) => /* @__PURE__ */ jsxs3("p", { children: [
876
+ /* @__PURE__ */ jsx4("strong", { children: item.label }),
877
+ /* @__PURE__ */ jsx4("span", { children: item.detail })
723
878
  ] }, item.id ?? index)) })
724
879
  ] });
725
880
  }
881
+ function MessageMeta({ entry, adapter }) {
882
+ const [copyState, setCopyState2] = useState2("idle");
883
+ const reset = useRef3(null);
884
+ useEffect3(() => () => clearTimeout(reset.current), []);
885
+ const date = entry.ts === null ? null : new Date(entry.ts);
886
+ const validDate = date && Number.isFinite(date.valueOf()) ? date : null;
887
+ if (!validDate && (!adapter?.copyText || !entry.text)) return null;
888
+ const copy = async () => {
889
+ try {
890
+ await adapter.copyText(entry.text);
891
+ setCopyState2("copied");
892
+ } catch {
893
+ setCopyState2("failed");
894
+ }
895
+ clearTimeout(reset.current);
896
+ reset.current = setTimeout(() => setCopyState2("idle"), 1500);
897
+ };
898
+ const copyLabel = copyState === "copied" ? "Message copied" : copyState === "failed" ? "Copy failed \xB7 retry" : "Copy message";
899
+ return /* @__PURE__ */ jsxs3("footer", { class: "scui-message-meta", children: [
900
+ validDate ? /* @__PURE__ */ jsx4("time", { dateTime: validDate.toISOString(), title: validDate.toLocaleString(), children: validDate.toLocaleTimeString([], { hour: "numeric", minute: "2-digit" }) }) : null,
901
+ adapter?.copyText && entry.text ? /* @__PURE__ */ jsx4("button", { type: "button", "aria-label": copyLabel, title: copyLabel, onClick: copy, "data-status": copyState, children: /* @__PURE__ */ jsx4(UiIcon, { name: "copy", size: 13 }) }) : null
902
+ ] });
903
+ }
726
904
  var TOOL_ICONS = {
727
- read: () => /* @__PURE__ */ jsxs2(Fragment, { children: [
728
- /* @__PURE__ */ jsx3("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" }),
729
- /* @__PURE__ */ jsx3("path", { d: "M12 2.9v3h2.85M6 9h6M6 12h4" })
905
+ read: () => /* @__PURE__ */ jsxs3(Fragment3, { children: [
906
+ /* @__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" }),
907
+ /* @__PURE__ */ jsx4("path", { d: "M12 2.9v3h2.85M6 9h6M6 12h4" })
730
908
  ] }),
731
- search: () => /* @__PURE__ */ jsxs2(Fragment, { children: [
732
- /* @__PURE__ */ jsx3("circle", { cx: "8", cy: "8", r: "4.5" }),
733
- /* @__PURE__ */ jsx3("path", { d: "m11.5 11.5 3 3" })
909
+ search: () => /* @__PURE__ */ jsxs3(Fragment3, { children: [
910
+ /* @__PURE__ */ jsx4("circle", { cx: "8", cy: "8", r: "4.5" }),
911
+ /* @__PURE__ */ jsx4("path", { d: "m11.5 11.5 3 3" })
734
912
  ] }),
735
- edit: () => /* @__PURE__ */ jsxs2(Fragment, { children: [
736
- /* @__PURE__ */ jsx3("path", { d: "m11.75 3.25 3 3-8.5 8.5-3.75.75.75-3.75 8.5-8.5Z" }),
737
- /* @__PURE__ */ jsx3("path", { d: "m10 5 3 3" })
913
+ edit: () => /* @__PURE__ */ jsxs3(Fragment3, { children: [
914
+ /* @__PURE__ */ jsx4("path", { d: "m11.75 3.25 3 3-8.5 8.5-3.75.75.75-3.75 8.5-8.5Z" }),
915
+ /* @__PURE__ */ jsx4("path", { d: "m10 5 3 3" })
738
916
  ] }),
739
- command: () => /* @__PURE__ */ jsx3(Fragment, { children: /* @__PURE__ */ jsx3("path", { d: "m3 5 3 3-3 3M8 12h6" }) }),
740
- test: () => /* @__PURE__ */ jsxs2(Fragment, { children: [
741
- /* @__PURE__ */ jsx3("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" }),
742
- /* @__PURE__ */ jsx3("path", { d: "M5 2.5h8" })
917
+ command: () => /* @__PURE__ */ jsx4(Fragment3, { children: /* @__PURE__ */ jsx4("path", { d: "m3 5 3 3-3 3M8 12h6" }) }),
918
+ test: () => /* @__PURE__ */ jsxs3(Fragment3, { children: [
919
+ /* @__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" }),
920
+ /* @__PURE__ */ jsx4("path", { d: "M5 2.5h8" })
743
921
  ] }),
744
- web: () => /* @__PURE__ */ jsxs2(Fragment, { children: [
745
- /* @__PURE__ */ jsx3("circle", { cx: "9", cy: "9", r: "6.5" }),
746
- /* @__PURE__ */ jsx3("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" })
922
+ web: () => /* @__PURE__ */ jsxs3(Fragment3, { children: [
923
+ /* @__PURE__ */ jsx4("circle", { cx: "9", cy: "9", r: "6.5" }),
924
+ /* @__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" })
747
925
  ] }),
748
- agent: () => /* @__PURE__ */ jsxs2(Fragment, { children: [
749
- /* @__PURE__ */ jsx3("circle", { cx: "9", cy: "6", r: "2.5" }),
750
- /* @__PURE__ */ jsx3("path", { d: "M4 15c.4-3 2-4.5 5-4.5s4.6 1.5 5 4.5" })
926
+ agent: () => /* @__PURE__ */ jsxs3(Fragment3, { children: [
927
+ /* @__PURE__ */ jsx4("circle", { cx: "9", cy: "6", r: "2.5" }),
928
+ /* @__PURE__ */ jsx4("path", { d: "M4 15c.4-3 2-4.5 5-4.5s4.6 1.5 5 4.5" })
751
929
  ] }),
752
- plan: () => /* @__PURE__ */ jsx3(Fragment, { children: /* @__PURE__ */ jsx3("path", { d: "m3 5 1 1 2-2M3 9l1 1 2-2M3 13l1 1 2-2M8 5h7M8 9h7M8 13h7" }) }),
753
- other: () => /* @__PURE__ */ jsxs2(Fragment, { children: [
754
- /* @__PURE__ */ jsx3("path", { d: "M9 2.5v3M9 12.5v3M2.5 9h3M12.5 9h3" }),
755
- /* @__PURE__ */ jsx3("circle", { cx: "9", cy: "9", r: "3.5" })
930
+ 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" }) }),
931
+ other: () => /* @__PURE__ */ jsxs3(Fragment3, { children: [
932
+ /* @__PURE__ */ jsx4("path", { d: "M9 2.5v3M9 12.5v3M2.5 9h3M12.5 9h3" }),
933
+ /* @__PURE__ */ jsx4("circle", { cx: "9", cy: "9", r: "3.5" })
756
934
  ] })
757
935
  };
758
936
  function ToolIcon({ category }) {
759
937
  const Glyph = TOOL_ICONS[category] ?? TOOL_ICONS.other;
760
- return /* @__PURE__ */ jsx3("svg", { class: "scui-tool-icon", viewBox: "0 0 18 18", fill: "none", stroke: "currentColor", "stroke-width": "1.35", "stroke-linecap": "round", "stroke-linejoin": "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx3(Glyph, {}) });
938
+ return /* @__PURE__ */ jsx4("svg", { class: "scui-tool-icon", viewBox: "0 0 18 18", fill: "none", stroke: "currentColor", "stroke-width": "1.35", "stroke-linecap": "round", "stroke-linejoin": "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx4(Glyph, {}) });
761
939
  }
762
940
  function toolAction2(entry, category, presentation) {
763
941
  if (presentation?.action) return presentation.action;
@@ -812,86 +990,86 @@ function ToolMetrics({ presentation }) {
812
990
  presentation.exitCode === null ? "" : `exit ${presentation.exitCode}`,
813
991
  formatDuration(presentation.durationMs)
814
992
  ].filter(Boolean);
815
- return metrics.length ? /* @__PURE__ */ jsx3("div", { class: "scui-tool-metrics", children: metrics.map((metric) => /* @__PURE__ */ jsx3("span", { children: metric }, metric)) }) : null;
993
+ return metrics.length ? /* @__PURE__ */ jsx4("div", { class: "scui-tool-metrics", children: metrics.map((metric) => /* @__PURE__ */ jsx4("span", { children: metric }, metric)) }) : null;
816
994
  }
817
995
  function PendingElapsed({ now }) {
818
996
  const clock = now ?? Date.now;
819
- const started = useRef2(clock());
997
+ const started = useRef3(clock());
820
998
  const [elapsed, setElapsed] = useState2(0);
821
- useEffect2(() => {
999
+ useEffect3(() => {
822
1000
  const timer = setInterval(() => setElapsed(Math.max(0, clock() - started.current)), 1e3);
823
1001
  return () => clearInterval(timer);
824
1002
  }, [clock]);
825
- return /* @__PURE__ */ jsx3("small", { class: "scui-tool-elapsed", children: elapsed < 1e3 ? "now" : formatDuration(elapsed) });
1003
+ return /* @__PURE__ */ jsx4("small", { class: "scui-tool-elapsed", children: elapsed < 1e3 ? "now" : formatDuration(elapsed) });
826
1004
  }
827
1005
  function LinePreview({ value, kind }) {
828
1006
  if (!value) return null;
829
- return /* @__PURE__ */ jsx3("ol", { class: "scui-code-preview", "data-kind": kind, children: stripAnsi(value).split("\n").map((line, index) => {
1007
+ return /* @__PURE__ */ jsx4("ol", { class: "scui-code-preview", "data-kind": kind, children: stripAnsi(value).split("\n").map((line, index) => {
830
1008
  const tone = kind === "diff" ? line.startsWith("+") && !line.startsWith("+++") ? "add" : line.startsWith("-") && !line.startsWith("---") ? "remove" : line.startsWith("@@") ? "hunk" : "plain" : "plain";
831
- return /* @__PURE__ */ jsxs2("li", { "data-tone": tone, children: [
832
- /* @__PURE__ */ jsx3("span", { children: index + 1 }),
833
- /* @__PURE__ */ jsx3("code", { children: line || " " })
1009
+ return /* @__PURE__ */ jsxs3("li", { "data-tone": tone, children: [
1010
+ /* @__PURE__ */ jsx4("span", { children: index + 1 }),
1011
+ /* @__PURE__ */ jsx4("code", { children: line || " " })
834
1012
  ] }, index);
835
1013
  }) });
836
1014
  }
837
1015
  function TerminalPreview({ presentation, pending, failed }) {
838
- return /* @__PURE__ */ jsxs2("section", { class: "scui-terminal", children: [
839
- /* @__PURE__ */ jsxs2("header", { children: [
840
- /* @__PURE__ */ jsxs2("span", { "aria-hidden": "true", children: [
841
- /* @__PURE__ */ jsx3("i", {}),
842
- /* @__PURE__ */ jsx3("i", {}),
843
- /* @__PURE__ */ jsx3("i", {})
1016
+ return /* @__PURE__ */ jsxs3("section", { class: "scui-terminal", children: [
1017
+ /* @__PURE__ */ jsxs3("header", { children: [
1018
+ /* @__PURE__ */ jsxs3("span", { "aria-hidden": "true", children: [
1019
+ /* @__PURE__ */ jsx4("i", {}),
1020
+ /* @__PURE__ */ jsx4("i", {}),
1021
+ /* @__PURE__ */ jsx4("i", {})
844
1022
  ] }),
845
- /* @__PURE__ */ jsx3("code", { children: presentation.command ? `$ ${presentation.command}` : "Terminal" })
1023
+ /* @__PURE__ */ jsx4("code", { children: presentation.command ? `$ ${presentation.command}` : "Terminal" })
846
1024
  ] }),
847
- presentation.preview ? /* @__PURE__ */ jsx3("pre", { "data-error": failed, children: stripAnsi(presentation.preview) }) : pending ? /* @__PURE__ */ jsxs2("div", { class: "scui-terminal-wait", children: [
848
- /* @__PURE__ */ jsx3("i", {}),
1025
+ presentation.preview ? /* @__PURE__ */ jsx4("pre", { "data-error": failed, children: stripAnsi(presentation.preview) }) : pending ? /* @__PURE__ */ jsxs3("div", { class: "scui-terminal-wait", children: [
1026
+ /* @__PURE__ */ jsx4("i", {}),
849
1027
  " Waiting for output"
850
- ] }) : /* @__PURE__ */ jsx3("div", { class: "scui-terminal-empty", children: "No output" })
1028
+ ] }) : /* @__PURE__ */ jsx4("div", { class: "scui-terminal-empty", children: "No output" })
851
1029
  ] });
852
1030
  }
853
1031
  function SearchPreview({ presentation }) {
854
1032
  const lines = stripAnsi(presentation.preview).split("\n").filter(Boolean);
855
- return /* @__PURE__ */ jsxs2("section", { class: "scui-search-preview", children: [
856
- presentation.query ? /* @__PURE__ */ jsxs2("header", { children: [
857
- /* @__PURE__ */ jsx3("span", { children: "Search" }),
858
- /* @__PURE__ */ jsx3("code", { children: presentation.query })
1033
+ return /* @__PURE__ */ jsxs3("section", { class: "scui-search-preview", children: [
1034
+ presentation.query ? /* @__PURE__ */ jsxs3("header", { children: [
1035
+ /* @__PURE__ */ jsx4("span", { children: "Search" }),
1036
+ /* @__PURE__ */ jsx4("code", { children: presentation.query })
859
1037
  ] }) : null,
860
- lines.length ? /* @__PURE__ */ jsx3("ol", { children: lines.map((line, index) => {
1038
+ lines.length ? /* @__PURE__ */ jsx4("ol", { children: lines.map((line, index) => {
861
1039
  const match = /^(.*?):(\d+)(?::(\d+))?:(.*)$/.exec(line);
862
- return /* @__PURE__ */ jsx3("li", { children: match ? /* @__PURE__ */ jsxs2(Fragment, { children: [
863
- /* @__PURE__ */ jsx3("code", { children: match[1] }),
864
- /* @__PURE__ */ jsxs2("small", { children: [
1040
+ return /* @__PURE__ */ jsx4("li", { children: match ? /* @__PURE__ */ jsxs3(Fragment3, { children: [
1041
+ /* @__PURE__ */ jsx4("code", { children: match[1] }),
1042
+ /* @__PURE__ */ jsxs3("small", { children: [
865
1043
  match[2],
866
1044
  match[3] ? `:${match[3]}` : ""
867
1045
  ] }),
868
- /* @__PURE__ */ jsx3("span", { children: match[4] })
869
- ] }) : /* @__PURE__ */ jsx3("span", { children: line }) }, index);
870
- }) }) : /* @__PURE__ */ jsx3("p", { children: "No textual results" })
1046
+ /* @__PURE__ */ jsx4("span", { children: match[4] })
1047
+ ] }) : /* @__PURE__ */ jsx4("span", { children: line }) }, index);
1048
+ }) }) : /* @__PURE__ */ jsx4("p", { children: "No textual results" })
871
1049
  ] });
872
1050
  }
873
1051
  function ToolPreview({ presentation, entry }) {
874
1052
  const pending = entry.status === "pending";
875
1053
  const failed = entry.status === "error";
876
- if (presentation.detail === "terminal") return /* @__PURE__ */ jsx3(TerminalPreview, { presentation, pending, failed });
877
- if (presentation.detail === "diff") return presentation.preview ? /* @__PURE__ */ jsx3(LinePreview, { value: presentation.preview, kind: "diff" }) : /* @__PURE__ */ jsx3("div", { class: "scui-tool-empty", children: "Edit completed without a textual diff" });
878
- if (presentation.detail === "file") return presentation.preview ? /* @__PURE__ */ jsx3(LinePreview, { value: presentation.preview, kind: "file" }) : /* @__PURE__ */ jsx3("div", { class: "scui-tool-empty", children: "File contents were not included in this event" });
879
- if (presentation.detail === "matches") return /* @__PURE__ */ jsx3(SearchPreview, { presentation });
880
- if (presentation.detail === "web") return /* @__PURE__ */ jsxs2("section", { class: "scui-web-preview", children: [
881
- presentation.url ? /* @__PURE__ */ jsx3("code", { children: presentation.url }) : null,
882
- presentation.preview ? /* @__PURE__ */ jsx3("p", { children: stripAnsi(presentation.preview) }) : /* @__PURE__ */ jsx3("small", { children: pending ? "Waiting for the page" : "No page summary returned" })
1054
+ if (presentation.detail === "terminal") return /* @__PURE__ */ jsx4(TerminalPreview, { presentation, pending, failed });
1055
+ if (presentation.detail === "diff") return presentation.preview ? /* @__PURE__ */ jsx4(LinePreview, { value: presentation.preview, kind: "diff" }) : /* @__PURE__ */ jsx4("div", { class: "scui-tool-empty", children: "Edit completed without a textual diff" });
1056
+ if (presentation.detail === "file") return presentation.preview ? /* @__PURE__ */ jsx4(LinePreview, { value: presentation.preview, kind: "file" }) : /* @__PURE__ */ jsx4("div", { class: "scui-tool-empty", children: "File contents were not included in this event" });
1057
+ if (presentation.detail === "matches") return /* @__PURE__ */ jsx4(SearchPreview, { presentation });
1058
+ if (presentation.detail === "web") return /* @__PURE__ */ jsxs3("section", { class: "scui-web-preview", children: [
1059
+ presentation.url ? /* @__PURE__ */ jsx4("code", { children: presentation.url }) : null,
1060
+ presentation.preview ? /* @__PURE__ */ jsx4("p", { children: stripAnsi(presentation.preview) }) : /* @__PURE__ */ jsx4("small", { children: pending ? "Waiting for the page" : "No page summary returned" })
883
1061
  ] });
884
- if (presentation.detail === "agent") return /* @__PURE__ */ jsx3("section", { class: "scui-agent-preview", children: presentation.preview ? /* @__PURE__ */ jsx3("pre", { children: stripAnsi(presentation.preview) }) : /* @__PURE__ */ jsx3("small", { children: pending ? "Agent is working" : "No textual handoff returned" }) });
885
- if (presentation.detail === "plan") return /* @__PURE__ */ jsx3("ol", { class: "scui-plan-preview", children: presentation.items?.map((item, index) => /* @__PURE__ */ jsxs2("li", { "data-status": item.status, children: [
886
- /* @__PURE__ */ jsx3("i", { "aria-hidden": "true" }),
887
- /* @__PURE__ */ jsx3("span", { children: item.label })
1062
+ if (presentation.detail === "agent") return /* @__PURE__ */ jsx4("section", { class: "scui-agent-preview", children: presentation.preview ? /* @__PURE__ */ jsx4("pre", { children: stripAnsi(presentation.preview) }) : /* @__PURE__ */ jsx4("small", { children: pending ? "Agent is working" : "No textual handoff returned" }) });
1063
+ if (presentation.detail === "plan") return /* @__PURE__ */ jsx4("ol", { class: "scui-plan-preview", children: presentation.items?.map((item, index) => /* @__PURE__ */ jsxs3("li", { "data-status": item.status, children: [
1064
+ /* @__PURE__ */ jsx4("i", { "aria-hidden": "true" }),
1065
+ /* @__PURE__ */ jsx4("span", { children: item.label })
888
1066
  ] }, `${item.label}:${index}`)) });
889
- return presentation.preview ? /* @__PURE__ */ jsx3("pre", { class: "scui-tool-output", "data-error": failed, children: stripAnsi(presentation.preview) }) : null;
1067
+ return presentation.preview ? /* @__PURE__ */ jsx4("pre", { class: "scui-tool-output", "data-error": failed, children: stripAnsi(presentation.preview) }) : null;
890
1068
  }
891
1069
  function ToolActions({ presentation, adapter }) {
892
1070
  const [copied, setCopied] = useState2(false);
893
- const reset = useRef2(null);
894
- useEffect2(() => () => clearTimeout(reset.current), []);
1071
+ const reset = useRef3(null);
1072
+ useEffect3(() => () => clearTimeout(reset.current), []);
895
1073
  if (!adapter?.copyText) return null;
896
1074
  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;
897
1075
  const copy = async () => {
@@ -900,27 +1078,27 @@ function ToolActions({ presentation, adapter }) {
900
1078
  clearTimeout(reset.current);
901
1079
  reset.current = setTimeout(() => setCopied(false), 1500);
902
1080
  };
903
- return action ? /* @__PURE__ */ jsx3("div", { class: "scui-tool-actions", children: /* @__PURE__ */ jsx3("button", { type: "button", onClick: copy, children: copied ? "Copied" : action[0] }) }) : null;
1081
+ return action ? /* @__PURE__ */ jsx4("div", { class: "scui-tool-actions", children: /* @__PURE__ */ jsx4("button", { type: "button", onClick: copy, children: copied ? "Copied" : action[0] }) }) : null;
904
1082
  }
905
1083
  function ToolStack({ tools }) {
906
1084
  if (tools.length < 2) return null;
907
- return /* @__PURE__ */ jsx3("div", { class: "scui-tool-stack", "aria-label": "Coordinated tools", children: tools.map((tool) => /* @__PURE__ */ jsx3("span", { children: tool.replaceAll("__", " \xB7 ").replaceAll("_", " ") }, tool)) });
1085
+ return /* @__PURE__ */ jsx4("div", { class: "scui-tool-stack", "aria-label": "Coordinated tools", children: tools.map((tool) => /* @__PURE__ */ jsx4("span", { children: tool.replaceAll("__", " \xB7 ").replaceAll("_", " ") }, tool)) });
908
1086
  }
909
1087
  function TechnicalDetails({ entry }) {
910
1088
  if (!entry.arguments && !entry.resultText) return null;
911
- return /* @__PURE__ */ jsxs2("details", { class: "scui-tool-technical", children: [
912
- /* @__PURE__ */ jsx3("summary", { children: "Technical details" }),
913
- /* @__PURE__ */ jsxs2("div", { children: [
914
- entry.arguments ? /* @__PURE__ */ jsxs2("section", { children: [
915
- /* @__PURE__ */ jsx3("strong", { children: "Native arguments" }),
916
- /* @__PURE__ */ jsx3("dl", { children: argumentRows(entry.arguments).map((row) => /* @__PURE__ */ jsxs2("div", { children: [
917
- /* @__PURE__ */ jsx3("dt", { children: row.label }),
918
- /* @__PURE__ */ jsx3("dd", { children: /* @__PURE__ */ jsx3("pre", { children: row.value }) })
1089
+ return /* @__PURE__ */ jsxs3("details", { class: "scui-tool-technical", children: [
1090
+ /* @__PURE__ */ jsx4("summary", { children: "Technical details" }),
1091
+ /* @__PURE__ */ jsxs3("div", { children: [
1092
+ entry.arguments ? /* @__PURE__ */ jsxs3("section", { children: [
1093
+ /* @__PURE__ */ jsx4("strong", { children: "Native arguments" }),
1094
+ /* @__PURE__ */ jsx4("dl", { children: argumentRows(entry.arguments).map((row) => /* @__PURE__ */ jsxs3("div", { children: [
1095
+ /* @__PURE__ */ jsx4("dt", { children: row.label }),
1096
+ /* @__PURE__ */ jsx4("dd", { children: /* @__PURE__ */ jsx4("pre", { children: row.value }) })
919
1097
  ] }, row.key)) })
920
1098
  ] }) : null,
921
- entry.resultText ? /* @__PURE__ */ jsxs2("section", { children: [
922
- /* @__PURE__ */ jsx3("strong", { children: "Native result" }),
923
- /* @__PURE__ */ jsxs2("pre", { "data-error": entry.status === "error", children: [
1099
+ entry.resultText ? /* @__PURE__ */ jsxs3("section", { children: [
1100
+ /* @__PURE__ */ jsx4("strong", { children: "Native result" }),
1101
+ /* @__PURE__ */ jsxs3("pre", { "data-error": entry.status === "error", children: [
924
1102
  entry.resultText,
925
1103
  entry.truncated ? "\n[truncated]" : ""
926
1104
  ] })
@@ -929,125 +1107,126 @@ function TechnicalDetails({ entry }) {
929
1107
  ] });
930
1108
  }
931
1109
  function TranscriptEntry({ entry, state, adapter }) {
932
- if (entry.role === "request") return /* @__PURE__ */ jsx3(RequestCard, { entry, adapter, canRespond: state.canRespond });
1110
+ if (entry.role === "request") return /* @__PURE__ */ jsx4(RequestCard, { entry, adapter, canRespond: state.canRespond });
933
1111
  if (entry.role === "reasoning") {
934
- return /* @__PURE__ */ jsxs2("details", { class: "scui-reasoning", open: entry.streaming, children: [
935
- /* @__PURE__ */ jsx3("summary", { children: entry.streaming ? "Reasoning\u2026" : "Reasoning" }),
936
- /* @__PURE__ */ jsx3(Markdown, { value: entry.text })
1112
+ return /* @__PURE__ */ jsxs3("details", { class: "scui-reasoning", open: entry.streaming, children: [
1113
+ /* @__PURE__ */ jsx4("summary", { children: entry.streaming ? "Reasoning\u2026" : "Reasoning" }),
1114
+ /* @__PURE__ */ jsx4(Markdown, { value: entry.text, copyText: adapter?.copyText })
937
1115
  ] });
938
1116
  }
939
- if (entry.role === "notice" || entry.role === "system") return /* @__PURE__ */ jsx3("div", { class: "scui-notice", "data-code": entry.code, children: entry.text });
940
- return /* @__PURE__ */ jsxs2("article", { class: "scui-message", "data-role": entry.role, children: [
941
- /* @__PURE__ */ jsx3(Markdown, { value: entry.text }),
942
- /* @__PURE__ */ jsx3(ContextDisclosure, { context: entry.context }),
943
- entry.truncated ? /* @__PURE__ */ jsx3("small", { class: "scui-truncated", children: "Entry truncated by host \xB7 load native session for the complete value" }) : null
1117
+ if (entry.role === "notice" || entry.role === "system") return /* @__PURE__ */ jsx4("div", { class: "scui-notice", "data-code": entry.code, children: entry.text });
1118
+ return /* @__PURE__ */ jsxs3("article", { class: "scui-message", "data-role": entry.role, "aria-label": `${entry.role === "user" ? "Your" : "Assistant"} message`, children: [
1119
+ /* @__PURE__ */ jsx4(Markdown, { value: entry.text, copyText: adapter?.copyText }),
1120
+ /* @__PURE__ */ jsx4(ContextDisclosure, { context: entry.context }),
1121
+ entry.truncated ? /* @__PURE__ */ jsx4("small", { class: "scui-truncated", children: "Entry truncated by host \xB7 load native session for the complete value" }) : null,
1122
+ /* @__PURE__ */ jsx4(MessageMeta, { entry, adapter })
944
1123
  ] });
945
1124
  }
946
1125
  function ToolRow({ entry, workspace, open = false, adapter }) {
947
1126
  const presentation = entry.presentation ?? createToolPresentation(entry);
948
1127
  const [expanded, setExpanded] = useState2(open || entry.status === "pending");
949
- useEffect2(() => {
1128
+ useEffect3(() => {
950
1129
  if (entry.status === "pending") setExpanded(true);
951
1130
  }, [entry.status]);
952
1131
  const target = compactToolTarget(presentation.target, workspace);
953
1132
  const hasDetail = Boolean(entry.arguments || entry.resultText || presentation.preview || presentation.fields.length);
954
1133
  const category = presentation.category ?? toolCategory(entry);
955
- const summary = /* @__PURE__ */ jsxs2(Fragment, { children: [
956
- /* @__PURE__ */ jsx3(ToolIcon, { category }),
957
- /* @__PURE__ */ jsx3("strong", { children: toolAction2(entry, category, presentation) }),
958
- target ? /* @__PURE__ */ jsx3("code", { class: "scui-tool-target", title: presentation.target, children: target }) : null,
959
- /* @__PURE__ */ jsx3("span", { class: "scui-spacer" }),
960
- entry.status === "pending" ? /* @__PURE__ */ jsx3(PendingElapsed, { now: adapter?.now }) : null,
961
- /* @__PURE__ */ jsx3("span", { class: "scui-tool-status", role: "status", "data-status": entry.status ?? "completed", "aria-label": entry.status ?? "completed", children: entry.status === "pending" ? /* @__PURE__ */ jsx3("i", {}) : entry.status === "error" ? "\xD7" : "\u2713" }),
962
- hasDetail ? /* @__PURE__ */ jsx3("span", { class: "scui-tool-chevron", "aria-hidden": "true", children: "\u203A" }) : null
1134
+ const summary = /* @__PURE__ */ jsxs3(Fragment3, { children: [
1135
+ /* @__PURE__ */ jsx4(ToolIcon, { category }),
1136
+ /* @__PURE__ */ jsx4("strong", { children: toolAction2(entry, category, presentation) }),
1137
+ target ? /* @__PURE__ */ jsx4("code", { class: "scui-tool-target", title: presentation.target, children: target }) : null,
1138
+ /* @__PURE__ */ jsx4("span", { class: "scui-spacer" }),
1139
+ entry.status === "pending" ? /* @__PURE__ */ jsx4(PendingElapsed, { now: adapter?.now }) : null,
1140
+ /* @__PURE__ */ jsx4("span", { class: "scui-tool-status", role: "status", "data-status": entry.status ?? "completed", "aria-label": entry.status ?? "completed", children: entry.status === "pending" ? /* @__PURE__ */ jsx4("i", {}) : /* @__PURE__ */ jsx4(UiIcon, { name: entry.status === "error" ? "close" : "check", size: 12 }) }),
1141
+ hasDetail ? /* @__PURE__ */ jsx4(UiIcon, { name: "chevron", size: 14, class: "scui-tool-chevron" }) : null
963
1142
  ] });
964
- if (!hasDetail) return /* @__PURE__ */ jsx3("div", { class: "scui-tool", "data-status": entry.status ?? "completed", "data-category": category, children: /* @__PURE__ */ jsx3("div", { class: "scui-tool-head", children: summary }) });
965
- return /* @__PURE__ */ jsxs2("details", { class: "scui-tool", "data-status": entry.status ?? "completed", "data-category": category, open: expanded, onToggle: (event) => setExpanded(event.currentTarget.open), children: [
966
- /* @__PURE__ */ jsx3("summary", { class: "scui-tool-head", children: summary }),
967
- /* @__PURE__ */ jsxs2("div", { class: "scui-tool-detail", children: [
968
- /* @__PURE__ */ jsx3(ToolMetrics, { presentation }),
969
- /* @__PURE__ */ jsx3(ToolStack, { tools: presentation.tools ?? [] }),
970
- /* @__PURE__ */ jsx3(ToolPreview, { presentation, entry }),
971
- presentation.fields.length ? /* @__PURE__ */ jsx3("dl", { class: "scui-tool-fields", children: presentation.fields.map((field) => /* @__PURE__ */ jsxs2("div", { children: [
972
- /* @__PURE__ */ jsx3("dt", { children: field.label }),
973
- /* @__PURE__ */ jsx3("dd", { children: field.value })
1143
+ if (!hasDetail) return /* @__PURE__ */ jsx4("div", { class: "scui-tool", "data-status": entry.status ?? "completed", "data-category": category, children: /* @__PURE__ */ jsx4("div", { class: "scui-tool-head", children: summary }) });
1144
+ return /* @__PURE__ */ jsxs3("details", { class: "scui-tool", "data-status": entry.status ?? "completed", "data-category": category, open: expanded, onToggle: (event) => setExpanded(event.currentTarget.open), children: [
1145
+ /* @__PURE__ */ jsx4("summary", { class: "scui-tool-head", children: summary }),
1146
+ /* @__PURE__ */ jsxs3("div", { class: "scui-tool-detail", children: [
1147
+ /* @__PURE__ */ jsx4(ToolMetrics, { presentation }),
1148
+ /* @__PURE__ */ jsx4(ToolStack, { tools: presentation.tools ?? [] }),
1149
+ /* @__PURE__ */ jsx4(ToolPreview, { presentation, entry }),
1150
+ presentation.fields.length ? /* @__PURE__ */ jsx4("dl", { class: "scui-tool-fields", children: presentation.fields.map((field) => /* @__PURE__ */ jsxs3("div", { children: [
1151
+ /* @__PURE__ */ jsx4("dt", { children: field.label }),
1152
+ /* @__PURE__ */ jsx4("dd", { children: field.value })
974
1153
  ] }, field.label)) }) : null,
975
- /* @__PURE__ */ jsx3(ToolActions, { presentation, adapter }),
976
- /* @__PURE__ */ jsx3(TechnicalDetails, { entry })
1154
+ /* @__PURE__ */ jsx4(ToolActions, { presentation, adapter }),
1155
+ /* @__PURE__ */ jsx4(TechnicalDetails, { entry })
977
1156
  ] })
978
1157
  ] });
979
1158
  }
980
1159
  function ActivityGroup({ entries, state, adapter }) {
981
- if (entries.length === 1) return /* @__PURE__ */ jsx3("section", { class: "scui-activity", "data-single": "true", children: /* @__PURE__ */ jsx3(ToolRow, { entry: entries[0], workspace: state.workspace, adapter }) });
1160
+ 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 }) });
982
1161
  const active = entries.some((entry) => entry.status === "pending");
983
1162
  const [open, setOpen] = useState2(active);
984
1163
  const id = useId();
985
- useEffect2(() => {
1164
+ useEffect3(() => {
986
1165
  if (active) setOpen(true);
987
1166
  }, [active]);
988
- return /* @__PURE__ */ jsxs2("section", { class: "scui-activity", children: [
989
- /* @__PURE__ */ jsxs2("button", { class: "scui-activity-head", type: "button", "aria-expanded": open, "aria-controls": id, onClick: () => setOpen((value) => !value), children: [
990
- /* @__PURE__ */ jsx3("span", { class: "scui-fold", "data-open": open, children: "\u203A" }),
991
- /* @__PURE__ */ jsx3("strong", { children: activitySummary(entries) }),
992
- /* @__PURE__ */ jsx3("span", { class: "scui-spacer" }),
993
- /* @__PURE__ */ jsxs2("small", { children: [
1167
+ return /* @__PURE__ */ jsxs3("section", { class: "scui-activity", children: [
1168
+ /* @__PURE__ */ jsxs3("button", { class: "scui-activity-head", type: "button", "aria-expanded": open, "aria-controls": id, onClick: () => setOpen((value) => !value), children: [
1169
+ /* @__PURE__ */ jsx4("span", { class: "scui-fold", "data-open": open, children: /* @__PURE__ */ jsx4(UiIcon, { name: "chevron", size: 14 }) }),
1170
+ /* @__PURE__ */ jsx4("strong", { children: activitySummary(entries) }),
1171
+ /* @__PURE__ */ jsx4("span", { class: "scui-spacer" }),
1172
+ /* @__PURE__ */ jsxs3("small", { children: [
994
1173
  entries.filter((entry) => entry.status !== "pending").length,
995
1174
  "/",
996
1175
  entries.length
997
1176
  ] })
998
1177
  ] }),
999
- open ? /* @__PURE__ */ jsx3("div", { id, children: entries.map((entry) => /* @__PURE__ */ jsx3(ToolRow, { entry, workspace: state.workspace, adapter }, entry.id)) }) : null
1178
+ open ? /* @__PURE__ */ jsx4("div", { id, children: entries.map((entry) => /* @__PURE__ */ jsx4(ToolRow, { entry, workspace: state.workspace, adapter }, entry.id)) }) : null
1000
1179
  ] });
1001
1180
  }
1002
1181
  function TaskPlan({ plan }) {
1003
1182
  if (!plan.items.length) return null;
1004
1183
  const complete = plan.items.filter((item) => item.status === "completed" || item.status === "cancelled").length;
1005
- return /* @__PURE__ */ jsxs2("details", { class: "scui-plan", children: [
1006
- /* @__PURE__ */ jsxs2("summary", { children: [
1007
- /* @__PURE__ */ jsx3("span", { children: "Plan" }),
1008
- /* @__PURE__ */ jsxs2("small", { children: [
1184
+ return /* @__PURE__ */ jsxs3("details", { class: "scui-plan", children: [
1185
+ /* @__PURE__ */ jsxs3("summary", { children: [
1186
+ /* @__PURE__ */ jsx4("span", { children: "Plan" }),
1187
+ /* @__PURE__ */ jsxs3("small", { children: [
1009
1188
  complete,
1010
1189
  "/",
1011
1190
  plan.items.length
1012
1191
  ] })
1013
1192
  ] }),
1014
- /* @__PURE__ */ jsx3("ol", { tabIndex: 0, "aria-label": "Task plan steps", children: plan.items.map((item) => /* @__PURE__ */ jsxs2("li", { "data-status": item.status, children: [
1015
- /* @__PURE__ */ jsx3("i", { "aria-hidden": "true" }),
1193
+ /* @__PURE__ */ jsx4("ol", { tabIndex: 0, "aria-label": "Task plan steps", children: plan.items.map((item) => /* @__PURE__ */ jsxs3("li", { "data-status": item.status, children: [
1194
+ /* @__PURE__ */ jsx4("i", { "aria-hidden": "true" }),
1016
1195
  " ",
1017
- /* @__PURE__ */ jsx3("span", { children: item.title })
1196
+ /* @__PURE__ */ jsx4("span", { children: item.title })
1018
1197
  ] }, item.id)) })
1019
1198
  ] });
1020
1199
  }
1021
1200
  function SessionDetails({ semantics }) {
1022
1201
  if (!semantics.fidelity && !semantics.residueCount && !semantics.parseErrors && !semantics.subagents.length) return null;
1023
- return /* @__PURE__ */ jsxs2("details", { class: "scui-details", children: [
1024
- /* @__PURE__ */ jsx3("summary", { children: "Session details" }),
1025
- /* @__PURE__ */ jsxs2("div", { children: [
1026
- semantics.fidelity ? /* @__PURE__ */ jsxs2("p", { children: [
1027
- /* @__PURE__ */ jsx3("strong", { children: "Fidelity" }),
1028
- /* @__PURE__ */ jsx3("span", { children: semantics.fidelity.replaceAll("_", " ") })
1202
+ return /* @__PURE__ */ jsxs3("details", { class: "scui-details", children: [
1203
+ /* @__PURE__ */ jsx4("summary", { children: "Session details" }),
1204
+ /* @__PURE__ */ jsxs3("div", { children: [
1205
+ semantics.fidelity ? /* @__PURE__ */ jsxs3("p", { children: [
1206
+ /* @__PURE__ */ jsx4("strong", { children: "Fidelity" }),
1207
+ /* @__PURE__ */ jsx4("span", { children: semantics.fidelity.replaceAll("_", " ") })
1029
1208
  ] }) : null,
1030
- /* @__PURE__ */ jsxs2("p", { children: [
1031
- /* @__PURE__ */ jsx3("strong", { children: "Native records" }),
1032
- /* @__PURE__ */ jsx3("span", { children: semantics.rawRecords })
1209
+ /* @__PURE__ */ jsxs3("p", { children: [
1210
+ /* @__PURE__ */ jsx4("strong", { children: "Native records" }),
1211
+ /* @__PURE__ */ jsx4("span", { children: semantics.rawRecords })
1033
1212
  ] }),
1034
- semantics.residueCount ? /* @__PURE__ */ jsxs2("p", { children: [
1035
- /* @__PURE__ */ jsx3("strong", { children: "Residue" }),
1036
- /* @__PURE__ */ jsxs2("span", { children: [
1213
+ semantics.residueCount ? /* @__PURE__ */ jsxs3("p", { children: [
1214
+ /* @__PURE__ */ jsx4("strong", { children: "Residue" }),
1215
+ /* @__PURE__ */ jsxs3("span", { children: [
1037
1216
  semantics.residueCount,
1038
1217
  " retained"
1039
1218
  ] })
1040
1219
  ] }) : null,
1041
- semantics.parseErrors ? /* @__PURE__ */ jsxs2("p", { children: [
1042
- /* @__PURE__ */ jsx3("strong", { children: "Parse diagnostics" }),
1043
- /* @__PURE__ */ jsx3("span", { children: semantics.parseErrors })
1220
+ semantics.parseErrors ? /* @__PURE__ */ jsxs3("p", { children: [
1221
+ /* @__PURE__ */ jsx4("strong", { children: "Parse diagnostics" }),
1222
+ /* @__PURE__ */ jsx4("span", { children: semantics.parseErrors })
1044
1223
  ] }) : null,
1045
- semantics.subagents.map((agent) => /* @__PURE__ */ jsxs2("p", { children: [
1046
- /* @__PURE__ */ jsxs2("strong", { children: [
1224
+ semantics.subagents.map((agent) => /* @__PURE__ */ jsxs3("p", { children: [
1225
+ /* @__PURE__ */ jsxs3("strong", { children: [
1047
1226
  agent.source,
1048
1227
  " subagent"
1049
1228
  ] }),
1050
- /* @__PURE__ */ jsxs2("span", { children: [
1229
+ /* @__PURE__ */ jsxs3("span", { children: [
1051
1230
  agent.messages,
1052
1231
  " messages \xB7 ",
1053
1232
  agent.fidelity.replaceAll("_", " ")
@@ -1057,13 +1236,29 @@ function SessionDetails({ semantics }) {
1057
1236
  ] });
1058
1237
  }
1059
1238
  var conversationMemory = /* @__PURE__ */ new Map();
1060
- function Conversation({ state, adapter, components = {}, slots = {}, memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`, pending = null }) {
1061
- const scroller = useRef2(null);
1239
+ function ConversationAnnouncements({ state }) {
1240
+ const previousBusy = useRef3(state.busy);
1241
+ const [announcement, setAnnouncement] = useState2("");
1242
+ useEffect3(() => {
1243
+ if (previousBusy.current && !state.busy && !state.error) {
1244
+ setAnnouncement(`${harnessDisplayName(state.harness) || "Coding agent"} finished working`);
1245
+ }
1246
+ previousBusy.current = state.busy;
1247
+ }, [state.busy, state.error, state.harness]);
1248
+ return /* @__PURE__ */ jsx4("span", { class: "scui-sr-only", role: "status", "aria-live": "polite", "aria-atomic": "true", children: announcement });
1249
+ }
1250
+ function Conversation({ state, adapter, components = {}, slots = {}, memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`, pending = null, unreadAfterMessages = null }) {
1251
+ const scroller = useRef3(null);
1062
1252
  const remembered = conversationMemory.get(memoryKey) ?? { top: null, atBottom: true };
1063
1253
  const [atBottom, setAtBottom] = useState2(remembered.atBottom);
1064
- const earlierAnchor = useRef2(null);
1065
- const restored = useRef2(false);
1254
+ const earlierAnchor = useRef3(null);
1255
+ const restored = useRef3(false);
1066
1256
  const blocks = groupConversation(state.transcript);
1257
+ const unreadBoundary = useRef3(Number.isSafeInteger(unreadAfterMessages) && unreadAfterMessages >= 0 ? unreadAfterMessages : null);
1258
+ const unreadBlock = unreadBoundary.current === null ? -1 : blocks.findIndex((block) => {
1259
+ const entries = block.kind === "activity" ? block.entries : [block.entry];
1260
+ return entries.some((entry) => Number.isSafeInteger(entry.messageIndex) && entry.messageIndex > unreadBoundary.current);
1261
+ });
1067
1262
  const Entry = components.TranscriptEntry ?? TranscriptEntry;
1068
1263
  const Group = components.ActivityGroup ?? ActivityGroup;
1069
1264
  const Plan = components.TaskPlan ?? TaskPlan;
@@ -1071,13 +1266,14 @@ function Conversation({ state, adapter, components = {}, slots = {}, memoryKey =
1071
1266
  const After = slots.afterConversation;
1072
1267
  const Empty = slots.emptyConversation;
1073
1268
  const remember = (value) => boundedSet(conversationMemory, memoryKey, value);
1269
+ const pendingMessage = typeof pending === "string" ? { text: pending, status: "sending" } : pending;
1074
1270
  const pin = () => {
1075
1271
  if (!scroller.current) return;
1076
1272
  scroller.current.scrollTop = scroller.current.scrollHeight;
1077
1273
  setAtBottom(true);
1078
1274
  remember({ top: scroller.current.scrollTop, atBottom: true });
1079
1275
  };
1080
- useLayoutEffect(() => {
1276
+ useLayoutEffect2(() => {
1081
1277
  const element = scroller.current;
1082
1278
  if (!element) return;
1083
1279
  const anchor = earlierAnchor.current;
@@ -1092,49 +1288,62 @@ function Conversation({ state, adapter, components = {}, slots = {}, memoryKey =
1092
1288
  if (remembered.top !== null && !remembered.atBottom) element.scrollTop = remembered.top;
1093
1289
  else pin();
1094
1290
  } else if (atBottom) pin();
1095
- }, [memoryKey, state.transcript, state.busy, state.operation]);
1096
- return /* @__PURE__ */ jsxs2("div", { class: "scui-conversation-wrap", children: [
1097
- /* @__PURE__ */ jsx3("div", { class: "scui-conversation", ref: scroller, tabIndex: 0, "aria-label": "Conversation", onScroll: (event) => {
1291
+ }, [memoryKey, state.transcript, state.busy, state.operation, pendingMessage?.text, pendingMessage?.status]);
1292
+ return /* @__PURE__ */ jsxs3("div", { class: "scui-conversation-wrap", children: [
1293
+ /* @__PURE__ */ jsx4(ConversationAnnouncements, { state }),
1294
+ /* @__PURE__ */ jsx4("div", { class: "scui-conversation", ref: scroller, tabIndex: 0, "aria-label": "Conversation", onScroll: (event) => {
1098
1295
  const element = event.currentTarget;
1099
1296
  const bottom = element.scrollHeight - element.scrollTop - element.clientHeight <= 64;
1100
1297
  setAtBottom(bottom);
1101
1298
  remember({ top: element.scrollTop, atBottom: bottom });
1102
- }, children: /* @__PURE__ */ jsxs2("div", { children: [
1103
- Before ? /* @__PURE__ */ jsx3(Before, { state, adapter, value: null }) : null,
1104
- /* @__PURE__ */ jsx3(Plan, { plan: state.taskPlan, value: state.taskPlan, state, adapter }),
1105
- /* @__PURE__ */ jsx3(SessionDetails, { semantics: state.semantics }),
1106
- state.history.hasEarlier ? /* @__PURE__ */ jsx3("button", { class: "scui-load", type: "button", disabled: Boolean(state.operation), onClick: () => {
1299
+ }, children: /* @__PURE__ */ jsxs3("div", { children: [
1300
+ Before ? /* @__PURE__ */ jsx4(Before, { state, adapter, value: null }) : null,
1301
+ /* @__PURE__ */ jsx4(Plan, { plan: state.taskPlan, value: state.taskPlan, state, adapter }),
1302
+ /* @__PURE__ */ jsx4(SessionDetails, { semantics: state.semantics }),
1303
+ state.history.hasEarlier ? /* @__PURE__ */ jsx4("button", { class: "scui-load", type: "button", disabled: Boolean(state.operation), onClick: () => {
1107
1304
  const element = scroller.current;
1108
1305
  if (element) earlierAnchor.current = { height: element.scrollHeight, top: element.scrollTop, entries: state.transcript.length };
1109
1306
  setAtBottom(false);
1110
1307
  adapter.onIntent({ action: "loadEarlier" });
1111
1308
  }, children: "Load earlier messages" }) : null,
1112
- !blocks.length && state.startup !== "ready" ? /* @__PURE__ */ jsx3(LoadingStatus, { state }) : null,
1113
- !blocks.length && state.startup === "ready" ? Empty ? /* @__PURE__ */ jsx3(Empty, { state, adapter, value: null }) : /* @__PURE__ */ jsx3("div", { class: "scui-empty", children: state.error ?? (state.harness ? `${harnessDisplayName(state.harness)} is listening. Say something.` : "No transcript yet.") }) : null,
1114
- blocks.map((block) => block.kind === "activity" ? /* @__PURE__ */ jsx3(Group, { value: block.entries, entries: block.entries, state, adapter }, block.id) : /* @__PURE__ */ jsx3(Entry, { value: block.entry, entry: block.entry, state, adapter }, block.id)),
1115
- pending ? /* @__PURE__ */ jsxs2("article", { class: "scui-message scui-pending", "data-role": "user", children: [
1116
- /* @__PURE__ */ jsx3(Markdown, { value: pending }),
1117
- /* @__PURE__ */ jsx3("small", { children: state.error && !state.busy ? "Not sent" : "Sending\u2026" })
1309
+ !blocks.length && state.startup !== "ready" ? /* @__PURE__ */ jsx4(LoadingStatus, { state }) : null,
1310
+ !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,
1311
+ blocks.map((block, index) => /* @__PURE__ */ jsxs3(Fragment2, { children: [
1312
+ index === unreadBlock ? /* @__PURE__ */ jsx4("div", { class: "scui-unread-divider", role: "separator", "aria-label": "New messages", children: /* @__PURE__ */ jsx4("span", { children: "New" }) }) : null,
1313
+ 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 })
1314
+ ] }, block.id)),
1315
+ pendingMessage ? /* @__PURE__ */ jsxs3("article", { class: "scui-message scui-pending", "data-role": "user", "data-status": pendingMessage.status, "aria-label": "Your pending message", children: [
1316
+ /* @__PURE__ */ jsx4(Markdown, { value: pendingMessage.text, copyText: adapter?.copyText }),
1317
+ /* @__PURE__ */ jsxs3("footer", { children: [
1318
+ /* @__PURE__ */ jsx4("small", { children: pendingMessage.status === "failed" ? "Not sent" : "Sending\u2026" }),
1319
+ pendingMessage.status === "failed" ? /* @__PURE__ */ jsxs3("span", { children: [
1320
+ /* @__PURE__ */ jsx4("button", { type: "button", onClick: pendingMessage.onRetry, children: "Retry" }),
1321
+ /* @__PURE__ */ jsx4("button", { type: "button", onClick: pendingMessage.onEdit, children: "Edit" })
1322
+ ] }) : null
1323
+ ] })
1118
1324
  ] }) : null,
1119
- state.busy ? /* @__PURE__ */ jsxs2("div", { class: "scui-working", role: "status", children: [
1120
- /* @__PURE__ */ jsx3("span", { "aria-hidden": "true", children: "\u2726" }),
1121
- /* @__PURE__ */ jsx3("i", {}),
1122
- /* @__PURE__ */ jsx3("i", {}),
1123
- /* @__PURE__ */ jsx3("i", {}),
1124
- /* @__PURE__ */ jsxs2("small", { children: [
1325
+ state.busy ? /* @__PURE__ */ jsxs3("div", { class: "scui-working", role: "status", children: [
1326
+ /* @__PURE__ */ jsx4("span", { "aria-hidden": "true", children: "\u2726" }),
1327
+ /* @__PURE__ */ jsx4("i", {}),
1328
+ /* @__PURE__ */ jsx4("i", {}),
1329
+ /* @__PURE__ */ jsx4("i", {}),
1330
+ /* @__PURE__ */ jsxs3("small", { children: [
1125
1331
  harnessDisplayName(state.harness),
1126
1332
  " is working"
1127
1333
  ] })
1128
1334
  ] }) : null,
1129
- After ? /* @__PURE__ */ jsx3(After, { state, adapter, value: null }) : null
1335
+ After ? /* @__PURE__ */ jsx4(After, { state, adapter, value: null }) : null
1130
1336
  ] }) }),
1131
- !atBottom ? /* @__PURE__ */ jsx3("button", { class: "scui-latest", type: "button", onClick: pin, children: "\u2193 Latest" }) : null
1337
+ !atBottom ? /* @__PURE__ */ jsxs3("button", { class: "scui-latest", type: "button", onClick: pin, children: [
1338
+ /* @__PURE__ */ jsx4(UiIcon, { name: "down", size: 13 }),
1339
+ " Latest"
1340
+ ] }) : null
1132
1341
  ] });
1133
1342
  }
1134
1343
 
1135
1344
  // src/logo.jsx
1136
- import { useEffect as useEffect3 } from "preact/hooks";
1137
- import { jsx as jsx4, jsxs as jsxs3 } from "preact/jsx-runtime";
1345
+ import { useEffect as useEffect4 } from "preact/hooks";
1346
+ import { jsx as jsx5, jsxs as jsxs4 } from "preact/jsx-runtime";
1138
1347
  var LOGOS = {
1139
1348
  "claude-code": {
1140
1349
  viewBox: "-1 3.5 26 18",
@@ -1181,74 +1390,106 @@ var LOGOS = {
1181
1390
  };
1182
1391
  function HarnessLogo({ id, activity, size = 28, onMissingLogo }) {
1183
1392
  const logo = LOGOS[id];
1184
- useEffect3(() => {
1393
+ useEffect4(() => {
1185
1394
  if (!logo) onMissingLogo?.(id);
1186
1395
  }, [id, logo, onMissingLogo]);
1187
1396
  if (!logo) return null;
1188
- return /* @__PURE__ */ jsxs3("span", { class: "scui-logo", "data-harness": id, "data-activity": activity, style: `--scui-logo-size:${size}px`, "aria-hidden": "true", children: [
1189
- /* @__PURE__ */ jsx4("svg", { viewBox: logo.viewBox, preserveAspectRatio: "xMidYMid meet", focusable: "false", children: logo.paths.map(([Tag, props], index) => /* @__PURE__ */ jsx4(Tag, { ...props }, index)) }),
1190
- activity && activity !== "idle" ? /* @__PURE__ */ jsx4("i", {}) : null
1397
+ return /* @__PURE__ */ jsxs4("span", { class: "scui-logo", "data-harness": id, "data-activity": activity, style: `--scui-logo-size:${size}px`, "aria-hidden": "true", children: [
1398
+ /* @__PURE__ */ jsx5("svg", { viewBox: logo.viewBox, preserveAspectRatio: "xMidYMid meet", focusable: "false", children: logo.paths.map(([Tag, props], index) => /* @__PURE__ */ jsx5(Tag, { ...props }, index)) }),
1399
+ activity && activity !== "idle" ? /* @__PURE__ */ jsx5("i", {}) : null
1191
1400
  ] });
1192
1401
  }
1193
1402
 
1194
1403
  // src/sessions.jsx
1195
- import { useState as useState3 } from "preact/hooks";
1196
- import { jsx as jsx5, jsxs as jsxs4 } from "preact/jsx-runtime";
1404
+ import { useEffect as useEffect5, useLayoutEffect as useLayoutEffect3, useRef as useRef4, useState as useState3 } from "preact/hooks";
1405
+ import { jsx as jsx6, jsxs as jsxs5 } from "preact/jsx-runtime";
1406
+ var sessionListMemory = /* @__PURE__ */ new Map();
1197
1407
  function SessionRow({ row, state, onOpen }) {
1198
1408
  const activity = sessionActivity(state, row);
1199
1409
  const preview = state.attention.find((item) => item.key === row.key)?.preview;
1200
- return /* @__PURE__ */ jsxs4("button", { class: "scui-session", "data-active": row.active, "data-activity": activity, type: "button", onClick: () => onOpen(row), children: [
1201
- /* @__PURE__ */ jsx5(HarnessLogo, { id: row.harness, activity, size: 34 }),
1202
- /* @__PURE__ */ jsxs4("span", { class: "scui-session-copy", children: [
1203
- /* @__PURE__ */ jsxs4("span", { children: [
1204
- /* @__PURE__ */ jsx5("strong", { children: sessionDisplayName(row) }),
1205
- /* @__PURE__ */ jsx5("small", { children: row.age })
1206
- ] }),
1207
- /* @__PURE__ */ jsxs4("span", { children: [
1208
- /* @__PURE__ */ jsx5("b", { children: harnessDisplayName(row.harness) }),
1209
- /* @__PURE__ */ jsx5("small", { children: preview || row.cwd || row.name })
1410
+ const title = sessionDisplayName(row);
1411
+ const detail = preview || (row.name && row.name !== title ? row.name : row.cwd);
1412
+ return /* @__PURE__ */ jsxs5("button", { class: "scui-session", "data-active": row.active, "data-activity": activity, "data-session-key": row.key, type: "button", "aria-label": `${title} \xB7 ${harnessDisplayName(row.harness)}${row.age ? ` \xB7 ${row.age}` : ""}`, "aria-current": row.active ? "true" : void 0, onClick: () => onOpen(row), children: [
1413
+ /* @__PURE__ */ jsx6(HarnessLogo, { id: row.harness, activity, size: 34 }),
1414
+ /* @__PURE__ */ jsxs5("span", { class: "scui-session-copy", children: [
1415
+ /* @__PURE__ */ jsxs5("span", { children: [
1416
+ /* @__PURE__ */ jsx6("strong", { children: title }),
1417
+ /* @__PURE__ */ jsx6("small", { children: row.age })
1210
1418
  ] }),
1211
- state.attachError?.key === row.key ? /* @__PURE__ */ jsx5("em", { children: state.attachError.message }) : null
1419
+ detail ? /* @__PURE__ */ jsx6("span", { children: /* @__PURE__ */ jsx6("small", { title: row.cwd, children: detail }) }) : null,
1420
+ state.attachError?.key === row.key ? /* @__PURE__ */ jsx6("em", { children: state.attachError.message }) : null
1212
1421
  ] })
1213
1422
  ] });
1214
1423
  }
1215
- function SessionList({ state, adapter, onOpen, onNew, onClose, components = {}, labels = DEFAULT_LABELS }) {
1216
- const [query, setQuery] = useState3("");
1424
+ function SessionList({ state, adapter, onOpen, onNew, onClose, components = {}, labels = DEFAULT_LABELS, focusKey = null, memoryKey = state.workspace || "@default" }) {
1425
+ const remembered = sessionListMemory.get(memoryKey) ?? { query: "", top: 0 };
1426
+ const [query, setQuery] = useState3(remembered.query);
1427
+ const [loadingMore, setLoadingMore] = useState3(false);
1428
+ const root = useRef4(null);
1429
+ const rowScroller = useRef4(null);
1217
1430
  const rows = filterSessions(state.sessions, query);
1218
1431
  const Row = components.SessionRow ?? SessionRow;
1219
- return /* @__PURE__ */ jsxs4("section", { class: "scui-list", children: [
1220
- /* @__PURE__ */ jsxs4("header", { class: "scui-head", children: [
1221
- /* @__PURE__ */ jsxs4("span", { class: "scui-head-copy", children: [
1222
- /* @__PURE__ */ jsx5("strong", { children: labels.chats }),
1223
- /* @__PURE__ */ jsxs4("small", { children: [
1432
+ useLayoutEffect3(() => {
1433
+ if (rowScroller.current) rowScroller.current.scrollTop = remembered.top;
1434
+ }, [memoryKey]);
1435
+ useEffect5(() => {
1436
+ if (!focusKey || !root.current) return;
1437
+ const target = focusKey === "@new" ? root.current.querySelector('[data-list-focus="new"]') : [...root.current.querySelectorAll("[data-session-key]")].find((element) => element.dataset.sessionKey === focusKey);
1438
+ (target ?? root.current.querySelector("input,button"))?.focus({ preventScroll: true });
1439
+ }, [focusKey, rows.length]);
1440
+ useEffect5(() => {
1441
+ if (loadingMore) setLoadingMore(false);
1442
+ }, [state.error, state.history.hasMoreSessions, state.sessions.length]);
1443
+ const loadMore = () => {
1444
+ if (loadingMore) return;
1445
+ setLoadingMore(true);
1446
+ const result = adapter.onIntent({ action: "loadSessions" });
1447
+ if (result && typeof result.then === "function") {
1448
+ Promise.resolve(result).then(() => setLoadingMore(false), () => setLoadingMore(false));
1449
+ }
1450
+ };
1451
+ return /* @__PURE__ */ jsxs5("section", { class: "scui-list", ref: root, children: [
1452
+ /* @__PURE__ */ jsxs5("header", { class: "scui-head", children: [
1453
+ /* @__PURE__ */ jsxs5("span", { class: "scui-head-copy", children: [
1454
+ /* @__PURE__ */ jsx6("strong", { children: labels.chats }),
1455
+ /* @__PURE__ */ jsxs5("small", { children: [
1224
1456
  state.sessions.length,
1225
1457
  " recent conversations"
1226
1458
  ] })
1227
1459
  ] }),
1228
- /* @__PURE__ */ jsx5("button", { type: "button", "aria-label": labels.newChat, disabled: !state.harnesses.some((item) => item.startable), onClick: onNew, children: "\uFF0B" }),
1229
- onClose ? /* @__PURE__ */ jsx5("button", { type: "button", "aria-label": "Close", onClick: onClose, children: "\xD7" }) : null
1460
+ /* @__PURE__ */ jsx6("button", { type: "button", "data-list-focus": "new", "aria-label": labels.newChat, disabled: !state.harnesses.some((item) => item.startable), onClick: onNew, children: /* @__PURE__ */ jsx6(UiIcon, { name: "plus", size: 18 }) }),
1461
+ onClose ? /* @__PURE__ */ jsx6("button", { type: "button", "aria-label": "Close", onClick: onClose, children: /* @__PURE__ */ jsx6(UiIcon, { name: "close", size: 18 }) }) : null
1230
1462
  ] }),
1231
- state.startup !== "ready" ? /* @__PURE__ */ jsx5(LoadingStatus, { state, compact: rows.length > 0 }) : null,
1232
- state.sessions.length > 4 ? /* @__PURE__ */ jsxs4("label", { class: "scui-search", children: [
1233
- /* @__PURE__ */ jsx5("span", { "aria-hidden": "true", children: "\u2315" }),
1234
- /* @__PURE__ */ jsx5("input", { type: "search", "aria-label": labels.searchChats, placeholder: labels.searchChats, value: query, onInput: (event) => setQuery(event.currentTarget.value) }),
1235
- /* @__PURE__ */ jsx5("small", { children: rows.length })
1463
+ state.startup !== "ready" ? /* @__PURE__ */ jsx6(LoadingStatus, { state, compact: rows.length > 0 }) : null,
1464
+ state.sessions.length > 4 ? /* @__PURE__ */ jsxs5("label", { class: "scui-search", children: [
1465
+ /* @__PURE__ */ jsx6(UiIcon, { name: "search", size: 15 }),
1466
+ /* @__PURE__ */ jsx6("input", { type: "search", "aria-label": labels.searchChats, placeholder: labels.searchChats, value: query, onInput: (event) => {
1467
+ const value = event.currentTarget.value;
1468
+ setQuery(value);
1469
+ boundedSet(sessionListMemory, memoryKey, { query: value, top: 0 });
1470
+ if (rowScroller.current) rowScroller.current.scrollTop = 0;
1471
+ } }),
1472
+ /* @__PURE__ */ jsx6("small", { children: rows.length })
1236
1473
  ] }) : null,
1237
- /* @__PURE__ */ jsxs4("div", { class: "scui-session-rows", children: [
1238
- !rows.length && state.startup === "ready" ? /* @__PURE__ */ jsx5("div", { class: "scui-empty", children: query ? "No chats match your search." : state.error ?? "No coding chats found." }) : null,
1239
- rows.map((row) => /* @__PURE__ */ jsx5(Row, { value: row, row, state, adapter, onOpen }, row.key)),
1240
- state.history.hasMoreSessions ? /* @__PURE__ */ jsx5("button", { class: "scui-load", type: "button", onClick: () => adapter.onIntent({ action: "loadSessions" }), children: "Load older chats" }) : null
1474
+ /* @__PURE__ */ jsxs5("div", { class: "scui-session-rows", ref: rowScroller, onScroll: (event) => boundedSet(sessionListMemory, memoryKey, { query, top: event.currentTarget.scrollTop }), children: [
1475
+ !rows.length && state.startup === "ready" ? /* @__PURE__ */ jsx6("div", { class: "scui-empty", children: query ? "No chats match your search." : state.error ?? "No coding chats found." }) : null,
1476
+ rows.map((row) => /* @__PURE__ */ jsx6(Row, { value: row, row, state, adapter, onOpen }, row.key)),
1477
+ state.history.hasMoreSessions ? /* @__PURE__ */ jsx6("button", { class: "scui-load", type: "button", disabled: loadingMore, onClick: loadMore, children: loadingMore ? "Loading older chats\u2026" : "Load older chats" }) : null
1241
1478
  ] })
1242
1479
  ] });
1243
1480
  }
1244
1481
 
1245
1482
  // src/messenger.jsx
1246
- import { jsx as jsx6, jsxs as jsxs5 } from "preact/jsx-runtime";
1483
+ import { jsx as jsx7, jsxs as jsxs6 } from "preact/jsx-runtime";
1484
+ var pendingMessageMemory = /* @__PURE__ */ new Map();
1485
+ var messengerViewMemory = /* @__PURE__ */ new Map();
1486
+ var newChatMemory = /* @__PURE__ */ new Map();
1487
+ var TRACKED_ACTIONS = /* @__PURE__ */ new Set(["resume", "join", "detach", "branch", "reduce", "terminal", "export", "interrupt", "respond", "refresh", "loadEarlier", "loadSessions"]);
1247
1488
  function Receipt({ state, adapter }) {
1248
1489
  const receipt = state.reductionReceipt;
1249
- if (receipt) return /* @__PURE__ */ jsx6("div", { class: "scui-receipt", children: /* @__PURE__ */ jsxs5("span", { children: [
1250
- /* @__PURE__ */ jsx6("strong", { children: "Reduced and verified" }),
1251
- /* @__PURE__ */ jsxs5("small", { children: [
1490
+ if (receipt) return /* @__PURE__ */ jsx7("div", { class: "scui-receipt", children: /* @__PURE__ */ jsxs6("span", { children: [
1491
+ /* @__PURE__ */ jsx7("strong", { children: "Reduced and verified" }),
1492
+ /* @__PURE__ */ jsxs6("small", { children: [
1252
1493
  receipt.sourceTokens.toLocaleString(),
1253
1494
  " \u2192 ",
1254
1495
  receipt.reducedTokens.toLocaleString(),
@@ -1257,76 +1498,177 @@ function Receipt({ state, adapter }) {
1257
1498
  "\xD7 \xB7 reversible"
1258
1499
  ] })
1259
1500
  ] }) });
1260
- if (state.exportReceipt) return /* @__PURE__ */ jsxs5("div", { class: "scui-receipt", children: [
1261
- /* @__PURE__ */ jsxs5("span", { children: [
1262
- /* @__PURE__ */ jsxs5("strong", { children: [
1501
+ if (state.exportReceipt) return /* @__PURE__ */ jsxs6("div", { class: "scui-receipt", children: [
1502
+ /* @__PURE__ */ jsxs6("span", { children: [
1503
+ /* @__PURE__ */ jsxs6("strong", { children: [
1263
1504
  "Lossless export ready \xB7 ",
1264
1505
  harnessDisplayName(state.exportReceipt.targetHarness)
1265
1506
  ] }),
1266
- /* @__PURE__ */ jsxs5("small", { children: [
1507
+ /* @__PURE__ */ jsxs6("small", { children: [
1267
1508
  state.exportReceipt.path,
1268
1509
  " \xB7 ",
1269
1510
  state.exportReceipt.files,
1270
1511
  " files"
1271
1512
  ] })
1272
1513
  ] }),
1273
- /* @__PURE__ */ jsx6("button", { type: "button", onClick: () => adapter.copyText?.(state.exportReceipt.path), children: "Copy path" })
1514
+ /* @__PURE__ */ jsx7("button", { type: "button", onClick: () => adapter.copyText?.(state.exportReceipt.path), children: "Copy path" })
1274
1515
  ] });
1275
- if (state.terminalHandoff) return /* @__PURE__ */ jsxs5("div", { class: "scui-receipt", children: [
1276
- /* @__PURE__ */ jsxs5("span", { children: [
1277
- /* @__PURE__ */ jsx6("strong", { children: "Terminal handoff ready" }),
1278
- /* @__PURE__ */ jsx6("small", { children: state.terminalHandoff.cwd })
1516
+ if (state.terminalHandoff) return /* @__PURE__ */ jsxs6("div", { class: "scui-receipt", children: [
1517
+ /* @__PURE__ */ jsxs6("span", { children: [
1518
+ /* @__PURE__ */ jsx7("strong", { children: "Terminal handoff ready" }),
1519
+ /* @__PURE__ */ jsx7("small", { children: state.terminalHandoff.cwd })
1279
1520
  ] }),
1280
- /* @__PURE__ */ jsx6("button", { type: "button", onClick: () => adapter.copyText?.(terminalCommand(state.terminalHandoff)), children: "Copy command" })
1521
+ /* @__PURE__ */ jsx7("button", { type: "button", onClick: () => adapter.copyText?.(terminalCommand(state.terminalHandoff)), children: "Copy command" })
1281
1522
  ] });
1282
1523
  return null;
1283
1524
  }
1284
- function ChatHeader({ state, adapter, onBack, onNew, onClose }) {
1285
- const harness = state.attached?.harness ?? state.harness;
1525
+ function ConversationActions({ state, adapter, actionPending }) {
1526
+ const [open, setOpen] = useState4(false);
1527
+ const root = useRef5(null);
1528
+ const panel = useRef5(null);
1529
+ const trigger = useRef5(null);
1530
+ const menuId = useId2();
1286
1531
  const targets = state.harnesses.filter((item) => item.startable);
1532
+ const groups = [
1533
+ {
1534
+ label: "Session",
1535
+ items: [
1536
+ state.canDetach ? { key: "detach", label: "Detach to read-only", intent: { action: "detach" } } : null,
1537
+ state.canOpenTerminal ? { key: "terminal", label: "Prepare terminal handoff", intent: { action: "terminal" } } : null,
1538
+ state.canExport && state.exportBackTarget ? { key: "export", label: `Export back to ${harnessDisplayName(state.exportBackTarget)}`, intent: { action: "export", targetHarness: state.exportBackTarget } } : null
1539
+ ].filter(Boolean)
1540
+ },
1541
+ {
1542
+ label: "Lower-cost continuation",
1543
+ items: state.canReduce ? targets.map((target) => ({ key: `reduce:${target.id}`, label: target.id === state.harness ? `Reduce and continue in ${target.label}` : `Reduce and switch to ${target.label}`, intent: { action: "reduce", targetHarness: target.id } })) : []
1544
+ },
1545
+ {
1546
+ label: "Independent continuation",
1547
+ items: state.canBranch ? targets.map((target) => ({ key: `branch:${target.id}`, label: target.id === state.harness ? `Fork in ${target.label}` : `Continue with ${target.label}`, intent: { action: "branch", targetHarness: target.id } })) : []
1548
+ }
1549
+ ].filter((group) => group.items.length);
1550
+ useEffect6(() => {
1551
+ if (!open) return;
1552
+ panel.current?.querySelector("button:not(:disabled)")?.focus({ preventScroll: true });
1553
+ const dismiss = (event) => {
1554
+ if (event.type === "keydown" && event.key === "Escape") {
1555
+ event.preventDefault();
1556
+ setOpen(false);
1557
+ trigger.current?.focus({ preventScroll: true });
1558
+ } else if (event.type === "pointerdown" && !root.current?.contains(event.target)) {
1559
+ setOpen(false);
1560
+ }
1561
+ };
1562
+ document.addEventListener("keydown", dismiss);
1563
+ document.addEventListener("pointerdown", dismiss, true);
1564
+ return () => {
1565
+ document.removeEventListener("keydown", dismiss);
1566
+ document.removeEventListener("pointerdown", dismiss, true);
1567
+ };
1568
+ }, [open]);
1569
+ const dispatch = (intent) => {
1570
+ setOpen(false);
1571
+ adapter.onIntent(intent);
1572
+ };
1573
+ const navigate = (event) => {
1574
+ if (event.key === "Escape") {
1575
+ event.preventDefault();
1576
+ event.stopPropagation();
1577
+ setOpen(false);
1578
+ trigger.current?.focus({ preventScroll: true });
1579
+ return;
1580
+ }
1581
+ if (!["ArrowDown", "ArrowUp", "Home", "End"].includes(event.key)) return;
1582
+ const items = [...panel.current.querySelectorAll("button:not(:disabled)")];
1583
+ if (!items.length) return;
1584
+ event.preventDefault();
1585
+ const current = items.indexOf(document.activeElement);
1586
+ const index = event.key === "Home" ? 0 : event.key === "End" ? items.length - 1 : event.key === "ArrowDown" ? (current + 1) % items.length : (current <= 0 ? items.length : current) - 1;
1587
+ items[index].focus({ preventScroll: true });
1588
+ };
1589
+ return /* @__PURE__ */ jsxs6("div", { class: "scui-menu", ref: root, onBlur: (event) => {
1590
+ if (event.relatedTarget && !event.currentTarget.contains(event.relatedTarget)) setOpen(false);
1591
+ }, children: [
1592
+ /* @__PURE__ */ jsx7("button", { ref: trigger, class: "scui-menu-trigger", type: "button", "aria-label": "Conversation actions", "aria-haspopup": "menu", "aria-expanded": open, "aria-controls": open ? menuId : void 0, onClick: () => setOpen((value) => !value), children: /* @__PURE__ */ jsx7(UiIcon, { name: "menu", size: 18 }) }),
1593
+ open ? /* @__PURE__ */ jsx7("div", { ref: panel, id: menuId, class: "scui-menu-panel", role: "menu", "aria-label": "Conversation actions", onKeyDown: navigate, children: groups.map((group) => /* @__PURE__ */ jsxs6("section", { role: "group", "aria-label": group.label, children: [
1594
+ /* @__PURE__ */ jsx7("strong", { children: group.label }),
1595
+ group.items.map((item) => /* @__PURE__ */ jsx7("button", { role: "menuitem", type: "button", disabled: actionPending, onClick: () => dispatch(item.intent), children: item.label }, item.key))
1596
+ ] }, group.label)) }) : null
1597
+ ] });
1598
+ }
1599
+ function ChatHeader({ state, adapter, pendingStatus, actionPending, onBack, onNew, onClose }) {
1600
+ const back = useRef5(null);
1601
+ const harness = state.attached?.harness ?? state.harness;
1287
1602
  const title = state.attached ? sessionDisplayName(state.attached) : harnessDisplayName(harness) || "Agent chat";
1288
- const status = state.needsInput ? "Needs input" : state.busy ? "Working" : state.mode === "mirror" ? "Read-only" : "Ready";
1603
+ const status = state.needsInput ? "Needs input" : pendingStatus === "failed" ? "Send failed" : state.busy ? "Working" : pendingStatus === "sending" ? "Sending" : pendingStatus === "editing" ? "Editing message" : state.mode === "mirror" ? "Read-only" : "Ready";
1289
1604
  const menu = state.canDetach || state.canOpenTerminal || state.canBranch || state.canAttach || state.canExport || state.canReduce;
1290
- return /* @__PURE__ */ jsxs5("header", { class: "scui-head scui-chat-head", children: [
1291
- /* @__PURE__ */ jsx6("button", { type: "button", "aria-label": "Back to chats", onClick: onBack, children: "\u2039" }),
1292
- /* @__PURE__ */ jsx6(HarnessLogo, { id: harness, size: 28 }),
1293
- /* @__PURE__ */ jsxs5("span", { class: "scui-head-copy", children: [
1294
- /* @__PURE__ */ jsx6("strong", { children: title }),
1295
- /* @__PURE__ */ jsxs5("small", { children: [
1605
+ useEffect6(() => {
1606
+ if (state.mode === "mirror" && !state.canSend) back.current?.focus({ preventScroll: true });
1607
+ }, []);
1608
+ return /* @__PURE__ */ jsxs6("header", { class: "scui-head scui-chat-head", children: [
1609
+ /* @__PURE__ */ jsx7("button", { ref: back, type: "button", "aria-label": "Back to chats", onClick: onBack, children: /* @__PURE__ */ jsx7(UiIcon, { name: "back", size: 18 }) }),
1610
+ /* @__PURE__ */ jsx7(HarnessLogo, { id: harness, size: 28 }),
1611
+ /* @__PURE__ */ jsxs6("span", { class: "scui-head-copy", children: [
1612
+ /* @__PURE__ */ jsx7("strong", { children: title }),
1613
+ /* @__PURE__ */ jsxs6("small", { children: [
1296
1614
  harnessDisplayName(harness),
1297
1615
  " \xB7 ",
1298
1616
  status
1299
1617
  ] })
1300
1618
  ] }),
1301
- menu ? /* @__PURE__ */ jsxs5("details", { class: "scui-menu", children: [
1302
- /* @__PURE__ */ jsx6("summary", { "aria-label": "Conversation actions", children: "\u2022\u2022\u2022" }),
1303
- /* @__PURE__ */ jsxs5("div", { children: [
1304
- state.canDetach ? /* @__PURE__ */ jsx6("button", { type: "button", onClick: () => adapter.onIntent({ action: "detach" }), children: "Detach to read-only" }) : null,
1305
- state.canOpenTerminal ? /* @__PURE__ */ jsx6("button", { type: "button", onClick: () => adapter.onIntent({ action: "terminal" }), children: "Prepare terminal handoff" }) : null,
1306
- state.canReduce ? targets.map((target) => /* @__PURE__ */ jsx6("button", { type: "button", onClick: () => adapter.onIntent({ action: "reduce", targetHarness: target.id }), children: target.id === state.harness ? `Reduce and continue in ${target.label}` : `Reduce and switch to ${target.label}` }, `reduce:${target.id}`)) : null,
1307
- state.canBranch ? targets.map((target) => /* @__PURE__ */ jsx6("button", { type: "button", onClick: () => adapter.onIntent({ action: "branch", targetHarness: target.id }), children: target.id === state.harness ? `Fork in ${target.label}` : `Continue with ${target.label}` }, `branch:${target.id}`)) : null,
1308
- state.canExport && state.exportBackTarget ? /* @__PURE__ */ jsxs5("button", { type: "button", onClick: () => adapter.onIntent({ action: "export", targetHarness: state.exportBackTarget }), children: [
1309
- "Export back to ",
1310
- harnessDisplayName(state.exportBackTarget)
1311
- ] }) : null
1312
- ] })
1313
- ] }) : null,
1314
- /* @__PURE__ */ jsx6("button", { type: "button", "aria-label": "New chat", onClick: onNew, children: "\uFF0B" }),
1315
- onClose ? /* @__PURE__ */ jsx6("button", { type: "button", "aria-label": "Close", onClick: onClose, children: "\xD7" }) : null
1619
+ menu ? /* @__PURE__ */ jsx7(ConversationActions, { state, adapter, actionPending }) : null,
1620
+ /* @__PURE__ */ jsx7("button", { type: "button", "aria-label": "New chat", onClick: onNew, children: /* @__PURE__ */ jsx7(UiIcon, { name: "plus", size: 18 }) }),
1621
+ onClose ? /* @__PURE__ */ jsx7("button", { type: "button", "aria-label": "Close", onClick: onClose, children: /* @__PURE__ */ jsx7(UiIcon, { name: "close", size: 18 }) }) : null
1316
1622
  ] });
1317
1623
  }
1318
1624
  function Chat({ state, adapter, onBack, onNew, onClose, components, slots, labels }) {
1319
1625
  const Header = slots.header;
1320
1626
  const memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`;
1321
- const [pending, setPending] = useState4(null);
1322
- const acknowledged = useRef3(/* @__PURE__ */ new Set());
1323
- useEffect4(() => {
1324
- setPending(null);
1627
+ const [pending, setPendingState] = useState4(() => pendingMessageMemory.get(memoryKey) ?? null);
1628
+ const [pendingAction, setPendingAction] = useState4(null);
1629
+ const [restoreDraft, setRestoreDraft] = useState4(null);
1630
+ const restoreSequence = useRef5(0);
1631
+ const actionSequence = useRef5(0);
1632
+ const acknowledged = useRef5(/* @__PURE__ */ new Set());
1633
+ const setPending = (update) => setPendingState((current) => {
1634
+ const next = typeof update === "function" ? update(current) : update;
1635
+ if (next) boundedSet(pendingMessageMemory, memoryKey, next);
1636
+ else pendingMessageMemory.delete(memoryKey);
1637
+ return next;
1638
+ });
1639
+ useEffect6(() => {
1640
+ setPendingState(pendingMessageMemory.get(memoryKey) ?? null);
1641
+ setPendingAction(null);
1642
+ setRestoreDraft(null);
1325
1643
  }, [memoryKey]);
1326
- useEffect4(() => {
1327
- if (pending && state.transcript.some((entry) => entry.role === "user" && entry.text.trim() === pending.trim())) setPending(null);
1328
- }, [pending, state.transcript]);
1329
- useEffect4(() => {
1644
+ useEffect6(() => {
1645
+ if (!pending) return;
1646
+ if (state.transcript.some((entry) => entry.role === "user" && entry.text.trim() === pending.text.trim() && !pending.baselineIds.includes(entry.id))) {
1647
+ setPending(null);
1648
+ return;
1649
+ }
1650
+ if (pending.status === "sending" && state.busy && !pending.seenBusy) setPending({ ...pending, seenBusy: true });
1651
+ else if (pending.status === "sending" && state.error && !state.busy && !state.operation && (pending.seenBusy || state.error !== pending.initialError)) setPending({ ...pending, status: "failed" });
1652
+ }, [pending, state.busy, state.error, state.operation, state.transcript]);
1653
+ const beginPending = (text) => setPending({
1654
+ text,
1655
+ status: "sending",
1656
+ initialError: state.error,
1657
+ seenBusy: state.busy,
1658
+ baselineIds: state.transcript.filter((entry) => entry.role === "user" && entry.text.trim() === text.trim()).map((entry) => entry.id)
1659
+ });
1660
+ const retryPending = () => {
1661
+ if (!pending) return;
1662
+ adapter.onIntent({ action: "send", text: pending.text });
1663
+ setPending({ ...pending, status: "sending", initialError: state.error, seenBusy: state.busy });
1664
+ };
1665
+ const editPending = () => {
1666
+ if (!pending) return;
1667
+ restoreSequence.current += 1;
1668
+ setRestoreDraft({ id: restoreSequence.current, text: pending.text });
1669
+ setPending({ ...pending, status: "editing" });
1670
+ };
1671
+ useEffect6(() => {
1330
1672
  const key = state.attached?.key;
1331
1673
  if (!key || !state.attention.some((item) => item.key === key)) {
1332
1674
  if (key) acknowledged.current.delete(key);
@@ -1337,74 +1679,143 @@ function Chat({ state, adapter, onBack, onNew, onClose, components, slots, label
1337
1679
  adapter.onIntent({ action: "ack", key });
1338
1680
  }
1339
1681
  }, [adapter, state.attached?.key, state.attention]);
1340
- return /* @__PURE__ */ jsxs5("section", { class: "scui-chat", children: [
1341
- Header ? /* @__PURE__ */ jsx6(Header, { state, adapter, value: null }) : /* @__PURE__ */ jsx6(ChatHeader, { state, adapter, onBack, onNew, onClose }),
1342
- operationLabel(state.operation) ? /* @__PURE__ */ jsxs5("div", { class: "scui-operation", role: "status", children: [
1343
- /* @__PURE__ */ jsx6("i", {}),
1344
- operationLabel(state.operation)
1682
+ const trackedAdapter = useMemo2(() => ({
1683
+ ...adapter,
1684
+ onIntent(intent) {
1685
+ if (!TRACKED_ACTIONS.has(intent.action)) return adapter.onIntent(intent);
1686
+ const id = actionSequence.current + 1;
1687
+ actionSequence.current = id;
1688
+ const action2 = intent.action;
1689
+ setPendingAction({ id, action: action2, initialError: state.error, seenOperation: false });
1690
+ let result;
1691
+ try {
1692
+ result = adapter.onIntent(intent);
1693
+ } catch (error) {
1694
+ setPendingAction((current) => current?.id === id ? null : current);
1695
+ throw error;
1696
+ }
1697
+ if (result && typeof result.then === "function") {
1698
+ Promise.resolve(result).then(
1699
+ () => setPendingAction((current) => current?.id === id ? null : current),
1700
+ () => setPendingAction((current) => current?.id === id ? null : current)
1701
+ );
1702
+ }
1703
+ return result;
1704
+ }
1705
+ }), [adapter, state.error]);
1706
+ useEffect6(() => {
1707
+ if (!pendingAction) return;
1708
+ if (state.operation && !pendingAction.seenOperation) {
1709
+ setPendingAction({ ...pendingAction, seenOperation: true });
1710
+ } else if (!state.operation && (pendingAction.seenOperation || state.error !== pendingAction.initialError)) {
1711
+ setPendingAction(null);
1712
+ }
1713
+ }, [pendingAction, state.error, state.operation]);
1714
+ const pendingMessage = pending && pending.status !== "editing" ? { text: pending.text, status: pending.status, onRetry: retryPending, onEdit: editPending } : null;
1715
+ const action = state.operation || pendingAction?.action || null;
1716
+ const actionLabel = operationLabel(action);
1717
+ const actionState = action ? { ...state, operation: action, canInterrupt: false, canRespond: false } : state;
1718
+ const unreadAfterMessages = state.attention.find((item) => item.key === state.attached?.key)?.afterMessages ?? null;
1719
+ return /* @__PURE__ */ jsxs6("section", { class: "scui-chat", children: [
1720
+ Header ? /* @__PURE__ */ jsx7(Header, { state: actionState, adapter: trackedAdapter, value: null }) : /* @__PURE__ */ jsx7(ChatHeader, { state: actionState, adapter: trackedAdapter, pendingStatus: pending?.status ?? null, actionPending: Boolean(action), onBack, onNew, onClose }),
1721
+ actionLabel ? /* @__PURE__ */ jsxs6("div", { class: "scui-operation", role: "status", children: [
1722
+ /* @__PURE__ */ jsx7("i", {}),
1723
+ actionLabel
1345
1724
  ] }) : null,
1346
- state.error ? /* @__PURE__ */ jsxs5("div", { class: "scui-error", role: "alert", children: [
1347
- /* @__PURE__ */ jsx6("span", { children: state.error }),
1348
- state.recoverable ? /* @__PURE__ */ jsx6("button", { type: "button", onClick: () => adapter.onIntent({ action: "refresh" }), children: "Retry" }) : null
1725
+ state.error ? /* @__PURE__ */ jsxs6("div", { class: "scui-error", role: "alert", children: [
1726
+ /* @__PURE__ */ jsx7("span", { children: state.error }),
1727
+ state.recoverable ? /* @__PURE__ */ jsx7("button", { type: "button", disabled: Boolean(action), onClick: () => trackedAdapter.onIntent({ action: "refresh" }), children: "Retry" }) : null
1349
1728
  ] }) : null,
1350
- /* @__PURE__ */ jsx6(Receipt, { state, adapter }),
1351
- /* @__PURE__ */ jsx6(Conversation, { state, adapter, components, slots, memoryKey, pending }, memoryKey),
1352
- /* @__PURE__ */ jsx6(ContinuationBar, { state, adapter, labels }),
1353
- state.mode !== "mirror" || state.canSend ? /* @__PURE__ */ jsx6(Composer, { state, adapter, labels, memoryKey, onPending: setPending }, memoryKey) : null
1729
+ /* @__PURE__ */ jsx7(Receipt, { state, adapter }),
1730
+ /* @__PURE__ */ jsx7(Conversation, { state: actionState, adapter: trackedAdapter, components, slots, memoryKey, pending: pendingMessage, unreadAfterMessages }, memoryKey),
1731
+ /* @__PURE__ */ jsx7(ContinuationBar, { state: actionState, adapter: trackedAdapter, labels }),
1732
+ state.mode !== "mirror" || state.canSend ? /* @__PURE__ */ jsx7(Composer, { state: actionState, adapter: trackedAdapter, labels, memoryKey, pendingStatus: pending?.status ?? null, restoreDraft, onDraftRestored: (id) => setRestoreDraft((value) => value?.id === id ? null : value), onPending: beginPending }, memoryKey) : null
1354
1733
  ] });
1355
1734
  }
1356
- function NewChat({ state, adapter, onBack, onClose, onStarted, labels }) {
1735
+ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey }) {
1357
1736
  const startable = state.harnesses.filter((item) => item.startable);
1358
1737
  const startableKey = startable.map((item) => item.id).join("\0");
1359
- const [harness, setHarness] = useState4(startable[0]?.id ?? "");
1360
- const [draft, setDraft] = useState4("");
1361
- const [starting, setStarting] = useState4(false);
1362
- useEffect4(() => {
1363
- if (!startable.some((item) => item.id === harness)) setHarness(startable[0]?.id ?? "");
1738
+ const remembered = newChatMemory.get(memoryKey) ?? { harness: startable[0]?.id ?? "", draft: "" };
1739
+ const [harness, setHarness] = useState4(remembered.harness);
1740
+ const [draft, setDraft] = useState4(remembered.draft);
1741
+ const [starting, setStarting] = useState4(null);
1742
+ const startSequence = useRef5(0);
1743
+ const textarea = useRef5(null);
1744
+ useAutosizeTextarea(textarea, draft);
1745
+ useEffect6(() => {
1746
+ if (startable.some((item) => item.id === harness)) return;
1747
+ const next = startable[0]?.id ?? "";
1748
+ setHarness(next);
1749
+ boundedSet(newChatMemory, memoryKey, { harness: next, draft });
1364
1750
  }, [harness, startableKey]);
1365
- useEffect4(() => {
1366
- if (starting && (state.busy || state.transcript.length)) onStarted();
1367
- }, [onStarted, starting, state.busy, state.transcript.length]);
1368
- useEffect4(() => {
1369
- if (starting && state.error && !state.busy && !state.operation) setStarting(false);
1751
+ useEffect6(() => {
1752
+ if (!starting) return;
1753
+ const sessionChanged = (state.attached?.key ?? null) !== starting.attachedKey;
1754
+ const beganWorking = !starting.busy && state.busy;
1755
+ if (!sessionChanged && !beganWorking) return;
1756
+ newChatMemory.delete(memoryKey);
1757
+ onStarted();
1758
+ }, [memoryKey, onStarted, starting, state.attached?.key, state.busy]);
1759
+ useEffect6(() => {
1760
+ if (starting && state.error !== starting.initialError && !state.busy && !state.operation) setStarting(null);
1370
1761
  }, [starting, state.busy, state.error, state.operation]);
1762
+ useEffect6(() => {
1763
+ textarea.current?.focus({ preventScroll: true });
1764
+ }, []);
1371
1765
  const send = () => {
1372
1766
  const text = draft.trim();
1373
1767
  if (!text || !harness || starting) return;
1374
- setStarting(true);
1375
- adapter.onIntent({ action: "new", harness, text });
1768
+ const id = startSequence.current + 1;
1769
+ startSequence.current = id;
1770
+ setStarting({ id, attachedKey: state.attached?.key ?? null, busy: state.busy, initialError: state.error });
1771
+ const result = adapter.onIntent({ action: "new", harness, text });
1772
+ if (result && typeof result.then === "function") {
1773
+ Promise.resolve(result).catch(() => setStarting((current) => current?.id === id ? null : current));
1774
+ }
1376
1775
  };
1377
- return /* @__PURE__ */ jsxs5("section", { class: "scui-chat", children: [
1378
- /* @__PURE__ */ jsxs5("header", { class: "scui-head", children: [
1379
- /* @__PURE__ */ jsx6("button", { type: "button", "aria-label": "Back", onClick: onBack, children: "\u2039" }),
1380
- /* @__PURE__ */ jsxs5("span", { class: "scui-head-copy", children: [
1381
- /* @__PURE__ */ jsx6("strong", { children: labels.newChat }),
1382
- /* @__PURE__ */ jsx6("small", { children: "No session is created until you send" })
1776
+ return /* @__PURE__ */ jsxs6("section", { class: "scui-chat", children: [
1777
+ /* @__PURE__ */ jsxs6("header", { class: "scui-head", children: [
1778
+ /* @__PURE__ */ jsx7("button", { type: "button", "aria-label": "Back", onClick: onBack, children: /* @__PURE__ */ jsx7(UiIcon, { name: "back", size: 18 }) }),
1779
+ /* @__PURE__ */ jsxs6("span", { class: "scui-head-copy", children: [
1780
+ /* @__PURE__ */ jsx7("strong", { children: labels.newChat }),
1781
+ /* @__PURE__ */ jsx7("small", { children: "No session is created until you send" })
1383
1782
  ] }),
1384
- onClose ? /* @__PURE__ */ jsx6("button", { type: "button", "aria-label": "Close", onClick: onClose, children: "\xD7" }) : null
1783
+ onClose ? /* @__PURE__ */ jsx7("button", { type: "button", "aria-label": "Close", onClick: onClose, children: /* @__PURE__ */ jsx7(UiIcon, { name: "close", size: 18 }) }) : null
1385
1784
  ] }),
1386
- /* @__PURE__ */ jsxs5("div", { class: "scui-new", children: [
1387
- /* @__PURE__ */ jsx6("span", { "aria-hidden": "true", children: "\u2726" }),
1388
- /* @__PURE__ */ jsx6("strong", { children: "What should the agent build or fix?" }),
1389
- /* @__PURE__ */ jsx6("small", { children: "Choose a coding harness and send the first message." })
1785
+ starting ? /* @__PURE__ */ jsxs6("div", { class: "scui-operation", role: "status", children: [
1786
+ /* @__PURE__ */ jsx7("i", {}),
1787
+ operationLabel("start")
1788
+ ] }) : null,
1789
+ /* @__PURE__ */ jsxs6("div", { class: "scui-new", children: [
1790
+ /* @__PURE__ */ jsx7("span", { "aria-hidden": "true", children: "\u2726" }),
1791
+ /* @__PURE__ */ jsx7("strong", { children: "What should the agent build or fix?" }),
1792
+ /* @__PURE__ */ jsx7("small", { children: "Choose a coding harness and send the first message." })
1390
1793
  ] }),
1391
- /* @__PURE__ */ jsxs5("div", { class: "scui-compose", children: [
1392
- /* @__PURE__ */ jsxs5("label", { class: "scui-harness-picker", children: [
1393
- /* @__PURE__ */ jsx6(HarnessLogo, { id: harness, size: 24 }),
1394
- /* @__PURE__ */ jsx6("span", { children: "Coding harness" }),
1395
- /* @__PURE__ */ jsx6("select", { value: harness, disabled: starting, onChange: (event) => setHarness(event.currentTarget.value), children: state.harnesses.map((item) => /* @__PURE__ */ jsxs5("option", { value: item.id, disabled: !item.startable, children: [
1794
+ /* @__PURE__ */ jsxs6("div", { class: "scui-compose", children: [
1795
+ /* @__PURE__ */ jsxs6("label", { class: "scui-harness-picker", children: [
1796
+ /* @__PURE__ */ jsx7(HarnessLogo, { id: harness, size: 24 }),
1797
+ /* @__PURE__ */ jsx7("span", { children: "Coding harness" }),
1798
+ /* @__PURE__ */ jsx7("select", { value: harness, disabled: Boolean(starting), onChange: (event) => {
1799
+ const value = event.currentTarget.value;
1800
+ setHarness(value);
1801
+ boundedSet(newChatMemory, memoryKey, { harness: value, draft });
1802
+ }, children: state.harnesses.map((item) => /* @__PURE__ */ jsxs6("option", { value: item.id, disabled: !item.startable, children: [
1396
1803
  item.label,
1397
1804
  item.startable ? "" : " \xB7 unavailable"
1398
1805
  ] }, item.id)) })
1399
1806
  ] }),
1400
- /* @__PURE__ */ jsxs5("div", { class: "scui-envelope", children: [
1401
- /* @__PURE__ */ jsx6("textarea", { rows: 3, "aria-label": "Message coding agent", placeholder: startable.length ? "What should the agent do?" : "No coding harness is available", value: draft, disabled: !startable.length || starting, onInput: (event) => setDraft(event.currentTarget.value), onKeyDown: (event) => {
1807
+ /* @__PURE__ */ jsxs6("div", { class: "scui-envelope", children: [
1808
+ /* @__PURE__ */ jsx7("textarea", { ref: textarea, rows: 3, "aria-label": "Message coding agent", placeholder: startable.length ? "What should the agent do?" : "No coding harness is available", value: draft, disabled: !startable.length || Boolean(starting), onInput: (event) => {
1809
+ const value = event.currentTarget.value;
1810
+ setDraft(value);
1811
+ boundedSet(newChatMemory, memoryKey, { harness, draft: value });
1812
+ }, onKeyDown: (event) => {
1402
1813
  if (isSendKey(event)) {
1403
1814
  event.preventDefault();
1404
1815
  send();
1405
1816
  }
1406
1817
  } }),
1407
- /* @__PURE__ */ jsx6("span", { children: /* @__PURE__ */ jsx6("button", { type: "button", class: "scui-send", "aria-label": "Start chat", disabled: !draft.trim() || !harness || starting, onClick: send, children: starting ? "\u25CC" : "\u2191" }) })
1818
+ /* @__PURE__ */ jsx7("span", { children: /* @__PURE__ */ jsx7("button", { type: "button", class: "scui-send", "aria-label": "Start chat", disabled: !draft.trim() || !harness || Boolean(starting), onClick: send, children: starting ? /* @__PURE__ */ jsx7("i", { class: "scui-control-spinner" }) : /* @__PURE__ */ jsx7(UiIcon, { name: "send", size: 17 }) }) })
1408
1819
  ] })
1409
1820
  ] })
1410
1821
  ] });
@@ -1412,9 +1823,15 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels }) {
1412
1823
  function SupercodeMessenger({ state: stateInput, adapter, class: className = "", initialView, labels, components = {}, slots = {} }) {
1413
1824
  const state = useMemo2(() => normalizeUiState(stateInput), [stateInput]);
1414
1825
  const copy = { ...DEFAULT_LABELS, ...labels };
1415
- const [view, setView] = useState4(initialView ?? (state.attention.length ? "list" : state.attached || state.transcript.length ? "chat" : "list"));
1826
+ const memoryKey = state.workspace || "@default";
1827
+ const [view, setViewState] = useState4(initialView ?? (state.attention.length ? "list" : messengerViewMemory.get(memoryKey) ?? (state.attached || state.transcript.length ? "chat" : "list")));
1416
1828
  const [opening, setOpening] = useState4(null);
1417
- useEffect4(() => {
1829
+ const [listFocus, setListFocus] = useState4(null);
1830
+ const setView = (next) => {
1831
+ boundedSet(messengerViewMemory, memoryKey, next);
1832
+ setViewState(next);
1833
+ };
1834
+ useEffect6(() => {
1418
1835
  if (!opening) return;
1419
1836
  if (state.attached?.key === opening.key) {
1420
1837
  setOpening(null);
@@ -1424,28 +1841,37 @@ function SupercodeMessenger({ state: stateInput, adapter, class: className = "",
1424
1841
  }
1425
1842
  }, [opening, state.attachError?.key, state.attached?.key, state.error, state.operation]);
1426
1843
  const open = (row) => {
1844
+ setListFocus(row.key);
1427
1845
  setOpening(row);
1428
- adapter.onIntent({ action: "ack", key: row.key });
1429
1846
  adapter.onIntent({ action: "attach", key: row.key });
1430
1847
  };
1431
1848
  const close = () => adapter.onClose?.();
1432
1849
  const Footer = slots.footer;
1433
- return /* @__PURE__ */ jsxs5("main", { class: `scui-root ${className}`, "data-view": view, "data-mode": state.mode, "aria-label": "Supercode messenger", children: [
1434
- view === "list" ? /* @__PURE__ */ jsx6(SessionList, { state, adapter, onOpen: open, onNew: () => setView("new"), onClose: adapter.onClose ? close : void 0, components, labels: copy }) : null,
1435
- view === "new" ? /* @__PURE__ */ jsx6(NewChat, { state, adapter, onBack: () => setView("list"), onClose: adapter.onClose ? close : void 0, onStarted: () => setView("chat"), labels: copy }) : null,
1436
- view === "chat" ? /* @__PURE__ */ jsx6(Chat, { state, adapter, onBack: () => setView("list"), onNew: () => setView("new"), onClose: adapter.onClose ? close : void 0, components, slots, labels: copy }) : null,
1437
- opening ? /* @__PURE__ */ jsxs5("div", { class: "scui-opening", role: "status", "aria-busy": "true", children: [
1438
- /* @__PURE__ */ jsx6(HarnessLogo, { id: opening.harness, size: 34 }),
1439
- /* @__PURE__ */ jsxs5("span", { children: [
1440
- /* @__PURE__ */ jsxs5("strong", { children: [
1850
+ return /* @__PURE__ */ jsxs6("main", { class: `scui-root ${className}`, "data-view": view, "data-mode": state.mode, "aria-label": "Supercode messenger", children: [
1851
+ view === "list" ? /* @__PURE__ */ jsx7(SessionList, { state, adapter, focusKey: listFocus, onOpen: open, onNew: () => {
1852
+ setListFocus("@new");
1853
+ setView("new");
1854
+ }, onClose: adapter.onClose ? close : void 0, components, labels: copy, memoryKey }) : null,
1855
+ view === "new" ? /* @__PURE__ */ jsx7(NewChat, { state, adapter, onBack: () => setView("list"), onClose: adapter.onClose ? close : void 0, onStarted: () => setView("chat"), labels: copy, memoryKey }) : null,
1856
+ view === "chat" ? /* @__PURE__ */ jsx7(Chat, { state, adapter, onBack: () => {
1857
+ setListFocus(state.attached?.key ?? listFocus);
1858
+ setView("list");
1859
+ }, onNew: () => {
1860
+ setListFocus("@new");
1861
+ setView("new");
1862
+ }, onClose: adapter.onClose ? close : void 0, components, slots, labels: copy }) : null,
1863
+ opening ? /* @__PURE__ */ jsxs6("div", { class: "scui-opening", role: "status", "aria-busy": "true", children: [
1864
+ /* @__PURE__ */ jsx7(HarnessLogo, { id: opening.harness, size: 34 }),
1865
+ /* @__PURE__ */ jsxs6("span", { children: [
1866
+ /* @__PURE__ */ jsxs6("strong", { children: [
1441
1867
  "Opening ",
1442
1868
  sessionDisplayName(opening)
1443
1869
  ] }),
1444
- /* @__PURE__ */ jsx6("small", { children: "Loading the latest transcript window\u2026" })
1870
+ /* @__PURE__ */ jsx7("small", { children: "Loading the latest transcript window\u2026" })
1445
1871
  ] }),
1446
- /* @__PURE__ */ jsx6("i", {})
1872
+ /* @__PURE__ */ jsx7("i", {})
1447
1873
  ] }) : null,
1448
- Footer ? /* @__PURE__ */ jsx6(Footer, { state, adapter, value: copy }) : null
1874
+ Footer ? /* @__PURE__ */ jsx7(Footer, { state, adapter, value: copy }) : null
1449
1875
  ] });
1450
1876
  }
1451
1877
  export {