@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/embed.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { render } from "preact";
|
|
3
3
|
|
|
4
4
|
// src/messenger.jsx
|
|
5
|
-
import { useEffect as
|
|
5
|
+
import { useEffect as useEffect6, useId as useId2, useMemo as useMemo2, useRef as useRef5, useState as useState4 } from "preact/hooks";
|
|
6
6
|
|
|
7
7
|
// core.mjs
|
|
8
8
|
var HARNESS_NAMES = Object.freeze({
|
|
@@ -295,7 +295,8 @@ function readTranscript(value) {
|
|
|
295
295
|
role: item.role,
|
|
296
296
|
text: item.text,
|
|
297
297
|
ts: nullableNumber(item.ts),
|
|
298
|
-
truncated: item.truncated === true
|
|
298
|
+
truncated: item.truncated === true,
|
|
299
|
+
...Number.isSafeInteger(item.messageIndex) && item.messageIndex > 0 ? { messageIndex: item.messageIndex } : {}
|
|
299
300
|
};
|
|
300
301
|
for (const key of ["label", "arguments", "resultText", "code"]) {
|
|
301
302
|
if (typeof item[key] === "string") entry[key] = item[key];
|
|
@@ -466,7 +467,7 @@ function normalizeUiState(value) {
|
|
|
466
467
|
savedDraft: string(raw.savedDraft),
|
|
467
468
|
attention: Array.isArray(raw.attention) ? raw.attention.flatMap((candidate) => {
|
|
468
469
|
const item = record(candidate);
|
|
469
|
-
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 } : {} }] : [];
|
|
470
|
+
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 } : {} }] : [];
|
|
470
471
|
}) : [],
|
|
471
472
|
sessions: readSessions(raw.sessions),
|
|
472
473
|
attached: readAttached(raw.attached),
|
|
@@ -548,7 +549,7 @@ function canContinueHere(state) {
|
|
|
548
549
|
}
|
|
549
550
|
function operationLabel(operation) {
|
|
550
551
|
if (!operation) return "";
|
|
551
|
-
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" };
|
|
552
|
+
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" };
|
|
552
553
|
return labels[operation] ?? `${operation.replaceAll("_", " ")}\u2026`;
|
|
553
554
|
}
|
|
554
555
|
function terminalCommand(handoff) {
|
|
@@ -570,8 +571,65 @@ function boundedSet(map, key, value) {
|
|
|
570
571
|
while (map.size > MEMORY_LIMIT) map.delete(map.keys().next().value);
|
|
571
572
|
}
|
|
572
573
|
|
|
574
|
+
// src/icon.jsx
|
|
575
|
+
import { Fragment, jsx, jsxs } from "preact/jsx-runtime";
|
|
576
|
+
var ICONS = {
|
|
577
|
+
back: () => /* @__PURE__ */ jsx("path", { d: "m11.5 4.5-4.5 4.5 4.5 4.5" }),
|
|
578
|
+
check: () => /* @__PURE__ */ jsx("path", { d: "m4 9 3.25 3.25L14 5.5" }),
|
|
579
|
+
chevron: () => /* @__PURE__ */ jsx("path", { d: "m7 4.5 4.5 4.5L7 13.5" }),
|
|
580
|
+
close: () => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
581
|
+
/* @__PURE__ */ jsx("path", { d: "m4.75 4.75 8.5 8.5" }),
|
|
582
|
+
/* @__PURE__ */ jsx("path", { d: "m13.25 4.75-8.5 8.5" })
|
|
583
|
+
] }),
|
|
584
|
+
copy: () => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
585
|
+
/* @__PURE__ */ jsx("rect", { x: "5", y: "5", width: "8", height: "8", rx: "1.5" }),
|
|
586
|
+
/* @__PURE__ */ jsx("path", { d: "M3 10.5V4.25C3 3.56 3.56 3 4.25 3h6.25" })
|
|
587
|
+
] }),
|
|
588
|
+
down: () => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
589
|
+
/* @__PURE__ */ jsx("path", { d: "M9 3.5v10" }),
|
|
590
|
+
/* @__PURE__ */ jsx("path", { d: "m4.75 9.5 4.25 4 4.25-4" })
|
|
591
|
+
] }),
|
|
592
|
+
menu: () => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
593
|
+
/* @__PURE__ */ jsx("circle", { cx: "4", cy: "9", r: "1.25", fill: "currentColor", stroke: "none" }),
|
|
594
|
+
/* @__PURE__ */ jsx("circle", { cx: "9", cy: "9", r: "1.25", fill: "currentColor", stroke: "none" }),
|
|
595
|
+
/* @__PURE__ */ jsx("circle", { cx: "14", cy: "9", r: "1.25", fill: "currentColor", stroke: "none" })
|
|
596
|
+
] }),
|
|
597
|
+
plus: () => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
598
|
+
/* @__PURE__ */ jsx("path", { d: "M9 3.5v11" }),
|
|
599
|
+
/* @__PURE__ */ jsx("path", { d: "M3.5 9h11" })
|
|
600
|
+
] }),
|
|
601
|
+
search: () => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
602
|
+
/* @__PURE__ */ jsx("circle", { cx: "7.75", cy: "7.75", r: "4.25" }),
|
|
603
|
+
/* @__PURE__ */ jsx("path", { d: "m11 11 3.5 3.5" })
|
|
604
|
+
] }),
|
|
605
|
+
send: () => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
606
|
+
/* @__PURE__ */ jsx("path", { d: "M9 14.5v-11" }),
|
|
607
|
+
/* @__PURE__ */ jsx("path", { d: "m4.75 7.75 4.25-4.25 4.25 4.25" })
|
|
608
|
+
] }),
|
|
609
|
+
stop: () => /* @__PURE__ */ jsx("rect", { x: "4.5", y: "4.5", width: "9", height: "9", rx: "1.5", fill: "currentColor", stroke: "none" })
|
|
610
|
+
};
|
|
611
|
+
function UiIcon({ name, size = 16, class: className = "" }) {
|
|
612
|
+
const Glyph = ICONS[name];
|
|
613
|
+
if (!Glyph) return null;
|
|
614
|
+
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, {}) });
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// src/textarea.js
|
|
618
|
+
import { useLayoutEffect } from "preact/hooks";
|
|
619
|
+
function useAutosizeTextarea(ref, value) {
|
|
620
|
+
useLayoutEffect(() => {
|
|
621
|
+
const element = ref.current;
|
|
622
|
+
if (!element) return;
|
|
623
|
+
element.style.height = "auto";
|
|
624
|
+
const maxHeight = Number.parseFloat(getComputedStyle(element).maxHeight) || 150;
|
|
625
|
+
const height = Math.min(element.scrollHeight, maxHeight);
|
|
626
|
+
element.style.height = `${height}px`;
|
|
627
|
+
element.style.overflowY = element.scrollHeight > maxHeight ? "auto" : "hidden";
|
|
628
|
+
}, [ref, value]);
|
|
629
|
+
}
|
|
630
|
+
|
|
573
631
|
// src/composer.jsx
|
|
574
|
-
import { jsx, jsxs } from "preact/jsx-runtime";
|
|
632
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "preact/jsx-runtime";
|
|
575
633
|
var composerMemory = /* @__PURE__ */ new Map();
|
|
576
634
|
function ContinuationBar({ state, adapter, labels = DEFAULT_LABELS }) {
|
|
577
635
|
if (state.mode !== "mirror" || state.canSend) return null;
|
|
@@ -581,38 +639,56 @@ function ContinuationBar({ state, adapter, labels = DEFAULT_LABELS }) {
|
|
|
581
639
|
const resume = canContinueHere(state);
|
|
582
640
|
const join = state.canAttach;
|
|
583
641
|
const branch = state.canBranch;
|
|
584
|
-
if (!resume && !join && !branch) return /* @__PURE__ */
|
|
585
|
-
/* @__PURE__ */
|
|
586
|
-
/* @__PURE__ */
|
|
642
|
+
if (!resume && !join && !branch) return /* @__PURE__ */ jsx2("div", { class: "scui-continuation", children: /* @__PURE__ */ jsxs2("span", { children: [
|
|
643
|
+
/* @__PURE__ */ jsx2("strong", { children: activeElsewhere ? "Active elsewhere" : "Read-only" }),
|
|
644
|
+
/* @__PURE__ */ jsx2("small", { children: "This session cannot be continued by an available harness." })
|
|
587
645
|
] }) });
|
|
588
|
-
return /* @__PURE__ */
|
|
589
|
-
/* @__PURE__ */
|
|
590
|
-
/* @__PURE__ */
|
|
591
|
-
/* @__PURE__ */
|
|
646
|
+
return /* @__PURE__ */ jsxs2("div", { class: "scui-continuation", children: [
|
|
647
|
+
/* @__PURE__ */ jsxs2("span", { children: [
|
|
648
|
+
/* @__PURE__ */ jsx2("strong", { children: activeElsewhere ? "Active elsewhere" : "Read-only" }),
|
|
649
|
+
/* @__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." })
|
|
592
650
|
] }),
|
|
593
|
-
/* @__PURE__ */
|
|
651
|
+
/* @__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 })
|
|
594
652
|
] });
|
|
595
653
|
}
|
|
596
|
-
function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`, onPending }) {
|
|
654
|
+
function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`, pendingStatus = null, restoreDraft = null, onPending, onDraftRestored }) {
|
|
597
655
|
const remembered = composerMemory.get(memoryKey) ?? { draft: state.savedDraft, queue: [] };
|
|
598
656
|
const [draft, setDraft] = useState(remembered.draft);
|
|
599
657
|
const [queue, setQueue] = useState(remembered.queue);
|
|
658
|
+
const [dispatching, setDispatching] = useState(false);
|
|
600
659
|
const textarea = useRef(null);
|
|
660
|
+
useAutosizeTextarea(textarea, draft);
|
|
601
661
|
const remember = (nextDraft, nextQueue) => boundedSet(composerMemory, memoryKey, { draft: nextDraft, queue: nextQueue });
|
|
602
662
|
const updateQueue = (update) => setQueue((items) => {
|
|
603
663
|
const next = update(items);
|
|
604
664
|
remember(draft, next);
|
|
605
665
|
return next;
|
|
606
666
|
});
|
|
667
|
+
const queueBlocked = state.busy || pendingStatus !== null || dispatching;
|
|
668
|
+
const queuesNewMessage = state.busy || pendingStatus === "sending" || pendingStatus === "failed" || dispatching;
|
|
607
669
|
useEffect(() => {
|
|
608
|
-
if (!
|
|
670
|
+
if (!queueBlocked && state.canSend && queue.length) {
|
|
609
671
|
const [next, ...rest] = queue;
|
|
672
|
+
setDispatching(true);
|
|
610
673
|
setQueue(rest);
|
|
611
674
|
remember(draft, rest);
|
|
612
675
|
onPending?.(next);
|
|
613
676
|
adapter.onIntent({ action: "send", text: next });
|
|
614
677
|
}
|
|
615
|
-
}, [adapter, draft, memoryKey, onPending, queue,
|
|
678
|
+
}, [adapter, draft, memoryKey, onPending, queue, queueBlocked, state.canSend]);
|
|
679
|
+
useEffect(() => {
|
|
680
|
+
if (pendingStatus !== null || state.busy) setDispatching(false);
|
|
681
|
+
}, [pendingStatus, state.busy]);
|
|
682
|
+
useEffect(() => {
|
|
683
|
+
textarea.current?.focus({ preventScroll: true });
|
|
684
|
+
}, [memoryKey]);
|
|
685
|
+
useEffect(() => {
|
|
686
|
+
if (!restoreDraft) return;
|
|
687
|
+
setDraft(restoreDraft.text);
|
|
688
|
+
remember(restoreDraft.text, queue);
|
|
689
|
+
textarea.current?.focus({ preventScroll: true });
|
|
690
|
+
onDraftRestored?.(restoreDraft.id);
|
|
691
|
+
}, [onDraftRestored, restoreDraft?.id]);
|
|
616
692
|
useEffect(() => {
|
|
617
693
|
const timer = setTimeout(() => adapter.onIntent({ action: "draft", text: draft }), 250);
|
|
618
694
|
return () => clearTimeout(timer);
|
|
@@ -620,27 +696,28 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
|
|
|
620
696
|
const send = () => {
|
|
621
697
|
const text = draft.trim();
|
|
622
698
|
if (!text) return;
|
|
623
|
-
if (
|
|
699
|
+
if (queuesNewMessage) updateQueue((items) => [...items, text]);
|
|
624
700
|
else if (state.canSend) {
|
|
701
|
+
if (onPending) setDispatching(true);
|
|
625
702
|
onPending?.(text);
|
|
626
703
|
adapter.onIntent({ action: "send", text });
|
|
627
704
|
} else return;
|
|
628
705
|
setDraft("");
|
|
629
|
-
remember("",
|
|
706
|
+
remember("", queuesNewMessage ? [...queue, text] : queue);
|
|
630
707
|
};
|
|
631
|
-
return /* @__PURE__ */
|
|
632
|
-
queue.length ? /* @__PURE__ */
|
|
633
|
-
/* @__PURE__ */
|
|
708
|
+
return /* @__PURE__ */ jsxs2("div", { class: "scui-compose", children: [
|
|
709
|
+
queue.length ? /* @__PURE__ */ jsxs2("div", { class: "scui-queue", children: [
|
|
710
|
+
/* @__PURE__ */ jsxs2("strong", { children: [
|
|
634
711
|
queue.length,
|
|
635
712
|
" queued"
|
|
636
713
|
] }),
|
|
637
|
-
queue.map((item, index) => /* @__PURE__ */
|
|
714
|
+
queue.map((item, index) => /* @__PURE__ */ jsxs2("span", { children: [
|
|
638
715
|
item,
|
|
639
|
-
/* @__PURE__ */
|
|
716
|
+
/* @__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 }) })
|
|
640
717
|
] }, `${index}:${item}`))
|
|
641
718
|
] }) : null,
|
|
642
|
-
/* @__PURE__ */
|
|
643
|
-
/* @__PURE__ */
|
|
719
|
+
/* @__PURE__ */ jsxs2("div", { class: "scui-envelope", children: [
|
|
720
|
+
/* @__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) => {
|
|
644
721
|
const value = event.currentTarget.value;
|
|
645
722
|
setDraft(value);
|
|
646
723
|
remember(value, queue);
|
|
@@ -650,35 +727,113 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
|
|
|
650
727
|
send();
|
|
651
728
|
}
|
|
652
729
|
} }),
|
|
653
|
-
/* @__PURE__ */
|
|
654
|
-
state.busy ? /* @__PURE__ */
|
|
655
|
-
/* @__PURE__ */
|
|
730
|
+
/* @__PURE__ */ jsxs2("span", { children: [
|
|
731
|
+
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,
|
|
732
|
+
/* @__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 }) })
|
|
656
733
|
] })
|
|
657
734
|
] })
|
|
658
735
|
] });
|
|
659
736
|
}
|
|
660
737
|
|
|
661
738
|
// src/conversation.jsx
|
|
662
|
-
import {
|
|
739
|
+
import { Fragment as Fragment2 } from "preact";
|
|
740
|
+
import { useEffect as useEffect3, useId, useLayoutEffect as useLayoutEffect2, useRef as useRef3, useState as useState2 } from "preact/hooks";
|
|
663
741
|
|
|
664
742
|
// src/markdown.jsx
|
|
665
743
|
import MarkdownIt from "markdown-it";
|
|
666
|
-
import { useMemo } from "preact/hooks";
|
|
667
|
-
import { jsx as
|
|
744
|
+
import { useEffect as useEffect2, useMemo, useRef as useRef2 } from "preact/hooks";
|
|
745
|
+
import { jsx as jsx3 } from "preact/jsx-runtime";
|
|
668
746
|
var markdown = new MarkdownIt({ html: false, linkify: true, breaks: false });
|
|
747
|
+
var LANGUAGE_LABELS = {
|
|
748
|
+
bash: "Shell",
|
|
749
|
+
css: "CSS",
|
|
750
|
+
html: "HTML",
|
|
751
|
+
js: "JavaScript",
|
|
752
|
+
javascript: "JavaScript",
|
|
753
|
+
jsx: "JSX",
|
|
754
|
+
md: "Markdown",
|
|
755
|
+
markdown: "Markdown",
|
|
756
|
+
py: "Python",
|
|
757
|
+
python: "Python",
|
|
758
|
+
rs: "Rust",
|
|
759
|
+
rust: "Rust",
|
|
760
|
+
sh: "Shell",
|
|
761
|
+
shell: "Shell",
|
|
762
|
+
ts: "TypeScript",
|
|
763
|
+
tsx: "TSX",
|
|
764
|
+
yaml: "YAML",
|
|
765
|
+
yml: "YAML",
|
|
766
|
+
json: "JSON",
|
|
767
|
+
jsonc: "JSONC",
|
|
768
|
+
sql: "SQL",
|
|
769
|
+
xml: "XML"
|
|
770
|
+
};
|
|
771
|
+
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>';
|
|
772
|
+
function languageLabel(info) {
|
|
773
|
+
const language = info.trim().split(/\s+/, 1)[0]?.toLocaleLowerCase() ?? "";
|
|
774
|
+
if (!language) return "Code";
|
|
775
|
+
return LANGUAGE_LABELS[language] ?? language.toLocaleUpperCase();
|
|
776
|
+
}
|
|
777
|
+
function frameCode(render2, tokens, index, options, env, self) {
|
|
778
|
+
const label = markdown.utils.escapeHtml(languageLabel(tokens[index]?.info ?? ""));
|
|
779
|
+
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>${render2(tokens, index, options, env, self)}</div>`;
|
|
780
|
+
}
|
|
781
|
+
for (const kind of ["fence", "code_block"]) {
|
|
782
|
+
const render2 = markdown.renderer.rules[kind];
|
|
783
|
+
markdown.renderer.rules[kind] = (tokens, index, options, env, self) => frameCode(render2, tokens, index, options, env, self);
|
|
784
|
+
}
|
|
669
785
|
var defaultLinkOpen = markdown.renderer.rules.link_open;
|
|
670
786
|
markdown.renderer.rules.link_open = (tokens, index, options, env, self) => {
|
|
671
787
|
tokens[index]?.attrSet("target", "_blank");
|
|
672
788
|
tokens[index]?.attrSet("rel", "noreferrer noopener");
|
|
673
789
|
return defaultLinkOpen ? defaultLinkOpen(tokens, index, options, env, self) : self.renderToken(tokens, index, options);
|
|
674
790
|
};
|
|
675
|
-
function
|
|
791
|
+
function setCopyState(button, status) {
|
|
792
|
+
const labels = {
|
|
793
|
+
idle: ["Copy code", "Copy"],
|
|
794
|
+
copied: ["Code copied", "Copied"],
|
|
795
|
+
failed: ["Copy failed \xB7 retry", "Retry"]
|
|
796
|
+
};
|
|
797
|
+
const [label, visible] = labels[status];
|
|
798
|
+
button.dataset.status = status;
|
|
799
|
+
button.setAttribute("aria-label", label);
|
|
800
|
+
button.setAttribute("title", label);
|
|
801
|
+
const text = button.querySelector("span");
|
|
802
|
+
if (text) text.textContent = visible;
|
|
803
|
+
}
|
|
804
|
+
function Markdown({ value, copyText }) {
|
|
676
805
|
const html = useMemo(() => markdown.render(value), [value]);
|
|
677
|
-
|
|
806
|
+
const resets = useRef2(/* @__PURE__ */ new Map());
|
|
807
|
+
useEffect2(() => () => {
|
|
808
|
+
for (const timer of resets.current.values()) clearTimeout(timer);
|
|
809
|
+
resets.current.clear();
|
|
810
|
+
}, []);
|
|
811
|
+
const copyCode = async (event) => {
|
|
812
|
+
const button = event.target.closest?.(".scui-code-copy");
|
|
813
|
+
if (!button || !event.currentTarget.contains(button) || !copyText) return;
|
|
814
|
+
const code = button.closest(".scui-code-block")?.querySelector("pre code");
|
|
815
|
+
if (!code) return;
|
|
816
|
+
button.disabled = true;
|
|
817
|
+
try {
|
|
818
|
+
await copyText(code.textContent ?? "");
|
|
819
|
+
setCopyState(button, "copied");
|
|
820
|
+
} catch {
|
|
821
|
+
setCopyState(button, "failed");
|
|
822
|
+
} finally {
|
|
823
|
+
button.disabled = false;
|
|
824
|
+
}
|
|
825
|
+
clearTimeout(resets.current.get(button));
|
|
826
|
+
const timer = setTimeout(() => {
|
|
827
|
+
if (button.isConnected) setCopyState(button, "idle");
|
|
828
|
+
resets.current.delete(button);
|
|
829
|
+
}, 1500);
|
|
830
|
+
resets.current.set(button, timer);
|
|
831
|
+
};
|
|
832
|
+
return /* @__PURE__ */ jsx3("div", { class: "scui-markdown", "data-copyable": Boolean(copyText), onClick: copyCode, dangerouslySetInnerHTML: { __html: html } });
|
|
678
833
|
}
|
|
679
834
|
|
|
680
835
|
// src/conversation.jsx
|
|
681
|
-
import { Fragment, jsx as
|
|
836
|
+
import { Fragment as Fragment3, jsx as jsx4, jsxs as jsxs3 } from "preact/jsx-runtime";
|
|
682
837
|
function LoadingStatus({ state, compact = false }) {
|
|
683
838
|
const copy = {
|
|
684
839
|
connecting: ["Connecting to coding agents", "Checking installed harnesses and capabilities.", 0],
|
|
@@ -686,81 +841,104 @@ function LoadingStatus({ state, compact = false }) {
|
|
|
686
841
|
discovering: ["Loading recent sessions", "Scanning native session stores without loading full transcripts.", 2],
|
|
687
842
|
ready: ["Ready", "Coding sessions are up to date.", 3]
|
|
688
843
|
}[state.startup];
|
|
689
|
-
return /* @__PURE__ */
|
|
690
|
-
/* @__PURE__ */
|
|
691
|
-
/* @__PURE__ */
|
|
692
|
-
/* @__PURE__ */
|
|
693
|
-
/* @__PURE__ */
|
|
844
|
+
return /* @__PURE__ */ jsxs3("div", { class: `scui-loading${compact ? " scui-loading-compact" : ""}`, role: "status", "aria-busy": state.startup !== "ready", children: [
|
|
845
|
+
/* @__PURE__ */ jsx4("span", { class: "scui-orbit", "aria-hidden": "true", children: /* @__PURE__ */ jsx4("i", {}) }),
|
|
846
|
+
/* @__PURE__ */ jsxs3("span", { class: "scui-loading-copy", children: [
|
|
847
|
+
/* @__PURE__ */ jsx4("strong", { children: copy[0] }),
|
|
848
|
+
/* @__PURE__ */ jsx4("small", { children: copy[1] })
|
|
694
849
|
] }),
|
|
695
|
-
/* @__PURE__ */
|
|
850
|
+
/* @__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)) })
|
|
696
851
|
] });
|
|
697
852
|
}
|
|
698
853
|
function RequestCard({ entry, adapter, canRespond }) {
|
|
699
854
|
const request = entry.request;
|
|
700
855
|
if (!request) return null;
|
|
701
856
|
if (request.status === "responded") {
|
|
702
|
-
return /* @__PURE__ */
|
|
857
|
+
return /* @__PURE__ */ jsxs3("div", { class: "scui-request-done", children: [
|
|
703
858
|
"\u2713 Request answered \xB7 ",
|
|
704
859
|
request.resolution?.name ?? request.requestKind
|
|
705
860
|
] });
|
|
706
861
|
}
|
|
707
|
-
return /* @__PURE__ */
|
|
708
|
-
/* @__PURE__ */
|
|
709
|
-
/* @__PURE__ */
|
|
710
|
-
/* @__PURE__ */
|
|
711
|
-
request.options.map((option) => /* @__PURE__ */
|
|
712
|
-
request.cancellable ? /* @__PURE__ */
|
|
862
|
+
return /* @__PURE__ */ jsxs3("section", { class: "scui-request", role: "alert", "aria-label": `${request.requestKind} needs input`, children: [
|
|
863
|
+
/* @__PURE__ */ jsx4("strong", { children: "Agent needs input" }),
|
|
864
|
+
/* @__PURE__ */ jsx4(Markdown, { value: request.payloadText || entry.text, copyText: adapter?.copyText }),
|
|
865
|
+
/* @__PURE__ */ jsxs3("div", { class: "scui-request-actions", children: [
|
|
866
|
+
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)),
|
|
867
|
+
request.cancellable ? /* @__PURE__ */ jsx4("button", { type: "button", disabled: !canRespond, onClick: () => adapter.onIntent({ action: "respond", requestId: request.requestId, optionId: null }), children: "Cancel" }) : null
|
|
713
868
|
] })
|
|
714
869
|
] });
|
|
715
870
|
}
|
|
716
871
|
function ContextDisclosure({ context }) {
|
|
717
872
|
if (!context?.length) return null;
|
|
718
|
-
return /* @__PURE__ */
|
|
719
|
-
/* @__PURE__ */
|
|
873
|
+
return /* @__PURE__ */ jsxs3("details", { class: "scui-context", children: [
|
|
874
|
+
/* @__PURE__ */ jsxs3("summary", { children: [
|
|
720
875
|
"Context \xB7 ",
|
|
721
876
|
context.length
|
|
722
877
|
] }),
|
|
723
|
-
/* @__PURE__ */
|
|
724
|
-
/* @__PURE__ */
|
|
725
|
-
/* @__PURE__ */
|
|
878
|
+
/* @__PURE__ */ jsx4("div", { children: context.map((item, index) => /* @__PURE__ */ jsxs3("p", { children: [
|
|
879
|
+
/* @__PURE__ */ jsx4("strong", { children: item.label }),
|
|
880
|
+
/* @__PURE__ */ jsx4("span", { children: item.detail })
|
|
726
881
|
] }, item.id ?? index)) })
|
|
727
882
|
] });
|
|
728
883
|
}
|
|
884
|
+
function MessageMeta({ entry, adapter }) {
|
|
885
|
+
const [copyState, setCopyState2] = useState2("idle");
|
|
886
|
+
const reset = useRef3(null);
|
|
887
|
+
useEffect3(() => () => clearTimeout(reset.current), []);
|
|
888
|
+
const date = entry.ts === null ? null : new Date(entry.ts);
|
|
889
|
+
const validDate = date && Number.isFinite(date.valueOf()) ? date : null;
|
|
890
|
+
if (!validDate && (!adapter?.copyText || !entry.text)) return null;
|
|
891
|
+
const copy = async () => {
|
|
892
|
+
try {
|
|
893
|
+
await adapter.copyText(entry.text);
|
|
894
|
+
setCopyState2("copied");
|
|
895
|
+
} catch {
|
|
896
|
+
setCopyState2("failed");
|
|
897
|
+
}
|
|
898
|
+
clearTimeout(reset.current);
|
|
899
|
+
reset.current = setTimeout(() => setCopyState2("idle"), 1500);
|
|
900
|
+
};
|
|
901
|
+
const copyLabel = copyState === "copied" ? "Message copied" : copyState === "failed" ? "Copy failed \xB7 retry" : "Copy message";
|
|
902
|
+
return /* @__PURE__ */ jsxs3("footer", { class: "scui-message-meta", children: [
|
|
903
|
+
validDate ? /* @__PURE__ */ jsx4("time", { dateTime: validDate.toISOString(), title: validDate.toLocaleString(), children: validDate.toLocaleTimeString([], { hour: "numeric", minute: "2-digit" }) }) : null,
|
|
904
|
+
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
|
|
905
|
+
] });
|
|
906
|
+
}
|
|
729
907
|
var TOOL_ICONS = {
|
|
730
|
-
read: () => /* @__PURE__ */
|
|
731
|
-
/* @__PURE__ */
|
|
732
|
-
/* @__PURE__ */
|
|
908
|
+
read: () => /* @__PURE__ */ jsxs3(Fragment3, { children: [
|
|
909
|
+
/* @__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" }),
|
|
910
|
+
/* @__PURE__ */ jsx4("path", { d: "M12 2.9v3h2.85M6 9h6M6 12h4" })
|
|
733
911
|
] }),
|
|
734
|
-
search: () => /* @__PURE__ */
|
|
735
|
-
/* @__PURE__ */
|
|
736
|
-
/* @__PURE__ */
|
|
912
|
+
search: () => /* @__PURE__ */ jsxs3(Fragment3, { children: [
|
|
913
|
+
/* @__PURE__ */ jsx4("circle", { cx: "8", cy: "8", r: "4.5" }),
|
|
914
|
+
/* @__PURE__ */ jsx4("path", { d: "m11.5 11.5 3 3" })
|
|
737
915
|
] }),
|
|
738
|
-
edit: () => /* @__PURE__ */
|
|
739
|
-
/* @__PURE__ */
|
|
740
|
-
/* @__PURE__ */
|
|
916
|
+
edit: () => /* @__PURE__ */ jsxs3(Fragment3, { children: [
|
|
917
|
+
/* @__PURE__ */ jsx4("path", { d: "m11.75 3.25 3 3-8.5 8.5-3.75.75.75-3.75 8.5-8.5Z" }),
|
|
918
|
+
/* @__PURE__ */ jsx4("path", { d: "m10 5 3 3" })
|
|
741
919
|
] }),
|
|
742
|
-
command: () => /* @__PURE__ */
|
|
743
|
-
test: () => /* @__PURE__ */
|
|
744
|
-
/* @__PURE__ */
|
|
745
|
-
/* @__PURE__ */
|
|
920
|
+
command: () => /* @__PURE__ */ jsx4(Fragment3, { children: /* @__PURE__ */ jsx4("path", { d: "m3 5 3 3-3 3M8 12h6" }) }),
|
|
921
|
+
test: () => /* @__PURE__ */ jsxs3(Fragment3, { children: [
|
|
922
|
+
/* @__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" }),
|
|
923
|
+
/* @__PURE__ */ jsx4("path", { d: "M5 2.5h8" })
|
|
746
924
|
] }),
|
|
747
|
-
web: () => /* @__PURE__ */
|
|
748
|
-
/* @__PURE__ */
|
|
749
|
-
/* @__PURE__ */
|
|
925
|
+
web: () => /* @__PURE__ */ jsxs3(Fragment3, { children: [
|
|
926
|
+
/* @__PURE__ */ jsx4("circle", { cx: "9", cy: "9", r: "6.5" }),
|
|
927
|
+
/* @__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" })
|
|
750
928
|
] }),
|
|
751
|
-
agent: () => /* @__PURE__ */
|
|
752
|
-
/* @__PURE__ */
|
|
753
|
-
/* @__PURE__ */
|
|
929
|
+
agent: () => /* @__PURE__ */ jsxs3(Fragment3, { children: [
|
|
930
|
+
/* @__PURE__ */ jsx4("circle", { cx: "9", cy: "6", r: "2.5" }),
|
|
931
|
+
/* @__PURE__ */ jsx4("path", { d: "M4 15c.4-3 2-4.5 5-4.5s4.6 1.5 5 4.5" })
|
|
754
932
|
] }),
|
|
755
|
-
plan: () => /* @__PURE__ */
|
|
756
|
-
other: () => /* @__PURE__ */
|
|
757
|
-
/* @__PURE__ */
|
|
758
|
-
/* @__PURE__ */
|
|
933
|
+
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" }) }),
|
|
934
|
+
other: () => /* @__PURE__ */ jsxs3(Fragment3, { children: [
|
|
935
|
+
/* @__PURE__ */ jsx4("path", { d: "M9 2.5v3M9 12.5v3M2.5 9h3M12.5 9h3" }),
|
|
936
|
+
/* @__PURE__ */ jsx4("circle", { cx: "9", cy: "9", r: "3.5" })
|
|
759
937
|
] })
|
|
760
938
|
};
|
|
761
939
|
function ToolIcon({ category }) {
|
|
762
940
|
const Glyph = TOOL_ICONS[category] ?? TOOL_ICONS.other;
|
|
763
|
-
return /* @__PURE__ */
|
|
941
|
+
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, {}) });
|
|
764
942
|
}
|
|
765
943
|
function toolAction2(entry, category, presentation) {
|
|
766
944
|
if (presentation?.action) return presentation.action;
|
|
@@ -815,86 +993,86 @@ function ToolMetrics({ presentation }) {
|
|
|
815
993
|
presentation.exitCode === null ? "" : `exit ${presentation.exitCode}`,
|
|
816
994
|
formatDuration(presentation.durationMs)
|
|
817
995
|
].filter(Boolean);
|
|
818
|
-
return metrics.length ? /* @__PURE__ */
|
|
996
|
+
return metrics.length ? /* @__PURE__ */ jsx4("div", { class: "scui-tool-metrics", children: metrics.map((metric) => /* @__PURE__ */ jsx4("span", { children: metric }, metric)) }) : null;
|
|
819
997
|
}
|
|
820
998
|
function PendingElapsed({ now }) {
|
|
821
999
|
const clock = now ?? Date.now;
|
|
822
|
-
const started =
|
|
1000
|
+
const started = useRef3(clock());
|
|
823
1001
|
const [elapsed, setElapsed] = useState2(0);
|
|
824
|
-
|
|
1002
|
+
useEffect3(() => {
|
|
825
1003
|
const timer = setInterval(() => setElapsed(Math.max(0, clock() - started.current)), 1e3);
|
|
826
1004
|
return () => clearInterval(timer);
|
|
827
1005
|
}, [clock]);
|
|
828
|
-
return /* @__PURE__ */
|
|
1006
|
+
return /* @__PURE__ */ jsx4("small", { class: "scui-tool-elapsed", children: elapsed < 1e3 ? "now" : formatDuration(elapsed) });
|
|
829
1007
|
}
|
|
830
1008
|
function LinePreview({ value, kind }) {
|
|
831
1009
|
if (!value) return null;
|
|
832
|
-
return /* @__PURE__ */
|
|
1010
|
+
return /* @__PURE__ */ jsx4("ol", { class: "scui-code-preview", "data-kind": kind, children: stripAnsi(value).split("\n").map((line, index) => {
|
|
833
1011
|
const tone = kind === "diff" ? line.startsWith("+") && !line.startsWith("+++") ? "add" : line.startsWith("-") && !line.startsWith("---") ? "remove" : line.startsWith("@@") ? "hunk" : "plain" : "plain";
|
|
834
|
-
return /* @__PURE__ */
|
|
835
|
-
/* @__PURE__ */
|
|
836
|
-
/* @__PURE__ */
|
|
1012
|
+
return /* @__PURE__ */ jsxs3("li", { "data-tone": tone, children: [
|
|
1013
|
+
/* @__PURE__ */ jsx4("span", { children: index + 1 }),
|
|
1014
|
+
/* @__PURE__ */ jsx4("code", { children: line || " " })
|
|
837
1015
|
] }, index);
|
|
838
1016
|
}) });
|
|
839
1017
|
}
|
|
840
1018
|
function TerminalPreview({ presentation, pending, failed }) {
|
|
841
|
-
return /* @__PURE__ */
|
|
842
|
-
/* @__PURE__ */
|
|
843
|
-
/* @__PURE__ */
|
|
844
|
-
/* @__PURE__ */
|
|
845
|
-
/* @__PURE__ */
|
|
846
|
-
/* @__PURE__ */
|
|
1019
|
+
return /* @__PURE__ */ jsxs3("section", { class: "scui-terminal", children: [
|
|
1020
|
+
/* @__PURE__ */ jsxs3("header", { children: [
|
|
1021
|
+
/* @__PURE__ */ jsxs3("span", { "aria-hidden": "true", children: [
|
|
1022
|
+
/* @__PURE__ */ jsx4("i", {}),
|
|
1023
|
+
/* @__PURE__ */ jsx4("i", {}),
|
|
1024
|
+
/* @__PURE__ */ jsx4("i", {})
|
|
847
1025
|
] }),
|
|
848
|
-
/* @__PURE__ */
|
|
1026
|
+
/* @__PURE__ */ jsx4("code", { children: presentation.command ? `$ ${presentation.command}` : "Terminal" })
|
|
849
1027
|
] }),
|
|
850
|
-
presentation.preview ? /* @__PURE__ */
|
|
851
|
-
/* @__PURE__ */
|
|
1028
|
+
presentation.preview ? /* @__PURE__ */ jsx4("pre", { "data-error": failed, children: stripAnsi(presentation.preview) }) : pending ? /* @__PURE__ */ jsxs3("div", { class: "scui-terminal-wait", children: [
|
|
1029
|
+
/* @__PURE__ */ jsx4("i", {}),
|
|
852
1030
|
" Waiting for output"
|
|
853
|
-
] }) : /* @__PURE__ */
|
|
1031
|
+
] }) : /* @__PURE__ */ jsx4("div", { class: "scui-terminal-empty", children: "No output" })
|
|
854
1032
|
] });
|
|
855
1033
|
}
|
|
856
1034
|
function SearchPreview({ presentation }) {
|
|
857
1035
|
const lines = stripAnsi(presentation.preview).split("\n").filter(Boolean);
|
|
858
|
-
return /* @__PURE__ */
|
|
859
|
-
presentation.query ? /* @__PURE__ */
|
|
860
|
-
/* @__PURE__ */
|
|
861
|
-
/* @__PURE__ */
|
|
1036
|
+
return /* @__PURE__ */ jsxs3("section", { class: "scui-search-preview", children: [
|
|
1037
|
+
presentation.query ? /* @__PURE__ */ jsxs3("header", { children: [
|
|
1038
|
+
/* @__PURE__ */ jsx4("span", { children: "Search" }),
|
|
1039
|
+
/* @__PURE__ */ jsx4("code", { children: presentation.query })
|
|
862
1040
|
] }) : null,
|
|
863
|
-
lines.length ? /* @__PURE__ */
|
|
1041
|
+
lines.length ? /* @__PURE__ */ jsx4("ol", { children: lines.map((line, index) => {
|
|
864
1042
|
const match = /^(.*?):(\d+)(?::(\d+))?:(.*)$/.exec(line);
|
|
865
|
-
return /* @__PURE__ */
|
|
866
|
-
/* @__PURE__ */
|
|
867
|
-
/* @__PURE__ */
|
|
1043
|
+
return /* @__PURE__ */ jsx4("li", { children: match ? /* @__PURE__ */ jsxs3(Fragment3, { children: [
|
|
1044
|
+
/* @__PURE__ */ jsx4("code", { children: match[1] }),
|
|
1045
|
+
/* @__PURE__ */ jsxs3("small", { children: [
|
|
868
1046
|
match[2],
|
|
869
1047
|
match[3] ? `:${match[3]}` : ""
|
|
870
1048
|
] }),
|
|
871
|
-
/* @__PURE__ */
|
|
872
|
-
] }) : /* @__PURE__ */
|
|
873
|
-
}) }) : /* @__PURE__ */
|
|
1049
|
+
/* @__PURE__ */ jsx4("span", { children: match[4] })
|
|
1050
|
+
] }) : /* @__PURE__ */ jsx4("span", { children: line }) }, index);
|
|
1051
|
+
}) }) : /* @__PURE__ */ jsx4("p", { children: "No textual results" })
|
|
874
1052
|
] });
|
|
875
1053
|
}
|
|
876
1054
|
function ToolPreview({ presentation, entry }) {
|
|
877
1055
|
const pending = entry.status === "pending";
|
|
878
1056
|
const failed = entry.status === "error";
|
|
879
|
-
if (presentation.detail === "terminal") return /* @__PURE__ */
|
|
880
|
-
if (presentation.detail === "diff") return presentation.preview ? /* @__PURE__ */
|
|
881
|
-
if (presentation.detail === "file") return presentation.preview ? /* @__PURE__ */
|
|
882
|
-
if (presentation.detail === "matches") return /* @__PURE__ */
|
|
883
|
-
if (presentation.detail === "web") return /* @__PURE__ */
|
|
884
|
-
presentation.url ? /* @__PURE__ */
|
|
885
|
-
presentation.preview ? /* @__PURE__ */
|
|
1057
|
+
if (presentation.detail === "terminal") return /* @__PURE__ */ jsx4(TerminalPreview, { presentation, pending, failed });
|
|
1058
|
+
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" });
|
|
1059
|
+
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" });
|
|
1060
|
+
if (presentation.detail === "matches") return /* @__PURE__ */ jsx4(SearchPreview, { presentation });
|
|
1061
|
+
if (presentation.detail === "web") return /* @__PURE__ */ jsxs3("section", { class: "scui-web-preview", children: [
|
|
1062
|
+
presentation.url ? /* @__PURE__ */ jsx4("code", { children: presentation.url }) : null,
|
|
1063
|
+
presentation.preview ? /* @__PURE__ */ jsx4("p", { children: stripAnsi(presentation.preview) }) : /* @__PURE__ */ jsx4("small", { children: pending ? "Waiting for the page" : "No page summary returned" })
|
|
886
1064
|
] });
|
|
887
|
-
if (presentation.detail === "agent") return /* @__PURE__ */
|
|
888
|
-
if (presentation.detail === "plan") return /* @__PURE__ */
|
|
889
|
-
/* @__PURE__ */
|
|
890
|
-
/* @__PURE__ */
|
|
1065
|
+
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" }) });
|
|
1066
|
+
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: [
|
|
1067
|
+
/* @__PURE__ */ jsx4("i", { "aria-hidden": "true" }),
|
|
1068
|
+
/* @__PURE__ */ jsx4("span", { children: item.label })
|
|
891
1069
|
] }, `${item.label}:${index}`)) });
|
|
892
|
-
return presentation.preview ? /* @__PURE__ */
|
|
1070
|
+
return presentation.preview ? /* @__PURE__ */ jsx4("pre", { class: "scui-tool-output", "data-error": failed, children: stripAnsi(presentation.preview) }) : null;
|
|
893
1071
|
}
|
|
894
1072
|
function ToolActions({ presentation, adapter }) {
|
|
895
1073
|
const [copied, setCopied] = useState2(false);
|
|
896
|
-
const reset =
|
|
897
|
-
|
|
1074
|
+
const reset = useRef3(null);
|
|
1075
|
+
useEffect3(() => () => clearTimeout(reset.current), []);
|
|
898
1076
|
if (!adapter?.copyText) return null;
|
|
899
1077
|
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;
|
|
900
1078
|
const copy = async () => {
|
|
@@ -903,27 +1081,27 @@ function ToolActions({ presentation, adapter }) {
|
|
|
903
1081
|
clearTimeout(reset.current);
|
|
904
1082
|
reset.current = setTimeout(() => setCopied(false), 1500);
|
|
905
1083
|
};
|
|
906
|
-
return action ? /* @__PURE__ */
|
|
1084
|
+
return action ? /* @__PURE__ */ jsx4("div", { class: "scui-tool-actions", children: /* @__PURE__ */ jsx4("button", { type: "button", onClick: copy, children: copied ? "Copied" : action[0] }) }) : null;
|
|
907
1085
|
}
|
|
908
1086
|
function ToolStack({ tools }) {
|
|
909
1087
|
if (tools.length < 2) return null;
|
|
910
|
-
return /* @__PURE__ */
|
|
1088
|
+
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)) });
|
|
911
1089
|
}
|
|
912
1090
|
function TechnicalDetails({ entry }) {
|
|
913
1091
|
if (!entry.arguments && !entry.resultText) return null;
|
|
914
|
-
return /* @__PURE__ */
|
|
915
|
-
/* @__PURE__ */
|
|
916
|
-
/* @__PURE__ */
|
|
917
|
-
entry.arguments ? /* @__PURE__ */
|
|
918
|
-
/* @__PURE__ */
|
|
919
|
-
/* @__PURE__ */
|
|
920
|
-
/* @__PURE__ */
|
|
921
|
-
/* @__PURE__ */
|
|
1092
|
+
return /* @__PURE__ */ jsxs3("details", { class: "scui-tool-technical", children: [
|
|
1093
|
+
/* @__PURE__ */ jsx4("summary", { children: "Technical details" }),
|
|
1094
|
+
/* @__PURE__ */ jsxs3("div", { children: [
|
|
1095
|
+
entry.arguments ? /* @__PURE__ */ jsxs3("section", { children: [
|
|
1096
|
+
/* @__PURE__ */ jsx4("strong", { children: "Native arguments" }),
|
|
1097
|
+
/* @__PURE__ */ jsx4("dl", { children: argumentRows(entry.arguments).map((row) => /* @__PURE__ */ jsxs3("div", { children: [
|
|
1098
|
+
/* @__PURE__ */ jsx4("dt", { children: row.label }),
|
|
1099
|
+
/* @__PURE__ */ jsx4("dd", { children: /* @__PURE__ */ jsx4("pre", { children: row.value }) })
|
|
922
1100
|
] }, row.key)) })
|
|
923
1101
|
] }) : null,
|
|
924
|
-
entry.resultText ? /* @__PURE__ */
|
|
925
|
-
/* @__PURE__ */
|
|
926
|
-
/* @__PURE__ */
|
|
1102
|
+
entry.resultText ? /* @__PURE__ */ jsxs3("section", { children: [
|
|
1103
|
+
/* @__PURE__ */ jsx4("strong", { children: "Native result" }),
|
|
1104
|
+
/* @__PURE__ */ jsxs3("pre", { "data-error": entry.status === "error", children: [
|
|
927
1105
|
entry.resultText,
|
|
928
1106
|
entry.truncated ? "\n[truncated]" : ""
|
|
929
1107
|
] })
|
|
@@ -932,125 +1110,126 @@ function TechnicalDetails({ entry }) {
|
|
|
932
1110
|
] });
|
|
933
1111
|
}
|
|
934
1112
|
function TranscriptEntry({ entry, state, adapter }) {
|
|
935
|
-
if (entry.role === "request") return /* @__PURE__ */
|
|
1113
|
+
if (entry.role === "request") return /* @__PURE__ */ jsx4(RequestCard, { entry, adapter, canRespond: state.canRespond });
|
|
936
1114
|
if (entry.role === "reasoning") {
|
|
937
|
-
return /* @__PURE__ */
|
|
938
|
-
/* @__PURE__ */
|
|
939
|
-
/* @__PURE__ */
|
|
1115
|
+
return /* @__PURE__ */ jsxs3("details", { class: "scui-reasoning", open: entry.streaming, children: [
|
|
1116
|
+
/* @__PURE__ */ jsx4("summary", { children: entry.streaming ? "Reasoning\u2026" : "Reasoning" }),
|
|
1117
|
+
/* @__PURE__ */ jsx4(Markdown, { value: entry.text, copyText: adapter?.copyText })
|
|
940
1118
|
] });
|
|
941
1119
|
}
|
|
942
|
-
if (entry.role === "notice" || entry.role === "system") return /* @__PURE__ */
|
|
943
|
-
return /* @__PURE__ */
|
|
944
|
-
/* @__PURE__ */
|
|
945
|
-
/* @__PURE__ */
|
|
946
|
-
entry.truncated ? /* @__PURE__ */
|
|
1120
|
+
if (entry.role === "notice" || entry.role === "system") return /* @__PURE__ */ jsx4("div", { class: "scui-notice", "data-code": entry.code, children: entry.text });
|
|
1121
|
+
return /* @__PURE__ */ jsxs3("article", { class: "scui-message", "data-role": entry.role, "aria-label": `${entry.role === "user" ? "Your" : "Assistant"} message`, children: [
|
|
1122
|
+
/* @__PURE__ */ jsx4(Markdown, { value: entry.text, copyText: adapter?.copyText }),
|
|
1123
|
+
/* @__PURE__ */ jsx4(ContextDisclosure, { context: entry.context }),
|
|
1124
|
+
entry.truncated ? /* @__PURE__ */ jsx4("small", { class: "scui-truncated", children: "Entry truncated by host \xB7 load native session for the complete value" }) : null,
|
|
1125
|
+
/* @__PURE__ */ jsx4(MessageMeta, { entry, adapter })
|
|
947
1126
|
] });
|
|
948
1127
|
}
|
|
949
1128
|
function ToolRow({ entry, workspace, open = false, adapter }) {
|
|
950
1129
|
const presentation = entry.presentation ?? createToolPresentation(entry);
|
|
951
1130
|
const [expanded, setExpanded] = useState2(open || entry.status === "pending");
|
|
952
|
-
|
|
1131
|
+
useEffect3(() => {
|
|
953
1132
|
if (entry.status === "pending") setExpanded(true);
|
|
954
1133
|
}, [entry.status]);
|
|
955
1134
|
const target = compactToolTarget(presentation.target, workspace);
|
|
956
1135
|
const hasDetail = Boolean(entry.arguments || entry.resultText || presentation.preview || presentation.fields.length);
|
|
957
1136
|
const category = presentation.category ?? toolCategory(entry);
|
|
958
|
-
const summary = /* @__PURE__ */
|
|
959
|
-
/* @__PURE__ */
|
|
960
|
-
/* @__PURE__ */
|
|
961
|
-
target ? /* @__PURE__ */
|
|
962
|
-
/* @__PURE__ */
|
|
963
|
-
entry.status === "pending" ? /* @__PURE__ */
|
|
964
|
-
/* @__PURE__ */
|
|
965
|
-
hasDetail ? /* @__PURE__ */
|
|
1137
|
+
const summary = /* @__PURE__ */ jsxs3(Fragment3, { children: [
|
|
1138
|
+
/* @__PURE__ */ jsx4(ToolIcon, { category }),
|
|
1139
|
+
/* @__PURE__ */ jsx4("strong", { children: toolAction2(entry, category, presentation) }),
|
|
1140
|
+
target ? /* @__PURE__ */ jsx4("code", { class: "scui-tool-target", title: presentation.target, children: target }) : null,
|
|
1141
|
+
/* @__PURE__ */ jsx4("span", { class: "scui-spacer" }),
|
|
1142
|
+
entry.status === "pending" ? /* @__PURE__ */ jsx4(PendingElapsed, { now: adapter?.now }) : null,
|
|
1143
|
+
/* @__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 }) }),
|
|
1144
|
+
hasDetail ? /* @__PURE__ */ jsx4(UiIcon, { name: "chevron", size: 14, class: "scui-tool-chevron" }) : null
|
|
966
1145
|
] });
|
|
967
|
-
if (!hasDetail) return /* @__PURE__ */
|
|
968
|
-
return /* @__PURE__ */
|
|
969
|
-
/* @__PURE__ */
|
|
970
|
-
/* @__PURE__ */
|
|
971
|
-
/* @__PURE__ */
|
|
972
|
-
/* @__PURE__ */
|
|
973
|
-
/* @__PURE__ */
|
|
974
|
-
presentation.fields.length ? /* @__PURE__ */
|
|
975
|
-
/* @__PURE__ */
|
|
976
|
-
/* @__PURE__ */
|
|
1146
|
+
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 }) });
|
|
1147
|
+
return /* @__PURE__ */ jsxs3("details", { class: "scui-tool", "data-status": entry.status ?? "completed", "data-category": category, open: expanded, onToggle: (event) => setExpanded(event.currentTarget.open), children: [
|
|
1148
|
+
/* @__PURE__ */ jsx4("summary", { class: "scui-tool-head", children: summary }),
|
|
1149
|
+
/* @__PURE__ */ jsxs3("div", { class: "scui-tool-detail", children: [
|
|
1150
|
+
/* @__PURE__ */ jsx4(ToolMetrics, { presentation }),
|
|
1151
|
+
/* @__PURE__ */ jsx4(ToolStack, { tools: presentation.tools ?? [] }),
|
|
1152
|
+
/* @__PURE__ */ jsx4(ToolPreview, { presentation, entry }),
|
|
1153
|
+
presentation.fields.length ? /* @__PURE__ */ jsx4("dl", { class: "scui-tool-fields", children: presentation.fields.map((field) => /* @__PURE__ */ jsxs3("div", { children: [
|
|
1154
|
+
/* @__PURE__ */ jsx4("dt", { children: field.label }),
|
|
1155
|
+
/* @__PURE__ */ jsx4("dd", { children: field.value })
|
|
977
1156
|
] }, field.label)) }) : null,
|
|
978
|
-
/* @__PURE__ */
|
|
979
|
-
/* @__PURE__ */
|
|
1157
|
+
/* @__PURE__ */ jsx4(ToolActions, { presentation, adapter }),
|
|
1158
|
+
/* @__PURE__ */ jsx4(TechnicalDetails, { entry })
|
|
980
1159
|
] })
|
|
981
1160
|
] });
|
|
982
1161
|
}
|
|
983
1162
|
function ActivityGroup({ entries, state, adapter }) {
|
|
984
|
-
if (entries.length === 1) return /* @__PURE__ */
|
|
1163
|
+
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 }) });
|
|
985
1164
|
const active = entries.some((entry) => entry.status === "pending");
|
|
986
1165
|
const [open, setOpen] = useState2(active);
|
|
987
1166
|
const id = useId();
|
|
988
|
-
|
|
1167
|
+
useEffect3(() => {
|
|
989
1168
|
if (active) setOpen(true);
|
|
990
1169
|
}, [active]);
|
|
991
|
-
return /* @__PURE__ */
|
|
992
|
-
/* @__PURE__ */
|
|
993
|
-
/* @__PURE__ */
|
|
994
|
-
/* @__PURE__ */
|
|
995
|
-
/* @__PURE__ */
|
|
996
|
-
/* @__PURE__ */
|
|
1170
|
+
return /* @__PURE__ */ jsxs3("section", { class: "scui-activity", children: [
|
|
1171
|
+
/* @__PURE__ */ jsxs3("button", { class: "scui-activity-head", type: "button", "aria-expanded": open, "aria-controls": id, onClick: () => setOpen((value) => !value), children: [
|
|
1172
|
+
/* @__PURE__ */ jsx4("span", { class: "scui-fold", "data-open": open, children: /* @__PURE__ */ jsx4(UiIcon, { name: "chevron", size: 14 }) }),
|
|
1173
|
+
/* @__PURE__ */ jsx4("strong", { children: activitySummary(entries) }),
|
|
1174
|
+
/* @__PURE__ */ jsx4("span", { class: "scui-spacer" }),
|
|
1175
|
+
/* @__PURE__ */ jsxs3("small", { children: [
|
|
997
1176
|
entries.filter((entry) => entry.status !== "pending").length,
|
|
998
1177
|
"/",
|
|
999
1178
|
entries.length
|
|
1000
1179
|
] })
|
|
1001
1180
|
] }),
|
|
1002
|
-
open ? /* @__PURE__ */
|
|
1181
|
+
open ? /* @__PURE__ */ jsx4("div", { id, children: entries.map((entry) => /* @__PURE__ */ jsx4(ToolRow, { entry, workspace: state.workspace, adapter }, entry.id)) }) : null
|
|
1003
1182
|
] });
|
|
1004
1183
|
}
|
|
1005
1184
|
function TaskPlan({ plan }) {
|
|
1006
1185
|
if (!plan.items.length) return null;
|
|
1007
1186
|
const complete = plan.items.filter((item) => item.status === "completed" || item.status === "cancelled").length;
|
|
1008
|
-
return /* @__PURE__ */
|
|
1009
|
-
/* @__PURE__ */
|
|
1010
|
-
/* @__PURE__ */
|
|
1011
|
-
/* @__PURE__ */
|
|
1187
|
+
return /* @__PURE__ */ jsxs3("details", { class: "scui-plan", children: [
|
|
1188
|
+
/* @__PURE__ */ jsxs3("summary", { children: [
|
|
1189
|
+
/* @__PURE__ */ jsx4("span", { children: "Plan" }),
|
|
1190
|
+
/* @__PURE__ */ jsxs3("small", { children: [
|
|
1012
1191
|
complete,
|
|
1013
1192
|
"/",
|
|
1014
1193
|
plan.items.length
|
|
1015
1194
|
] })
|
|
1016
1195
|
] }),
|
|
1017
|
-
/* @__PURE__ */
|
|
1018
|
-
/* @__PURE__ */
|
|
1196
|
+
/* @__PURE__ */ jsx4("ol", { tabIndex: 0, "aria-label": "Task plan steps", children: plan.items.map((item) => /* @__PURE__ */ jsxs3("li", { "data-status": item.status, children: [
|
|
1197
|
+
/* @__PURE__ */ jsx4("i", { "aria-hidden": "true" }),
|
|
1019
1198
|
" ",
|
|
1020
|
-
/* @__PURE__ */
|
|
1199
|
+
/* @__PURE__ */ jsx4("span", { children: item.title })
|
|
1021
1200
|
] }, item.id)) })
|
|
1022
1201
|
] });
|
|
1023
1202
|
}
|
|
1024
1203
|
function SessionDetails({ semantics }) {
|
|
1025
1204
|
if (!semantics.fidelity && !semantics.residueCount && !semantics.parseErrors && !semantics.subagents.length) return null;
|
|
1026
|
-
return /* @__PURE__ */
|
|
1027
|
-
/* @__PURE__ */
|
|
1028
|
-
/* @__PURE__ */
|
|
1029
|
-
semantics.fidelity ? /* @__PURE__ */
|
|
1030
|
-
/* @__PURE__ */
|
|
1031
|
-
/* @__PURE__ */
|
|
1205
|
+
return /* @__PURE__ */ jsxs3("details", { class: "scui-details", children: [
|
|
1206
|
+
/* @__PURE__ */ jsx4("summary", { children: "Session details" }),
|
|
1207
|
+
/* @__PURE__ */ jsxs3("div", { children: [
|
|
1208
|
+
semantics.fidelity ? /* @__PURE__ */ jsxs3("p", { children: [
|
|
1209
|
+
/* @__PURE__ */ jsx4("strong", { children: "Fidelity" }),
|
|
1210
|
+
/* @__PURE__ */ jsx4("span", { children: semantics.fidelity.replaceAll("_", " ") })
|
|
1032
1211
|
] }) : null,
|
|
1033
|
-
/* @__PURE__ */
|
|
1034
|
-
/* @__PURE__ */
|
|
1035
|
-
/* @__PURE__ */
|
|
1212
|
+
/* @__PURE__ */ jsxs3("p", { children: [
|
|
1213
|
+
/* @__PURE__ */ jsx4("strong", { children: "Native records" }),
|
|
1214
|
+
/* @__PURE__ */ jsx4("span", { children: semantics.rawRecords })
|
|
1036
1215
|
] }),
|
|
1037
|
-
semantics.residueCount ? /* @__PURE__ */
|
|
1038
|
-
/* @__PURE__ */
|
|
1039
|
-
/* @__PURE__ */
|
|
1216
|
+
semantics.residueCount ? /* @__PURE__ */ jsxs3("p", { children: [
|
|
1217
|
+
/* @__PURE__ */ jsx4("strong", { children: "Residue" }),
|
|
1218
|
+
/* @__PURE__ */ jsxs3("span", { children: [
|
|
1040
1219
|
semantics.residueCount,
|
|
1041
1220
|
" retained"
|
|
1042
1221
|
] })
|
|
1043
1222
|
] }) : null,
|
|
1044
|
-
semantics.parseErrors ? /* @__PURE__ */
|
|
1045
|
-
/* @__PURE__ */
|
|
1046
|
-
/* @__PURE__ */
|
|
1223
|
+
semantics.parseErrors ? /* @__PURE__ */ jsxs3("p", { children: [
|
|
1224
|
+
/* @__PURE__ */ jsx4("strong", { children: "Parse diagnostics" }),
|
|
1225
|
+
/* @__PURE__ */ jsx4("span", { children: semantics.parseErrors })
|
|
1047
1226
|
] }) : null,
|
|
1048
|
-
semantics.subagents.map((agent) => /* @__PURE__ */
|
|
1049
|
-
/* @__PURE__ */
|
|
1227
|
+
semantics.subagents.map((agent) => /* @__PURE__ */ jsxs3("p", { children: [
|
|
1228
|
+
/* @__PURE__ */ jsxs3("strong", { children: [
|
|
1050
1229
|
agent.source,
|
|
1051
1230
|
" subagent"
|
|
1052
1231
|
] }),
|
|
1053
|
-
/* @__PURE__ */
|
|
1232
|
+
/* @__PURE__ */ jsxs3("span", { children: [
|
|
1054
1233
|
agent.messages,
|
|
1055
1234
|
" messages \xB7 ",
|
|
1056
1235
|
agent.fidelity.replaceAll("_", " ")
|
|
@@ -1060,13 +1239,29 @@ function SessionDetails({ semantics }) {
|
|
|
1060
1239
|
] });
|
|
1061
1240
|
}
|
|
1062
1241
|
var conversationMemory = /* @__PURE__ */ new Map();
|
|
1063
|
-
function
|
|
1064
|
-
const
|
|
1242
|
+
function ConversationAnnouncements({ state }) {
|
|
1243
|
+
const previousBusy = useRef3(state.busy);
|
|
1244
|
+
const [announcement, setAnnouncement] = useState2("");
|
|
1245
|
+
useEffect3(() => {
|
|
1246
|
+
if (previousBusy.current && !state.busy && !state.error) {
|
|
1247
|
+
setAnnouncement(`${harnessDisplayName(state.harness) || "Coding agent"} finished working`);
|
|
1248
|
+
}
|
|
1249
|
+
previousBusy.current = state.busy;
|
|
1250
|
+
}, [state.busy, state.error, state.harness]);
|
|
1251
|
+
return /* @__PURE__ */ jsx4("span", { class: "scui-sr-only", role: "status", "aria-live": "polite", "aria-atomic": "true", children: announcement });
|
|
1252
|
+
}
|
|
1253
|
+
function Conversation({ state, adapter, components = {}, slots = {}, memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`, pending = null, unreadAfterMessages = null }) {
|
|
1254
|
+
const scroller = useRef3(null);
|
|
1065
1255
|
const remembered = conversationMemory.get(memoryKey) ?? { top: null, atBottom: true };
|
|
1066
1256
|
const [atBottom, setAtBottom] = useState2(remembered.atBottom);
|
|
1067
|
-
const earlierAnchor =
|
|
1068
|
-
const restored =
|
|
1257
|
+
const earlierAnchor = useRef3(null);
|
|
1258
|
+
const restored = useRef3(false);
|
|
1069
1259
|
const blocks = groupConversation(state.transcript);
|
|
1260
|
+
const unreadBoundary = useRef3(Number.isSafeInteger(unreadAfterMessages) && unreadAfterMessages >= 0 ? unreadAfterMessages : null);
|
|
1261
|
+
const unreadBlock = unreadBoundary.current === null ? -1 : blocks.findIndex((block) => {
|
|
1262
|
+
const entries = block.kind === "activity" ? block.entries : [block.entry];
|
|
1263
|
+
return entries.some((entry) => Number.isSafeInteger(entry.messageIndex) && entry.messageIndex > unreadBoundary.current);
|
|
1264
|
+
});
|
|
1070
1265
|
const Entry = components.TranscriptEntry ?? TranscriptEntry;
|
|
1071
1266
|
const Group = components.ActivityGroup ?? ActivityGroup;
|
|
1072
1267
|
const Plan = components.TaskPlan ?? TaskPlan;
|
|
@@ -1074,13 +1269,14 @@ function Conversation({ state, adapter, components = {}, slots = {}, memoryKey =
|
|
|
1074
1269
|
const After = slots.afterConversation;
|
|
1075
1270
|
const Empty = slots.emptyConversation;
|
|
1076
1271
|
const remember = (value) => boundedSet(conversationMemory, memoryKey, value);
|
|
1272
|
+
const pendingMessage = typeof pending === "string" ? { text: pending, status: "sending" } : pending;
|
|
1077
1273
|
const pin = () => {
|
|
1078
1274
|
if (!scroller.current) return;
|
|
1079
1275
|
scroller.current.scrollTop = scroller.current.scrollHeight;
|
|
1080
1276
|
setAtBottom(true);
|
|
1081
1277
|
remember({ top: scroller.current.scrollTop, atBottom: true });
|
|
1082
1278
|
};
|
|
1083
|
-
|
|
1279
|
+
useLayoutEffect2(() => {
|
|
1084
1280
|
const element = scroller.current;
|
|
1085
1281
|
if (!element) return;
|
|
1086
1282
|
const anchor = earlierAnchor.current;
|
|
@@ -1095,49 +1291,62 @@ function Conversation({ state, adapter, components = {}, slots = {}, memoryKey =
|
|
|
1095
1291
|
if (remembered.top !== null && !remembered.atBottom) element.scrollTop = remembered.top;
|
|
1096
1292
|
else pin();
|
|
1097
1293
|
} else if (atBottom) pin();
|
|
1098
|
-
}, [memoryKey, state.transcript, state.busy, state.operation]);
|
|
1099
|
-
return /* @__PURE__ */
|
|
1100
|
-
/* @__PURE__ */
|
|
1294
|
+
}, [memoryKey, state.transcript, state.busy, state.operation, pendingMessage?.text, pendingMessage?.status]);
|
|
1295
|
+
return /* @__PURE__ */ jsxs3("div", { class: "scui-conversation-wrap", children: [
|
|
1296
|
+
/* @__PURE__ */ jsx4(ConversationAnnouncements, { state }),
|
|
1297
|
+
/* @__PURE__ */ jsx4("div", { class: "scui-conversation", ref: scroller, tabIndex: 0, "aria-label": "Conversation", onScroll: (event) => {
|
|
1101
1298
|
const element = event.currentTarget;
|
|
1102
1299
|
const bottom = element.scrollHeight - element.scrollTop - element.clientHeight <= 64;
|
|
1103
1300
|
setAtBottom(bottom);
|
|
1104
1301
|
remember({ top: element.scrollTop, atBottom: bottom });
|
|
1105
|
-
}, children: /* @__PURE__ */
|
|
1106
|
-
Before ? /* @__PURE__ */
|
|
1107
|
-
/* @__PURE__ */
|
|
1108
|
-
/* @__PURE__ */
|
|
1109
|
-
state.history.hasEarlier ? /* @__PURE__ */
|
|
1302
|
+
}, children: /* @__PURE__ */ jsxs3("div", { children: [
|
|
1303
|
+
Before ? /* @__PURE__ */ jsx4(Before, { state, adapter, value: null }) : null,
|
|
1304
|
+
/* @__PURE__ */ jsx4(Plan, { plan: state.taskPlan, value: state.taskPlan, state, adapter }),
|
|
1305
|
+
/* @__PURE__ */ jsx4(SessionDetails, { semantics: state.semantics }),
|
|
1306
|
+
state.history.hasEarlier ? /* @__PURE__ */ jsx4("button", { class: "scui-load", type: "button", disabled: Boolean(state.operation), onClick: () => {
|
|
1110
1307
|
const element = scroller.current;
|
|
1111
1308
|
if (element) earlierAnchor.current = { height: element.scrollHeight, top: element.scrollTop, entries: state.transcript.length };
|
|
1112
1309
|
setAtBottom(false);
|
|
1113
1310
|
adapter.onIntent({ action: "loadEarlier" });
|
|
1114
1311
|
}, children: "Load earlier messages" }) : null,
|
|
1115
|
-
!blocks.length && state.startup !== "ready" ? /* @__PURE__ */
|
|
1116
|
-
!blocks.length && state.startup === "ready" ? Empty ? /* @__PURE__ */
|
|
1117
|
-
blocks.map((block
|
|
1118
|
-
|
|
1119
|
-
/* @__PURE__ */
|
|
1120
|
-
|
|
1312
|
+
!blocks.length && state.startup !== "ready" ? /* @__PURE__ */ jsx4(LoadingStatus, { state }) : null,
|
|
1313
|
+
!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,
|
|
1314
|
+
blocks.map((block, index) => /* @__PURE__ */ jsxs3(Fragment2, { children: [
|
|
1315
|
+
index === unreadBlock ? /* @__PURE__ */ jsx4("div", { class: "scui-unread-divider", role: "separator", "aria-label": "New messages", children: /* @__PURE__ */ jsx4("span", { children: "New" }) }) : null,
|
|
1316
|
+
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 })
|
|
1317
|
+
] }, block.id)),
|
|
1318
|
+
pendingMessage ? /* @__PURE__ */ jsxs3("article", { class: "scui-message scui-pending", "data-role": "user", "data-status": pendingMessage.status, "aria-label": "Your pending message", children: [
|
|
1319
|
+
/* @__PURE__ */ jsx4(Markdown, { value: pendingMessage.text, copyText: adapter?.copyText }),
|
|
1320
|
+
/* @__PURE__ */ jsxs3("footer", { children: [
|
|
1321
|
+
/* @__PURE__ */ jsx4("small", { children: pendingMessage.status === "failed" ? "Not sent" : "Sending\u2026" }),
|
|
1322
|
+
pendingMessage.status === "failed" ? /* @__PURE__ */ jsxs3("span", { children: [
|
|
1323
|
+
/* @__PURE__ */ jsx4("button", { type: "button", onClick: pendingMessage.onRetry, children: "Retry" }),
|
|
1324
|
+
/* @__PURE__ */ jsx4("button", { type: "button", onClick: pendingMessage.onEdit, children: "Edit" })
|
|
1325
|
+
] }) : null
|
|
1326
|
+
] })
|
|
1121
1327
|
] }) : null,
|
|
1122
|
-
state.busy ? /* @__PURE__ */
|
|
1123
|
-
/* @__PURE__ */
|
|
1124
|
-
/* @__PURE__ */
|
|
1125
|
-
/* @__PURE__ */
|
|
1126
|
-
/* @__PURE__ */
|
|
1127
|
-
/* @__PURE__ */
|
|
1328
|
+
state.busy ? /* @__PURE__ */ jsxs3("div", { class: "scui-working", role: "status", children: [
|
|
1329
|
+
/* @__PURE__ */ jsx4("span", { "aria-hidden": "true", children: "\u2726" }),
|
|
1330
|
+
/* @__PURE__ */ jsx4("i", {}),
|
|
1331
|
+
/* @__PURE__ */ jsx4("i", {}),
|
|
1332
|
+
/* @__PURE__ */ jsx4("i", {}),
|
|
1333
|
+
/* @__PURE__ */ jsxs3("small", { children: [
|
|
1128
1334
|
harnessDisplayName(state.harness),
|
|
1129
1335
|
" is working"
|
|
1130
1336
|
] })
|
|
1131
1337
|
] }) : null,
|
|
1132
|
-
After ? /* @__PURE__ */
|
|
1338
|
+
After ? /* @__PURE__ */ jsx4(After, { state, adapter, value: null }) : null
|
|
1133
1339
|
] }) }),
|
|
1134
|
-
!atBottom ? /* @__PURE__ */
|
|
1340
|
+
!atBottom ? /* @__PURE__ */ jsxs3("button", { class: "scui-latest", type: "button", onClick: pin, children: [
|
|
1341
|
+
/* @__PURE__ */ jsx4(UiIcon, { name: "down", size: 13 }),
|
|
1342
|
+
" Latest"
|
|
1343
|
+
] }) : null
|
|
1135
1344
|
] });
|
|
1136
1345
|
}
|
|
1137
1346
|
|
|
1138
1347
|
// src/logo.jsx
|
|
1139
|
-
import { useEffect as
|
|
1140
|
-
import { jsx as
|
|
1348
|
+
import { useEffect as useEffect4 } from "preact/hooks";
|
|
1349
|
+
import { jsx as jsx5, jsxs as jsxs4 } from "preact/jsx-runtime";
|
|
1141
1350
|
var LOGOS = {
|
|
1142
1351
|
"claude-code": {
|
|
1143
1352
|
viewBox: "-1 3.5 26 18",
|
|
@@ -1184,74 +1393,106 @@ var LOGOS = {
|
|
|
1184
1393
|
};
|
|
1185
1394
|
function HarnessLogo({ id, activity, size = 28, onMissingLogo }) {
|
|
1186
1395
|
const logo = LOGOS[id];
|
|
1187
|
-
|
|
1396
|
+
useEffect4(() => {
|
|
1188
1397
|
if (!logo) onMissingLogo?.(id);
|
|
1189
1398
|
}, [id, logo, onMissingLogo]);
|
|
1190
1399
|
if (!logo) return null;
|
|
1191
|
-
return /* @__PURE__ */
|
|
1192
|
-
/* @__PURE__ */
|
|
1193
|
-
activity && activity !== "idle" ? /* @__PURE__ */
|
|
1400
|
+
return /* @__PURE__ */ jsxs4("span", { class: "scui-logo", "data-harness": id, "data-activity": activity, style: `--scui-logo-size:${size}px`, "aria-hidden": "true", children: [
|
|
1401
|
+
/* @__PURE__ */ jsx5("svg", { viewBox: logo.viewBox, preserveAspectRatio: "xMidYMid meet", focusable: "false", children: logo.paths.map(([Tag, props], index) => /* @__PURE__ */ jsx5(Tag, { ...props }, index)) }),
|
|
1402
|
+
activity && activity !== "idle" ? /* @__PURE__ */ jsx5("i", {}) : null
|
|
1194
1403
|
] });
|
|
1195
1404
|
}
|
|
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,37 +1844,46 @@ 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
|
|
|
1455
1881
|
// src/embed.jsx
|
|
1456
|
-
import { jsx as
|
|
1882
|
+
import { jsx as jsx8 } from "preact/jsx-runtime";
|
|
1457
1883
|
function mountSupercodeMessenger(element, options) {
|
|
1458
1884
|
if (!(element instanceof Element)) throw new TypeError("mountSupercodeMessenger requires an Element");
|
|
1459
1885
|
let state = options.state;
|
|
1460
|
-
const draw = () => render(/* @__PURE__ */
|
|
1886
|
+
const draw = () => render(/* @__PURE__ */ jsx8(SupercodeMessenger, { ...options, state }), element);
|
|
1461
1887
|
options.adapter.onIntent({ action: "mounted" });
|
|
1462
1888
|
draw();
|
|
1463
1889
|
return {
|