@volter-ai-dev/supercode-ui 0.1.41 → 0.1.43
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 +13 -4
- package/components.d.ts +1 -0
- package/components.mjs +139 -22
- package/composer.mjs +22 -1
- package/embed.mjs +138 -22
- package/index.d.ts +19 -1
- package/messenger.mjs +138 -22
- package/package.json +1 -1
- package/react/components.mjs +139 -22
- package/react/composer.mjs +22 -1
- package/react/index.d.ts +8 -1
- package/react/messenger.mjs +138 -22
- package/react/settings.d.ts +2 -2
- package/react/settings.mjs +74 -0
- package/settings.d.ts +2 -2
- package/settings.mjs +74 -0
- package/styles.css +6 -1
package/embed.mjs
CHANGED
|
@@ -1283,7 +1283,7 @@ function ContinuationBar({ state, adapter, labels = DEFAULT_LABELS }) {
|
|
|
1283
1283
|
] })
|
|
1284
1284
|
] });
|
|
1285
1285
|
}
|
|
1286
|
-
function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`, pendingStatus = null, restoreDraft = null, contextCandidates = [], components = {}, onPending, onDraftRestored }) {
|
|
1286
|
+
function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`, pendingStatus = null, restoreDraft = null, command = null, contextCandidates = [], components = {}, onPending, onDraftRestored }) {
|
|
1287
1287
|
const remembered = composerMemory.get(memoryKey) ?? { draft: state.savedDraft, context: [], images: [], queue: [] };
|
|
1288
1288
|
const [draft, setDraft] = useState2(remembered.draft);
|
|
1289
1289
|
const [context, setContext] = useState2(remembered.context ?? []);
|
|
@@ -1295,6 +1295,7 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
|
|
|
1295
1295
|
const [dragging, setDragging] = useState2(false);
|
|
1296
1296
|
const [pickerError, setPickerError] = useState2(null);
|
|
1297
1297
|
const textarea = useRef2(null);
|
|
1298
|
+
const lastCommand = useRef2(null);
|
|
1298
1299
|
useAutosizeTextarea(textarea, draft);
|
|
1299
1300
|
const remember = (nextDraft, nextContext, nextImages, nextQueue) => boundedSet(composerMemory, memoryKey, { draft: nextDraft, context: nextContext, images: nextImages, queue: nextQueue });
|
|
1300
1301
|
useEffect2(() => {
|
|
@@ -1336,6 +1337,26 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
|
|
|
1336
1337
|
textarea.current?.focus({ preventScroll: true });
|
|
1337
1338
|
onDraftRestored?.(restoreDraft.id);
|
|
1338
1339
|
}, [onDraftRestored, restoreDraft?.id]);
|
|
1340
|
+
useEffect2(() => {
|
|
1341
|
+
if (!command || command.id === lastCommand.current) return;
|
|
1342
|
+
lastCommand.current = command.id;
|
|
1343
|
+
if (command.action === "attach") {
|
|
1344
|
+
try {
|
|
1345
|
+
const attachments = partitionAttachments(command.attachments);
|
|
1346
|
+
if (context.length + attachments.context.length > MAX_CONTEXT_ITEMS) throw new Error(`Attach at most ${MAX_CONTEXT_ITEMS} text items.`);
|
|
1347
|
+
if (images.length + attachments.images.length > MAX_IMAGE_ITEMS) throw new Error(`Attach at most ${MAX_IMAGE_ITEMS} images.`);
|
|
1348
|
+
const nextContext = mergeContext(context, attachments.context);
|
|
1349
|
+
const nextImages = mergeImages(images, attachments.images);
|
|
1350
|
+
setContext(nextContext);
|
|
1351
|
+
setImages(nextImages);
|
|
1352
|
+
setPickerError(null);
|
|
1353
|
+
remember(draft, nextContext, nextImages, queue);
|
|
1354
|
+
} catch (error) {
|
|
1355
|
+
setPickerError(error instanceof Error ? error.message : "Could not attach context.");
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
textarea.current?.focus({ preventScroll: true });
|
|
1359
|
+
}, [command?.id]);
|
|
1339
1360
|
useEffect2(() => {
|
|
1340
1361
|
const timer = setTimeout(() => adapter.onIntent({ action: "draft", text: draft }), 250);
|
|
1341
1362
|
return () => clearTimeout(timer);
|
|
@@ -2397,6 +2418,79 @@ function HarnessReadiness({ harnesses, adapter }) {
|
|
|
2397
2418
|
] }, item.id)) })
|
|
2398
2419
|
] });
|
|
2399
2420
|
}
|
|
2421
|
+
function HarnessPicker({ harnesses, value, disabled = false, adapter, onChange, logo: Logo = HarnessLogo }) {
|
|
2422
|
+
const [open, setOpen] = useState5(false);
|
|
2423
|
+
const menuId = useId2();
|
|
2424
|
+
const root = useRef6(null);
|
|
2425
|
+
const trigger = useRef6(null);
|
|
2426
|
+
const panel = useRef6(null);
|
|
2427
|
+
const available = harnesses.filter((item) => item.startable);
|
|
2428
|
+
const unavailable = harnesses.filter((item) => !item.startable);
|
|
2429
|
+
const selected = available.find((item) => item.id === value) ?? available[0] ?? null;
|
|
2430
|
+
const close = () => {
|
|
2431
|
+
setOpen(false);
|
|
2432
|
+
trigger.current?.focus({ preventScroll: true });
|
|
2433
|
+
};
|
|
2434
|
+
useEffect7(() => {
|
|
2435
|
+
if (!open) return;
|
|
2436
|
+
panel.current?.querySelector('[aria-selected="true"], [role="option"]')?.focus({ preventScroll: true });
|
|
2437
|
+
const dismiss = (event) => {
|
|
2438
|
+
if (event.type === "keydown" && event.key === "Escape") {
|
|
2439
|
+
event.preventDefault();
|
|
2440
|
+
close();
|
|
2441
|
+
} else if (event.type === "pointerdown" && !root.current?.contains(event.target)) {
|
|
2442
|
+
setOpen(false);
|
|
2443
|
+
}
|
|
2444
|
+
};
|
|
2445
|
+
document.addEventListener("keydown", dismiss);
|
|
2446
|
+
document.addEventListener("pointerdown", dismiss, true);
|
|
2447
|
+
return () => {
|
|
2448
|
+
document.removeEventListener("keydown", dismiss);
|
|
2449
|
+
document.removeEventListener("pointerdown", dismiss, true);
|
|
2450
|
+
};
|
|
2451
|
+
}, [open]);
|
|
2452
|
+
const navigate = (event) => {
|
|
2453
|
+
if (!["ArrowDown", "ArrowUp", "Home", "End"].includes(event.key)) return;
|
|
2454
|
+
const items = [...panel.current.querySelectorAll('[role="option"]')];
|
|
2455
|
+
if (!items.length) return;
|
|
2456
|
+
event.preventDefault();
|
|
2457
|
+
const current = items.indexOf(document.activeElement);
|
|
2458
|
+
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;
|
|
2459
|
+
items[index].focus({ preventScroll: true });
|
|
2460
|
+
};
|
|
2461
|
+
const select = (id) => {
|
|
2462
|
+
onChange(id);
|
|
2463
|
+
close();
|
|
2464
|
+
};
|
|
2465
|
+
return /* @__PURE__ */ jsxs7("div", { className: "scui-harness-menu", ref: root, onBlur: (event) => {
|
|
2466
|
+
if (event.relatedTarget && !event.currentTarget.contains(event.relatedTarget)) setOpen(false);
|
|
2467
|
+
}, children: [
|
|
2468
|
+
/* @__PURE__ */ jsxs7("button", { ref: trigger, className: "scui-harness-trigger", type: "button", disabled: disabled || !selected, "aria-label": selected ? `Coding harness: ${selected.label}` : "No coding harness available", "aria-haspopup": "dialog", "aria-expanded": open, "aria-controls": open ? menuId : void 0, onClick: () => setOpen((current) => !current), children: [
|
|
2469
|
+
selected ? /* @__PURE__ */ jsx8(Logo, { id: selected.id, size: 20 }) : null,
|
|
2470
|
+
/* @__PURE__ */ jsx8("strong", { children: selected?.label ?? "No harness available" }),
|
|
2471
|
+
/* @__PURE__ */ jsx8(UiIcon, { name: "chevron", size: 13 })
|
|
2472
|
+
] }),
|
|
2473
|
+
open ? /* @__PURE__ */ jsxs7("div", { ref: panel, id: menuId, className: "scui-harness-popover", role: "dialog", "aria-label": "Choose coding harness", onKeyDown: navigate, children: [
|
|
2474
|
+
/* @__PURE__ */ jsx8("strong", { className: "scui-harness-popover-title", children: "Choose coding harness" }),
|
|
2475
|
+
/* @__PURE__ */ jsx8("div", { className: "scui-harness-options", role: "listbox", "aria-label": "Available coding harnesses", children: available.map((item) => /* @__PURE__ */ jsxs7("button", { type: "button", role: "option", "aria-selected": item.id === selected?.id, onClick: () => select(item.id), children: [
|
|
2476
|
+
/* @__PURE__ */ jsx8(Logo, { id: item.id, size: 24 }),
|
|
2477
|
+
/* @__PURE__ */ jsx8("strong", { children: item.label }),
|
|
2478
|
+
/* @__PURE__ */ jsx8("span", { children: item.id === selected?.id ? /* @__PURE__ */ jsx8(UiIcon, { name: "check", size: 15 }) : null })
|
|
2479
|
+
] }, item.id)) }),
|
|
2480
|
+
unavailable.length ? /* @__PURE__ */ jsxs7("details", { className: "scui-harness-manage", children: [
|
|
2481
|
+
/* @__PURE__ */ jsx8("summary", { children: "Manage additional harnesses" }),
|
|
2482
|
+
/* @__PURE__ */ jsx8("div", { children: unavailable.map((item) => /* @__PURE__ */ jsxs7("section", { children: [
|
|
2483
|
+
/* @__PURE__ */ jsx8(Logo, { id: item.id, size: 24 }),
|
|
2484
|
+
/* @__PURE__ */ jsxs7("span", { children: [
|
|
2485
|
+
/* @__PURE__ */ jsx8("strong", { children: item.label }),
|
|
2486
|
+
/* @__PURE__ */ jsx8("small", { children: item.reason || (item.auth === "required" ? "Authentication required" : "Unavailable") })
|
|
2487
|
+
] }),
|
|
2488
|
+
item.repair ? /* @__PURE__ */ jsx8("button", { type: "button", onClick: () => adapter?.copyText?.(item.repair), children: "Copy fix" }) : null
|
|
2489
|
+
] }, item.id)) })
|
|
2490
|
+
] }) : null
|
|
2491
|
+
] }) : null
|
|
2492
|
+
] });
|
|
2493
|
+
}
|
|
2400
2494
|
|
|
2401
2495
|
// src/messenger.jsx
|
|
2402
2496
|
import { jsx as jsx9, jsxs as jsxs8 } from "preact/jsx-runtime";
|
|
@@ -2543,7 +2637,7 @@ function ChatHeader({ state, adapter, pendingStatus, actionPending, onBack, onNe
|
|
|
2543
2637
|
onClose ? /* @__PURE__ */ jsx9("button", { type: "button", "aria-label": "Close", onClick: onClose, children: /* @__PURE__ */ jsx9(UiIcon, { name: "close", size: 18 }) }) : null
|
|
2544
2638
|
] });
|
|
2545
2639
|
}
|
|
2546
|
-
function Chat({ state, adapter, onBack, onNew, onClose, components, slots, labels, contextCandidates, navigation }) {
|
|
2640
|
+
function Chat({ state, adapter, onBack, onNew, onClose, components, slots, labels, contextCandidates, navigation, composerCommand }) {
|
|
2547
2641
|
const Header = slots.header;
|
|
2548
2642
|
const HeaderActions = slots.headerActions;
|
|
2549
2643
|
const memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`;
|
|
@@ -2670,11 +2764,11 @@ function Chat({ state, adapter, onBack, onNew, onClose, components, slots, label
|
|
|
2670
2764
|
/* @__PURE__ */ jsx9(Conversation, { state: actionState, adapter: trackedAdapter, components, slots, memoryKey, pending: pendingMessage, unreadAfterMessages }, memoryKey),
|
|
2671
2765
|
/* @__PURE__ */ jsx9(ContinuationBar, { state: actionState, adapter: trackedAdapter, labels }),
|
|
2672
2766
|
/* @__PURE__ */ jsx9(Advisory, { state: actionState, adapter: trackedAdapter, value: state.interopSettings?.advisories[0] ?? null, onReview: (recommendedChange) => setSettings({ recommendedChange }) }),
|
|
2673
|
-
state.mode !== "mirror" || state.canSend ? /* @__PURE__ */ jsx9(Composer, { state: actionState, adapter: trackedAdapter, labels, memoryKey, pendingStatus: pending?.status ?? null, restoreDraft, contextCandidates, components, onDraftRestored: (id) => setRestoreDraft((value) => value?.id === id ? null : value), onPending: beginPending }, memoryKey) : null,
|
|
2767
|
+
state.mode !== "mirror" || state.canSend ? /* @__PURE__ */ jsx9(Composer, { state: actionState, adapter: trackedAdapter, labels, memoryKey, pendingStatus: pending?.status ?? null, restoreDraft, command: composerCommand, contextCandidates, components, onDraftRestored: (id) => setRestoreDraft((value) => value?.id === id ? null : value), onPending: beginPending }, memoryKey) : null,
|
|
2674
2768
|
settings ? /* @__PURE__ */ jsx9(SettingsPanel, { state: actionState, adapter: trackedAdapter, value: state.interopSettings, recommendedChange: settings.recommendedChange, onClose: () => setSettings(null) }) : null
|
|
2675
2769
|
] });
|
|
2676
2770
|
}
|
|
2677
|
-
function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey, headerActions: HeaderActions, contextCandidates, components, navigation }) {
|
|
2771
|
+
function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey, headerActions: HeaderActions, contextCandidates, components, navigation, composerCommand }) {
|
|
2678
2772
|
const startable = state.harnesses.filter((item) => item.startable);
|
|
2679
2773
|
const startableKey = startable.map((item) => `${item.id}:${item.launchModes?.join(",")}:${item.preferredLaunchMode ?? ""}`).join("\0");
|
|
2680
2774
|
const remembered = newChatMemory.get(memoryKey) ?? { harness: startable[0]?.id ?? "", draft: "", context: [], images: [], modes: {} };
|
|
@@ -2688,8 +2782,10 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2688
2782
|
const [dragging, setDragging] = useState6(false);
|
|
2689
2783
|
const [pickerError, setPickerError] = useState6(null);
|
|
2690
2784
|
const startSequence = useRef7(0);
|
|
2785
|
+
const lastComposerCommand = useRef7(null);
|
|
2691
2786
|
const textarea = useRef7(null);
|
|
2692
2787
|
const selectedHarness = startable.find((item) => item.id === harness) ?? null;
|
|
2788
|
+
const Picker = components.HarnessPicker ?? HarnessPicker;
|
|
2693
2789
|
const Readiness = components.HarnessReadiness ?? HarnessReadiness;
|
|
2694
2790
|
const launchModes = selectedHarness?.launchModes?.length ? selectedHarness.launchModes : ["headless"];
|
|
2695
2791
|
const rememberedMode = modes[harness];
|
|
@@ -2730,6 +2826,32 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2730
2826
|
remember({ harness: nextHarness, draft: nextDraft, context: nextContext, images: nextImages });
|
|
2731
2827
|
textarea.current?.focus({ preventScroll: true });
|
|
2732
2828
|
}, [navigation?.id]);
|
|
2829
|
+
useEffect8(() => {
|
|
2830
|
+
if (!composerCommand || composerCommand.id === lastComposerCommand.current) return;
|
|
2831
|
+
lastComposerCommand.current = composerCommand.id;
|
|
2832
|
+
if (composerCommand.action === "attach") {
|
|
2833
|
+
if (mode === "terminal" && !launchModes.includes("headless")) {
|
|
2834
|
+
setPickerError("Terminal starts currently accept text only. Choose Chat to attach context or images.");
|
|
2835
|
+
} else {
|
|
2836
|
+
try {
|
|
2837
|
+
const attachments = partitionAttachments(composerCommand.attachments);
|
|
2838
|
+
if (context.length + attachments.context.length > MAX_CONTEXT_ITEMS) throw new Error(`Attach at most ${MAX_CONTEXT_ITEMS} text items.`);
|
|
2839
|
+
if (images.length + attachments.images.length > MAX_IMAGE_ITEMS) throw new Error(`Attach at most ${MAX_IMAGE_ITEMS} images.`);
|
|
2840
|
+
const nextContext = mergeContext(context, attachments.context);
|
|
2841
|
+
const nextImages = mergeImages(images, attachments.images);
|
|
2842
|
+
const nextModes = mode === "terminal" ? { ...modes, [harness]: "headless" } : modes;
|
|
2843
|
+
if (nextModes !== modes) setModes(nextModes);
|
|
2844
|
+
setContext(nextContext);
|
|
2845
|
+
setImages(nextImages);
|
|
2846
|
+
setPickerError(null);
|
|
2847
|
+
remember({ context: nextContext, images: nextImages, modes: nextModes });
|
|
2848
|
+
} catch (error) {
|
|
2849
|
+
setPickerError(error instanceof Error ? error.message : "Could not attach context.");
|
|
2850
|
+
}
|
|
2851
|
+
}
|
|
2852
|
+
}
|
|
2853
|
+
textarea.current?.focus({ preventScroll: true });
|
|
2854
|
+
}, [composerCommand?.id]);
|
|
2733
2855
|
const pickContext = () => {
|
|
2734
2856
|
if (mode === "terminal" || !adapter.pickContext || picking || context.length >= MAX_CONTEXT_ITEMS && images.length >= MAX_IMAGE_ITEMS) return;
|
|
2735
2857
|
setPicking(true);
|
|
@@ -2827,18 +2949,6 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2827
2949
|
/* @__PURE__ */ jsx9("small", { children: "Choose a coding harness and send the first message." })
|
|
2828
2950
|
] }),
|
|
2829
2951
|
/* @__PURE__ */ jsxs8("div", { className: "scui-compose", children: [
|
|
2830
|
-
/* @__PURE__ */ jsxs8("label", { className: "scui-harness-picker", children: [
|
|
2831
|
-
/* @__PURE__ */ jsx9(HarnessLogo, { id: harness, size: 24 }),
|
|
2832
|
-
/* @__PURE__ */ jsx9("span", { children: "Coding harness" }),
|
|
2833
|
-
/* @__PURE__ */ jsx9("select", { value: harness, disabled: Boolean(starting), onChange: (event) => {
|
|
2834
|
-
const value = event.currentTarget.value;
|
|
2835
|
-
setHarness(value);
|
|
2836
|
-
remember({ harness: value });
|
|
2837
|
-
}, children: state.harnesses.map((item) => /* @__PURE__ */ jsxs8("option", { value: item.id, disabled: !item.startable, children: [
|
|
2838
|
-
item.label,
|
|
2839
|
-
item.startable ? "" : " \xB7 unavailable"
|
|
2840
|
-
] }, item.id)) })
|
|
2841
|
-
] }),
|
|
2842
2952
|
/* @__PURE__ */ jsx9(Readiness, { harnesses: state.harnesses, state, adapter }),
|
|
2843
2953
|
launchModes.length > 1 ? /* @__PURE__ */ jsxs8("fieldset", { className: "scui-launch-modes", disabled: Boolean(starting), children: [
|
|
2844
2954
|
/* @__PURE__ */ jsx9("legend", { children: "Run as" }),
|
|
@@ -2867,7 +2977,7 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2867
2977
|
return next;
|
|
2868
2978
|
}) }),
|
|
2869
2979
|
terminalAttachments ? /* @__PURE__ */ jsx9("small", { className: "scui-context-error", role: "alert", children: "Terminal starts currently accept text only. Remove attachments or choose Chat." }) : pickerError ? /* @__PURE__ */ jsx9("small", { className: "scui-context-error", role: "alert", children: pickerError }) : null,
|
|
2870
|
-
/* @__PURE__ */ jsxs8("div", { className: `scui-envelope${dragging ? " scui-drop-target" : ""}`, onDragEnter: (event) => {
|
|
2980
|
+
/* @__PURE__ */ jsxs8("div", { className: `scui-envelope scui-new-envelope${dragging ? " scui-drop-target" : ""}`, onDragEnter: (event) => {
|
|
2871
2981
|
if (Array.from(event.dataTransfer?.types ?? []).includes("Files")) {
|
|
2872
2982
|
event.preventDefault();
|
|
2873
2983
|
setDragging(true);
|
|
@@ -2877,7 +2987,6 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2877
2987
|
}, onDragLeave: (event) => {
|
|
2878
2988
|
if (!event.currentTarget.contains(event.relatedTarget)) setDragging(false);
|
|
2879
2989
|
}, onDrop: dropImages, children: [
|
|
2880
|
-
adapter.pickContext ? /* @__PURE__ */ jsx9("button", { className: "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", { className: "scui-control-spinner" }) : /* @__PURE__ */ jsx9(UiIcon, { name: "attach", size: 17 }) }) : null,
|
|
2881
2990
|
/* @__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) => {
|
|
2882
2991
|
const value = event.currentTarget.value;
|
|
2883
2992
|
setDraft(value);
|
|
@@ -2888,12 +2997,19 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2888
2997
|
send();
|
|
2889
2998
|
}
|
|
2890
2999
|
} }),
|
|
2891
|
-
/* @__PURE__ */
|
|
3000
|
+
/* @__PURE__ */ jsxs8("footer", { className: "scui-new-envelope-controls", children: [
|
|
3001
|
+
adapter.pickContext ? /* @__PURE__ */ jsx9("button", { className: "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", { className: "scui-control-spinner" }) : /* @__PURE__ */ jsx9(UiIcon, { name: "attach", size: 17 }) }) : null,
|
|
3002
|
+
/* @__PURE__ */ jsx9(Picker, { harnesses: state.harnesses, value: harness, disabled: Boolean(starting), adapter, logo: components.HarnessLogo, onChange: (value) => {
|
|
3003
|
+
setHarness(value);
|
|
3004
|
+
remember({ harness: value });
|
|
3005
|
+
} }),
|
|
3006
|
+
/* @__PURE__ */ jsx9("span", { children: /* @__PURE__ */ jsx9("button", { type: "button", className: "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", { className: "scui-control-spinner" }) : /* @__PURE__ */ jsx9(UiIcon, { name: "send", size: 17 }) }) })
|
|
3007
|
+
] })
|
|
2892
3008
|
] })
|
|
2893
3009
|
] })
|
|
2894
3010
|
] });
|
|
2895
3011
|
}
|
|
2896
|
-
function SupercodeMessenger({ state: stateInput, adapter, class: className = "", initialView, navigation = null, onViewChange, contextCandidates: candidatesInput = [], labels, components = {}, slots = {} }) {
|
|
3012
|
+
function SupercodeMessenger({ state: stateInput, adapter, class: className = "", initialView, navigation = null, composerCommand = null, onViewChange, contextCandidates: candidatesInput = [], labels, components = {}, slots = {} }) {
|
|
2897
3013
|
const state = useMemo3(() => normalizeUiState(stateInput), [stateInput]);
|
|
2898
3014
|
const contextCandidates = useMemo3(() => normalizeAttachmentCandidates(candidatesInput), [candidatesInput]);
|
|
2899
3015
|
const copy = { ...DEFAULT_LABELS, ...labels };
|
|
@@ -2964,7 +3080,7 @@ function SupercodeMessenger({ state: stateInput, adapter, class: className = "",
|
|
|
2964
3080
|
setNewNavigation(null);
|
|
2965
3081
|
setView("new");
|
|
2966
3082
|
}, onClose: adapter.onClose ? close : void 0, components, labels: copy, memoryKey, slots, headerActions: HeaderActions }) : null,
|
|
2967
|
-
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, contextCandidates, components, navigation: newNavigation }) : null,
|
|
3083
|
+
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, contextCandidates, components, navigation: newNavigation, composerCommand }) : null,
|
|
2968
3084
|
view === "chat" ? /* @__PURE__ */ jsx9(Chat, { state, adapter, onBack: () => {
|
|
2969
3085
|
setListFocus(state.attached?.key ?? listFocus);
|
|
2970
3086
|
setView("list");
|
|
@@ -2973,7 +3089,7 @@ function SupercodeMessenger({ state: stateInput, adapter, class: className = "",
|
|
|
2973
3089
|
setChatNavigation(null);
|
|
2974
3090
|
setNewNavigation(null);
|
|
2975
3091
|
setView("new");
|
|
2976
|
-
}, onClose: adapter.onClose ? close : void 0, components, slots, labels: copy, contextCandidates, navigation: chatNavigation?.sessionKey === state.attached?.key ? chatNavigation : null }) : null,
|
|
3092
|
+
}, onClose: adapter.onClose ? close : void 0, components, slots, labels: copy, contextCandidates, navigation: chatNavigation?.sessionKey === state.attached?.key ? chatNavigation : null, composerCommand }) : null,
|
|
2977
3093
|
opening ? /* @__PURE__ */ jsxs8("div", { className: "scui-opening", role: "status", "aria-busy": "true", children: [
|
|
2978
3094
|
/* @__PURE__ */ jsx9(HarnessLogo, { id: opening.harness, size: 34 }),
|
|
2979
3095
|
/* @__PURE__ */ jsxs8("span", { children: [
|
package/index.d.ts
CHANGED
|
@@ -433,6 +433,15 @@ export interface HarnessSettingsPanelProps {
|
|
|
433
433
|
onClose(): void;
|
|
434
434
|
}
|
|
435
435
|
|
|
436
|
+
export interface HarnessPickerProps {
|
|
437
|
+
harnesses: HarnessOption[];
|
|
438
|
+
value: HarnessId;
|
|
439
|
+
disabled?: boolean;
|
|
440
|
+
adapter?: Pick<UiAdapter, 'copyText'>;
|
|
441
|
+
onChange(harness: HarnessId): void;
|
|
442
|
+
logo?: ComponentType<{ id: HarnessId; activity?: SessionActivity; size?: number }>;
|
|
443
|
+
}
|
|
444
|
+
|
|
436
445
|
export interface MessengerComponents {
|
|
437
446
|
SessionRow?: ComponentType<SessionRowProps>;
|
|
438
447
|
TranscriptEntry?: ComponentType<TranscriptEntryProps>;
|
|
@@ -441,6 +450,7 @@ export interface MessengerComponents {
|
|
|
441
450
|
HarnessLogo?: ComponentType<{ id: HarnessId; activity?: SessionActivity; size?: number }>;
|
|
442
451
|
HarnessAdvisory?: ComponentType<HarnessAdvisoryProps>;
|
|
443
452
|
HarnessSettingsPanel?: ComponentType<HarnessSettingsPanelProps>;
|
|
453
|
+
HarnessPicker?: ComponentType<HarnessPickerProps>;
|
|
444
454
|
ContextCandidate?: ComponentType<ContextCandidateProps>;
|
|
445
455
|
HarnessReadiness?: ComponentType<{ harnesses: HarnessOption[]; state: SupercodeUiState; adapter: UiAdapter }>;
|
|
446
456
|
}
|
|
@@ -473,6 +483,11 @@ export type MessengerNavigation =
|
|
|
473
483
|
| { id: string | number; view: 'chat'; sessionKey: string; draft?: string; context?: TranscriptContext[]; images?: Array<TranscriptImage & { url: string }> }
|
|
474
484
|
| { id: string | number; view: 'new'; harness?: HarnessId; draft?: string; context?: TranscriptContext[]; images?: Array<TranscriptImage & { url: string }> };
|
|
475
485
|
|
|
486
|
+
/** Event-like host command that focuses the active composer or appends context without replacing its draft. */
|
|
487
|
+
export type MessengerComposerCommand =
|
|
488
|
+
| { id: string | number; action: 'focus' }
|
|
489
|
+
| { id: string | number; action: 'attach'; attachments: TranscriptAttachment | TranscriptAttachment[] };
|
|
490
|
+
|
|
476
491
|
export interface MessengerProps {
|
|
477
492
|
state: SupercodeUiState | unknown;
|
|
478
493
|
adapter: UiAdapter;
|
|
@@ -480,6 +495,8 @@ export interface MessengerProps {
|
|
|
480
495
|
initialView?: 'list' | 'chat' | 'new';
|
|
481
496
|
/** Event-like host navigation. Change `id` to issue a new request. */
|
|
482
497
|
navigation?: MessengerNavigation | null;
|
|
498
|
+
/** Event-like composer command. Change `id` to focus or append attachments to the current draft. */
|
|
499
|
+
composerCommand?: MessengerComposerCommand | null;
|
|
483
500
|
onViewChange?(view: 'list' | 'chat' | 'new'): void;
|
|
484
501
|
/** Deliberate host-provided items that can be attached without opening a picker. */
|
|
485
502
|
contextCandidates?: TranscriptAttachment[];
|
|
@@ -532,13 +549,14 @@ export function ToolRow(props: ToolRowProps): VNode;
|
|
|
532
549
|
export function TaskPlan(props: { plan: TaskPlanModel }): VNode | null;
|
|
533
550
|
export function HarnessAdvisory(props: HarnessAdvisoryProps): VNode | null;
|
|
534
551
|
export function HarnessSettingsPanel(props: HarnessSettingsPanelProps): VNode;
|
|
552
|
+
export function HarnessPicker(props: HarnessPickerProps): VNode;
|
|
535
553
|
export function HarnessReadiness(props: { harnesses: HarnessOption[]; state: SupercodeUiState; adapter: UiAdapter }): VNode | null;
|
|
536
554
|
export function SessionDetails(props: { semantics: SessionSemanticsModel }): VNode | null;
|
|
537
555
|
export function Conversation(props: { state: SupercodeUiState; adapter: UiAdapter; components?: MessengerComponents; slots?: MessengerSlots; memoryKey?: string; pending?: string | PendingMessageModel | null; unreadAfterMessages?: number | null }): VNode;
|
|
538
556
|
export function SessionRow(props: { row: SessionRowModel; state: SupercodeUiState; adapter: UiAdapter; now?: number; onOpen(row: SessionRowModel): void }): VNode;
|
|
539
557
|
export function SessionList(props: { state: SupercodeUiState; adapter: UiAdapter; onOpen(row: SessionRowModel): void; onNew?(): void; onClose?(): void; components?: MessengerComponents; slots?: MessengerSlots; labels?: MessengerLabels; focusKey?: string | null; memoryKey?: string; headerActions?: MessengerSlots['headerActions'] }): VNode;
|
|
540
558
|
export function ContinuationBar(props: { state: SupercodeUiState; adapter: UiAdapter; labels?: MessengerLabels }): VNode | null;
|
|
541
|
-
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; contextCandidates?: TranscriptAttachment[]; components?: Pick<MessengerComponents, 'ContextCandidate'>; onPending?(text: string, context?: TranscriptContext[], images?: TranscriptImage[]): void; onDraftRestored?(id: string | number): void }): VNode;
|
|
559
|
+
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; command?: MessengerComposerCommand | null; contextCandidates?: TranscriptAttachment[]; components?: Pick<MessengerComponents, 'ContextCandidate'>; onPending?(text: string, context?: TranscriptContext[], images?: TranscriptImage[]): void; onDraftRestored?(id: string | number): void }): VNode;
|
|
542
560
|
export function SupercodeMessenger(props: MessengerProps): VNode;
|
|
543
561
|
|
|
544
562
|
export interface MountedMessenger {
|
package/messenger.mjs
CHANGED
|
@@ -1280,7 +1280,7 @@ function ContinuationBar({ state, adapter, labels = DEFAULT_LABELS }) {
|
|
|
1280
1280
|
] })
|
|
1281
1281
|
] });
|
|
1282
1282
|
}
|
|
1283
|
-
function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`, pendingStatus = null, restoreDraft = null, contextCandidates = [], components = {}, onPending, onDraftRestored }) {
|
|
1283
|
+
function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`, pendingStatus = null, restoreDraft = null, command = null, contextCandidates = [], components = {}, onPending, onDraftRestored }) {
|
|
1284
1284
|
const remembered = composerMemory.get(memoryKey) ?? { draft: state.savedDraft, context: [], images: [], queue: [] };
|
|
1285
1285
|
const [draft, setDraft] = useState2(remembered.draft);
|
|
1286
1286
|
const [context, setContext] = useState2(remembered.context ?? []);
|
|
@@ -1292,6 +1292,7 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
|
|
|
1292
1292
|
const [dragging, setDragging] = useState2(false);
|
|
1293
1293
|
const [pickerError, setPickerError] = useState2(null);
|
|
1294
1294
|
const textarea = useRef2(null);
|
|
1295
|
+
const lastCommand = useRef2(null);
|
|
1295
1296
|
useAutosizeTextarea(textarea, draft);
|
|
1296
1297
|
const remember = (nextDraft, nextContext, nextImages, nextQueue) => boundedSet(composerMemory, memoryKey, { draft: nextDraft, context: nextContext, images: nextImages, queue: nextQueue });
|
|
1297
1298
|
useEffect2(() => {
|
|
@@ -1333,6 +1334,26 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
|
|
|
1333
1334
|
textarea.current?.focus({ preventScroll: true });
|
|
1334
1335
|
onDraftRestored?.(restoreDraft.id);
|
|
1335
1336
|
}, [onDraftRestored, restoreDraft?.id]);
|
|
1337
|
+
useEffect2(() => {
|
|
1338
|
+
if (!command || command.id === lastCommand.current) return;
|
|
1339
|
+
lastCommand.current = command.id;
|
|
1340
|
+
if (command.action === "attach") {
|
|
1341
|
+
try {
|
|
1342
|
+
const attachments = partitionAttachments(command.attachments);
|
|
1343
|
+
if (context.length + attachments.context.length > MAX_CONTEXT_ITEMS) throw new Error(`Attach at most ${MAX_CONTEXT_ITEMS} text items.`);
|
|
1344
|
+
if (images.length + attachments.images.length > MAX_IMAGE_ITEMS) throw new Error(`Attach at most ${MAX_IMAGE_ITEMS} images.`);
|
|
1345
|
+
const nextContext = mergeContext(context, attachments.context);
|
|
1346
|
+
const nextImages = mergeImages(images, attachments.images);
|
|
1347
|
+
setContext(nextContext);
|
|
1348
|
+
setImages(nextImages);
|
|
1349
|
+
setPickerError(null);
|
|
1350
|
+
remember(draft, nextContext, nextImages, queue);
|
|
1351
|
+
} catch (error) {
|
|
1352
|
+
setPickerError(error instanceof Error ? error.message : "Could not attach context.");
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
textarea.current?.focus({ preventScroll: true });
|
|
1356
|
+
}, [command?.id]);
|
|
1336
1357
|
useEffect2(() => {
|
|
1337
1358
|
const timer = setTimeout(() => adapter.onIntent({ action: "draft", text: draft }), 250);
|
|
1338
1359
|
return () => clearTimeout(timer);
|
|
@@ -2394,6 +2415,79 @@ function HarnessReadiness({ harnesses, adapter }) {
|
|
|
2394
2415
|
] }, item.id)) })
|
|
2395
2416
|
] });
|
|
2396
2417
|
}
|
|
2418
|
+
function HarnessPicker({ harnesses, value, disabled = false, adapter, onChange, logo: Logo = HarnessLogo }) {
|
|
2419
|
+
const [open, setOpen] = useState5(false);
|
|
2420
|
+
const menuId = useId2();
|
|
2421
|
+
const root = useRef6(null);
|
|
2422
|
+
const trigger = useRef6(null);
|
|
2423
|
+
const panel = useRef6(null);
|
|
2424
|
+
const available = harnesses.filter((item) => item.startable);
|
|
2425
|
+
const unavailable = harnesses.filter((item) => !item.startable);
|
|
2426
|
+
const selected = available.find((item) => item.id === value) ?? available[0] ?? null;
|
|
2427
|
+
const close = () => {
|
|
2428
|
+
setOpen(false);
|
|
2429
|
+
trigger.current?.focus({ preventScroll: true });
|
|
2430
|
+
};
|
|
2431
|
+
useEffect7(() => {
|
|
2432
|
+
if (!open) return;
|
|
2433
|
+
panel.current?.querySelector('[aria-selected="true"], [role="option"]')?.focus({ preventScroll: true });
|
|
2434
|
+
const dismiss = (event) => {
|
|
2435
|
+
if (event.type === "keydown" && event.key === "Escape") {
|
|
2436
|
+
event.preventDefault();
|
|
2437
|
+
close();
|
|
2438
|
+
} else if (event.type === "pointerdown" && !root.current?.contains(event.target)) {
|
|
2439
|
+
setOpen(false);
|
|
2440
|
+
}
|
|
2441
|
+
};
|
|
2442
|
+
document.addEventListener("keydown", dismiss);
|
|
2443
|
+
document.addEventListener("pointerdown", dismiss, true);
|
|
2444
|
+
return () => {
|
|
2445
|
+
document.removeEventListener("keydown", dismiss);
|
|
2446
|
+
document.removeEventListener("pointerdown", dismiss, true);
|
|
2447
|
+
};
|
|
2448
|
+
}, [open]);
|
|
2449
|
+
const navigate = (event) => {
|
|
2450
|
+
if (!["ArrowDown", "ArrowUp", "Home", "End"].includes(event.key)) return;
|
|
2451
|
+
const items = [...panel.current.querySelectorAll('[role="option"]')];
|
|
2452
|
+
if (!items.length) return;
|
|
2453
|
+
event.preventDefault();
|
|
2454
|
+
const current = items.indexOf(document.activeElement);
|
|
2455
|
+
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;
|
|
2456
|
+
items[index].focus({ preventScroll: true });
|
|
2457
|
+
};
|
|
2458
|
+
const select = (id) => {
|
|
2459
|
+
onChange(id);
|
|
2460
|
+
close();
|
|
2461
|
+
};
|
|
2462
|
+
return /* @__PURE__ */ jsxs7("div", { className: "scui-harness-menu", ref: root, onBlur: (event) => {
|
|
2463
|
+
if (event.relatedTarget && !event.currentTarget.contains(event.relatedTarget)) setOpen(false);
|
|
2464
|
+
}, children: [
|
|
2465
|
+
/* @__PURE__ */ jsxs7("button", { ref: trigger, className: "scui-harness-trigger", type: "button", disabled: disabled || !selected, "aria-label": selected ? `Coding harness: ${selected.label}` : "No coding harness available", "aria-haspopup": "dialog", "aria-expanded": open, "aria-controls": open ? menuId : void 0, onClick: () => setOpen((current) => !current), children: [
|
|
2466
|
+
selected ? /* @__PURE__ */ jsx8(Logo, { id: selected.id, size: 20 }) : null,
|
|
2467
|
+
/* @__PURE__ */ jsx8("strong", { children: selected?.label ?? "No harness available" }),
|
|
2468
|
+
/* @__PURE__ */ jsx8(UiIcon, { name: "chevron", size: 13 })
|
|
2469
|
+
] }),
|
|
2470
|
+
open ? /* @__PURE__ */ jsxs7("div", { ref: panel, id: menuId, className: "scui-harness-popover", role: "dialog", "aria-label": "Choose coding harness", onKeyDown: navigate, children: [
|
|
2471
|
+
/* @__PURE__ */ jsx8("strong", { className: "scui-harness-popover-title", children: "Choose coding harness" }),
|
|
2472
|
+
/* @__PURE__ */ jsx8("div", { className: "scui-harness-options", role: "listbox", "aria-label": "Available coding harnesses", children: available.map((item) => /* @__PURE__ */ jsxs7("button", { type: "button", role: "option", "aria-selected": item.id === selected?.id, onClick: () => select(item.id), children: [
|
|
2473
|
+
/* @__PURE__ */ jsx8(Logo, { id: item.id, size: 24 }),
|
|
2474
|
+
/* @__PURE__ */ jsx8("strong", { children: item.label }),
|
|
2475
|
+
/* @__PURE__ */ jsx8("span", { children: item.id === selected?.id ? /* @__PURE__ */ jsx8(UiIcon, { name: "check", size: 15 }) : null })
|
|
2476
|
+
] }, item.id)) }),
|
|
2477
|
+
unavailable.length ? /* @__PURE__ */ jsxs7("details", { className: "scui-harness-manage", children: [
|
|
2478
|
+
/* @__PURE__ */ jsx8("summary", { children: "Manage additional harnesses" }),
|
|
2479
|
+
/* @__PURE__ */ jsx8("div", { children: unavailable.map((item) => /* @__PURE__ */ jsxs7("section", { children: [
|
|
2480
|
+
/* @__PURE__ */ jsx8(Logo, { id: item.id, size: 24 }),
|
|
2481
|
+
/* @__PURE__ */ jsxs7("span", { children: [
|
|
2482
|
+
/* @__PURE__ */ jsx8("strong", { children: item.label }),
|
|
2483
|
+
/* @__PURE__ */ jsx8("small", { children: item.reason || (item.auth === "required" ? "Authentication required" : "Unavailable") })
|
|
2484
|
+
] }),
|
|
2485
|
+
item.repair ? /* @__PURE__ */ jsx8("button", { type: "button", onClick: () => adapter?.copyText?.(item.repair), children: "Copy fix" }) : null
|
|
2486
|
+
] }, item.id)) })
|
|
2487
|
+
] }) : null
|
|
2488
|
+
] }) : null
|
|
2489
|
+
] });
|
|
2490
|
+
}
|
|
2397
2491
|
|
|
2398
2492
|
// src/messenger.jsx
|
|
2399
2493
|
import { jsx as jsx9, jsxs as jsxs8 } from "preact/jsx-runtime";
|
|
@@ -2540,7 +2634,7 @@ function ChatHeader({ state, adapter, pendingStatus, actionPending, onBack, onNe
|
|
|
2540
2634
|
onClose ? /* @__PURE__ */ jsx9("button", { type: "button", "aria-label": "Close", onClick: onClose, children: /* @__PURE__ */ jsx9(UiIcon, { name: "close", size: 18 }) }) : null
|
|
2541
2635
|
] });
|
|
2542
2636
|
}
|
|
2543
|
-
function Chat({ state, adapter, onBack, onNew, onClose, components, slots, labels, contextCandidates, navigation }) {
|
|
2637
|
+
function Chat({ state, adapter, onBack, onNew, onClose, components, slots, labels, contextCandidates, navigation, composerCommand }) {
|
|
2544
2638
|
const Header = slots.header;
|
|
2545
2639
|
const HeaderActions = slots.headerActions;
|
|
2546
2640
|
const memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`;
|
|
@@ -2667,11 +2761,11 @@ function Chat({ state, adapter, onBack, onNew, onClose, components, slots, label
|
|
|
2667
2761
|
/* @__PURE__ */ jsx9(Conversation, { state: actionState, adapter: trackedAdapter, components, slots, memoryKey, pending: pendingMessage, unreadAfterMessages }, memoryKey),
|
|
2668
2762
|
/* @__PURE__ */ jsx9(ContinuationBar, { state: actionState, adapter: trackedAdapter, labels }),
|
|
2669
2763
|
/* @__PURE__ */ jsx9(Advisory, { state: actionState, adapter: trackedAdapter, value: state.interopSettings?.advisories[0] ?? null, onReview: (recommendedChange) => setSettings({ recommendedChange }) }),
|
|
2670
|
-
state.mode !== "mirror" || state.canSend ? /* @__PURE__ */ jsx9(Composer, { state: actionState, adapter: trackedAdapter, labels, memoryKey, pendingStatus: pending?.status ?? null, restoreDraft, contextCandidates, components, onDraftRestored: (id) => setRestoreDraft((value) => value?.id === id ? null : value), onPending: beginPending }, memoryKey) : null,
|
|
2764
|
+
state.mode !== "mirror" || state.canSend ? /* @__PURE__ */ jsx9(Composer, { state: actionState, adapter: trackedAdapter, labels, memoryKey, pendingStatus: pending?.status ?? null, restoreDraft, command: composerCommand, contextCandidates, components, onDraftRestored: (id) => setRestoreDraft((value) => value?.id === id ? null : value), onPending: beginPending }, memoryKey) : null,
|
|
2671
2765
|
settings ? /* @__PURE__ */ jsx9(SettingsPanel, { state: actionState, adapter: trackedAdapter, value: state.interopSettings, recommendedChange: settings.recommendedChange, onClose: () => setSettings(null) }) : null
|
|
2672
2766
|
] });
|
|
2673
2767
|
}
|
|
2674
|
-
function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey, headerActions: HeaderActions, contextCandidates, components, navigation }) {
|
|
2768
|
+
function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey, headerActions: HeaderActions, contextCandidates, components, navigation, composerCommand }) {
|
|
2675
2769
|
const startable = state.harnesses.filter((item) => item.startable);
|
|
2676
2770
|
const startableKey = startable.map((item) => `${item.id}:${item.launchModes?.join(",")}:${item.preferredLaunchMode ?? ""}`).join("\0");
|
|
2677
2771
|
const remembered = newChatMemory.get(memoryKey) ?? { harness: startable[0]?.id ?? "", draft: "", context: [], images: [], modes: {} };
|
|
@@ -2685,8 +2779,10 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2685
2779
|
const [dragging, setDragging] = useState6(false);
|
|
2686
2780
|
const [pickerError, setPickerError] = useState6(null);
|
|
2687
2781
|
const startSequence = useRef7(0);
|
|
2782
|
+
const lastComposerCommand = useRef7(null);
|
|
2688
2783
|
const textarea = useRef7(null);
|
|
2689
2784
|
const selectedHarness = startable.find((item) => item.id === harness) ?? null;
|
|
2785
|
+
const Picker = components.HarnessPicker ?? HarnessPicker;
|
|
2690
2786
|
const Readiness = components.HarnessReadiness ?? HarnessReadiness;
|
|
2691
2787
|
const launchModes = selectedHarness?.launchModes?.length ? selectedHarness.launchModes : ["headless"];
|
|
2692
2788
|
const rememberedMode = modes[harness];
|
|
@@ -2727,6 +2823,32 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2727
2823
|
remember({ harness: nextHarness, draft: nextDraft, context: nextContext, images: nextImages });
|
|
2728
2824
|
textarea.current?.focus({ preventScroll: true });
|
|
2729
2825
|
}, [navigation?.id]);
|
|
2826
|
+
useEffect8(() => {
|
|
2827
|
+
if (!composerCommand || composerCommand.id === lastComposerCommand.current) return;
|
|
2828
|
+
lastComposerCommand.current = composerCommand.id;
|
|
2829
|
+
if (composerCommand.action === "attach") {
|
|
2830
|
+
if (mode === "terminal" && !launchModes.includes("headless")) {
|
|
2831
|
+
setPickerError("Terminal starts currently accept text only. Choose Chat to attach context or images.");
|
|
2832
|
+
} else {
|
|
2833
|
+
try {
|
|
2834
|
+
const attachments = partitionAttachments(composerCommand.attachments);
|
|
2835
|
+
if (context.length + attachments.context.length > MAX_CONTEXT_ITEMS) throw new Error(`Attach at most ${MAX_CONTEXT_ITEMS} text items.`);
|
|
2836
|
+
if (images.length + attachments.images.length > MAX_IMAGE_ITEMS) throw new Error(`Attach at most ${MAX_IMAGE_ITEMS} images.`);
|
|
2837
|
+
const nextContext = mergeContext(context, attachments.context);
|
|
2838
|
+
const nextImages = mergeImages(images, attachments.images);
|
|
2839
|
+
const nextModes = mode === "terminal" ? { ...modes, [harness]: "headless" } : modes;
|
|
2840
|
+
if (nextModes !== modes) setModes(nextModes);
|
|
2841
|
+
setContext(nextContext);
|
|
2842
|
+
setImages(nextImages);
|
|
2843
|
+
setPickerError(null);
|
|
2844
|
+
remember({ context: nextContext, images: nextImages, modes: nextModes });
|
|
2845
|
+
} catch (error) {
|
|
2846
|
+
setPickerError(error instanceof Error ? error.message : "Could not attach context.");
|
|
2847
|
+
}
|
|
2848
|
+
}
|
|
2849
|
+
}
|
|
2850
|
+
textarea.current?.focus({ preventScroll: true });
|
|
2851
|
+
}, [composerCommand?.id]);
|
|
2730
2852
|
const pickContext = () => {
|
|
2731
2853
|
if (mode === "terminal" || !adapter.pickContext || picking || context.length >= MAX_CONTEXT_ITEMS && images.length >= MAX_IMAGE_ITEMS) return;
|
|
2732
2854
|
setPicking(true);
|
|
@@ -2824,18 +2946,6 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2824
2946
|
/* @__PURE__ */ jsx9("small", { children: "Choose a coding harness and send the first message." })
|
|
2825
2947
|
] }),
|
|
2826
2948
|
/* @__PURE__ */ jsxs8("div", { className: "scui-compose", children: [
|
|
2827
|
-
/* @__PURE__ */ jsxs8("label", { className: "scui-harness-picker", children: [
|
|
2828
|
-
/* @__PURE__ */ jsx9(HarnessLogo, { id: harness, size: 24 }),
|
|
2829
|
-
/* @__PURE__ */ jsx9("span", { children: "Coding harness" }),
|
|
2830
|
-
/* @__PURE__ */ jsx9("select", { value: harness, disabled: Boolean(starting), onChange: (event) => {
|
|
2831
|
-
const value = event.currentTarget.value;
|
|
2832
|
-
setHarness(value);
|
|
2833
|
-
remember({ harness: value });
|
|
2834
|
-
}, children: state.harnesses.map((item) => /* @__PURE__ */ jsxs8("option", { value: item.id, disabled: !item.startable, children: [
|
|
2835
|
-
item.label,
|
|
2836
|
-
item.startable ? "" : " \xB7 unavailable"
|
|
2837
|
-
] }, item.id)) })
|
|
2838
|
-
] }),
|
|
2839
2949
|
/* @__PURE__ */ jsx9(Readiness, { harnesses: state.harnesses, state, adapter }),
|
|
2840
2950
|
launchModes.length > 1 ? /* @__PURE__ */ jsxs8("fieldset", { className: "scui-launch-modes", disabled: Boolean(starting), children: [
|
|
2841
2951
|
/* @__PURE__ */ jsx9("legend", { children: "Run as" }),
|
|
@@ -2864,7 +2974,7 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2864
2974
|
return next;
|
|
2865
2975
|
}) }),
|
|
2866
2976
|
terminalAttachments ? /* @__PURE__ */ jsx9("small", { className: "scui-context-error", role: "alert", children: "Terminal starts currently accept text only. Remove attachments or choose Chat." }) : pickerError ? /* @__PURE__ */ jsx9("small", { className: "scui-context-error", role: "alert", children: pickerError }) : null,
|
|
2867
|
-
/* @__PURE__ */ jsxs8("div", { className: `scui-envelope${dragging ? " scui-drop-target" : ""}`, onDragEnter: (event) => {
|
|
2977
|
+
/* @__PURE__ */ jsxs8("div", { className: `scui-envelope scui-new-envelope${dragging ? " scui-drop-target" : ""}`, onDragEnter: (event) => {
|
|
2868
2978
|
if (Array.from(event.dataTransfer?.types ?? []).includes("Files")) {
|
|
2869
2979
|
event.preventDefault();
|
|
2870
2980
|
setDragging(true);
|
|
@@ -2874,7 +2984,6 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2874
2984
|
}, onDragLeave: (event) => {
|
|
2875
2985
|
if (!event.currentTarget.contains(event.relatedTarget)) setDragging(false);
|
|
2876
2986
|
}, onDrop: dropImages, children: [
|
|
2877
|
-
adapter.pickContext ? /* @__PURE__ */ jsx9("button", { className: "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", { className: "scui-control-spinner" }) : /* @__PURE__ */ jsx9(UiIcon, { name: "attach", size: 17 }) }) : null,
|
|
2878
2987
|
/* @__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) => {
|
|
2879
2988
|
const value = event.currentTarget.value;
|
|
2880
2989
|
setDraft(value);
|
|
@@ -2885,12 +2994,19 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2885
2994
|
send();
|
|
2886
2995
|
}
|
|
2887
2996
|
} }),
|
|
2888
|
-
/* @__PURE__ */
|
|
2997
|
+
/* @__PURE__ */ jsxs8("footer", { className: "scui-new-envelope-controls", children: [
|
|
2998
|
+
adapter.pickContext ? /* @__PURE__ */ jsx9("button", { className: "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", { className: "scui-control-spinner" }) : /* @__PURE__ */ jsx9(UiIcon, { name: "attach", size: 17 }) }) : null,
|
|
2999
|
+
/* @__PURE__ */ jsx9(Picker, { harnesses: state.harnesses, value: harness, disabled: Boolean(starting), adapter, logo: components.HarnessLogo, onChange: (value) => {
|
|
3000
|
+
setHarness(value);
|
|
3001
|
+
remember({ harness: value });
|
|
3002
|
+
} }),
|
|
3003
|
+
/* @__PURE__ */ jsx9("span", { children: /* @__PURE__ */ jsx9("button", { type: "button", className: "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", { className: "scui-control-spinner" }) : /* @__PURE__ */ jsx9(UiIcon, { name: "send", size: 17 }) }) })
|
|
3004
|
+
] })
|
|
2889
3005
|
] })
|
|
2890
3006
|
] })
|
|
2891
3007
|
] });
|
|
2892
3008
|
}
|
|
2893
|
-
function SupercodeMessenger({ state: stateInput, adapter, class: className = "", initialView, navigation = null, onViewChange, contextCandidates: candidatesInput = [], labels, components = {}, slots = {} }) {
|
|
3009
|
+
function SupercodeMessenger({ state: stateInput, adapter, class: className = "", initialView, navigation = null, composerCommand = null, onViewChange, contextCandidates: candidatesInput = [], labels, components = {}, slots = {} }) {
|
|
2894
3010
|
const state = useMemo3(() => normalizeUiState(stateInput), [stateInput]);
|
|
2895
3011
|
const contextCandidates = useMemo3(() => normalizeAttachmentCandidates(candidatesInput), [candidatesInput]);
|
|
2896
3012
|
const copy = { ...DEFAULT_LABELS, ...labels };
|
|
@@ -2961,7 +3077,7 @@ function SupercodeMessenger({ state: stateInput, adapter, class: className = "",
|
|
|
2961
3077
|
setNewNavigation(null);
|
|
2962
3078
|
setView("new");
|
|
2963
3079
|
}, onClose: adapter.onClose ? close : void 0, components, labels: copy, memoryKey, slots, headerActions: HeaderActions }) : null,
|
|
2964
|
-
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, contextCandidates, components, navigation: newNavigation }) : null,
|
|
3080
|
+
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, contextCandidates, components, navigation: newNavigation, composerCommand }) : null,
|
|
2965
3081
|
view === "chat" ? /* @__PURE__ */ jsx9(Chat, { state, adapter, onBack: () => {
|
|
2966
3082
|
setListFocus(state.attached?.key ?? listFocus);
|
|
2967
3083
|
setView("list");
|
|
@@ -2970,7 +3086,7 @@ function SupercodeMessenger({ state: stateInput, adapter, class: className = "",
|
|
|
2970
3086
|
setChatNavigation(null);
|
|
2971
3087
|
setNewNavigation(null);
|
|
2972
3088
|
setView("new");
|
|
2973
|
-
}, onClose: adapter.onClose ? close : void 0, components, slots, labels: copy, contextCandidates, navigation: chatNavigation?.sessionKey === state.attached?.key ? chatNavigation : null }) : null,
|
|
3089
|
+
}, onClose: adapter.onClose ? close : void 0, components, slots, labels: copy, contextCandidates, navigation: chatNavigation?.sessionKey === state.attached?.key ? chatNavigation : null, composerCommand }) : null,
|
|
2974
3090
|
opening ? /* @__PURE__ */ jsxs8("div", { className: "scui-opening", role: "status", "aria-busy": "true", children: [
|
|
2975
3091
|
/* @__PURE__ */ jsx9(HarnessLogo, { id: opening.harness, size: 34 }),
|
|
2976
3092
|
/* @__PURE__ */ jsxs8("span", { children: [
|