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