@volter-ai-dev/supercode-ui 0.1.32 → 0.1.34
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 +9 -0
- package/components.mjs +69 -29
- package/composer.mjs +1 -1
- package/controller.d.ts +1 -0
- package/controller.mjs +5 -0
- package/core.mjs +10 -2
- package/embed.mjs +69 -29
- package/index.d.ts +11 -3
- package/messenger.mjs +69 -29
- package/package.json +1 -1
- package/sessions.mjs +2 -1
- package/styles.css +1 -0
package/README.md
CHANGED
|
@@ -103,6 +103,13 @@ Native continuation is headless by default. A host with a real terminal provider
|
|
|
103
103
|
that strategy beside “Continue here.” The UI never infers terminal support from a generic runtime
|
|
104
104
|
handoff, and never presents a terminal strategy when the host cannot create one.
|
|
105
105
|
|
|
106
|
+
New-session execution is advertised per harness. Set `launchModes: ['headless', 'terminal']` and
|
|
107
|
+
`preferredLaunchMode` on a `HarnessOption`; the complete messenger renders a compact “Chat” versus
|
|
108
|
+
“Terminal” choice and emits that `mode` on the `new` intent. Direct controller bindings keep Chat as
|
|
109
|
+
the standard path and delegate Terminal only through `onStartTerminal`, so a terminal selection can
|
|
110
|
+
never accidentally create a second headless runtime. Hosts may remember the preference per harness;
|
|
111
|
+
the messenger also retains the current browser draft choice while its New Chat view is open.
|
|
112
|
+
|
|
106
113
|
The default controller projection is display-bounded: 120 visible transcript rows, at most 480
|
|
107
114
|
native entries inspected to fill that tail, 16,000 characters per independent entry field, 100
|
|
108
115
|
session rows, 20 fidelity-residue details, and 50 subagents. It filters harness-injected context
|
|
@@ -116,6 +123,8 @@ errors, and owned/attached identities without reimplementing transcript or capab
|
|
|
116
123
|
- Every component accepts data and callbacks. No component reaches into a global controller.
|
|
117
124
|
- The complete messenger accepts `slots` for high-level replacement and `components` for row-level
|
|
118
125
|
replacement. Replacements receive the same typed, capability-filtered props as defaults.
|
|
126
|
+
- `slots.headerActions` adds compact product controls to the default list, chat, and new-chat
|
|
127
|
+
headers without replacing their navigation, status, or accessibility behavior.
|
|
119
128
|
- Stable `scui-*` classes and `data-*` attributes support additive styling; CSS custom properties
|
|
120
129
|
are the supported theme API.
|
|
121
130
|
- `styles.css` contains both neutral tokens and default component rules. Teams may load it whole,
|
package/components.mjs
CHANGED
|
@@ -744,7 +744,11 @@ function normalizeUiState(value) {
|
|
|
744
744
|
recoverable: raw.recoverable === true,
|
|
745
745
|
harnesses: Array.isArray(raw.harnesses) ? raw.harnesses.flatMap((candidate) => {
|
|
746
746
|
const item = record(candidate);
|
|
747
|
-
|
|
747
|
+
if (!item || typeof item.id !== "string") return [];
|
|
748
|
+
const startable = item.startable === true;
|
|
749
|
+
const launchModes = Array.isArray(item.launchModes) ? [...new Set(item.launchModes.filter((mode) => mode === "headless" || mode === "terminal"))] : startable ? ["headless"] : [];
|
|
750
|
+
const preferredLaunchMode = launchModes.includes(item.preferredLaunchMode) ? item.preferredLaunchMode : launchModes[0] ?? null;
|
|
751
|
+
return [{ id: item.id, label: string(item.label, harnessDisplayName(item.id)), installed: item.installed === true, startable, reason: typeof item.reason === "string" ? item.reason : null, launchModes, preferredLaunchMode }];
|
|
748
752
|
}) : [],
|
|
749
753
|
history: { sessionLimit: number(history?.sessionLimit), hasMoreSessions: history?.hasMoreSessions === true, transcriptLimit: number(history?.transcriptLimit, 120), hasEarlier: history?.hasEarlier === true },
|
|
750
754
|
savedDraft: string(raw.savedDraft),
|
|
@@ -829,7 +833,7 @@ function activitySummary(entries) {
|
|
|
829
833
|
function canContinueHere(state) {
|
|
830
834
|
if (state.mode !== "mirror" || state.canSend) return false;
|
|
831
835
|
const row = state.attached?.key ? state.sessions.find((item) => item.key === state.attached.key) : null;
|
|
832
|
-
return state.canResume && row
|
|
836
|
+
return Boolean(row) && state.canResume && row.runtimeStatus !== "running" && row.runtimeStatus !== "busy" && row.runtimeStatus !== "idle";
|
|
833
837
|
}
|
|
834
838
|
function operationLabel(operation) {
|
|
835
839
|
if (!operation) return "";
|
|
@@ -2126,7 +2130,7 @@ function SessionRow({ row, state, onOpen, now = Date.now() }) {
|
|
|
2126
2130
|
] })
|
|
2127
2131
|
] });
|
|
2128
2132
|
}
|
|
2129
|
-
function SessionList({ state, adapter, onOpen, onNew, onClose, components = {}, labels = DEFAULT_LABELS, focusKey = null, memoryKey = state.workspace || "@default" }) {
|
|
2133
|
+
function SessionList({ state, adapter, onOpen, onNew, onClose, components = {}, labels = DEFAULT_LABELS, focusKey = null, memoryKey = state.workspace || "@default", headerActions: HeaderActions }) {
|
|
2130
2134
|
const remembered = sessionListMemory.get(memoryKey) ?? { query: "", top: 0 };
|
|
2131
2135
|
const [query, setQuery] = useState4(remembered.query);
|
|
2132
2136
|
const [loadingMore, setLoadingMore] = useState4(false);
|
|
@@ -2167,6 +2171,7 @@ function SessionList({ state, adapter, onOpen, onNew, onClose, components = {},
|
|
|
2167
2171
|
" recent conversations"
|
|
2168
2172
|
] })
|
|
2169
2173
|
] }),
|
|
2174
|
+
HeaderActions ? /* @__PURE__ */ jsx7(HeaderActions, { state, adapter, value: "list" }) : null,
|
|
2170
2175
|
/* @__PURE__ */ jsx7("button", { type: "button", "data-list-focus": "new", "aria-label": labels.newChat, disabled: !state.harnesses.some((item) => item.startable), onClick: onNew, children: /* @__PURE__ */ jsx7(UiIcon, { name: "plus", size: 18 }) }),
|
|
2171
2176
|
onClose ? /* @__PURE__ */ jsx7("button", { type: "button", "aria-label": "Close", onClick: onClose, children: /* @__PURE__ */ jsx7(UiIcon, { name: "close", size: 18 }) }) : null
|
|
2172
2177
|
] }),
|
|
@@ -2405,7 +2410,7 @@ function ConversationActions({ state, adapter, actionPending, onSettings }) {
|
|
|
2405
2410
|
] }, group.label)) }) : null
|
|
2406
2411
|
] });
|
|
2407
2412
|
}
|
|
2408
|
-
function ChatHeader({ state, adapter, pendingStatus, actionPending, onBack, onNew, onClose, onSettings }) {
|
|
2413
|
+
function ChatHeader({ state, adapter, pendingStatus, actionPending, onBack, onNew, onClose, onSettings, headerActions: HeaderActions }) {
|
|
2409
2414
|
const back = useRef7(null);
|
|
2410
2415
|
const harness = state.attached?.harness ?? state.harness;
|
|
2411
2416
|
const title = state.attached ? sessionDisplayName(state.attached) : harnessDisplayName(harness) || "Agent chat";
|
|
@@ -2425,6 +2430,7 @@ function ChatHeader({ state, adapter, pendingStatus, actionPending, onBack, onNe
|
|
|
2425
2430
|
status
|
|
2426
2431
|
] })
|
|
2427
2432
|
] }),
|
|
2433
|
+
HeaderActions ? /* @__PURE__ */ jsx9(HeaderActions, { state, adapter, value: "chat" }) : null,
|
|
2428
2434
|
menu ? /* @__PURE__ */ jsx9(ConversationActions, { state, adapter, actionPending, onSettings }) : null,
|
|
2429
2435
|
/* @__PURE__ */ jsx9("button", { type: "button", "aria-label": "New chat", onClick: onNew, children: /* @__PURE__ */ jsx9(UiIcon, { name: "plus", size: 18 }) }),
|
|
2430
2436
|
onClose ? /* @__PURE__ */ jsx9("button", { type: "button", "aria-label": "Close", onClick: onClose, children: /* @__PURE__ */ jsx9(UiIcon, { name: "close", size: 18 }) }) : null
|
|
@@ -2432,6 +2438,7 @@ function ChatHeader({ state, adapter, pendingStatus, actionPending, onBack, onNe
|
|
|
2432
2438
|
}
|
|
2433
2439
|
function Chat({ state, adapter, onBack, onNew, onClose, components, slots, labels }) {
|
|
2434
2440
|
const Header = slots.header;
|
|
2441
|
+
const HeaderActions = slots.headerActions;
|
|
2435
2442
|
const memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`;
|
|
2436
2443
|
const [pending, setPendingState] = useState6(() => pendingMessageMemory.get(memoryKey) ?? null);
|
|
2437
2444
|
const [pendingAction, setPendingAction] = useState6(null);
|
|
@@ -2531,7 +2538,7 @@ function Chat({ state, adapter, onBack, onNew, onClose, components, slots, label
|
|
|
2531
2538
|
const Advisory = components.HarnessAdvisory ?? HarnessAdvisory;
|
|
2532
2539
|
const SettingsPanel = components.HarnessSettingsPanel ?? HarnessSettingsPanel;
|
|
2533
2540
|
return /* @__PURE__ */ jsxs8("section", { class: "scui-chat", children: [
|
|
2534
|
-
Header ? /* @__PURE__ */ jsx9(Header, { state: actionState, adapter: trackedAdapter, value: null }) : /* @__PURE__ */ jsx9(ChatHeader, { state: actionState, adapter: trackedAdapter, pendingStatus: pending?.status ?? null, actionPending: Boolean(action), onBack, onNew, onClose, onSettings: () => setSettings({ recommendedChange: null }) }),
|
|
2541
|
+
Header ? /* @__PURE__ */ jsx9(Header, { state: actionState, adapter: trackedAdapter, value: null }) : /* @__PURE__ */ jsx9(ChatHeader, { state: actionState, adapter: trackedAdapter, pendingStatus: pending?.status ?? null, actionPending: Boolean(action), onBack, onNew, onClose, onSettings: () => setSettings({ recommendedChange: null }), headerActions: HeaderActions }),
|
|
2535
2542
|
actionLabel ? /* @__PURE__ */ jsxs8("div", { class: "scui-operation", role: "status", children: [
|
|
2536
2543
|
/* @__PURE__ */ jsx9("i", {}),
|
|
2537
2544
|
actionLabel
|
|
@@ -2548,34 +2555,41 @@ function Chat({ state, adapter, onBack, onNew, onClose, components, slots, label
|
|
|
2548
2555
|
settings ? /* @__PURE__ */ jsx9(SettingsPanel, { state: actionState, adapter: trackedAdapter, value: state.interopSettings, recommendedChange: settings.recommendedChange, onClose: () => setSettings(null) }) : null
|
|
2549
2556
|
] });
|
|
2550
2557
|
}
|
|
2551
|
-
function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey }) {
|
|
2558
|
+
function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey, headerActions: HeaderActions }) {
|
|
2552
2559
|
const startable = state.harnesses.filter((item) => item.startable);
|
|
2553
|
-
const startableKey = startable.map((item) => item.id).join("\0");
|
|
2554
|
-
const remembered = newChatMemory.get(memoryKey) ?? { harness: startable[0]?.id ?? "", draft: "", context: [], images: [] };
|
|
2560
|
+
const startableKey = startable.map((item) => `${item.id}:${item.launchModes?.join(",")}:${item.preferredLaunchMode ?? ""}`).join("\0");
|
|
2561
|
+
const remembered = newChatMemory.get(memoryKey) ?? { harness: startable[0]?.id ?? "", draft: "", context: [], images: [], modes: {} };
|
|
2555
2562
|
const [harness, setHarness] = useState6(remembered.harness);
|
|
2556
2563
|
const [draft, setDraft] = useState6(remembered.draft);
|
|
2557
2564
|
const [context, setContext] = useState6(remembered.context);
|
|
2558
2565
|
const [images, setImages] = useState6(remembered.images ?? []);
|
|
2566
|
+
const [modes, setModes] = useState6(remembered.modes ?? {});
|
|
2559
2567
|
const [starting, setStarting] = useState6(null);
|
|
2560
2568
|
const [picking, setPicking] = useState6(false);
|
|
2561
2569
|
const [dragging, setDragging] = useState6(false);
|
|
2562
2570
|
const [pickerError, setPickerError] = useState6(null);
|
|
2563
2571
|
const startSequence = useRef7(0);
|
|
2564
2572
|
const textarea = useRef7(null);
|
|
2573
|
+
const selectedHarness = startable.find((item) => item.id === harness) ?? null;
|
|
2574
|
+
const launchModes = selectedHarness?.launchModes?.length ? selectedHarness.launchModes : ["headless"];
|
|
2575
|
+
const rememberedMode = modes[harness];
|
|
2576
|
+
const mode = launchModes.includes(rememberedMode) ? rememberedMode : launchModes.includes(selectedHarness?.preferredLaunchMode) ? selectedHarness.preferredLaunchMode : launchModes[0];
|
|
2577
|
+
const terminalAttachments = mode === "terminal" && (context.length > 0 || images.length > 0);
|
|
2578
|
+
const remember = (overrides = {}) => boundedSet(newChatMemory, memoryKey, { harness, draft, context, images, modes, ...overrides });
|
|
2565
2579
|
useAutosizeTextarea(textarea, draft);
|
|
2566
2580
|
useEffect8(() => {
|
|
2567
2581
|
if (startable.some((item) => item.id === harness)) return;
|
|
2568
2582
|
const next = startable[0]?.id ?? "";
|
|
2569
2583
|
setHarness(next);
|
|
2570
|
-
|
|
2584
|
+
remember({ harness: next });
|
|
2571
2585
|
}, [harness, startableKey]);
|
|
2572
2586
|
useEffect8(() => {
|
|
2573
|
-
if (!starting) return;
|
|
2587
|
+
if (!starting || starting.mode === "terminal") return;
|
|
2574
2588
|
const sessionChanged = (state.attached?.key ?? null) !== starting.attachedKey;
|
|
2575
2589
|
const beganWorking = !starting.busy && state.busy;
|
|
2576
2590
|
if (!sessionChanged && !beganWorking) return;
|
|
2577
2591
|
newChatMemory.delete(memoryKey);
|
|
2578
|
-
onStarted();
|
|
2592
|
+
onStarted("headless");
|
|
2579
2593
|
}, [memoryKey, onStarted, starting, state.attached?.key, state.busy]);
|
|
2580
2594
|
useEffect8(() => {
|
|
2581
2595
|
if (starting && state.error !== starting.initialError && !state.busy && !state.operation) setStarting(null);
|
|
@@ -2584,7 +2598,7 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2584
2598
|
textarea.current?.focus({ preventScroll: true });
|
|
2585
2599
|
}, []);
|
|
2586
2600
|
const pickContext = () => {
|
|
2587
|
-
if (!adapter.pickContext || picking || context.length >= MAX_CONTEXT_ITEMS && images.length >= MAX_IMAGE_ITEMS) return;
|
|
2601
|
+
if (mode === "terminal" || !adapter.pickContext || picking || context.length >= MAX_CONTEXT_ITEMS && images.length >= MAX_IMAGE_ITEMS) return;
|
|
2588
2602
|
setPicking(true);
|
|
2589
2603
|
setPickerError(null);
|
|
2590
2604
|
Promise.resolve().then(() => adapter.pickContext()).then((picked) => {
|
|
@@ -2593,12 +2607,12 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2593
2607
|
if (images.length + attachments.images.length > MAX_IMAGE_ITEMS) throw new Error(`Attach at most ${MAX_IMAGE_ITEMS} images.`);
|
|
2594
2608
|
setContext((current) => {
|
|
2595
2609
|
const next = mergeContext(current, attachments.context);
|
|
2596
|
-
|
|
2610
|
+
remember({ context: next });
|
|
2597
2611
|
return next;
|
|
2598
2612
|
});
|
|
2599
2613
|
setImages((current) => {
|
|
2600
2614
|
const next = mergeImages(current, attachments.images);
|
|
2601
|
-
|
|
2615
|
+
remember({ images: next });
|
|
2602
2616
|
return next;
|
|
2603
2617
|
});
|
|
2604
2618
|
}).catch((error) => setPickerError(error instanceof Error ? error.message : "Could not attach context.")).finally(() => setPicking(false));
|
|
@@ -2606,6 +2620,10 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2606
2620
|
const addImageFiles = (value, source) => {
|
|
2607
2621
|
const allFiles = Array.from(value ?? []);
|
|
2608
2622
|
if (!allFiles.length) return false;
|
|
2623
|
+
if (mode === "terminal") {
|
|
2624
|
+
setPickerError("Terminal starts currently accept text only. Choose Chat to attach context or images.");
|
|
2625
|
+
return true;
|
|
2626
|
+
}
|
|
2609
2627
|
const files = allFiles.filter((file) => file.type.startsWith("image/"));
|
|
2610
2628
|
if (files.length !== allFiles.length) {
|
|
2611
2629
|
setPickerError("Drop or paste PNG, JPEG, GIF, or WebP images.");
|
|
@@ -2619,7 +2637,7 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2619
2637
|
setPickerError(null);
|
|
2620
2638
|
imageAttachmentsFromFiles(files).then((picked) => setImages((current) => {
|
|
2621
2639
|
const next = mergeImages(current, picked);
|
|
2622
|
-
|
|
2640
|
+
remember({ images: next });
|
|
2623
2641
|
return next;
|
|
2624
2642
|
}), (error) => setPickerError(error instanceof Error ? error.message : `Could not ${source} image.`)).finally(() => setPicking(false));
|
|
2625
2643
|
return true;
|
|
@@ -2633,12 +2651,17 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2633
2651
|
};
|
|
2634
2652
|
const send = () => {
|
|
2635
2653
|
const text = draft.trim();
|
|
2636
|
-
if (!text && !images.length || !harness || starting) return;
|
|
2654
|
+
if (!text && !images.length || !harness || starting || terminalAttachments || mode === "terminal" && !text) return;
|
|
2637
2655
|
const id = startSequence.current + 1;
|
|
2638
2656
|
startSequence.current = id;
|
|
2639
|
-
setStarting({ id, attachedKey: state.attached?.key ?? null, busy: state.busy, initialError: state.error });
|
|
2640
|
-
const result = adapter.onIntent({ action: "new", harness, text, ...context.length ? { context } : {}, ...images.length ? { images } : {} });
|
|
2641
|
-
if (
|
|
2657
|
+
setStarting({ id, mode, attachedKey: state.attached?.key ?? null, busy: state.busy, initialError: state.error });
|
|
2658
|
+
const result = adapter.onIntent({ action: "new", harness, mode, text, ...context.length ? { context } : {}, ...images.length ? { images } : {} });
|
|
2659
|
+
if (mode === "terminal") {
|
|
2660
|
+
Promise.resolve(result).then(() => {
|
|
2661
|
+
newChatMemory.delete(memoryKey);
|
|
2662
|
+
onStarted("terminal");
|
|
2663
|
+
}, () => setStarting((current) => current?.id === id ? null : current));
|
|
2664
|
+
} else if (result && typeof result.then === "function") {
|
|
2642
2665
|
Promise.resolve(result).catch(() => setStarting((current) => current?.id === id ? null : current));
|
|
2643
2666
|
}
|
|
2644
2667
|
};
|
|
@@ -2649,11 +2672,12 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2649
2672
|
/* @__PURE__ */ jsx9("strong", { children: labels.newChat }),
|
|
2650
2673
|
/* @__PURE__ */ jsx9("small", { children: "No session is created until you send" })
|
|
2651
2674
|
] }),
|
|
2675
|
+
HeaderActions ? /* @__PURE__ */ jsx9(HeaderActions, { state, adapter, value: "new" }) : null,
|
|
2652
2676
|
onClose ? /* @__PURE__ */ jsx9("button", { type: "button", "aria-label": "Close", onClick: onClose, children: /* @__PURE__ */ jsx9(UiIcon, { name: "close", size: 18 }) }) : null
|
|
2653
2677
|
] }),
|
|
2654
2678
|
starting ? /* @__PURE__ */ jsxs8("div", { class: "scui-operation", role: "status", children: [
|
|
2655
2679
|
/* @__PURE__ */ jsx9("i", {}),
|
|
2656
|
-
operationLabel("start")
|
|
2680
|
+
mode === "terminal" ? "Opening terminal\u2026" : operationLabel("start")
|
|
2657
2681
|
] }) : null,
|
|
2658
2682
|
/* @__PURE__ */ jsxs8("div", { class: "scui-new", children: [
|
|
2659
2683
|
/* @__PURE__ */ jsx9("span", { "aria-hidden": "true", children: "\u2726" }),
|
|
@@ -2667,23 +2691,38 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2667
2691
|
/* @__PURE__ */ jsx9("select", { value: harness, disabled: Boolean(starting), onChange: (event) => {
|
|
2668
2692
|
const value = event.currentTarget.value;
|
|
2669
2693
|
setHarness(value);
|
|
2670
|
-
|
|
2694
|
+
remember({ harness: value });
|
|
2671
2695
|
}, children: state.harnesses.map((item) => /* @__PURE__ */ jsxs8("option", { value: item.id, disabled: !item.startable, children: [
|
|
2672
2696
|
item.label,
|
|
2673
2697
|
item.startable ? "" : " \xB7 unavailable"
|
|
2674
2698
|
] }, item.id)) })
|
|
2675
2699
|
] }),
|
|
2700
|
+
launchModes.length > 1 ? /* @__PURE__ */ jsxs8("fieldset", { class: "scui-launch-modes", disabled: Boolean(starting), children: [
|
|
2701
|
+
/* @__PURE__ */ jsx9("legend", { children: "Run as" }),
|
|
2702
|
+
launchModes.map((item) => /* @__PURE__ */ jsxs8("label", { children: [
|
|
2703
|
+
/* @__PURE__ */ jsx9("input", { type: "radio", name: `launch-mode-${memoryKey}`, value: item, checked: mode === item, onChange: () => {
|
|
2704
|
+
const next = { ...modes, [harness]: item };
|
|
2705
|
+
setModes(next);
|
|
2706
|
+
setPickerError(null);
|
|
2707
|
+
remember({ modes: next });
|
|
2708
|
+
} }),
|
|
2709
|
+
/* @__PURE__ */ jsxs8("span", { children: [
|
|
2710
|
+
/* @__PURE__ */ jsx9("strong", { children: item === "terminal" ? "Terminal" : "Chat" }),
|
|
2711
|
+
/* @__PURE__ */ jsx9("small", { children: item === "terminal" ? "Interactive tmux session" : "Managed here" })
|
|
2712
|
+
] })
|
|
2713
|
+
] }, item))
|
|
2714
|
+
] }) : null,
|
|
2676
2715
|
/* @__PURE__ */ jsx9(ImageTray, { items: images, onRemove: (index) => setImages((items) => {
|
|
2677
2716
|
const next = items.filter((_, itemIndex) => itemIndex !== index);
|
|
2678
|
-
|
|
2717
|
+
remember({ images: next });
|
|
2679
2718
|
return next;
|
|
2680
2719
|
}) }),
|
|
2681
2720
|
/* @__PURE__ */ jsx9(ContextTray, { items: context, onRemove: (index) => setContext((items) => {
|
|
2682
2721
|
const next = items.filter((_, itemIndex) => itemIndex !== index);
|
|
2683
|
-
|
|
2722
|
+
remember({ context: next });
|
|
2684
2723
|
return next;
|
|
2685
2724
|
}) }),
|
|
2686
|
-
pickerError ? /* @__PURE__ */ jsx9("small", { class: "scui-context-error", role: "alert", children: pickerError }) : null,
|
|
2725
|
+
terminalAttachments ? /* @__PURE__ */ jsx9("small", { class: "scui-context-error", role: "alert", children: "Terminal starts currently accept text only. Remove attachments or choose Chat." }) : pickerError ? /* @__PURE__ */ jsx9("small", { class: "scui-context-error", role: "alert", children: pickerError }) : null,
|
|
2687
2726
|
/* @__PURE__ */ jsxs8("div", { class: `scui-envelope${dragging ? " scui-drop-target" : ""}`, onDragEnter: (event) => {
|
|
2688
2727
|
if (Array.from(event.dataTransfer?.types ?? []).includes("Files")) {
|
|
2689
2728
|
event.preventDefault();
|
|
@@ -2694,18 +2733,18 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2694
2733
|
}, onDragLeave: (event) => {
|
|
2695
2734
|
if (!event.currentTarget.contains(event.relatedTarget)) setDragging(false);
|
|
2696
2735
|
}, onDrop: dropImages, children: [
|
|
2697
|
-
adapter.pickContext ? /* @__PURE__ */ jsx9("button", { class: "scui-attach", type: "button", "aria-label": "Attach files or images", disabled: picking || context.length >= MAX_CONTEXT_ITEMS && images.length >= MAX_IMAGE_ITEMS || Boolean(starting), onClick: pickContext, children: picking ? /* @__PURE__ */ jsx9("i", { class: "scui-control-spinner" }) : /* @__PURE__ */ jsx9(UiIcon, { name: "attach", size: 17 }) }) : null,
|
|
2736
|
+
adapter.pickContext ? /* @__PURE__ */ jsx9("button", { class: "scui-attach", type: "button", "aria-label": "Attach files or images", disabled: mode === "terminal" || picking || context.length >= MAX_CONTEXT_ITEMS && images.length >= MAX_IMAGE_ITEMS || Boolean(starting), onClick: pickContext, children: picking ? /* @__PURE__ */ jsx9("i", { class: "scui-control-spinner" }) : /* @__PURE__ */ jsx9(UiIcon, { name: "attach", size: 17 }) }) : null,
|
|
2698
2737
|
/* @__PURE__ */ jsx9("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), onPaste: pasteImages, onInput: (event) => {
|
|
2699
2738
|
const value = event.currentTarget.value;
|
|
2700
2739
|
setDraft(value);
|
|
2701
|
-
|
|
2740
|
+
remember({ draft: value });
|
|
2702
2741
|
}, onKeyDown: (event) => {
|
|
2703
2742
|
if (isSendKey(event)) {
|
|
2704
2743
|
event.preventDefault();
|
|
2705
2744
|
send();
|
|
2706
2745
|
}
|
|
2707
2746
|
} }),
|
|
2708
|
-
/* @__PURE__ */ jsx9("span", { children: /* @__PURE__ */ jsx9("button", { type: "button", class: "scui-send", "aria-label": "Start chat", disabled: !draft.trim() && !images.length || !harness || Boolean(starting), onClick: send, children: starting ? /* @__PURE__ */ jsx9("i", { class: "scui-control-spinner" }) : /* @__PURE__ */ jsx9(UiIcon, { name: "send", size: 17 }) }) })
|
|
2747
|
+
/* @__PURE__ */ jsx9("span", { children: /* @__PURE__ */ jsx9("button", { type: "button", class: "scui-send", "aria-label": mode === "terminal" ? "Start terminal session" : "Start chat", disabled: !draft.trim() && !images.length || !harness || Boolean(starting) || terminalAttachments || mode === "terminal" && !draft.trim(), onClick: send, children: starting ? /* @__PURE__ */ jsx9("i", { class: "scui-control-spinner" }) : /* @__PURE__ */ jsx9(UiIcon, { name: "send", size: 17 }) }) })
|
|
2709
2748
|
] })
|
|
2710
2749
|
] })
|
|
2711
2750
|
] });
|
|
@@ -2737,12 +2776,13 @@ function SupercodeMessenger({ state: stateInput, adapter, class: className = "",
|
|
|
2737
2776
|
};
|
|
2738
2777
|
const close = () => adapter.onClose?.();
|
|
2739
2778
|
const Footer = slots.footer;
|
|
2779
|
+
const HeaderActions = slots.headerActions;
|
|
2740
2780
|
return /* @__PURE__ */ jsxs8("main", { class: `scui-root ${className}`, "data-view": view, "data-mode": state.mode, "aria-label": "Supercode messenger", children: [
|
|
2741
2781
|
view === "list" ? /* @__PURE__ */ jsx9(SessionList, { state, adapter, focusKey: listFocus, onOpen: open, onNew: () => {
|
|
2742
2782
|
setListFocus("@new");
|
|
2743
2783
|
setView("new");
|
|
2744
|
-
}, onClose: adapter.onClose ? close : void 0, components, labels: copy, memoryKey }) : null,
|
|
2745
|
-
view === "new" ? /* @__PURE__ */ jsx9(NewChat, { state, adapter, onBack: () => setView("list"), onClose: adapter.onClose ? close : void 0, onStarted: () => setView("chat"), labels: copy, memoryKey }) : null,
|
|
2784
|
+
}, onClose: adapter.onClose ? close : void 0, components, labels: copy, memoryKey, headerActions: HeaderActions }) : null,
|
|
2785
|
+
view === "new" ? /* @__PURE__ */ jsx9(NewChat, { state, adapter, onBack: () => setView("list"), onClose: adapter.onClose ? close : void 0, onStarted: (mode) => setView(mode === "terminal" ? "list" : "chat"), labels: copy, memoryKey, headerActions: HeaderActions }) : null,
|
|
2746
2786
|
view === "chat" ? /* @__PURE__ */ jsx9(Chat, { state, adapter, onBack: () => {
|
|
2747
2787
|
setListFocus(state.attached?.key ?? listFocus);
|
|
2748
2788
|
setView("list");
|
package/composer.mjs
CHANGED
|
@@ -70,7 +70,7 @@ function harnessDisplayName(id) {
|
|
|
70
70
|
function canContinueHere(state) {
|
|
71
71
|
if (state.mode !== "mirror" || state.canSend) return false;
|
|
72
72
|
const row = state.attached?.key ? state.sessions.find((item) => item.key === state.attached.key) : null;
|
|
73
|
-
return state.canResume && row
|
|
73
|
+
return Boolean(row) && state.canResume && row.runtimeStatus !== "running" && row.runtimeStatus !== "busy" && row.runtimeStatus !== "idle";
|
|
74
74
|
}
|
|
75
75
|
function isSendKey(event) {
|
|
76
76
|
return event.key === "Enter" && !event.shiftKey && !event.isComposing;
|
package/controller.d.ts
CHANGED
|
@@ -84,6 +84,7 @@ export interface ControllerBindingOptions {
|
|
|
84
84
|
onAcknowledge?: (key: string) => void | Promise<void>;
|
|
85
85
|
onLoadSessions?: () => void | Promise<void>;
|
|
86
86
|
onLoadEarlier?: () => void | Promise<void>;
|
|
87
|
+
onStartTerminal?: (intent: Extract<SupercodeUiIntent, { action: 'new' }>) => void | Promise<void>;
|
|
87
88
|
onResumeTerminal?: (intent: Extract<SupercodeUiIntent, { action: 'resume' }>) => void | Promise<void>;
|
|
88
89
|
copyText?: UiAdapter['copyText'];
|
|
89
90
|
resolveImage?: UiAdapter['resolveImage'];
|
package/controller.mjs
CHANGED
|
@@ -447,6 +447,11 @@ async function dispatchStandard(controller, intent, options) {
|
|
|
447
447
|
if (intent.action === 'attach') return controller.dispatch({ type: 'observe', sessionKey: intent.key });
|
|
448
448
|
if (intent.action === 'send') return controller.dispatch({ type: 'send', text: intent.text, ...(intent.context?.length ? { context: intent.context } : {}), ...(intent.images?.length ? { images: intent.images } : {}) });
|
|
449
449
|
if (intent.action === 'new') {
|
|
450
|
+
if (intent.mode === 'terminal') {
|
|
451
|
+
return options.onStartTerminal
|
|
452
|
+
? options.onStartTerminal(intent)
|
|
453
|
+
: options.onUnsupported?.(intent);
|
|
454
|
+
}
|
|
450
455
|
await controller.dispatch({ type: 'start', harness: intent.harness });
|
|
451
456
|
return controller.dispatch({ type: 'send', text: intent.text, ...(intent.context?.length ? { context: intent.context } : {}), ...(intent.images?.length ? { images: intent.images } : {}) });
|
|
452
457
|
}
|
package/core.mjs
CHANGED
|
@@ -796,7 +796,15 @@ export function normalizeUiState(value) {
|
|
|
796
796
|
recoverable: raw.recoverable === true,
|
|
797
797
|
harnesses: Array.isArray(raw.harnesses) ? raw.harnesses.flatMap((candidate) => {
|
|
798
798
|
const item = record(candidate);
|
|
799
|
-
|
|
799
|
+
if (!item || typeof item.id !== 'string') return [];
|
|
800
|
+
const startable = item.startable === true;
|
|
801
|
+
const launchModes = Array.isArray(item.launchModes)
|
|
802
|
+
? [...new Set(item.launchModes.filter((mode) => mode === 'headless' || mode === 'terminal'))]
|
|
803
|
+
: startable ? ['headless'] : [];
|
|
804
|
+
const preferredLaunchMode = launchModes.includes(item.preferredLaunchMode)
|
|
805
|
+
? item.preferredLaunchMode
|
|
806
|
+
: launchModes[0] ?? null;
|
|
807
|
+
return [{ id: item.id, label: string(item.label, harnessDisplayName(item.id)), installed: item.installed === true, startable, reason: typeof item.reason === 'string' ? item.reason : null, launchModes, preferredLaunchMode }];
|
|
800
808
|
}) : [],
|
|
801
809
|
history: { sessionLimit: number(history?.sessionLimit), hasMoreSessions: history?.hasMoreSessions === true, transcriptLimit: number(history?.transcriptLimit, 120), hasEarlier: history?.hasEarlier === true },
|
|
802
810
|
savedDraft: string(raw.savedDraft),
|
|
@@ -895,7 +903,7 @@ export function activitySummary(entries) {
|
|
|
895
903
|
export function canContinueHere(state) {
|
|
896
904
|
if (state.mode !== 'mirror' || state.canSend) return false;
|
|
897
905
|
const row = state.attached?.key ? state.sessions.find((item) => item.key === state.attached.key) : null;
|
|
898
|
-
return state.canResume && row
|
|
906
|
+
return Boolean(row) && state.canResume && row.runtimeStatus !== 'running' && row.runtimeStatus !== 'busy' && row.runtimeStatus !== 'idle';
|
|
899
907
|
}
|
|
900
908
|
|
|
901
909
|
export function operationLabel(operation) {
|
package/embed.mjs
CHANGED
|
@@ -747,7 +747,11 @@ function normalizeUiState(value) {
|
|
|
747
747
|
recoverable: raw.recoverable === true,
|
|
748
748
|
harnesses: Array.isArray(raw.harnesses) ? raw.harnesses.flatMap((candidate) => {
|
|
749
749
|
const item = record(candidate);
|
|
750
|
-
|
|
750
|
+
if (!item || typeof item.id !== "string") return [];
|
|
751
|
+
const startable = item.startable === true;
|
|
752
|
+
const launchModes = Array.isArray(item.launchModes) ? [...new Set(item.launchModes.filter((mode) => mode === "headless" || mode === "terminal"))] : startable ? ["headless"] : [];
|
|
753
|
+
const preferredLaunchMode = launchModes.includes(item.preferredLaunchMode) ? item.preferredLaunchMode : launchModes[0] ?? null;
|
|
754
|
+
return [{ id: item.id, label: string(item.label, harnessDisplayName(item.id)), installed: item.installed === true, startable, reason: typeof item.reason === "string" ? item.reason : null, launchModes, preferredLaunchMode }];
|
|
751
755
|
}) : [],
|
|
752
756
|
history: { sessionLimit: number(history?.sessionLimit), hasMoreSessions: history?.hasMoreSessions === true, transcriptLimit: number(history?.transcriptLimit, 120), hasEarlier: history?.hasEarlier === true },
|
|
753
757
|
savedDraft: string(raw.savedDraft),
|
|
@@ -832,7 +836,7 @@ function activitySummary(entries) {
|
|
|
832
836
|
function canContinueHere(state) {
|
|
833
837
|
if (state.mode !== "mirror" || state.canSend) return false;
|
|
834
838
|
const row = state.attached?.key ? state.sessions.find((item) => item.key === state.attached.key) : null;
|
|
835
|
-
return state.canResume && row
|
|
839
|
+
return Boolean(row) && state.canResume && row.runtimeStatus !== "running" && row.runtimeStatus !== "busy" && row.runtimeStatus !== "idle";
|
|
836
840
|
}
|
|
837
841
|
function operationLabel(operation) {
|
|
838
842
|
if (!operation) return "";
|
|
@@ -2105,7 +2109,7 @@ function SessionRow({ row, state, onOpen, now = Date.now() }) {
|
|
|
2105
2109
|
] })
|
|
2106
2110
|
] });
|
|
2107
2111
|
}
|
|
2108
|
-
function SessionList({ state, adapter, onOpen, onNew, onClose, components = {}, labels = DEFAULT_LABELS, focusKey = null, memoryKey = state.workspace || "@default" }) {
|
|
2112
|
+
function SessionList({ state, adapter, onOpen, onNew, onClose, components = {}, labels = DEFAULT_LABELS, focusKey = null, memoryKey = state.workspace || "@default", headerActions: HeaderActions }) {
|
|
2109
2113
|
const remembered = sessionListMemory.get(memoryKey) ?? { query: "", top: 0 };
|
|
2110
2114
|
const [query, setQuery] = useState4(remembered.query);
|
|
2111
2115
|
const [loadingMore, setLoadingMore] = useState4(false);
|
|
@@ -2146,6 +2150,7 @@ function SessionList({ state, adapter, onOpen, onNew, onClose, components = {},
|
|
|
2146
2150
|
" recent conversations"
|
|
2147
2151
|
] })
|
|
2148
2152
|
] }),
|
|
2153
|
+
HeaderActions ? /* @__PURE__ */ jsx7(HeaderActions, { state, adapter, value: "list" }) : null,
|
|
2149
2154
|
/* @__PURE__ */ jsx7("button", { type: "button", "data-list-focus": "new", "aria-label": labels.newChat, disabled: !state.harnesses.some((item) => item.startable), onClick: onNew, children: /* @__PURE__ */ jsx7(UiIcon, { name: "plus", size: 18 }) }),
|
|
2150
2155
|
onClose ? /* @__PURE__ */ jsx7("button", { type: "button", "aria-label": "Close", onClick: onClose, children: /* @__PURE__ */ jsx7(UiIcon, { name: "close", size: 18 }) }) : null
|
|
2151
2156
|
] }),
|
|
@@ -2384,7 +2389,7 @@ function ConversationActions({ state, adapter, actionPending, onSettings }) {
|
|
|
2384
2389
|
] }, group.label)) }) : null
|
|
2385
2390
|
] });
|
|
2386
2391
|
}
|
|
2387
|
-
function ChatHeader({ state, adapter, pendingStatus, actionPending, onBack, onNew, onClose, onSettings }) {
|
|
2392
|
+
function ChatHeader({ state, adapter, pendingStatus, actionPending, onBack, onNew, onClose, onSettings, headerActions: HeaderActions }) {
|
|
2388
2393
|
const back = useRef7(null);
|
|
2389
2394
|
const harness = state.attached?.harness ?? state.harness;
|
|
2390
2395
|
const title = state.attached ? sessionDisplayName(state.attached) : harnessDisplayName(harness) || "Agent chat";
|
|
@@ -2404,6 +2409,7 @@ function ChatHeader({ state, adapter, pendingStatus, actionPending, onBack, onNe
|
|
|
2404
2409
|
status
|
|
2405
2410
|
] })
|
|
2406
2411
|
] }),
|
|
2412
|
+
HeaderActions ? /* @__PURE__ */ jsx9(HeaderActions, { state, adapter, value: "chat" }) : null,
|
|
2407
2413
|
menu ? /* @__PURE__ */ jsx9(ConversationActions, { state, adapter, actionPending, onSettings }) : null,
|
|
2408
2414
|
/* @__PURE__ */ jsx9("button", { type: "button", "aria-label": "New chat", onClick: onNew, children: /* @__PURE__ */ jsx9(UiIcon, { name: "plus", size: 18 }) }),
|
|
2409
2415
|
onClose ? /* @__PURE__ */ jsx9("button", { type: "button", "aria-label": "Close", onClick: onClose, children: /* @__PURE__ */ jsx9(UiIcon, { name: "close", size: 18 }) }) : null
|
|
@@ -2411,6 +2417,7 @@ function ChatHeader({ state, adapter, pendingStatus, actionPending, onBack, onNe
|
|
|
2411
2417
|
}
|
|
2412
2418
|
function Chat({ state, adapter, onBack, onNew, onClose, components, slots, labels }) {
|
|
2413
2419
|
const Header = slots.header;
|
|
2420
|
+
const HeaderActions = slots.headerActions;
|
|
2414
2421
|
const memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`;
|
|
2415
2422
|
const [pending, setPendingState] = useState6(() => pendingMessageMemory.get(memoryKey) ?? null);
|
|
2416
2423
|
const [pendingAction, setPendingAction] = useState6(null);
|
|
@@ -2510,7 +2517,7 @@ function Chat({ state, adapter, onBack, onNew, onClose, components, slots, label
|
|
|
2510
2517
|
const Advisory = components.HarnessAdvisory ?? HarnessAdvisory;
|
|
2511
2518
|
const SettingsPanel = components.HarnessSettingsPanel ?? HarnessSettingsPanel;
|
|
2512
2519
|
return /* @__PURE__ */ jsxs8("section", { class: "scui-chat", children: [
|
|
2513
|
-
Header ? /* @__PURE__ */ jsx9(Header, { state: actionState, adapter: trackedAdapter, value: null }) : /* @__PURE__ */ jsx9(ChatHeader, { state: actionState, adapter: trackedAdapter, pendingStatus: pending?.status ?? null, actionPending: Boolean(action), onBack, onNew, onClose, onSettings: () => setSettings({ recommendedChange: null }) }),
|
|
2520
|
+
Header ? /* @__PURE__ */ jsx9(Header, { state: actionState, adapter: trackedAdapter, value: null }) : /* @__PURE__ */ jsx9(ChatHeader, { state: actionState, adapter: trackedAdapter, pendingStatus: pending?.status ?? null, actionPending: Boolean(action), onBack, onNew, onClose, onSettings: () => setSettings({ recommendedChange: null }), headerActions: HeaderActions }),
|
|
2514
2521
|
actionLabel ? /* @__PURE__ */ jsxs8("div", { class: "scui-operation", role: "status", children: [
|
|
2515
2522
|
/* @__PURE__ */ jsx9("i", {}),
|
|
2516
2523
|
actionLabel
|
|
@@ -2527,34 +2534,41 @@ function Chat({ state, adapter, onBack, onNew, onClose, components, slots, label
|
|
|
2527
2534
|
settings ? /* @__PURE__ */ jsx9(SettingsPanel, { state: actionState, adapter: trackedAdapter, value: state.interopSettings, recommendedChange: settings.recommendedChange, onClose: () => setSettings(null) }) : null
|
|
2528
2535
|
] });
|
|
2529
2536
|
}
|
|
2530
|
-
function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey }) {
|
|
2537
|
+
function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey, headerActions: HeaderActions }) {
|
|
2531
2538
|
const startable = state.harnesses.filter((item) => item.startable);
|
|
2532
|
-
const startableKey = startable.map((item) => item.id).join("\0");
|
|
2533
|
-
const remembered = newChatMemory.get(memoryKey) ?? { harness: startable[0]?.id ?? "", draft: "", context: [], images: [] };
|
|
2539
|
+
const startableKey = startable.map((item) => `${item.id}:${item.launchModes?.join(",")}:${item.preferredLaunchMode ?? ""}`).join("\0");
|
|
2540
|
+
const remembered = newChatMemory.get(memoryKey) ?? { harness: startable[0]?.id ?? "", draft: "", context: [], images: [], modes: {} };
|
|
2534
2541
|
const [harness, setHarness] = useState6(remembered.harness);
|
|
2535
2542
|
const [draft, setDraft] = useState6(remembered.draft);
|
|
2536
2543
|
const [context, setContext] = useState6(remembered.context);
|
|
2537
2544
|
const [images, setImages] = useState6(remembered.images ?? []);
|
|
2545
|
+
const [modes, setModes] = useState6(remembered.modes ?? {});
|
|
2538
2546
|
const [starting, setStarting] = useState6(null);
|
|
2539
2547
|
const [picking, setPicking] = useState6(false);
|
|
2540
2548
|
const [dragging, setDragging] = useState6(false);
|
|
2541
2549
|
const [pickerError, setPickerError] = useState6(null);
|
|
2542
2550
|
const startSequence = useRef7(0);
|
|
2543
2551
|
const textarea = useRef7(null);
|
|
2552
|
+
const selectedHarness = startable.find((item) => item.id === harness) ?? null;
|
|
2553
|
+
const launchModes = selectedHarness?.launchModes?.length ? selectedHarness.launchModes : ["headless"];
|
|
2554
|
+
const rememberedMode = modes[harness];
|
|
2555
|
+
const mode = launchModes.includes(rememberedMode) ? rememberedMode : launchModes.includes(selectedHarness?.preferredLaunchMode) ? selectedHarness.preferredLaunchMode : launchModes[0];
|
|
2556
|
+
const terminalAttachments = mode === "terminal" && (context.length > 0 || images.length > 0);
|
|
2557
|
+
const remember = (overrides = {}) => boundedSet(newChatMemory, memoryKey, { harness, draft, context, images, modes, ...overrides });
|
|
2544
2558
|
useAutosizeTextarea(textarea, draft);
|
|
2545
2559
|
useEffect8(() => {
|
|
2546
2560
|
if (startable.some((item) => item.id === harness)) return;
|
|
2547
2561
|
const next = startable[0]?.id ?? "";
|
|
2548
2562
|
setHarness(next);
|
|
2549
|
-
|
|
2563
|
+
remember({ harness: next });
|
|
2550
2564
|
}, [harness, startableKey]);
|
|
2551
2565
|
useEffect8(() => {
|
|
2552
|
-
if (!starting) return;
|
|
2566
|
+
if (!starting || starting.mode === "terminal") return;
|
|
2553
2567
|
const sessionChanged = (state.attached?.key ?? null) !== starting.attachedKey;
|
|
2554
2568
|
const beganWorking = !starting.busy && state.busy;
|
|
2555
2569
|
if (!sessionChanged && !beganWorking) return;
|
|
2556
2570
|
newChatMemory.delete(memoryKey);
|
|
2557
|
-
onStarted();
|
|
2571
|
+
onStarted("headless");
|
|
2558
2572
|
}, [memoryKey, onStarted, starting, state.attached?.key, state.busy]);
|
|
2559
2573
|
useEffect8(() => {
|
|
2560
2574
|
if (starting && state.error !== starting.initialError && !state.busy && !state.operation) setStarting(null);
|
|
@@ -2563,7 +2577,7 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2563
2577
|
textarea.current?.focus({ preventScroll: true });
|
|
2564
2578
|
}, []);
|
|
2565
2579
|
const pickContext = () => {
|
|
2566
|
-
if (!adapter.pickContext || picking || context.length >= MAX_CONTEXT_ITEMS && images.length >= MAX_IMAGE_ITEMS) return;
|
|
2580
|
+
if (mode === "terminal" || !adapter.pickContext || picking || context.length >= MAX_CONTEXT_ITEMS && images.length >= MAX_IMAGE_ITEMS) return;
|
|
2567
2581
|
setPicking(true);
|
|
2568
2582
|
setPickerError(null);
|
|
2569
2583
|
Promise.resolve().then(() => adapter.pickContext()).then((picked) => {
|
|
@@ -2572,12 +2586,12 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2572
2586
|
if (images.length + attachments.images.length > MAX_IMAGE_ITEMS) throw new Error(`Attach at most ${MAX_IMAGE_ITEMS} images.`);
|
|
2573
2587
|
setContext((current) => {
|
|
2574
2588
|
const next = mergeContext(current, attachments.context);
|
|
2575
|
-
|
|
2589
|
+
remember({ context: next });
|
|
2576
2590
|
return next;
|
|
2577
2591
|
});
|
|
2578
2592
|
setImages((current) => {
|
|
2579
2593
|
const next = mergeImages(current, attachments.images);
|
|
2580
|
-
|
|
2594
|
+
remember({ images: next });
|
|
2581
2595
|
return next;
|
|
2582
2596
|
});
|
|
2583
2597
|
}).catch((error) => setPickerError(error instanceof Error ? error.message : "Could not attach context.")).finally(() => setPicking(false));
|
|
@@ -2585,6 +2599,10 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2585
2599
|
const addImageFiles = (value, source) => {
|
|
2586
2600
|
const allFiles = Array.from(value ?? []);
|
|
2587
2601
|
if (!allFiles.length) return false;
|
|
2602
|
+
if (mode === "terminal") {
|
|
2603
|
+
setPickerError("Terminal starts currently accept text only. Choose Chat to attach context or images.");
|
|
2604
|
+
return true;
|
|
2605
|
+
}
|
|
2588
2606
|
const files = allFiles.filter((file) => file.type.startsWith("image/"));
|
|
2589
2607
|
if (files.length !== allFiles.length) {
|
|
2590
2608
|
setPickerError("Drop or paste PNG, JPEG, GIF, or WebP images.");
|
|
@@ -2598,7 +2616,7 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2598
2616
|
setPickerError(null);
|
|
2599
2617
|
imageAttachmentsFromFiles(files).then((picked) => setImages((current) => {
|
|
2600
2618
|
const next = mergeImages(current, picked);
|
|
2601
|
-
|
|
2619
|
+
remember({ images: next });
|
|
2602
2620
|
return next;
|
|
2603
2621
|
}), (error) => setPickerError(error instanceof Error ? error.message : `Could not ${source} image.`)).finally(() => setPicking(false));
|
|
2604
2622
|
return true;
|
|
@@ -2612,12 +2630,17 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2612
2630
|
};
|
|
2613
2631
|
const send = () => {
|
|
2614
2632
|
const text = draft.trim();
|
|
2615
|
-
if (!text && !images.length || !harness || starting) return;
|
|
2633
|
+
if (!text && !images.length || !harness || starting || terminalAttachments || mode === "terminal" && !text) return;
|
|
2616
2634
|
const id = startSequence.current + 1;
|
|
2617
2635
|
startSequence.current = id;
|
|
2618
|
-
setStarting({ id, attachedKey: state.attached?.key ?? null, busy: state.busy, initialError: state.error });
|
|
2619
|
-
const result = adapter.onIntent({ action: "new", harness, text, ...context.length ? { context } : {}, ...images.length ? { images } : {} });
|
|
2620
|
-
if (
|
|
2636
|
+
setStarting({ id, mode, attachedKey: state.attached?.key ?? null, busy: state.busy, initialError: state.error });
|
|
2637
|
+
const result = adapter.onIntent({ action: "new", harness, mode, text, ...context.length ? { context } : {}, ...images.length ? { images } : {} });
|
|
2638
|
+
if (mode === "terminal") {
|
|
2639
|
+
Promise.resolve(result).then(() => {
|
|
2640
|
+
newChatMemory.delete(memoryKey);
|
|
2641
|
+
onStarted("terminal");
|
|
2642
|
+
}, () => setStarting((current) => current?.id === id ? null : current));
|
|
2643
|
+
} else if (result && typeof result.then === "function") {
|
|
2621
2644
|
Promise.resolve(result).catch(() => setStarting((current) => current?.id === id ? null : current));
|
|
2622
2645
|
}
|
|
2623
2646
|
};
|
|
@@ -2628,11 +2651,12 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2628
2651
|
/* @__PURE__ */ jsx9("strong", { children: labels.newChat }),
|
|
2629
2652
|
/* @__PURE__ */ jsx9("small", { children: "No session is created until you send" })
|
|
2630
2653
|
] }),
|
|
2654
|
+
HeaderActions ? /* @__PURE__ */ jsx9(HeaderActions, { state, adapter, value: "new" }) : null,
|
|
2631
2655
|
onClose ? /* @__PURE__ */ jsx9("button", { type: "button", "aria-label": "Close", onClick: onClose, children: /* @__PURE__ */ jsx9(UiIcon, { name: "close", size: 18 }) }) : null
|
|
2632
2656
|
] }),
|
|
2633
2657
|
starting ? /* @__PURE__ */ jsxs8("div", { class: "scui-operation", role: "status", children: [
|
|
2634
2658
|
/* @__PURE__ */ jsx9("i", {}),
|
|
2635
|
-
operationLabel("start")
|
|
2659
|
+
mode === "terminal" ? "Opening terminal\u2026" : operationLabel("start")
|
|
2636
2660
|
] }) : null,
|
|
2637
2661
|
/* @__PURE__ */ jsxs8("div", { class: "scui-new", children: [
|
|
2638
2662
|
/* @__PURE__ */ jsx9("span", { "aria-hidden": "true", children: "\u2726" }),
|
|
@@ -2646,23 +2670,38 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2646
2670
|
/* @__PURE__ */ jsx9("select", { value: harness, disabled: Boolean(starting), onChange: (event) => {
|
|
2647
2671
|
const value = event.currentTarget.value;
|
|
2648
2672
|
setHarness(value);
|
|
2649
|
-
|
|
2673
|
+
remember({ harness: value });
|
|
2650
2674
|
}, children: state.harnesses.map((item) => /* @__PURE__ */ jsxs8("option", { value: item.id, disabled: !item.startable, children: [
|
|
2651
2675
|
item.label,
|
|
2652
2676
|
item.startable ? "" : " \xB7 unavailable"
|
|
2653
2677
|
] }, item.id)) })
|
|
2654
2678
|
] }),
|
|
2679
|
+
launchModes.length > 1 ? /* @__PURE__ */ jsxs8("fieldset", { class: "scui-launch-modes", disabled: Boolean(starting), children: [
|
|
2680
|
+
/* @__PURE__ */ jsx9("legend", { children: "Run as" }),
|
|
2681
|
+
launchModes.map((item) => /* @__PURE__ */ jsxs8("label", { children: [
|
|
2682
|
+
/* @__PURE__ */ jsx9("input", { type: "radio", name: `launch-mode-${memoryKey}`, value: item, checked: mode === item, onChange: () => {
|
|
2683
|
+
const next = { ...modes, [harness]: item };
|
|
2684
|
+
setModes(next);
|
|
2685
|
+
setPickerError(null);
|
|
2686
|
+
remember({ modes: next });
|
|
2687
|
+
} }),
|
|
2688
|
+
/* @__PURE__ */ jsxs8("span", { children: [
|
|
2689
|
+
/* @__PURE__ */ jsx9("strong", { children: item === "terminal" ? "Terminal" : "Chat" }),
|
|
2690
|
+
/* @__PURE__ */ jsx9("small", { children: item === "terminal" ? "Interactive tmux session" : "Managed here" })
|
|
2691
|
+
] })
|
|
2692
|
+
] }, item))
|
|
2693
|
+
] }) : null,
|
|
2655
2694
|
/* @__PURE__ */ jsx9(ImageTray, { items: images, onRemove: (index) => setImages((items) => {
|
|
2656
2695
|
const next = items.filter((_, itemIndex) => itemIndex !== index);
|
|
2657
|
-
|
|
2696
|
+
remember({ images: next });
|
|
2658
2697
|
return next;
|
|
2659
2698
|
}) }),
|
|
2660
2699
|
/* @__PURE__ */ jsx9(ContextTray, { items: context, onRemove: (index) => setContext((items) => {
|
|
2661
2700
|
const next = items.filter((_, itemIndex) => itemIndex !== index);
|
|
2662
|
-
|
|
2701
|
+
remember({ context: next });
|
|
2663
2702
|
return next;
|
|
2664
2703
|
}) }),
|
|
2665
|
-
pickerError ? /* @__PURE__ */ jsx9("small", { class: "scui-context-error", role: "alert", children: pickerError }) : null,
|
|
2704
|
+
terminalAttachments ? /* @__PURE__ */ jsx9("small", { class: "scui-context-error", role: "alert", children: "Terminal starts currently accept text only. Remove attachments or choose Chat." }) : pickerError ? /* @__PURE__ */ jsx9("small", { class: "scui-context-error", role: "alert", children: pickerError }) : null,
|
|
2666
2705
|
/* @__PURE__ */ jsxs8("div", { class: `scui-envelope${dragging ? " scui-drop-target" : ""}`, onDragEnter: (event) => {
|
|
2667
2706
|
if (Array.from(event.dataTransfer?.types ?? []).includes("Files")) {
|
|
2668
2707
|
event.preventDefault();
|
|
@@ -2673,18 +2712,18 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2673
2712
|
}, onDragLeave: (event) => {
|
|
2674
2713
|
if (!event.currentTarget.contains(event.relatedTarget)) setDragging(false);
|
|
2675
2714
|
}, onDrop: dropImages, children: [
|
|
2676
|
-
adapter.pickContext ? /* @__PURE__ */ jsx9("button", { class: "scui-attach", type: "button", "aria-label": "Attach files or images", disabled: picking || context.length >= MAX_CONTEXT_ITEMS && images.length >= MAX_IMAGE_ITEMS || Boolean(starting), onClick: pickContext, children: picking ? /* @__PURE__ */ jsx9("i", { class: "scui-control-spinner" }) : /* @__PURE__ */ jsx9(UiIcon, { name: "attach", size: 17 }) }) : null,
|
|
2715
|
+
adapter.pickContext ? /* @__PURE__ */ jsx9("button", { class: "scui-attach", type: "button", "aria-label": "Attach files or images", disabled: mode === "terminal" || picking || context.length >= MAX_CONTEXT_ITEMS && images.length >= MAX_IMAGE_ITEMS || Boolean(starting), onClick: pickContext, children: picking ? /* @__PURE__ */ jsx9("i", { class: "scui-control-spinner" }) : /* @__PURE__ */ jsx9(UiIcon, { name: "attach", size: 17 }) }) : null,
|
|
2677
2716
|
/* @__PURE__ */ jsx9("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), onPaste: pasteImages, onInput: (event) => {
|
|
2678
2717
|
const value = event.currentTarget.value;
|
|
2679
2718
|
setDraft(value);
|
|
2680
|
-
|
|
2719
|
+
remember({ draft: value });
|
|
2681
2720
|
}, onKeyDown: (event) => {
|
|
2682
2721
|
if (isSendKey(event)) {
|
|
2683
2722
|
event.preventDefault();
|
|
2684
2723
|
send();
|
|
2685
2724
|
}
|
|
2686
2725
|
} }),
|
|
2687
|
-
/* @__PURE__ */ jsx9("span", { children: /* @__PURE__ */ jsx9("button", { type: "button", class: "scui-send", "aria-label": "Start chat", disabled: !draft.trim() && !images.length || !harness || Boolean(starting), onClick: send, children: starting ? /* @__PURE__ */ jsx9("i", { class: "scui-control-spinner" }) : /* @__PURE__ */ jsx9(UiIcon, { name: "send", size: 17 }) }) })
|
|
2726
|
+
/* @__PURE__ */ jsx9("span", { children: /* @__PURE__ */ jsx9("button", { type: "button", class: "scui-send", "aria-label": mode === "terminal" ? "Start terminal session" : "Start chat", disabled: !draft.trim() && !images.length || !harness || Boolean(starting) || terminalAttachments || mode === "terminal" && !draft.trim(), onClick: send, children: starting ? /* @__PURE__ */ jsx9("i", { class: "scui-control-spinner" }) : /* @__PURE__ */ jsx9(UiIcon, { name: "send", size: 17 }) }) })
|
|
2688
2727
|
] })
|
|
2689
2728
|
] })
|
|
2690
2729
|
] });
|
|
@@ -2716,12 +2755,13 @@ function SupercodeMessenger({ state: stateInput, adapter, class: className = "",
|
|
|
2716
2755
|
};
|
|
2717
2756
|
const close = () => adapter.onClose?.();
|
|
2718
2757
|
const Footer = slots.footer;
|
|
2758
|
+
const HeaderActions = slots.headerActions;
|
|
2719
2759
|
return /* @__PURE__ */ jsxs8("main", { class: `scui-root ${className}`, "data-view": view, "data-mode": state.mode, "aria-label": "Supercode messenger", children: [
|
|
2720
2760
|
view === "list" ? /* @__PURE__ */ jsx9(SessionList, { state, adapter, focusKey: listFocus, onOpen: open, onNew: () => {
|
|
2721
2761
|
setListFocus("@new");
|
|
2722
2762
|
setView("new");
|
|
2723
|
-
}, onClose: adapter.onClose ? close : void 0, components, labels: copy, memoryKey }) : null,
|
|
2724
|
-
view === "new" ? /* @__PURE__ */ jsx9(NewChat, { state, adapter, onBack: () => setView("list"), onClose: adapter.onClose ? close : void 0, onStarted: () => setView("chat"), labels: copy, memoryKey }) : null,
|
|
2763
|
+
}, onClose: adapter.onClose ? close : void 0, components, labels: copy, memoryKey, headerActions: HeaderActions }) : null,
|
|
2764
|
+
view === "new" ? /* @__PURE__ */ jsx9(NewChat, { state, adapter, onBack: () => setView("list"), onClose: adapter.onClose ? close : void 0, onStarted: (mode) => setView(mode === "terminal" ? "list" : "chat"), labels: copy, memoryKey, headerActions: HeaderActions }) : null,
|
|
2725
2765
|
view === "chat" ? /* @__PURE__ */ jsx9(Chat, { state, adapter, onBack: () => {
|
|
2726
2766
|
setListFocus(state.attached?.key ?? listFocus);
|
|
2727
2767
|
setView("list");
|
package/index.d.ts
CHANGED
|
@@ -5,7 +5,9 @@ export type UiTone = 'live' | 'warn' | 'dead' | 'off';
|
|
|
5
5
|
export type StartupPhase = 'connecting' | 'starting' | 'discovering' | 'ready';
|
|
6
6
|
export type SessionMode = 'none' | 'control' | 'mirror';
|
|
7
7
|
export type ControlStrategy = 'start' | 'resume' | 'attach' | 'branch' | 'reduce' | null;
|
|
8
|
-
export type
|
|
8
|
+
export type ExecutionMode = 'headless' | 'terminal';
|
|
9
|
+
/** @deprecated Use ExecutionMode. */
|
|
10
|
+
export type ContinuationMode = ExecutionMode;
|
|
9
11
|
export type SessionActivity = 'idle' | 'recent' | 'running' | 'working' | 'needs-input' | 'finished' | 'failed' | 'unseen';
|
|
10
12
|
|
|
11
13
|
export interface HarnessOption {
|
|
@@ -14,6 +16,10 @@ export interface HarnessOption {
|
|
|
14
16
|
installed: boolean;
|
|
15
17
|
startable: boolean;
|
|
16
18
|
reason: string | null;
|
|
19
|
+
/** Host-supported ways to start this harness. Omitted means headless when startable. */
|
|
20
|
+
launchModes?: ExecutionMode[];
|
|
21
|
+
/** Host preference, used unless this browser has a remembered choice for the harness. */
|
|
22
|
+
preferredLaunchMode?: ExecutionMode | null;
|
|
17
23
|
}
|
|
18
24
|
|
|
19
25
|
export interface SessionRowModel {
|
|
@@ -287,7 +293,7 @@ export type SupercodeUiIntent =
|
|
|
287
293
|
| { action: 'loadEarlier' }
|
|
288
294
|
| { action: 'draft'; text: string }
|
|
289
295
|
| { action: 'send'; text: string; context?: TranscriptContext[]; images?: Array<TranscriptImage & { url: string }> }
|
|
290
|
-
| { action: 'new'; harness: HarnessId; text: string; context?: TranscriptContext[]; images?: Array<TranscriptImage & { url: string }> }
|
|
296
|
+
| { action: 'new'; harness: HarnessId; mode?: ExecutionMode; text: string; context?: TranscriptContext[]; images?: Array<TranscriptImage & { url: string }> }
|
|
291
297
|
| { action: 'resume'; mode?: ContinuationMode }
|
|
292
298
|
| { action: 'join' }
|
|
293
299
|
| { action: 'detach' }
|
|
@@ -403,6 +409,8 @@ export interface MessengerComponents {
|
|
|
403
409
|
|
|
404
410
|
export interface MessengerSlots {
|
|
405
411
|
header?: ComponentType<ComponentOverrideProps>;
|
|
412
|
+
/** Add a compact consumer control to every default messenger header. `value` is the current view. */
|
|
413
|
+
headerActions?: ComponentType<ComponentOverrideProps<'list' | 'chat' | 'new'>>;
|
|
406
414
|
emptyConversation?: ComponentType<ComponentOverrideProps>;
|
|
407
415
|
beforeConversation?: ComponentType<ComponentOverrideProps>;
|
|
408
416
|
afterConversation?: ComponentType<ComponentOverrideProps>;
|
|
@@ -460,7 +468,7 @@ export function HarnessSettingsPanel(props: HarnessSettingsPanelProps): VNode;
|
|
|
460
468
|
export function SessionDetails(props: { semantics: SessionSemanticsModel }): VNode | null;
|
|
461
469
|
export function Conversation(props: { state: SupercodeUiState; adapter: UiAdapter; components?: MessengerComponents; slots?: MessengerSlots; memoryKey?: string; pending?: string | PendingMessageModel | null; unreadAfterMessages?: number | null }): VNode;
|
|
462
470
|
export function SessionRow(props: { row: SessionRowModel; state: SupercodeUiState; adapter: UiAdapter; now?: number; onOpen(row: SessionRowModel): void }): VNode;
|
|
463
|
-
export function SessionList(props: { state: SupercodeUiState; adapter: UiAdapter; onOpen(row: SessionRowModel): void; onNew?(): void; onClose?(): void; components?: MessengerComponents; labels?: MessengerLabels; focusKey?: string | null; memoryKey?: string }): VNode;
|
|
471
|
+
export function SessionList(props: { state: SupercodeUiState; adapter: UiAdapter; onOpen(row: SessionRowModel): void; onNew?(): void; onClose?(): void; components?: MessengerComponents; labels?: MessengerLabels; focusKey?: string | null; memoryKey?: string; headerActions?: MessengerSlots['headerActions'] }): VNode;
|
|
464
472
|
export function ContinuationBar(props: { state: SupercodeUiState; adapter: UiAdapter; labels?: MessengerLabels }): VNode | null;
|
|
465
473
|
export function Composer(props: { state: SupercodeUiState; adapter: UiAdapter; labels?: MessengerLabels; memoryKey?: string; pendingStatus?: PendingMessageModel['status'] | 'editing' | null; restoreDraft?: { id: string | number; text: string; context?: TranscriptContext[]; images?: TranscriptImage[] } | null; onPending?(text: string, context?: TranscriptContext[], images?: TranscriptImage[]): void; onDraftRestored?(id: string | number): void }): VNode;
|
|
466
474
|
export function SupercodeMessenger(props: MessengerProps): VNode;
|
package/messenger.mjs
CHANGED
|
@@ -744,7 +744,11 @@ function normalizeUiState(value) {
|
|
|
744
744
|
recoverable: raw.recoverable === true,
|
|
745
745
|
harnesses: Array.isArray(raw.harnesses) ? raw.harnesses.flatMap((candidate) => {
|
|
746
746
|
const item = record(candidate);
|
|
747
|
-
|
|
747
|
+
if (!item || typeof item.id !== "string") return [];
|
|
748
|
+
const startable = item.startable === true;
|
|
749
|
+
const launchModes = Array.isArray(item.launchModes) ? [...new Set(item.launchModes.filter((mode) => mode === "headless" || mode === "terminal"))] : startable ? ["headless"] : [];
|
|
750
|
+
const preferredLaunchMode = launchModes.includes(item.preferredLaunchMode) ? item.preferredLaunchMode : launchModes[0] ?? null;
|
|
751
|
+
return [{ id: item.id, label: string(item.label, harnessDisplayName(item.id)), installed: item.installed === true, startable, reason: typeof item.reason === "string" ? item.reason : null, launchModes, preferredLaunchMode }];
|
|
748
752
|
}) : [],
|
|
749
753
|
history: { sessionLimit: number(history?.sessionLimit), hasMoreSessions: history?.hasMoreSessions === true, transcriptLimit: number(history?.transcriptLimit, 120), hasEarlier: history?.hasEarlier === true },
|
|
750
754
|
savedDraft: string(raw.savedDraft),
|
|
@@ -829,7 +833,7 @@ function activitySummary(entries) {
|
|
|
829
833
|
function canContinueHere(state) {
|
|
830
834
|
if (state.mode !== "mirror" || state.canSend) return false;
|
|
831
835
|
const row = state.attached?.key ? state.sessions.find((item) => item.key === state.attached.key) : null;
|
|
832
|
-
return state.canResume && row
|
|
836
|
+
return Boolean(row) && state.canResume && row.runtimeStatus !== "running" && row.runtimeStatus !== "busy" && row.runtimeStatus !== "idle";
|
|
833
837
|
}
|
|
834
838
|
function operationLabel(operation) {
|
|
835
839
|
if (!operation) return "";
|
|
@@ -2102,7 +2106,7 @@ function SessionRow({ row, state, onOpen, now = Date.now() }) {
|
|
|
2102
2106
|
] })
|
|
2103
2107
|
] });
|
|
2104
2108
|
}
|
|
2105
|
-
function SessionList({ state, adapter, onOpen, onNew, onClose, components = {}, labels = DEFAULT_LABELS, focusKey = null, memoryKey = state.workspace || "@default" }) {
|
|
2109
|
+
function SessionList({ state, adapter, onOpen, onNew, onClose, components = {}, labels = DEFAULT_LABELS, focusKey = null, memoryKey = state.workspace || "@default", headerActions: HeaderActions }) {
|
|
2106
2110
|
const remembered = sessionListMemory.get(memoryKey) ?? { query: "", top: 0 };
|
|
2107
2111
|
const [query, setQuery] = useState4(remembered.query);
|
|
2108
2112
|
const [loadingMore, setLoadingMore] = useState4(false);
|
|
@@ -2143,6 +2147,7 @@ function SessionList({ state, adapter, onOpen, onNew, onClose, components = {},
|
|
|
2143
2147
|
" recent conversations"
|
|
2144
2148
|
] })
|
|
2145
2149
|
] }),
|
|
2150
|
+
HeaderActions ? /* @__PURE__ */ jsx7(HeaderActions, { state, adapter, value: "list" }) : null,
|
|
2146
2151
|
/* @__PURE__ */ jsx7("button", { type: "button", "data-list-focus": "new", "aria-label": labels.newChat, disabled: !state.harnesses.some((item) => item.startable), onClick: onNew, children: /* @__PURE__ */ jsx7(UiIcon, { name: "plus", size: 18 }) }),
|
|
2147
2152
|
onClose ? /* @__PURE__ */ jsx7("button", { type: "button", "aria-label": "Close", onClick: onClose, children: /* @__PURE__ */ jsx7(UiIcon, { name: "close", size: 18 }) }) : null
|
|
2148
2153
|
] }),
|
|
@@ -2381,7 +2386,7 @@ function ConversationActions({ state, adapter, actionPending, onSettings }) {
|
|
|
2381
2386
|
] }, group.label)) }) : null
|
|
2382
2387
|
] });
|
|
2383
2388
|
}
|
|
2384
|
-
function ChatHeader({ state, adapter, pendingStatus, actionPending, onBack, onNew, onClose, onSettings }) {
|
|
2389
|
+
function ChatHeader({ state, adapter, pendingStatus, actionPending, onBack, onNew, onClose, onSettings, headerActions: HeaderActions }) {
|
|
2385
2390
|
const back = useRef7(null);
|
|
2386
2391
|
const harness = state.attached?.harness ?? state.harness;
|
|
2387
2392
|
const title = state.attached ? sessionDisplayName(state.attached) : harnessDisplayName(harness) || "Agent chat";
|
|
@@ -2401,6 +2406,7 @@ function ChatHeader({ state, adapter, pendingStatus, actionPending, onBack, onNe
|
|
|
2401
2406
|
status
|
|
2402
2407
|
] })
|
|
2403
2408
|
] }),
|
|
2409
|
+
HeaderActions ? /* @__PURE__ */ jsx9(HeaderActions, { state, adapter, value: "chat" }) : null,
|
|
2404
2410
|
menu ? /* @__PURE__ */ jsx9(ConversationActions, { state, adapter, actionPending, onSettings }) : null,
|
|
2405
2411
|
/* @__PURE__ */ jsx9("button", { type: "button", "aria-label": "New chat", onClick: onNew, children: /* @__PURE__ */ jsx9(UiIcon, { name: "plus", size: 18 }) }),
|
|
2406
2412
|
onClose ? /* @__PURE__ */ jsx9("button", { type: "button", "aria-label": "Close", onClick: onClose, children: /* @__PURE__ */ jsx9(UiIcon, { name: "close", size: 18 }) }) : null
|
|
@@ -2408,6 +2414,7 @@ function ChatHeader({ state, adapter, pendingStatus, actionPending, onBack, onNe
|
|
|
2408
2414
|
}
|
|
2409
2415
|
function Chat({ state, adapter, onBack, onNew, onClose, components, slots, labels }) {
|
|
2410
2416
|
const Header = slots.header;
|
|
2417
|
+
const HeaderActions = slots.headerActions;
|
|
2411
2418
|
const memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`;
|
|
2412
2419
|
const [pending, setPendingState] = useState6(() => pendingMessageMemory.get(memoryKey) ?? null);
|
|
2413
2420
|
const [pendingAction, setPendingAction] = useState6(null);
|
|
@@ -2507,7 +2514,7 @@ function Chat({ state, adapter, onBack, onNew, onClose, components, slots, label
|
|
|
2507
2514
|
const Advisory = components.HarnessAdvisory ?? HarnessAdvisory;
|
|
2508
2515
|
const SettingsPanel = components.HarnessSettingsPanel ?? HarnessSettingsPanel;
|
|
2509
2516
|
return /* @__PURE__ */ jsxs8("section", { class: "scui-chat", children: [
|
|
2510
|
-
Header ? /* @__PURE__ */ jsx9(Header, { state: actionState, adapter: trackedAdapter, value: null }) : /* @__PURE__ */ jsx9(ChatHeader, { state: actionState, adapter: trackedAdapter, pendingStatus: pending?.status ?? null, actionPending: Boolean(action), onBack, onNew, onClose, onSettings: () => setSettings({ recommendedChange: null }) }),
|
|
2517
|
+
Header ? /* @__PURE__ */ jsx9(Header, { state: actionState, adapter: trackedAdapter, value: null }) : /* @__PURE__ */ jsx9(ChatHeader, { state: actionState, adapter: trackedAdapter, pendingStatus: pending?.status ?? null, actionPending: Boolean(action), onBack, onNew, onClose, onSettings: () => setSettings({ recommendedChange: null }), headerActions: HeaderActions }),
|
|
2511
2518
|
actionLabel ? /* @__PURE__ */ jsxs8("div", { class: "scui-operation", role: "status", children: [
|
|
2512
2519
|
/* @__PURE__ */ jsx9("i", {}),
|
|
2513
2520
|
actionLabel
|
|
@@ -2524,34 +2531,41 @@ function Chat({ state, adapter, onBack, onNew, onClose, components, slots, label
|
|
|
2524
2531
|
settings ? /* @__PURE__ */ jsx9(SettingsPanel, { state: actionState, adapter: trackedAdapter, value: state.interopSettings, recommendedChange: settings.recommendedChange, onClose: () => setSettings(null) }) : null
|
|
2525
2532
|
] });
|
|
2526
2533
|
}
|
|
2527
|
-
function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey }) {
|
|
2534
|
+
function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey, headerActions: HeaderActions }) {
|
|
2528
2535
|
const startable = state.harnesses.filter((item) => item.startable);
|
|
2529
|
-
const startableKey = startable.map((item) => item.id).join("\0");
|
|
2530
|
-
const remembered = newChatMemory.get(memoryKey) ?? { harness: startable[0]?.id ?? "", draft: "", context: [], images: [] };
|
|
2536
|
+
const startableKey = startable.map((item) => `${item.id}:${item.launchModes?.join(",")}:${item.preferredLaunchMode ?? ""}`).join("\0");
|
|
2537
|
+
const remembered = newChatMemory.get(memoryKey) ?? { harness: startable[0]?.id ?? "", draft: "", context: [], images: [], modes: {} };
|
|
2531
2538
|
const [harness, setHarness] = useState6(remembered.harness);
|
|
2532
2539
|
const [draft, setDraft] = useState6(remembered.draft);
|
|
2533
2540
|
const [context, setContext] = useState6(remembered.context);
|
|
2534
2541
|
const [images, setImages] = useState6(remembered.images ?? []);
|
|
2542
|
+
const [modes, setModes] = useState6(remembered.modes ?? {});
|
|
2535
2543
|
const [starting, setStarting] = useState6(null);
|
|
2536
2544
|
const [picking, setPicking] = useState6(false);
|
|
2537
2545
|
const [dragging, setDragging] = useState6(false);
|
|
2538
2546
|
const [pickerError, setPickerError] = useState6(null);
|
|
2539
2547
|
const startSequence = useRef7(0);
|
|
2540
2548
|
const textarea = useRef7(null);
|
|
2549
|
+
const selectedHarness = startable.find((item) => item.id === harness) ?? null;
|
|
2550
|
+
const launchModes = selectedHarness?.launchModes?.length ? selectedHarness.launchModes : ["headless"];
|
|
2551
|
+
const rememberedMode = modes[harness];
|
|
2552
|
+
const mode = launchModes.includes(rememberedMode) ? rememberedMode : launchModes.includes(selectedHarness?.preferredLaunchMode) ? selectedHarness.preferredLaunchMode : launchModes[0];
|
|
2553
|
+
const terminalAttachments = mode === "terminal" && (context.length > 0 || images.length > 0);
|
|
2554
|
+
const remember = (overrides = {}) => boundedSet(newChatMemory, memoryKey, { harness, draft, context, images, modes, ...overrides });
|
|
2541
2555
|
useAutosizeTextarea(textarea, draft);
|
|
2542
2556
|
useEffect8(() => {
|
|
2543
2557
|
if (startable.some((item) => item.id === harness)) return;
|
|
2544
2558
|
const next = startable[0]?.id ?? "";
|
|
2545
2559
|
setHarness(next);
|
|
2546
|
-
|
|
2560
|
+
remember({ harness: next });
|
|
2547
2561
|
}, [harness, startableKey]);
|
|
2548
2562
|
useEffect8(() => {
|
|
2549
|
-
if (!starting) return;
|
|
2563
|
+
if (!starting || starting.mode === "terminal") return;
|
|
2550
2564
|
const sessionChanged = (state.attached?.key ?? null) !== starting.attachedKey;
|
|
2551
2565
|
const beganWorking = !starting.busy && state.busy;
|
|
2552
2566
|
if (!sessionChanged && !beganWorking) return;
|
|
2553
2567
|
newChatMemory.delete(memoryKey);
|
|
2554
|
-
onStarted();
|
|
2568
|
+
onStarted("headless");
|
|
2555
2569
|
}, [memoryKey, onStarted, starting, state.attached?.key, state.busy]);
|
|
2556
2570
|
useEffect8(() => {
|
|
2557
2571
|
if (starting && state.error !== starting.initialError && !state.busy && !state.operation) setStarting(null);
|
|
@@ -2560,7 +2574,7 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2560
2574
|
textarea.current?.focus({ preventScroll: true });
|
|
2561
2575
|
}, []);
|
|
2562
2576
|
const pickContext = () => {
|
|
2563
|
-
if (!adapter.pickContext || picking || context.length >= MAX_CONTEXT_ITEMS && images.length >= MAX_IMAGE_ITEMS) return;
|
|
2577
|
+
if (mode === "terminal" || !adapter.pickContext || picking || context.length >= MAX_CONTEXT_ITEMS && images.length >= MAX_IMAGE_ITEMS) return;
|
|
2564
2578
|
setPicking(true);
|
|
2565
2579
|
setPickerError(null);
|
|
2566
2580
|
Promise.resolve().then(() => adapter.pickContext()).then((picked) => {
|
|
@@ -2569,12 +2583,12 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2569
2583
|
if (images.length + attachments.images.length > MAX_IMAGE_ITEMS) throw new Error(`Attach at most ${MAX_IMAGE_ITEMS} images.`);
|
|
2570
2584
|
setContext((current) => {
|
|
2571
2585
|
const next = mergeContext(current, attachments.context);
|
|
2572
|
-
|
|
2586
|
+
remember({ context: next });
|
|
2573
2587
|
return next;
|
|
2574
2588
|
});
|
|
2575
2589
|
setImages((current) => {
|
|
2576
2590
|
const next = mergeImages(current, attachments.images);
|
|
2577
|
-
|
|
2591
|
+
remember({ images: next });
|
|
2578
2592
|
return next;
|
|
2579
2593
|
});
|
|
2580
2594
|
}).catch((error) => setPickerError(error instanceof Error ? error.message : "Could not attach context.")).finally(() => setPicking(false));
|
|
@@ -2582,6 +2596,10 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2582
2596
|
const addImageFiles = (value, source) => {
|
|
2583
2597
|
const allFiles = Array.from(value ?? []);
|
|
2584
2598
|
if (!allFiles.length) return false;
|
|
2599
|
+
if (mode === "terminal") {
|
|
2600
|
+
setPickerError("Terminal starts currently accept text only. Choose Chat to attach context or images.");
|
|
2601
|
+
return true;
|
|
2602
|
+
}
|
|
2585
2603
|
const files = allFiles.filter((file) => file.type.startsWith("image/"));
|
|
2586
2604
|
if (files.length !== allFiles.length) {
|
|
2587
2605
|
setPickerError("Drop or paste PNG, JPEG, GIF, or WebP images.");
|
|
@@ -2595,7 +2613,7 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2595
2613
|
setPickerError(null);
|
|
2596
2614
|
imageAttachmentsFromFiles(files).then((picked) => setImages((current) => {
|
|
2597
2615
|
const next = mergeImages(current, picked);
|
|
2598
|
-
|
|
2616
|
+
remember({ images: next });
|
|
2599
2617
|
return next;
|
|
2600
2618
|
}), (error) => setPickerError(error instanceof Error ? error.message : `Could not ${source} image.`)).finally(() => setPicking(false));
|
|
2601
2619
|
return true;
|
|
@@ -2609,12 +2627,17 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2609
2627
|
};
|
|
2610
2628
|
const send = () => {
|
|
2611
2629
|
const text = draft.trim();
|
|
2612
|
-
if (!text && !images.length || !harness || starting) return;
|
|
2630
|
+
if (!text && !images.length || !harness || starting || terminalAttachments || mode === "terminal" && !text) return;
|
|
2613
2631
|
const id = startSequence.current + 1;
|
|
2614
2632
|
startSequence.current = id;
|
|
2615
|
-
setStarting({ id, attachedKey: state.attached?.key ?? null, busy: state.busy, initialError: state.error });
|
|
2616
|
-
const result = adapter.onIntent({ action: "new", harness, text, ...context.length ? { context } : {}, ...images.length ? { images } : {} });
|
|
2617
|
-
if (
|
|
2633
|
+
setStarting({ id, mode, attachedKey: state.attached?.key ?? null, busy: state.busy, initialError: state.error });
|
|
2634
|
+
const result = adapter.onIntent({ action: "new", harness, mode, text, ...context.length ? { context } : {}, ...images.length ? { images } : {} });
|
|
2635
|
+
if (mode === "terminal") {
|
|
2636
|
+
Promise.resolve(result).then(() => {
|
|
2637
|
+
newChatMemory.delete(memoryKey);
|
|
2638
|
+
onStarted("terminal");
|
|
2639
|
+
}, () => setStarting((current) => current?.id === id ? null : current));
|
|
2640
|
+
} else if (result && typeof result.then === "function") {
|
|
2618
2641
|
Promise.resolve(result).catch(() => setStarting((current) => current?.id === id ? null : current));
|
|
2619
2642
|
}
|
|
2620
2643
|
};
|
|
@@ -2625,11 +2648,12 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2625
2648
|
/* @__PURE__ */ jsx9("strong", { children: labels.newChat }),
|
|
2626
2649
|
/* @__PURE__ */ jsx9("small", { children: "No session is created until you send" })
|
|
2627
2650
|
] }),
|
|
2651
|
+
HeaderActions ? /* @__PURE__ */ jsx9(HeaderActions, { state, adapter, value: "new" }) : null,
|
|
2628
2652
|
onClose ? /* @__PURE__ */ jsx9("button", { type: "button", "aria-label": "Close", onClick: onClose, children: /* @__PURE__ */ jsx9(UiIcon, { name: "close", size: 18 }) }) : null
|
|
2629
2653
|
] }),
|
|
2630
2654
|
starting ? /* @__PURE__ */ jsxs8("div", { class: "scui-operation", role: "status", children: [
|
|
2631
2655
|
/* @__PURE__ */ jsx9("i", {}),
|
|
2632
|
-
operationLabel("start")
|
|
2656
|
+
mode === "terminal" ? "Opening terminal\u2026" : operationLabel("start")
|
|
2633
2657
|
] }) : null,
|
|
2634
2658
|
/* @__PURE__ */ jsxs8("div", { class: "scui-new", children: [
|
|
2635
2659
|
/* @__PURE__ */ jsx9("span", { "aria-hidden": "true", children: "\u2726" }),
|
|
@@ -2643,23 +2667,38 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2643
2667
|
/* @__PURE__ */ jsx9("select", { value: harness, disabled: Boolean(starting), onChange: (event) => {
|
|
2644
2668
|
const value = event.currentTarget.value;
|
|
2645
2669
|
setHarness(value);
|
|
2646
|
-
|
|
2670
|
+
remember({ harness: value });
|
|
2647
2671
|
}, children: state.harnesses.map((item) => /* @__PURE__ */ jsxs8("option", { value: item.id, disabled: !item.startable, children: [
|
|
2648
2672
|
item.label,
|
|
2649
2673
|
item.startable ? "" : " \xB7 unavailable"
|
|
2650
2674
|
] }, item.id)) })
|
|
2651
2675
|
] }),
|
|
2676
|
+
launchModes.length > 1 ? /* @__PURE__ */ jsxs8("fieldset", { class: "scui-launch-modes", disabled: Boolean(starting), children: [
|
|
2677
|
+
/* @__PURE__ */ jsx9("legend", { children: "Run as" }),
|
|
2678
|
+
launchModes.map((item) => /* @__PURE__ */ jsxs8("label", { children: [
|
|
2679
|
+
/* @__PURE__ */ jsx9("input", { type: "radio", name: `launch-mode-${memoryKey}`, value: item, checked: mode === item, onChange: () => {
|
|
2680
|
+
const next = { ...modes, [harness]: item };
|
|
2681
|
+
setModes(next);
|
|
2682
|
+
setPickerError(null);
|
|
2683
|
+
remember({ modes: next });
|
|
2684
|
+
} }),
|
|
2685
|
+
/* @__PURE__ */ jsxs8("span", { children: [
|
|
2686
|
+
/* @__PURE__ */ jsx9("strong", { children: item === "terminal" ? "Terminal" : "Chat" }),
|
|
2687
|
+
/* @__PURE__ */ jsx9("small", { children: item === "terminal" ? "Interactive tmux session" : "Managed here" })
|
|
2688
|
+
] })
|
|
2689
|
+
] }, item))
|
|
2690
|
+
] }) : null,
|
|
2652
2691
|
/* @__PURE__ */ jsx9(ImageTray, { items: images, onRemove: (index) => setImages((items) => {
|
|
2653
2692
|
const next = items.filter((_, itemIndex) => itemIndex !== index);
|
|
2654
|
-
|
|
2693
|
+
remember({ images: next });
|
|
2655
2694
|
return next;
|
|
2656
2695
|
}) }),
|
|
2657
2696
|
/* @__PURE__ */ jsx9(ContextTray, { items: context, onRemove: (index) => setContext((items) => {
|
|
2658
2697
|
const next = items.filter((_, itemIndex) => itemIndex !== index);
|
|
2659
|
-
|
|
2698
|
+
remember({ context: next });
|
|
2660
2699
|
return next;
|
|
2661
2700
|
}) }),
|
|
2662
|
-
pickerError ? /* @__PURE__ */ jsx9("small", { class: "scui-context-error", role: "alert", children: pickerError }) : null,
|
|
2701
|
+
terminalAttachments ? /* @__PURE__ */ jsx9("small", { class: "scui-context-error", role: "alert", children: "Terminal starts currently accept text only. Remove attachments or choose Chat." }) : pickerError ? /* @__PURE__ */ jsx9("small", { class: "scui-context-error", role: "alert", children: pickerError }) : null,
|
|
2663
2702
|
/* @__PURE__ */ jsxs8("div", { class: `scui-envelope${dragging ? " scui-drop-target" : ""}`, onDragEnter: (event) => {
|
|
2664
2703
|
if (Array.from(event.dataTransfer?.types ?? []).includes("Files")) {
|
|
2665
2704
|
event.preventDefault();
|
|
@@ -2670,18 +2709,18 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2670
2709
|
}, onDragLeave: (event) => {
|
|
2671
2710
|
if (!event.currentTarget.contains(event.relatedTarget)) setDragging(false);
|
|
2672
2711
|
}, onDrop: dropImages, children: [
|
|
2673
|
-
adapter.pickContext ? /* @__PURE__ */ jsx9("button", { class: "scui-attach", type: "button", "aria-label": "Attach files or images", disabled: picking || context.length >= MAX_CONTEXT_ITEMS && images.length >= MAX_IMAGE_ITEMS || Boolean(starting), onClick: pickContext, children: picking ? /* @__PURE__ */ jsx9("i", { class: "scui-control-spinner" }) : /* @__PURE__ */ jsx9(UiIcon, { name: "attach", size: 17 }) }) : null,
|
|
2712
|
+
adapter.pickContext ? /* @__PURE__ */ jsx9("button", { class: "scui-attach", type: "button", "aria-label": "Attach files or images", disabled: mode === "terminal" || picking || context.length >= MAX_CONTEXT_ITEMS && images.length >= MAX_IMAGE_ITEMS || Boolean(starting), onClick: pickContext, children: picking ? /* @__PURE__ */ jsx9("i", { class: "scui-control-spinner" }) : /* @__PURE__ */ jsx9(UiIcon, { name: "attach", size: 17 }) }) : null,
|
|
2674
2713
|
/* @__PURE__ */ jsx9("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), onPaste: pasteImages, onInput: (event) => {
|
|
2675
2714
|
const value = event.currentTarget.value;
|
|
2676
2715
|
setDraft(value);
|
|
2677
|
-
|
|
2716
|
+
remember({ draft: value });
|
|
2678
2717
|
}, onKeyDown: (event) => {
|
|
2679
2718
|
if (isSendKey(event)) {
|
|
2680
2719
|
event.preventDefault();
|
|
2681
2720
|
send();
|
|
2682
2721
|
}
|
|
2683
2722
|
} }),
|
|
2684
|
-
/* @__PURE__ */ jsx9("span", { children: /* @__PURE__ */ jsx9("button", { type: "button", class: "scui-send", "aria-label": "Start chat", disabled: !draft.trim() && !images.length || !harness || Boolean(starting), onClick: send, children: starting ? /* @__PURE__ */ jsx9("i", { class: "scui-control-spinner" }) : /* @__PURE__ */ jsx9(UiIcon, { name: "send", size: 17 }) }) })
|
|
2723
|
+
/* @__PURE__ */ jsx9("span", { children: /* @__PURE__ */ jsx9("button", { type: "button", class: "scui-send", "aria-label": mode === "terminal" ? "Start terminal session" : "Start chat", disabled: !draft.trim() && !images.length || !harness || Boolean(starting) || terminalAttachments || mode === "terminal" && !draft.trim(), onClick: send, children: starting ? /* @__PURE__ */ jsx9("i", { class: "scui-control-spinner" }) : /* @__PURE__ */ jsx9(UiIcon, { name: "send", size: 17 }) }) })
|
|
2685
2724
|
] })
|
|
2686
2725
|
] })
|
|
2687
2726
|
] });
|
|
@@ -2713,12 +2752,13 @@ function SupercodeMessenger({ state: stateInput, adapter, class: className = "",
|
|
|
2713
2752
|
};
|
|
2714
2753
|
const close = () => adapter.onClose?.();
|
|
2715
2754
|
const Footer = slots.footer;
|
|
2755
|
+
const HeaderActions = slots.headerActions;
|
|
2716
2756
|
return /* @__PURE__ */ jsxs8("main", { class: `scui-root ${className}`, "data-view": view, "data-mode": state.mode, "aria-label": "Supercode messenger", children: [
|
|
2717
2757
|
view === "list" ? /* @__PURE__ */ jsx9(SessionList, { state, adapter, focusKey: listFocus, onOpen: open, onNew: () => {
|
|
2718
2758
|
setListFocus("@new");
|
|
2719
2759
|
setView("new");
|
|
2720
|
-
}, onClose: adapter.onClose ? close : void 0, components, labels: copy, memoryKey }) : null,
|
|
2721
|
-
view === "new" ? /* @__PURE__ */ jsx9(NewChat, { state, adapter, onBack: () => setView("list"), onClose: adapter.onClose ? close : void 0, onStarted: () => setView("chat"), labels: copy, memoryKey }) : null,
|
|
2760
|
+
}, onClose: adapter.onClose ? close : void 0, components, labels: copy, memoryKey, headerActions: HeaderActions }) : null,
|
|
2761
|
+
view === "new" ? /* @__PURE__ */ jsx9(NewChat, { state, adapter, onBack: () => setView("list"), onClose: adapter.onClose ? close : void 0, onStarted: (mode) => setView(mode === "terminal" ? "list" : "chat"), labels: copy, memoryKey, headerActions: HeaderActions }) : null,
|
|
2722
2762
|
view === "chat" ? /* @__PURE__ */ jsx9(Chat, { state, adapter, onBack: () => {
|
|
2723
2763
|
setListFocus(state.attached?.key ?? listFocus);
|
|
2724
2764
|
setView("list");
|
package/package.json
CHANGED
package/sessions.mjs
CHANGED
|
@@ -340,7 +340,7 @@ function SessionRow({ row, state, onOpen, now = Date.now() }) {
|
|
|
340
340
|
] })
|
|
341
341
|
] });
|
|
342
342
|
}
|
|
343
|
-
function SessionList({ state, adapter, onOpen, onNew, onClose, components = {}, labels = DEFAULT_LABELS, focusKey = null, memoryKey = state.workspace || "@default" }) {
|
|
343
|
+
function SessionList({ state, adapter, onOpen, onNew, onClose, components = {}, labels = DEFAULT_LABELS, focusKey = null, memoryKey = state.workspace || "@default", headerActions: HeaderActions }) {
|
|
344
344
|
const remembered = sessionListMemory.get(memoryKey) ?? { query: "", top: 0 };
|
|
345
345
|
const [query, setQuery] = useState3(remembered.query);
|
|
346
346
|
const [loadingMore, setLoadingMore] = useState3(false);
|
|
@@ -381,6 +381,7 @@ function SessionList({ state, adapter, onOpen, onNew, onClose, components = {},
|
|
|
381
381
|
" recent conversations"
|
|
382
382
|
] })
|
|
383
383
|
] }),
|
|
384
|
+
HeaderActions ? /* @__PURE__ */ jsx6(HeaderActions, { state, adapter, value: "list" }) : null,
|
|
384
385
|
/* @__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 }) }),
|
|
385
386
|
onClose ? /* @__PURE__ */ jsx6("button", { type: "button", "aria-label": "Close", onClick: onClose, children: /* @__PURE__ */ jsx6(UiIcon, { name: "close", size: 18 }) }) : null
|
|
386
387
|
] }),
|
package/styles.css
CHANGED
|
@@ -191,6 +191,7 @@
|
|
|
191
191
|
.scui-send,.scui-stop { display:grid; width:29px; height:29px; padding:0; place-items:center; border:0; border-radius:8px; background:var(--scui-accent); color:#fff; cursor:pointer }.scui-stop { background:var(--scui-danger) }.scui-send:disabled,.scui-stop:disabled { opacity:.4; cursor:default }.scui-control-spinner { box-sizing:border-box; width:13px; height:13px; border:1.5px solid currentColor; border-right-color:transparent; border-radius:50%; animation:scui-spin .75s linear infinite }
|
|
192
192
|
.scui-queue { display:grid; gap:4px; max-height:85px; margin-bottom:6px; overflow:auto; font-size:10.5px }.scui-queue > span { display:flex; gap:6px; padding:4px 6px; border-radius:5px; background:var(--scui-fill) }.scui-queue > span > span { display:grid; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap }.scui-queue small { color:var(--scui-muted) }.scui-queue > span button { margin-left:auto; border:0; background:transparent }
|
|
193
193
|
.scui-harness-picker { display:flex; align-items:center; gap:7px; margin-bottom:7px }.scui-harness-picker select { margin-left:auto; max-width:55%; padding:4px; border:1px solid var(--scui-border); border-radius:6px; background:var(--scui-bg); color:var(--scui-fg) }
|
|
194
|
+
.scui-launch-modes { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:5px; margin:0 0 7px; padding:0; border:0 }.scui-launch-modes legend { position:absolute; overflow:hidden; width:1px; height:1px; clip:rect(0 0 0 0); white-space:nowrap }.scui-launch-modes label { position:relative; min-width:0; cursor:pointer }.scui-launch-modes input { position:absolute; opacity:0; pointer-events:none }.scui-launch-modes label > span { display:flex; min-height:42px; flex-direction:column; justify-content:center; padding:6px 9px; border:1px solid var(--scui-border); border-radius:7px; background:var(--scui-bg); transition:border-color .14s ease,background .14s ease }.scui-launch-modes strong { font-size:11px; font-weight:650 }.scui-launch-modes small { overflow:hidden; color:var(--scui-muted); font-size:9px; text-overflow:ellipsis; white-space:nowrap }.scui-launch-modes input:checked + span { border-color:color-mix(in srgb,var(--scui-accent) 58%,var(--scui-border)); background:color-mix(in srgb,var(--scui-accent) 8%,var(--scui-bg)) }.scui-launch-modes input:focus-visible + span { outline:2px solid var(--scui-accent); outline-offset:1px }.scui-launch-modes:disabled label { cursor:default; opacity:.62 }
|
|
194
195
|
.scui-new { display:grid; justify-items:center; gap:5px; margin:auto; padding:20px; color:var(--scui-muted); text-align:center }.scui-new > span { color:var(--scui-accent); font-size:28px }.scui-new strong { color:var(--scui-fg) }
|
|
195
196
|
.scui-opening { position:absolute; z-index:30; inset:49px 0 0; display:flex; align-items:center; justify-content:center; gap:10px; padding:20px; background:var(--scui-bg) }.scui-opening > span { display:grid }.scui-opening small { color:var(--scui-muted) }.scui-opening > i { width:16px; height:16px; border:2px solid var(--scui-border); border-top-color:var(--scui-accent); border-radius:50%; animation:scui-spin .8s linear infinite }
|
|
196
197
|
|