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