@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/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__ */ 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 }) }) })
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__ */ 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 }) }) })
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: [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter-ai-dev/supercode-ui",
3
- "version": "0.1.41",
3
+ "version": "0.1.43",
4
4
  "type": "module",
5
5
  "description": "Composable default UI kit for Supercode-powered coding-agent experiences",
6
6
  "exports": {