@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/README.md
CHANGED
|
@@ -60,14 +60,14 @@ import {
|
|
|
60
60
|
SessionList,
|
|
61
61
|
} from '@volter-ai-dev/supercode-ui/preact';
|
|
62
62
|
import { HarnessLogo, harnessLogoDataUrl } from '@volter-ai-dev/supercode-ui/preact/logo';
|
|
63
|
-
import { HarnessAdvisory, HarnessSettingsPanel } from '@volter-ai-dev/supercode-ui/preact/settings';
|
|
63
|
+
import { HarnessAdvisory, HarnessPicker, HarnessSettingsPanel } from '@volter-ai-dev/supercode-ui/preact/settings';
|
|
64
64
|
import { groupConversation } from '@volter-ai-dev/supercode-ui/core';
|
|
65
65
|
```
|
|
66
66
|
|
|
67
67
|
The public components are `SupercodeMessenger`, `AgentActivityLauncher`, `Conversation`, `TranscriptEntry`,
|
|
68
68
|
`ActivityGroup`, `RequestCard`, `SessionList`, `SessionRow`, `Composer`, `ContinuationBar`,
|
|
69
|
-
`ContextCandidate`, `ContextCandidates`, `LoadingStatus`, `TaskPlan`, `SessionDetails`, `HarnessLogo`, `HarnessAdvisory`,
|
|
70
|
-
`HarnessSettingsPanel`. Pure state readers, selectors,
|
|
69
|
+
`ContextCandidate`, `ContextCandidates`, `LoadingStatus`, `TaskPlan`, `SessionDetails`, `HarnessLogo`, `HarnessAdvisory`,
|
|
70
|
+
`HarnessPicker`, and `HarnessSettingsPanel`. Pure state readers, selectors,
|
|
71
71
|
formatters, and intent constructors live at `supercode-ui/core` and have no DOM or Preact imports.
|
|
72
72
|
For genuine partial delivery, the `preact/logo`, `preact/icon`, `preact/conversation`, `preact/sessions`,
|
|
73
73
|
`preact/composer`, `preact/settings`, and `preact/messenger` subpaths are independently tree-shaken artifacts; taking
|
|
@@ -128,6 +128,11 @@ Hosts do not need to fork the messenger to connect their own object model:
|
|
|
128
128
|
draft: 'Inspect this selection',
|
|
129
129
|
context: [selectedObject.asTranscriptContext()],
|
|
130
130
|
}}
|
|
131
|
+
composerCommand={{
|
|
132
|
+
id: browserCapture.revision,
|
|
133
|
+
action: 'attach',
|
|
134
|
+
attachments: browserCapture.asTranscriptAttachment(),
|
|
135
|
+
}}
|
|
131
136
|
contextCandidates={visibleObjects.map(asTranscriptContext)}
|
|
132
137
|
components={{ ContextCandidate: ObjectPreview }}
|
|
133
138
|
slots={{ beforeSessions: ProjectThreads }}
|
|
@@ -135,7 +140,11 @@ Hosts do not need to fork the messenger to connect their own object model:
|
|
|
135
140
|
```
|
|
136
141
|
|
|
137
142
|
`navigation` is an event, not duplicated router state: change its `id` to open a conversation or
|
|
138
|
-
prefill either an existing or new one, while `onViewChange` observes user navigation.
|
|
143
|
+
prefill either an existing or new one, while `onViewChange` observes user navigation.
|
|
144
|
+
`composerCommand` is a separate event-like host seam: `focus` focuses the active composer and
|
|
145
|
+
`attach` appends bounded context without replacing its draft or existing attachments. On New Chat,
|
|
146
|
+
an attachment selects the compatible headless lane when the remembered terminal lane cannot carry
|
|
147
|
+
structured context. Context candidates use the exact
|
|
139
148
|
bounded `TranscriptAttachment` envelope sent to the harness; a custom candidate component changes
|
|
140
149
|
presentation without inventing a second context protocol. `beforeSessions` and `afterSessions`
|
|
141
150
|
place host-owned rows inside the same searchable scroller. `confirmIntent` runs before resume,
|
package/components.d.ts
CHANGED
package/components.mjs
CHANGED
|
@@ -1317,7 +1317,7 @@ function ContinuationBar({ state, adapter, labels = DEFAULT_LABELS }) {
|
|
|
1317
1317
|
] })
|
|
1318
1318
|
] });
|
|
1319
1319
|
}
|
|
1320
|
-
function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`, pendingStatus = null, restoreDraft = null, contextCandidates = [], components = {}, onPending, onDraftRestored }) {
|
|
1320
|
+
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 }) {
|
|
1321
1321
|
const remembered = composerMemory.get(memoryKey) ?? { draft: state.savedDraft, context: [], images: [], queue: [] };
|
|
1322
1322
|
const [draft, setDraft] = useState2(remembered.draft);
|
|
1323
1323
|
const [context, setContext] = useState2(remembered.context ?? []);
|
|
@@ -1329,6 +1329,7 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
|
|
|
1329
1329
|
const [dragging, setDragging] = useState2(false);
|
|
1330
1330
|
const [pickerError, setPickerError] = useState2(null);
|
|
1331
1331
|
const textarea = useRef2(null);
|
|
1332
|
+
const lastCommand = useRef2(null);
|
|
1332
1333
|
useAutosizeTextarea(textarea, draft);
|
|
1333
1334
|
const remember = (nextDraft, nextContext, nextImages, nextQueue) => boundedSet(composerMemory, memoryKey, { draft: nextDraft, context: nextContext, images: nextImages, queue: nextQueue });
|
|
1334
1335
|
useEffect2(() => {
|
|
@@ -1370,6 +1371,26 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
|
|
|
1370
1371
|
textarea.current?.focus({ preventScroll: true });
|
|
1371
1372
|
onDraftRestored?.(restoreDraft.id);
|
|
1372
1373
|
}, [onDraftRestored, restoreDraft?.id]);
|
|
1374
|
+
useEffect2(() => {
|
|
1375
|
+
if (!command || command.id === lastCommand.current) return;
|
|
1376
|
+
lastCommand.current = command.id;
|
|
1377
|
+
if (command.action === "attach") {
|
|
1378
|
+
try {
|
|
1379
|
+
const attachments = partitionAttachments(command.attachments);
|
|
1380
|
+
if (context.length + attachments.context.length > MAX_CONTEXT_ITEMS) throw new Error(`Attach at most ${MAX_CONTEXT_ITEMS} text items.`);
|
|
1381
|
+
if (images.length + attachments.images.length > MAX_IMAGE_ITEMS) throw new Error(`Attach at most ${MAX_IMAGE_ITEMS} images.`);
|
|
1382
|
+
const nextContext = mergeContext(context, attachments.context);
|
|
1383
|
+
const nextImages = mergeImages(images, attachments.images);
|
|
1384
|
+
setContext(nextContext);
|
|
1385
|
+
setImages(nextImages);
|
|
1386
|
+
setPickerError(null);
|
|
1387
|
+
remember(draft, nextContext, nextImages, queue);
|
|
1388
|
+
} catch (error) {
|
|
1389
|
+
setPickerError(error instanceof Error ? error.message : "Could not attach context.");
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
textarea.current?.focus({ preventScroll: true });
|
|
1393
|
+
}, [command?.id]);
|
|
1373
1394
|
useEffect2(() => {
|
|
1374
1395
|
const timer = setTimeout(() => adapter.onIntent({ action: "draft", text: draft }), 250);
|
|
1375
1396
|
return () => clearTimeout(timer);
|
|
@@ -2477,6 +2498,79 @@ function HarnessReadiness({ harnesses, adapter }) {
|
|
|
2477
2498
|
] }, item.id)) })
|
|
2478
2499
|
] });
|
|
2479
2500
|
}
|
|
2501
|
+
function HarnessPicker({ harnesses, value, disabled = false, adapter, onChange, logo: Logo = HarnessLogo }) {
|
|
2502
|
+
const [open, setOpen] = useState5(false);
|
|
2503
|
+
const menuId = useId2();
|
|
2504
|
+
const root = useRef6(null);
|
|
2505
|
+
const trigger = useRef6(null);
|
|
2506
|
+
const panel = useRef6(null);
|
|
2507
|
+
const available = harnesses.filter((item) => item.startable);
|
|
2508
|
+
const unavailable = harnesses.filter((item) => !item.startable);
|
|
2509
|
+
const selected = available.find((item) => item.id === value) ?? available[0] ?? null;
|
|
2510
|
+
const close = () => {
|
|
2511
|
+
setOpen(false);
|
|
2512
|
+
trigger.current?.focus({ preventScroll: true });
|
|
2513
|
+
};
|
|
2514
|
+
useEffect7(() => {
|
|
2515
|
+
if (!open) return;
|
|
2516
|
+
panel.current?.querySelector('[aria-selected="true"], [role="option"]')?.focus({ preventScroll: true });
|
|
2517
|
+
const dismiss = (event) => {
|
|
2518
|
+
if (event.type === "keydown" && event.key === "Escape") {
|
|
2519
|
+
event.preventDefault();
|
|
2520
|
+
close();
|
|
2521
|
+
} else if (event.type === "pointerdown" && !root.current?.contains(event.target)) {
|
|
2522
|
+
setOpen(false);
|
|
2523
|
+
}
|
|
2524
|
+
};
|
|
2525
|
+
document.addEventListener("keydown", dismiss);
|
|
2526
|
+
document.addEventListener("pointerdown", dismiss, true);
|
|
2527
|
+
return () => {
|
|
2528
|
+
document.removeEventListener("keydown", dismiss);
|
|
2529
|
+
document.removeEventListener("pointerdown", dismiss, true);
|
|
2530
|
+
};
|
|
2531
|
+
}, [open]);
|
|
2532
|
+
const navigate = (event) => {
|
|
2533
|
+
if (!["ArrowDown", "ArrowUp", "Home", "End"].includes(event.key)) return;
|
|
2534
|
+
const items = [...panel.current.querySelectorAll('[role="option"]')];
|
|
2535
|
+
if (!items.length) return;
|
|
2536
|
+
event.preventDefault();
|
|
2537
|
+
const current = items.indexOf(document.activeElement);
|
|
2538
|
+
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;
|
|
2539
|
+
items[index].focus({ preventScroll: true });
|
|
2540
|
+
};
|
|
2541
|
+
const select = (id) => {
|
|
2542
|
+
onChange(id);
|
|
2543
|
+
close();
|
|
2544
|
+
};
|
|
2545
|
+
return /* @__PURE__ */ jsxs8("div", { className: "scui-harness-menu", ref: root, onBlur: (event) => {
|
|
2546
|
+
if (event.relatedTarget && !event.currentTarget.contains(event.relatedTarget)) setOpen(false);
|
|
2547
|
+
}, children: [
|
|
2548
|
+
/* @__PURE__ */ jsxs8("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: [
|
|
2549
|
+
selected ? /* @__PURE__ */ jsx9(Logo, { id: selected.id, size: 20 }) : null,
|
|
2550
|
+
/* @__PURE__ */ jsx9("strong", { children: selected?.label ?? "No harness available" }),
|
|
2551
|
+
/* @__PURE__ */ jsx9(UiIcon, { name: "chevron", size: 13 })
|
|
2552
|
+
] }),
|
|
2553
|
+
open ? /* @__PURE__ */ jsxs8("div", { ref: panel, id: menuId, className: "scui-harness-popover", role: "dialog", "aria-label": "Choose coding harness", onKeyDown: navigate, children: [
|
|
2554
|
+
/* @__PURE__ */ jsx9("strong", { className: "scui-harness-popover-title", children: "Choose coding harness" }),
|
|
2555
|
+
/* @__PURE__ */ jsx9("div", { className: "scui-harness-options", role: "listbox", "aria-label": "Available coding harnesses", children: available.map((item) => /* @__PURE__ */ jsxs8("button", { type: "button", role: "option", "aria-selected": item.id === selected?.id, onClick: () => select(item.id), children: [
|
|
2556
|
+
/* @__PURE__ */ jsx9(Logo, { id: item.id, size: 24 }),
|
|
2557
|
+
/* @__PURE__ */ jsx9("strong", { children: item.label }),
|
|
2558
|
+
/* @__PURE__ */ jsx9("span", { children: item.id === selected?.id ? /* @__PURE__ */ jsx9(UiIcon, { name: "check", size: 15 }) : null })
|
|
2559
|
+
] }, item.id)) }),
|
|
2560
|
+
unavailable.length ? /* @__PURE__ */ jsxs8("details", { className: "scui-harness-manage", children: [
|
|
2561
|
+
/* @__PURE__ */ jsx9("summary", { children: "Manage additional harnesses" }),
|
|
2562
|
+
/* @__PURE__ */ jsx9("div", { children: unavailable.map((item) => /* @__PURE__ */ jsxs8("section", { children: [
|
|
2563
|
+
/* @__PURE__ */ jsx9(Logo, { id: item.id, size: 24 }),
|
|
2564
|
+
/* @__PURE__ */ jsxs8("span", { children: [
|
|
2565
|
+
/* @__PURE__ */ jsx9("strong", { children: item.label }),
|
|
2566
|
+
/* @__PURE__ */ jsx9("small", { children: item.reason || (item.auth === "required" ? "Authentication required" : "Unavailable") })
|
|
2567
|
+
] }),
|
|
2568
|
+
item.repair ? /* @__PURE__ */ jsx9("button", { type: "button", onClick: () => adapter?.copyText?.(item.repair), children: "Copy fix" }) : null
|
|
2569
|
+
] }, item.id)) })
|
|
2570
|
+
] }) : null
|
|
2571
|
+
] }) : null
|
|
2572
|
+
] });
|
|
2573
|
+
}
|
|
2480
2574
|
|
|
2481
2575
|
// src/messenger.jsx
|
|
2482
2576
|
import { jsx as jsx10, jsxs as jsxs9 } from "preact/jsx-runtime";
|
|
@@ -2623,7 +2717,7 @@ function ChatHeader({ state, adapter, pendingStatus, actionPending, onBack, onNe
|
|
|
2623
2717
|
onClose ? /* @__PURE__ */ jsx10("button", { type: "button", "aria-label": "Close", onClick: onClose, children: /* @__PURE__ */ jsx10(UiIcon, { name: "close", size: 18 }) }) : null
|
|
2624
2718
|
] });
|
|
2625
2719
|
}
|
|
2626
|
-
function Chat({ state, adapter, onBack, onNew, onClose, components, slots, labels, contextCandidates, navigation }) {
|
|
2720
|
+
function Chat({ state, adapter, onBack, onNew, onClose, components, slots, labels, contextCandidates, navigation, composerCommand }) {
|
|
2627
2721
|
const Header = slots.header;
|
|
2628
2722
|
const HeaderActions = slots.headerActions;
|
|
2629
2723
|
const memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`;
|
|
@@ -2750,11 +2844,11 @@ function Chat({ state, adapter, onBack, onNew, onClose, components, slots, label
|
|
|
2750
2844
|
/* @__PURE__ */ jsx10(Conversation, { state: actionState, adapter: trackedAdapter, components, slots, memoryKey, pending: pendingMessage, unreadAfterMessages }, memoryKey),
|
|
2751
2845
|
/* @__PURE__ */ jsx10(ContinuationBar, { state: actionState, adapter: trackedAdapter, labels }),
|
|
2752
2846
|
/* @__PURE__ */ jsx10(Advisory, { state: actionState, adapter: trackedAdapter, value: state.interopSettings?.advisories[0] ?? null, onReview: (recommendedChange) => setSettings({ recommendedChange }) }),
|
|
2753
|
-
state.mode !== "mirror" || state.canSend ? /* @__PURE__ */ jsx10(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,
|
|
2847
|
+
state.mode !== "mirror" || state.canSend ? /* @__PURE__ */ jsx10(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,
|
|
2754
2848
|
settings ? /* @__PURE__ */ jsx10(SettingsPanel, { state: actionState, adapter: trackedAdapter, value: state.interopSettings, recommendedChange: settings.recommendedChange, onClose: () => setSettings(null) }) : null
|
|
2755
2849
|
] });
|
|
2756
2850
|
}
|
|
2757
|
-
function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey, headerActions: HeaderActions, contextCandidates, components, navigation }) {
|
|
2851
|
+
function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey, headerActions: HeaderActions, contextCandidates, components, navigation, composerCommand }) {
|
|
2758
2852
|
const startable = state.harnesses.filter((item) => item.startable);
|
|
2759
2853
|
const startableKey = startable.map((item) => `${item.id}:${item.launchModes?.join(",")}:${item.preferredLaunchMode ?? ""}`).join("\0");
|
|
2760
2854
|
const remembered = newChatMemory.get(memoryKey) ?? { harness: startable[0]?.id ?? "", draft: "", context: [], images: [], modes: {} };
|
|
@@ -2768,8 +2862,10 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2768
2862
|
const [dragging, setDragging] = useState6(false);
|
|
2769
2863
|
const [pickerError, setPickerError] = useState6(null);
|
|
2770
2864
|
const startSequence = useRef7(0);
|
|
2865
|
+
const lastComposerCommand = useRef7(null);
|
|
2771
2866
|
const textarea = useRef7(null);
|
|
2772
2867
|
const selectedHarness = startable.find((item) => item.id === harness) ?? null;
|
|
2868
|
+
const Picker = components.HarnessPicker ?? HarnessPicker;
|
|
2773
2869
|
const Readiness = components.HarnessReadiness ?? HarnessReadiness;
|
|
2774
2870
|
const launchModes = selectedHarness?.launchModes?.length ? selectedHarness.launchModes : ["headless"];
|
|
2775
2871
|
const rememberedMode = modes[harness];
|
|
@@ -2810,6 +2906,32 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2810
2906
|
remember({ harness: nextHarness, draft: nextDraft, context: nextContext, images: nextImages });
|
|
2811
2907
|
textarea.current?.focus({ preventScroll: true });
|
|
2812
2908
|
}, [navigation?.id]);
|
|
2909
|
+
useEffect8(() => {
|
|
2910
|
+
if (!composerCommand || composerCommand.id === lastComposerCommand.current) return;
|
|
2911
|
+
lastComposerCommand.current = composerCommand.id;
|
|
2912
|
+
if (composerCommand.action === "attach") {
|
|
2913
|
+
if (mode === "terminal" && !launchModes.includes("headless")) {
|
|
2914
|
+
setPickerError("Terminal starts currently accept text only. Choose Chat to attach context or images.");
|
|
2915
|
+
} else {
|
|
2916
|
+
try {
|
|
2917
|
+
const attachments = partitionAttachments(composerCommand.attachments);
|
|
2918
|
+
if (context.length + attachments.context.length > MAX_CONTEXT_ITEMS) throw new Error(`Attach at most ${MAX_CONTEXT_ITEMS} text items.`);
|
|
2919
|
+
if (images.length + attachments.images.length > MAX_IMAGE_ITEMS) throw new Error(`Attach at most ${MAX_IMAGE_ITEMS} images.`);
|
|
2920
|
+
const nextContext = mergeContext(context, attachments.context);
|
|
2921
|
+
const nextImages = mergeImages(images, attachments.images);
|
|
2922
|
+
const nextModes = mode === "terminal" ? { ...modes, [harness]: "headless" } : modes;
|
|
2923
|
+
if (nextModes !== modes) setModes(nextModes);
|
|
2924
|
+
setContext(nextContext);
|
|
2925
|
+
setImages(nextImages);
|
|
2926
|
+
setPickerError(null);
|
|
2927
|
+
remember({ context: nextContext, images: nextImages, modes: nextModes });
|
|
2928
|
+
} catch (error) {
|
|
2929
|
+
setPickerError(error instanceof Error ? error.message : "Could not attach context.");
|
|
2930
|
+
}
|
|
2931
|
+
}
|
|
2932
|
+
}
|
|
2933
|
+
textarea.current?.focus({ preventScroll: true });
|
|
2934
|
+
}, [composerCommand?.id]);
|
|
2813
2935
|
const pickContext = () => {
|
|
2814
2936
|
if (mode === "terminal" || !adapter.pickContext || picking || context.length >= MAX_CONTEXT_ITEMS && images.length >= MAX_IMAGE_ITEMS) return;
|
|
2815
2937
|
setPicking(true);
|
|
@@ -2907,18 +3029,6 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2907
3029
|
/* @__PURE__ */ jsx10("small", { children: "Choose a coding harness and send the first message." })
|
|
2908
3030
|
] }),
|
|
2909
3031
|
/* @__PURE__ */ jsxs9("div", { className: "scui-compose", children: [
|
|
2910
|
-
/* @__PURE__ */ jsxs9("label", { className: "scui-harness-picker", children: [
|
|
2911
|
-
/* @__PURE__ */ jsx10(HarnessLogo, { id: harness, size: 24 }),
|
|
2912
|
-
/* @__PURE__ */ jsx10("span", { children: "Coding harness" }),
|
|
2913
|
-
/* @__PURE__ */ jsx10("select", { value: harness, disabled: Boolean(starting), onChange: (event) => {
|
|
2914
|
-
const value = event.currentTarget.value;
|
|
2915
|
-
setHarness(value);
|
|
2916
|
-
remember({ harness: value });
|
|
2917
|
-
}, children: state.harnesses.map((item) => /* @__PURE__ */ jsxs9("option", { value: item.id, disabled: !item.startable, children: [
|
|
2918
|
-
item.label,
|
|
2919
|
-
item.startable ? "" : " \xB7 unavailable"
|
|
2920
|
-
] }, item.id)) })
|
|
2921
|
-
] }),
|
|
2922
3032
|
/* @__PURE__ */ jsx10(Readiness, { harnesses: state.harnesses, state, adapter }),
|
|
2923
3033
|
launchModes.length > 1 ? /* @__PURE__ */ jsxs9("fieldset", { className: "scui-launch-modes", disabled: Boolean(starting), children: [
|
|
2924
3034
|
/* @__PURE__ */ jsx10("legend", { children: "Run as" }),
|
|
@@ -2947,7 +3057,7 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2947
3057
|
return next;
|
|
2948
3058
|
}) }),
|
|
2949
3059
|
terminalAttachments ? /* @__PURE__ */ jsx10("small", { className: "scui-context-error", role: "alert", children: "Terminal starts currently accept text only. Remove attachments or choose Chat." }) : pickerError ? /* @__PURE__ */ jsx10("small", { className: "scui-context-error", role: "alert", children: pickerError }) : null,
|
|
2950
|
-
/* @__PURE__ */ jsxs9("div", { className: `scui-envelope${dragging ? " scui-drop-target" : ""}`, onDragEnter: (event) => {
|
|
3060
|
+
/* @__PURE__ */ jsxs9("div", { className: `scui-envelope scui-new-envelope${dragging ? " scui-drop-target" : ""}`, onDragEnter: (event) => {
|
|
2951
3061
|
if (Array.from(event.dataTransfer?.types ?? []).includes("Files")) {
|
|
2952
3062
|
event.preventDefault();
|
|
2953
3063
|
setDragging(true);
|
|
@@ -2957,7 +3067,6 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2957
3067
|
}, onDragLeave: (event) => {
|
|
2958
3068
|
if (!event.currentTarget.contains(event.relatedTarget)) setDragging(false);
|
|
2959
3069
|
}, onDrop: dropImages, children: [
|
|
2960
|
-
adapter.pickContext ? /* @__PURE__ */ jsx10("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__ */ jsx10("i", { className: "scui-control-spinner" }) : /* @__PURE__ */ jsx10(UiIcon, { name: "attach", size: 17 }) }) : null,
|
|
2961
3070
|
/* @__PURE__ */ jsx10("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) => {
|
|
2962
3071
|
const value = event.currentTarget.value;
|
|
2963
3072
|
setDraft(value);
|
|
@@ -2968,12 +3077,19 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2968
3077
|
send();
|
|
2969
3078
|
}
|
|
2970
3079
|
} }),
|
|
2971
|
-
/* @__PURE__ */
|
|
3080
|
+
/* @__PURE__ */ jsxs9("footer", { className: "scui-new-envelope-controls", children: [
|
|
3081
|
+
adapter.pickContext ? /* @__PURE__ */ jsx10("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__ */ jsx10("i", { className: "scui-control-spinner" }) : /* @__PURE__ */ jsx10(UiIcon, { name: "attach", size: 17 }) }) : null,
|
|
3082
|
+
/* @__PURE__ */ jsx10(Picker, { harnesses: state.harnesses, value: harness, disabled: Boolean(starting), adapter, logo: components.HarnessLogo, onChange: (value) => {
|
|
3083
|
+
setHarness(value);
|
|
3084
|
+
remember({ harness: value });
|
|
3085
|
+
} }),
|
|
3086
|
+
/* @__PURE__ */ jsx10("span", { children: /* @__PURE__ */ jsx10("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__ */ jsx10("i", { className: "scui-control-spinner" }) : /* @__PURE__ */ jsx10(UiIcon, { name: "send", size: 17 }) }) })
|
|
3087
|
+
] })
|
|
2972
3088
|
] })
|
|
2973
3089
|
] })
|
|
2974
3090
|
] });
|
|
2975
3091
|
}
|
|
2976
|
-
function SupercodeMessenger({ state: stateInput, adapter, class: className = "", initialView, navigation = null, onViewChange, contextCandidates: candidatesInput = [], labels, components = {}, slots = {} }) {
|
|
3092
|
+
function SupercodeMessenger({ state: stateInput, adapter, class: className = "", initialView, navigation = null, composerCommand = null, onViewChange, contextCandidates: candidatesInput = [], labels, components = {}, slots = {} }) {
|
|
2977
3093
|
const state = useMemo3(() => normalizeUiState(stateInput), [stateInput]);
|
|
2978
3094
|
const contextCandidates = useMemo3(() => normalizeAttachmentCandidates(candidatesInput), [candidatesInput]);
|
|
2979
3095
|
const copy = { ...DEFAULT_LABELS, ...labels };
|
|
@@ -3044,7 +3160,7 @@ function SupercodeMessenger({ state: stateInput, adapter, class: className = "",
|
|
|
3044
3160
|
setNewNavigation(null);
|
|
3045
3161
|
setView("new");
|
|
3046
3162
|
}, onClose: adapter.onClose ? close : void 0, components, labels: copy, memoryKey, slots, headerActions: HeaderActions }) : null,
|
|
3047
|
-
view === "new" ? /* @__PURE__ */ jsx10(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,
|
|
3163
|
+
view === "new" ? /* @__PURE__ */ jsx10(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,
|
|
3048
3164
|
view === "chat" ? /* @__PURE__ */ jsx10(Chat, { state, adapter, onBack: () => {
|
|
3049
3165
|
setListFocus(state.attached?.key ?? listFocus);
|
|
3050
3166
|
setView("list");
|
|
@@ -3053,7 +3169,7 @@ function SupercodeMessenger({ state: stateInput, adapter, class: className = "",
|
|
|
3053
3169
|
setChatNavigation(null);
|
|
3054
3170
|
setNewNavigation(null);
|
|
3055
3171
|
setView("new");
|
|
3056
|
-
}, onClose: adapter.onClose ? close : void 0, components, slots, labels: copy, contextCandidates, navigation: chatNavigation?.sessionKey === state.attached?.key ? chatNavigation : null }) : null,
|
|
3172
|
+
}, onClose: adapter.onClose ? close : void 0, components, slots, labels: copy, contextCandidates, navigation: chatNavigation?.sessionKey === state.attached?.key ? chatNavigation : null, composerCommand }) : null,
|
|
3057
3173
|
opening ? /* @__PURE__ */ jsxs9("div", { className: "scui-opening", role: "status", "aria-busy": "true", children: [
|
|
3058
3174
|
/* @__PURE__ */ jsx10(HarnessLogo, { id: opening.harness, size: 34 }),
|
|
3059
3175
|
/* @__PURE__ */ jsxs9("span", { children: [
|
|
@@ -3078,6 +3194,7 @@ export {
|
|
|
3078
3194
|
Conversation,
|
|
3079
3195
|
HarnessAdvisory,
|
|
3080
3196
|
HarnessLogo,
|
|
3197
|
+
HarnessPicker,
|
|
3081
3198
|
HarnessReadiness,
|
|
3082
3199
|
HarnessSettingsPanel,
|
|
3083
3200
|
ImageViewer,
|
package/composer.mjs
CHANGED
|
@@ -318,7 +318,7 @@ function ContinuationBar({ state, adapter, labels = DEFAULT_LABELS }) {
|
|
|
318
318
|
] })
|
|
319
319
|
] });
|
|
320
320
|
}
|
|
321
|
-
function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`, pendingStatus = null, restoreDraft = null, contextCandidates = [], components = {}, onPending, onDraftRestored }) {
|
|
321
|
+
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 }) {
|
|
322
322
|
const remembered = composerMemory.get(memoryKey) ?? { draft: state.savedDraft, context: [], images: [], queue: [] };
|
|
323
323
|
const [draft, setDraft] = useState2(remembered.draft);
|
|
324
324
|
const [context, setContext] = useState2(remembered.context ?? []);
|
|
@@ -330,6 +330,7 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
|
|
|
330
330
|
const [dragging, setDragging] = useState2(false);
|
|
331
331
|
const [pickerError, setPickerError] = useState2(null);
|
|
332
332
|
const textarea = useRef2(null);
|
|
333
|
+
const lastCommand = useRef2(null);
|
|
333
334
|
useAutosizeTextarea(textarea, draft);
|
|
334
335
|
const remember = (nextDraft, nextContext, nextImages, nextQueue) => boundedSet(composerMemory, memoryKey, { draft: nextDraft, context: nextContext, images: nextImages, queue: nextQueue });
|
|
335
336
|
useEffect2(() => {
|
|
@@ -371,6 +372,26 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
|
|
|
371
372
|
textarea.current?.focus({ preventScroll: true });
|
|
372
373
|
onDraftRestored?.(restoreDraft.id);
|
|
373
374
|
}, [onDraftRestored, restoreDraft?.id]);
|
|
375
|
+
useEffect2(() => {
|
|
376
|
+
if (!command || command.id === lastCommand.current) return;
|
|
377
|
+
lastCommand.current = command.id;
|
|
378
|
+
if (command.action === "attach") {
|
|
379
|
+
try {
|
|
380
|
+
const attachments = partitionAttachments(command.attachments);
|
|
381
|
+
if (context.length + attachments.context.length > MAX_CONTEXT_ITEMS) throw new Error(`Attach at most ${MAX_CONTEXT_ITEMS} text items.`);
|
|
382
|
+
if (images.length + attachments.images.length > MAX_IMAGE_ITEMS) throw new Error(`Attach at most ${MAX_IMAGE_ITEMS} images.`);
|
|
383
|
+
const nextContext = mergeContext(context, attachments.context);
|
|
384
|
+
const nextImages = mergeImages(images, attachments.images);
|
|
385
|
+
setContext(nextContext);
|
|
386
|
+
setImages(nextImages);
|
|
387
|
+
setPickerError(null);
|
|
388
|
+
remember(draft, nextContext, nextImages, queue);
|
|
389
|
+
} catch (error) {
|
|
390
|
+
setPickerError(error instanceof Error ? error.message : "Could not attach context.");
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
textarea.current?.focus({ preventScroll: true });
|
|
394
|
+
}, [command?.id]);
|
|
374
395
|
useEffect2(() => {
|
|
375
396
|
const timer = setTimeout(() => adapter.onIntent({ action: "draft", text: draft }), 250);
|
|
376
397
|
return () => clearTimeout(timer);
|