@volter-ai-dev/supercode-ui 0.1.43 → 0.1.45

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 CHANGED
@@ -183,15 +183,16 @@ drafts, and artifact materialization remain explicit callbacks. A
183
183
  browser should receive projected state from a trusted host rather than instantiate a local
184
184
  controller or gain filesystem authority.
185
185
 
186
- Native continuation is headless by default. A host with a real terminal provider can add
187
- `terminal` to `continuationModes` and handle `onResumeTerminal`; the continuation bar then exposes
188
- that strategy beside “Continue here.” The UI never infers terminal support from a generic runtime
189
- handoff, and never presents a terminal strategy when the host cannot create one.
186
+ Native continuation exposes one action and a quiet execution-transport selector. A host with a real
187
+ terminal provider can add `terminal` to `continuationModes` and handle `onResumeTerminal`; Terminal
188
+ is then the initial choice and Headless remains available from the selector. The UI never infers
189
+ terminal support from a generic runtime handoff, and never presents a terminal transport when the
190
+ host cannot create one.
190
191
 
191
192
  New-session execution is advertised per harness. Set `launchModes: ['headless', 'terminal']` and
192
- `preferredLaunchMode` on a `HarnessOption`; the complete messenger renders a compact “Chat” versus
193
- “Terminal” choice and emits that `mode` on the `new` intent. Direct controller bindings keep Chat as
194
- the standard path and delegate Terminal only through `onStartTerminal`, so a terminal selection can
193
+ `preferredLaunchMode` on a `HarnessOption`; the complete messenger renders the same compact Terminal
194
+ versus Headless selector in the composer footer and emits that `mode` on the `new` intent. Direct
195
+ controller bindings delegate Terminal only through `onStartTerminal`, so a terminal selection can
195
196
  never accidentally create a second headless runtime. Hosts may remember the preference per harness;
196
197
  the messenger also retains the current browser draft choice while its New Chat view is open.
197
198
 
package/activity.mjs CHANGED
@@ -13,6 +13,7 @@ var DEFAULT_LABELS = Object.freeze({
13
13
  newChat: "New chat",
14
14
  searchChats: "Search chats",
15
15
  askAgent: "Ask your agent\u2026",
16
+ attachContext: "Attach files or images",
16
17
  continueHere: "Continue here",
17
18
  continueWithTerminal: "Continue with terminal",
18
19
  joinLive: "Join live",
@@ -737,7 +738,7 @@ function normalizeUiState(value) {
737
738
  if (!item || typeof item.id !== "string") return [];
738
739
  const startable = item.startable === true;
739
740
  const launchModes = Array.isArray(item.launchModes) ? [...new Set(item.launchModes.filter((mode) => mode === "headless" || mode === "terminal"))] : startable ? ["headless"] : [];
740
- const preferredLaunchMode = launchModes.includes(item.preferredLaunchMode) ? item.preferredLaunchMode : launchModes[0] ?? null;
741
+ const preferredLaunchMode = launchModes.includes(item.preferredLaunchMode) ? item.preferredLaunchMode : launchModes.includes("terminal") ? "terminal" : launchModes[0] ?? null;
741
742
  const capabilities = record(item.capabilities);
742
743
  return [{
743
744
  id: item.id,
package/components.d.ts CHANGED
@@ -6,6 +6,7 @@ export {
6
6
  ContextCandidate,
7
7
  ContextCandidates,
8
8
  ContinuationBar,
9
+ ExecutionModeSelect,
9
10
  Conversation,
10
11
  HarnessLogo,
11
12
  HarnessPicker,
package/components.mjs CHANGED
@@ -16,6 +16,7 @@ var DEFAULT_LABELS = Object.freeze({
16
16
  newChat: "New chat",
17
17
  searchChats: "Search chats",
18
18
  askAgent: "Ask your agent\u2026",
19
+ attachContext: "Attach files or images",
19
20
  continueHere: "Continue here",
20
21
  continueWithTerminal: "Continue with terminal",
21
22
  joinLive: "Join live",
@@ -749,7 +750,7 @@ function normalizeUiState(value) {
749
750
  if (!item || typeof item.id !== "string") return [];
750
751
  const startable = item.startable === true;
751
752
  const launchModes = Array.isArray(item.launchModes) ? [...new Set(item.launchModes.filter((mode) => mode === "headless" || mode === "terminal"))] : startable ? ["headless"] : [];
752
- const preferredLaunchMode = launchModes.includes(item.preferredLaunchMode) ? item.preferredLaunchMode : launchModes[0] ?? null;
753
+ const preferredLaunchMode = launchModes.includes(item.preferredLaunchMode) ? item.preferredLaunchMode : launchModes.includes("terminal") ? "terminal" : launchModes[0] ?? null;
753
754
  const capabilities = record(item.capabilities);
754
755
  return [{
755
756
  id: item.id,
@@ -1293,13 +1294,23 @@ function useAutosizeTextarea(ref, value) {
1293
1294
  // src/composer.jsx
1294
1295
  import { jsx as jsx3, jsxs as jsxs3 } from "preact/jsx-runtime";
1295
1296
  var composerMemory = /* @__PURE__ */ new Map();
1297
+ function ExecutionModeSelect({ modes = [], value, onChange, disabled = false, label = "Run in" }) {
1298
+ if (modes.length < 2) return null;
1299
+ return /* @__PURE__ */ jsxs3("label", { className: "scui-execution-mode", title: disabled ? void 0 : `${label}: ${value}`, children: [
1300
+ /* @__PURE__ */ jsx3("span", { children: label }),
1301
+ /* @__PURE__ */ jsx3("select", { "aria-label": label, value, disabled, onChange: (event) => onChange?.(event.currentTarget.value), children: modes.map((mode) => /* @__PURE__ */ jsx3("option", { value: mode, children: mode === "terminal" ? "Terminal" : "Headless" }, mode)) })
1302
+ ] });
1303
+ }
1296
1304
  function ContinuationBar({ state, adapter, labels = DEFAULT_LABELS }) {
1305
+ const continuationModes = state.continuationModes?.length ? state.continuationModes : ["headless"];
1306
+ const [selection, setSelection] = useState2({ key: null, mode: null });
1307
+ const requestedMode = selection.key === state.attached?.key ? selection.mode : null;
1308
+ const executionMode = continuationModes.includes(requestedMode) ? requestedMode : continuationModes.includes("terminal") ? "terminal" : continuationModes[0];
1297
1309
  if (state.mode !== "mirror" || state.canSend) return null;
1298
1310
  const attached = state.attached;
1299
1311
  const row = attached?.key ? state.sessions.find((item) => item.key === attached.key) : null;
1300
1312
  const activeElsewhere = row?.runtimeStatus === "running" || row?.runtimeStatus === "busy" || row?.runtimeStatus === "idle";
1301
1313
  const resume = canContinueHere(state);
1302
- const terminal = resume && state.continuationModes?.includes("terminal");
1303
1314
  const join = state.canAttach;
1304
1315
  const branch = state.canBranch;
1305
1316
  if (!resume && !join && !branch) return /* @__PURE__ */ jsx3("div", { className: "scui-continuation", children: /* @__PURE__ */ jsxs3("span", { children: [
@@ -1312,8 +1323,8 @@ function ContinuationBar({ state, adapter, labels = DEFAULT_LABELS }) {
1312
1323
  /* @__PURE__ */ jsx3("small", { children: join ? "Join the proven live runtime without taking it over." : resume ? `Resume this ${harnessDisplayName(state.harness)} session here.` : "Start an independent continuation." })
1313
1324
  ] }),
1314
1325
  /* @__PURE__ */ jsxs3("span", { className: "scui-continuation-actions", children: [
1315
- /* @__PURE__ */ jsx3("button", { type: "button", disabled: Boolean(state.operation), onClick: () => dispatchConfirmedIntent(adapter, join ? { action: "join" } : resume ? { action: "resume", mode: "headless" } : { action: "branch" }), children: join ? labels.joinLive : resume ? labels.continueHere : labels.forkHere }),
1316
- terminal ? /* @__PURE__ */ jsx3("button", { type: "button", className: "scui-secondary", disabled: Boolean(state.operation), onClick: () => dispatchConfirmedIntent(adapter, { action: "resume", mode: "terminal" }), children: labels.continueWithTerminal ?? DEFAULT_LABELS.continueWithTerminal }) : null
1326
+ !join && resume ? /* @__PURE__ */ jsx3(ExecutionModeSelect, { modes: continuationModes, value: executionMode, disabled: Boolean(state.operation), onChange: (mode) => setSelection({ key: state.attached?.key ?? null, mode }) }) : null,
1327
+ /* @__PURE__ */ jsx3("button", { type: "button", disabled: Boolean(state.operation), onClick: () => dispatchConfirmedIntent(adapter, join ? { action: "join" } : resume ? { action: "resume", mode: executionMode } : { action: "branch" }), children: join ? labels.joinLive : resume ? labels.continueHere : labels.forkHere })
1317
1328
  ] })
1318
1329
  ] });
1319
1330
  }
@@ -1513,7 +1524,7 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
1513
1524
  }, onDragLeave: (event) => {
1514
1525
  if (!event.currentTarget.contains(event.relatedTarget)) setDragging(false);
1515
1526
  }, onDrop: dropImages, children: [
1516
- adapter.pickContext && state.mode === "control" ? /* @__PURE__ */ jsx3("button", { className: "scui-attach", type: "button", "aria-label": "Attach files or images", disabled: picking || context.length >= MAX_CONTEXT_ITEMS && images.length >= MAX_IMAGE_ITEMS, onClick: pickContext, children: picking ? /* @__PURE__ */ jsx3("i", { className: "scui-control-spinner" }) : /* @__PURE__ */ jsx3(UiIcon, { name: "attach", size: 17 }) }) : null,
1527
+ adapter.pickContext && state.mode === "control" ? /* @__PURE__ */ jsx3("button", { className: "scui-attach", type: "button", "aria-label": labels.attachContext ?? DEFAULT_LABELS.attachContext, disabled: picking || context.length >= MAX_CONTEXT_ITEMS && images.length >= MAX_IMAGE_ITEMS, onClick: pickContext, children: picking ? /* @__PURE__ */ jsx3("i", { className: "scui-control-spinner" }) : /* @__PURE__ */ jsx3(UiIcon, { name: "attach", size: 17 }) }) : null,
1517
1528
  /* @__PURE__ */ jsx3("textarea", { ref: textarea, rows: 1, "aria-label": `Message ${harnessDisplayName(state.harness) || "agent"}`, placeholder: state.startup !== "ready" ? "Connecting\u2026" : pendingStatus === "failed" ? "Retry or edit the unsent message\u2026" : pendingStatus === "editing" ? "Edit and resend\u2026" : steerAvailable && context.length === 0 && images.length === 0 ? "Redirect the current turn\u2026" : queueBlocked ? "Queue a follow-up\u2026" : labels.askAgent, value: draft, disabled: state.mode !== "control" && !state.canSend, onPaste: pasteImages, onInput: (event) => {
1518
1529
  const value = event.currentTarget.value;
1519
1530
  setDraft(value);
@@ -2869,7 +2880,8 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
2869
2880
  const Readiness = components.HarnessReadiness ?? HarnessReadiness;
2870
2881
  const launchModes = selectedHarness?.launchModes?.length ? selectedHarness.launchModes : ["headless"];
2871
2882
  const rememberedMode = modes[harness];
2872
- const mode = launchModes.includes(rememberedMode) ? rememberedMode : launchModes.includes(selectedHarness?.preferredLaunchMode) ? selectedHarness.preferredLaunchMode : launchModes[0];
2883
+ const requestedMode = launchModes.includes(rememberedMode) ? rememberedMode : launchModes.includes(selectedHarness?.preferredLaunchMode) ? selectedHarness.preferredLaunchMode : launchModes[0];
2884
+ const mode = (context.length > 0 || images.length > 0) && launchModes.includes("headless") ? "headless" : requestedMode;
2873
2885
  const terminalAttachments = mode === "terminal" && (context.length > 0 || images.length > 0);
2874
2886
  const remember = (overrides = {}) => boundedSet(newChatMemory, memoryKey, { harness, draft, context, images, modes, ...overrides });
2875
2887
  useAutosizeTextarea(textarea, draft);
@@ -2911,7 +2923,7 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
2911
2923
  lastComposerCommand.current = composerCommand.id;
2912
2924
  if (composerCommand.action === "attach") {
2913
2925
  if (mode === "terminal" && !launchModes.includes("headless")) {
2914
- setPickerError("Terminal starts currently accept text only. Choose Chat to attach context or images.");
2926
+ setPickerError("Terminal starts currently accept text only. Choose Headless to attach context or images.");
2915
2927
  } else {
2916
2928
  try {
2917
2929
  const attachments = partitionAttachments(composerCommand.attachments);
@@ -2919,12 +2931,10 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
2919
2931
  if (images.length + attachments.images.length > MAX_IMAGE_ITEMS) throw new Error(`Attach at most ${MAX_IMAGE_ITEMS} images.`);
2920
2932
  const nextContext = mergeContext(context, attachments.context);
2921
2933
  const nextImages = mergeImages(images, attachments.images);
2922
- const nextModes = mode === "terminal" ? { ...modes, [harness]: "headless" } : modes;
2923
- if (nextModes !== modes) setModes(nextModes);
2924
2934
  setContext(nextContext);
2925
2935
  setImages(nextImages);
2926
2936
  setPickerError(null);
2927
- remember({ context: nextContext, images: nextImages, modes: nextModes });
2937
+ remember({ context: nextContext, images: nextImages });
2928
2938
  } catch (error) {
2929
2939
  setPickerError(error instanceof Error ? error.message : "Could not attach context.");
2930
2940
  }
@@ -2965,7 +2975,7 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
2965
2975
  const allFiles = Array.from(value ?? []);
2966
2976
  if (!allFiles.length) return false;
2967
2977
  if (mode === "terminal") {
2968
- setPickerError("Terminal starts currently accept text only. Choose Chat to attach context or images.");
2978
+ setPickerError("Terminal starts currently accept text only. Choose Headless to attach context or images.");
2969
2979
  return true;
2970
2980
  }
2971
2981
  const files = allFiles.filter((file) => file.type.startsWith("image/"));
@@ -3030,21 +3040,6 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
3030
3040
  ] }),
3031
3041
  /* @__PURE__ */ jsxs9("div", { className: "scui-compose", children: [
3032
3042
  /* @__PURE__ */ jsx10(Readiness, { harnesses: state.harnesses, state, adapter }),
3033
- launchModes.length > 1 ? /* @__PURE__ */ jsxs9("fieldset", { className: "scui-launch-modes", disabled: Boolean(starting), children: [
3034
- /* @__PURE__ */ jsx10("legend", { children: "Run as" }),
3035
- launchModes.map((item) => /* @__PURE__ */ jsxs9("label", { children: [
3036
- /* @__PURE__ */ jsx10("input", { type: "radio", name: `launch-mode-${memoryKey}`, value: item, checked: mode === item, onChange: () => {
3037
- const next = { ...modes, [harness]: item };
3038
- setModes(next);
3039
- setPickerError(null);
3040
- remember({ modes: next });
3041
- } }),
3042
- /* @__PURE__ */ jsxs9("span", { children: [
3043
- /* @__PURE__ */ jsx10("strong", { children: item === "terminal" ? "Terminal" : "Chat" }),
3044
- /* @__PURE__ */ jsx10("small", { children: item === "terminal" ? "Interactive tmux session" : "Managed here" })
3045
- ] })
3046
- ] }, item))
3047
- ] }) : null,
3048
3043
  mode !== "terminal" ? /* @__PURE__ */ jsx10(ContextCandidates, { items: contextCandidates, context, images, state, adapter, onAttach: attachCandidate, component: components.ContextCandidate }) : null,
3049
3044
  /* @__PURE__ */ jsx10(ImageTray, { items: images, onRemove: (index) => setImages((items) => {
3050
3045
  const next = items.filter((_, itemIndex) => itemIndex !== index);
@@ -3056,7 +3051,7 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
3056
3051
  remember({ context: next });
3057
3052
  return next;
3058
3053
  }) }),
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,
3054
+ terminalAttachments ? /* @__PURE__ */ jsx10("small", { className: "scui-context-error", role: "alert", children: "Terminal starts currently accept text only. Remove attachments or choose Headless." }) : pickerError ? /* @__PURE__ */ jsx10("small", { className: "scui-context-error", role: "alert", children: pickerError }) : null,
3060
3055
  /* @__PURE__ */ jsxs9("div", { className: `scui-envelope scui-new-envelope${dragging ? " scui-drop-target" : ""}`, onDragEnter: (event) => {
3061
3056
  if (Array.from(event.dataTransfer?.types ?? []).includes("Files")) {
3062
3057
  event.preventDefault();
@@ -3078,12 +3073,18 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
3078
3073
  }
3079
3074
  } }),
3080
3075
  /* @__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,
3076
+ adapter.pickContext ? /* @__PURE__ */ jsx10("button", { className: "scui-attach", type: "button", "aria-label": labels.attachContext ?? DEFAULT_LABELS.attachContext, 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
3077
  /* @__PURE__ */ jsx10(Picker, { harnesses: state.harnesses, value: harness, disabled: Boolean(starting), adapter, logo: components.HarnessLogo, onChange: (value) => {
3083
3078
  setHarness(value);
3084
3079
  remember({ harness: value });
3085
3080
  } }),
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 }) }) })
3081
+ /* @__PURE__ */ jsx10(ExecutionModeSelect, { modes: launchModes, value: mode, disabled: Boolean(starting) || mode !== requestedMode, onChange: (item) => {
3082
+ const next = { ...modes, [harness]: item };
3083
+ setModes(next);
3084
+ setPickerError(null);
3085
+ remember({ modes: next });
3086
+ } }),
3087
+ /* @__PURE__ */ jsx10("span", { children: /* @__PURE__ */ jsx10("button", { type: "button", className: "scui-send", "aria-label": mode === "terminal" ? "Start terminal session" : "Start headless session", 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
3088
  ] })
3088
3089
  ] })
3089
3090
  ] })
@@ -3192,6 +3193,7 @@ export {
3192
3193
  ContextCandidates,
3193
3194
  ContinuationBar,
3194
3195
  Conversation,
3196
+ ExecutionModeSelect,
3195
3197
  HarnessAdvisory,
3196
3198
  HarnessLogo,
3197
3199
  HarnessPicker,
package/composer.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  export type { MessengerLabels, PendingMessageModel, SupercodeUiState, UiAdapter } from './index.js';
2
- export { Composer, ContinuationBar } from './index.js';
2
+ export { Composer, ContinuationBar, ExecutionModeSelect } from './index.js';
package/composer.mjs CHANGED
@@ -16,6 +16,7 @@ var DEFAULT_LABELS = Object.freeze({
16
16
  newChat: "New chat",
17
17
  searchChats: "Search chats",
18
18
  askAgent: "Ask your agent\u2026",
19
+ attachContext: "Attach files or images",
19
20
  continueHere: "Continue here",
20
21
  continueWithTerminal: "Continue with terminal",
21
22
  joinLive: "Join live",
@@ -294,13 +295,23 @@ function useAutosizeTextarea(ref, value) {
294
295
  // src/composer.jsx
295
296
  import { jsx as jsx3, jsxs as jsxs3 } from "preact/jsx-runtime";
296
297
  var composerMemory = /* @__PURE__ */ new Map();
298
+ function ExecutionModeSelect({ modes = [], value, onChange, disabled = false, label = "Run in" }) {
299
+ if (modes.length < 2) return null;
300
+ return /* @__PURE__ */ jsxs3("label", { className: "scui-execution-mode", title: disabled ? void 0 : `${label}: ${value}`, children: [
301
+ /* @__PURE__ */ jsx3("span", { children: label }),
302
+ /* @__PURE__ */ jsx3("select", { "aria-label": label, value, disabled, onChange: (event) => onChange?.(event.currentTarget.value), children: modes.map((mode) => /* @__PURE__ */ jsx3("option", { value: mode, children: mode === "terminal" ? "Terminal" : "Headless" }, mode)) })
303
+ ] });
304
+ }
297
305
  function ContinuationBar({ state, adapter, labels = DEFAULT_LABELS }) {
306
+ const continuationModes = state.continuationModes?.length ? state.continuationModes : ["headless"];
307
+ const [selection, setSelection] = useState2({ key: null, mode: null });
308
+ const requestedMode = selection.key === state.attached?.key ? selection.mode : null;
309
+ const executionMode = continuationModes.includes(requestedMode) ? requestedMode : continuationModes.includes("terminal") ? "terminal" : continuationModes[0];
298
310
  if (state.mode !== "mirror" || state.canSend) return null;
299
311
  const attached = state.attached;
300
312
  const row = attached?.key ? state.sessions.find((item) => item.key === attached.key) : null;
301
313
  const activeElsewhere = row?.runtimeStatus === "running" || row?.runtimeStatus === "busy" || row?.runtimeStatus === "idle";
302
314
  const resume = canContinueHere(state);
303
- const terminal = resume && state.continuationModes?.includes("terminal");
304
315
  const join = state.canAttach;
305
316
  const branch = state.canBranch;
306
317
  if (!resume && !join && !branch) return /* @__PURE__ */ jsx3("div", { className: "scui-continuation", children: /* @__PURE__ */ jsxs3("span", { children: [
@@ -313,8 +324,8 @@ function ContinuationBar({ state, adapter, labels = DEFAULT_LABELS }) {
313
324
  /* @__PURE__ */ jsx3("small", { children: join ? "Join the proven live runtime without taking it over." : resume ? `Resume this ${harnessDisplayName(state.harness)} session here.` : "Start an independent continuation." })
314
325
  ] }),
315
326
  /* @__PURE__ */ jsxs3("span", { className: "scui-continuation-actions", children: [
316
- /* @__PURE__ */ jsx3("button", { type: "button", disabled: Boolean(state.operation), onClick: () => dispatchConfirmedIntent(adapter, join ? { action: "join" } : resume ? { action: "resume", mode: "headless" } : { action: "branch" }), children: join ? labels.joinLive : resume ? labels.continueHere : labels.forkHere }),
317
- terminal ? /* @__PURE__ */ jsx3("button", { type: "button", className: "scui-secondary", disabled: Boolean(state.operation), onClick: () => dispatchConfirmedIntent(adapter, { action: "resume", mode: "terminal" }), children: labels.continueWithTerminal ?? DEFAULT_LABELS.continueWithTerminal }) : null
327
+ !join && resume ? /* @__PURE__ */ jsx3(ExecutionModeSelect, { modes: continuationModes, value: executionMode, disabled: Boolean(state.operation), onChange: (mode) => setSelection({ key: state.attached?.key ?? null, mode }) }) : null,
328
+ /* @__PURE__ */ jsx3("button", { type: "button", disabled: Boolean(state.operation), onClick: () => dispatchConfirmedIntent(adapter, join ? { action: "join" } : resume ? { action: "resume", mode: executionMode } : { action: "branch" }), children: join ? labels.joinLive : resume ? labels.continueHere : labels.forkHere })
318
329
  ] })
319
330
  ] });
320
331
  }
@@ -514,7 +525,7 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
514
525
  }, onDragLeave: (event) => {
515
526
  if (!event.currentTarget.contains(event.relatedTarget)) setDragging(false);
516
527
  }, onDrop: dropImages, children: [
517
- adapter.pickContext && state.mode === "control" ? /* @__PURE__ */ jsx3("button", { className: "scui-attach", type: "button", "aria-label": "Attach files or images", disabled: picking || context.length >= MAX_CONTEXT_ITEMS && images.length >= MAX_IMAGE_ITEMS, onClick: pickContext, children: picking ? /* @__PURE__ */ jsx3("i", { className: "scui-control-spinner" }) : /* @__PURE__ */ jsx3(UiIcon, { name: "attach", size: 17 }) }) : null,
528
+ adapter.pickContext && state.mode === "control" ? /* @__PURE__ */ jsx3("button", { className: "scui-attach", type: "button", "aria-label": labels.attachContext ?? DEFAULT_LABELS.attachContext, disabled: picking || context.length >= MAX_CONTEXT_ITEMS && images.length >= MAX_IMAGE_ITEMS, onClick: pickContext, children: picking ? /* @__PURE__ */ jsx3("i", { className: "scui-control-spinner" }) : /* @__PURE__ */ jsx3(UiIcon, { name: "attach", size: 17 }) }) : null,
518
529
  /* @__PURE__ */ jsx3("textarea", { ref: textarea, rows: 1, "aria-label": `Message ${harnessDisplayName(state.harness) || "agent"}`, placeholder: state.startup !== "ready" ? "Connecting\u2026" : pendingStatus === "failed" ? "Retry or edit the unsent message\u2026" : pendingStatus === "editing" ? "Edit and resend\u2026" : steerAvailable && context.length === 0 && images.length === 0 ? "Redirect the current turn\u2026" : queueBlocked ? "Queue a follow-up\u2026" : labels.askAgent, value: draft, disabled: state.mode !== "control" && !state.canSend, onPaste: pasteImages, onInput: (event) => {
519
530
  const value = event.currentTarget.value;
520
531
  setDraft(value);
@@ -535,5 +546,6 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
535
546
  }
536
547
  export {
537
548
  Composer,
538
- ContinuationBar
549
+ ContinuationBar,
550
+ ExecutionModeSelect
539
551
  };
package/conversation.mjs CHANGED
@@ -17,6 +17,7 @@ var DEFAULT_LABELS = Object.freeze({
17
17
  newChat: "New chat",
18
18
  searchChats: "Search chats",
19
19
  askAgent: "Ask your agent\u2026",
20
+ attachContext: "Attach files or images",
20
21
  continueHere: "Continue here",
21
22
  continueWithTerminal: "Continue with terminal",
22
23
  joinLive: "Join live",
package/core.mjs CHANGED
@@ -13,6 +13,7 @@ export const DEFAULT_LABELS = Object.freeze({
13
13
  newChat: 'New chat',
14
14
  searchChats: 'Search chats',
15
15
  askAgent: 'Ask your agent…',
16
+ attachContext: 'Attach files or images',
16
17
  continueHere: 'Continue here',
17
18
  continueWithTerminal: 'Continue with terminal',
18
19
  joinLive: 'Join live',
@@ -805,7 +806,9 @@ export function normalizeUiState(value) {
805
806
  : startable ? ['headless'] : [];
806
807
  const preferredLaunchMode = launchModes.includes(item.preferredLaunchMode)
807
808
  ? item.preferredLaunchMode
808
- : launchModes[0] ?? null;
809
+ : launchModes.includes('terminal')
810
+ ? 'terminal'
811
+ : launchModes[0] ?? null;
809
812
  const capabilities = record(item.capabilities);
810
813
  return [{
811
814
  id: item.id,
package/embed.mjs CHANGED
@@ -19,6 +19,7 @@ var DEFAULT_LABELS = Object.freeze({
19
19
  newChat: "New chat",
20
20
  searchChats: "Search chats",
21
21
  askAgent: "Ask your agent\u2026",
22
+ attachContext: "Attach files or images",
22
23
  continueHere: "Continue here",
23
24
  continueWithTerminal: "Continue with terminal",
24
25
  joinLive: "Join live",
@@ -752,7 +753,7 @@ function normalizeUiState(value) {
752
753
  if (!item || typeof item.id !== "string") return [];
753
754
  const startable = item.startable === true;
754
755
  const launchModes = Array.isArray(item.launchModes) ? [...new Set(item.launchModes.filter((mode) => mode === "headless" || mode === "terminal"))] : startable ? ["headless"] : [];
755
- const preferredLaunchMode = launchModes.includes(item.preferredLaunchMode) ? item.preferredLaunchMode : launchModes[0] ?? null;
756
+ const preferredLaunchMode = launchModes.includes(item.preferredLaunchMode) ? item.preferredLaunchMode : launchModes.includes("terminal") ? "terminal" : launchModes[0] ?? null;
756
757
  const capabilities = record(item.capabilities);
757
758
  return [{
758
759
  id: item.id,
@@ -1259,13 +1260,23 @@ function useAutosizeTextarea(ref, value) {
1259
1260
  // src/composer.jsx
1260
1261
  import { jsx as jsx3, jsxs as jsxs3 } from "preact/jsx-runtime";
1261
1262
  var composerMemory = /* @__PURE__ */ new Map();
1263
+ function ExecutionModeSelect({ modes = [], value, onChange, disabled = false, label = "Run in" }) {
1264
+ if (modes.length < 2) return null;
1265
+ return /* @__PURE__ */ jsxs3("label", { className: "scui-execution-mode", title: disabled ? void 0 : `${label}: ${value}`, children: [
1266
+ /* @__PURE__ */ jsx3("span", { children: label }),
1267
+ /* @__PURE__ */ jsx3("select", { "aria-label": label, value, disabled, onChange: (event) => onChange?.(event.currentTarget.value), children: modes.map((mode) => /* @__PURE__ */ jsx3("option", { value: mode, children: mode === "terminal" ? "Terminal" : "Headless" }, mode)) })
1268
+ ] });
1269
+ }
1262
1270
  function ContinuationBar({ state, adapter, labels = DEFAULT_LABELS }) {
1271
+ const continuationModes = state.continuationModes?.length ? state.continuationModes : ["headless"];
1272
+ const [selection, setSelection] = useState2({ key: null, mode: null });
1273
+ const requestedMode = selection.key === state.attached?.key ? selection.mode : null;
1274
+ const executionMode = continuationModes.includes(requestedMode) ? requestedMode : continuationModes.includes("terminal") ? "terminal" : continuationModes[0];
1263
1275
  if (state.mode !== "mirror" || state.canSend) return null;
1264
1276
  const attached = state.attached;
1265
1277
  const row = attached?.key ? state.sessions.find((item) => item.key === attached.key) : null;
1266
1278
  const activeElsewhere = row?.runtimeStatus === "running" || row?.runtimeStatus === "busy" || row?.runtimeStatus === "idle";
1267
1279
  const resume = canContinueHere(state);
1268
- const terminal = resume && state.continuationModes?.includes("terminal");
1269
1280
  const join = state.canAttach;
1270
1281
  const branch = state.canBranch;
1271
1282
  if (!resume && !join && !branch) return /* @__PURE__ */ jsx3("div", { className: "scui-continuation", children: /* @__PURE__ */ jsxs3("span", { children: [
@@ -1278,8 +1289,8 @@ function ContinuationBar({ state, adapter, labels = DEFAULT_LABELS }) {
1278
1289
  /* @__PURE__ */ jsx3("small", { children: join ? "Join the proven live runtime without taking it over." : resume ? `Resume this ${harnessDisplayName(state.harness)} session here.` : "Start an independent continuation." })
1279
1290
  ] }),
1280
1291
  /* @__PURE__ */ jsxs3("span", { className: "scui-continuation-actions", children: [
1281
- /* @__PURE__ */ jsx3("button", { type: "button", disabled: Boolean(state.operation), onClick: () => dispatchConfirmedIntent(adapter, join ? { action: "join" } : resume ? { action: "resume", mode: "headless" } : { action: "branch" }), children: join ? labels.joinLive : resume ? labels.continueHere : labels.forkHere }),
1282
- terminal ? /* @__PURE__ */ jsx3("button", { type: "button", className: "scui-secondary", disabled: Boolean(state.operation), onClick: () => dispatchConfirmedIntent(adapter, { action: "resume", mode: "terminal" }), children: labels.continueWithTerminal ?? DEFAULT_LABELS.continueWithTerminal }) : null
1292
+ !join && resume ? /* @__PURE__ */ jsx3(ExecutionModeSelect, { modes: continuationModes, value: executionMode, disabled: Boolean(state.operation), onChange: (mode) => setSelection({ key: state.attached?.key ?? null, mode }) }) : null,
1293
+ /* @__PURE__ */ jsx3("button", { type: "button", disabled: Boolean(state.operation), onClick: () => dispatchConfirmedIntent(adapter, join ? { action: "join" } : resume ? { action: "resume", mode: executionMode } : { action: "branch" }), children: join ? labels.joinLive : resume ? labels.continueHere : labels.forkHere })
1283
1294
  ] })
1284
1295
  ] });
1285
1296
  }
@@ -1479,7 +1490,7 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
1479
1490
  }, onDragLeave: (event) => {
1480
1491
  if (!event.currentTarget.contains(event.relatedTarget)) setDragging(false);
1481
1492
  }, onDrop: dropImages, children: [
1482
- adapter.pickContext && state.mode === "control" ? /* @__PURE__ */ jsx3("button", { className: "scui-attach", type: "button", "aria-label": "Attach files or images", disabled: picking || context.length >= MAX_CONTEXT_ITEMS && images.length >= MAX_IMAGE_ITEMS, onClick: pickContext, children: picking ? /* @__PURE__ */ jsx3("i", { className: "scui-control-spinner" }) : /* @__PURE__ */ jsx3(UiIcon, { name: "attach", size: 17 }) }) : null,
1493
+ adapter.pickContext && state.mode === "control" ? /* @__PURE__ */ jsx3("button", { className: "scui-attach", type: "button", "aria-label": labels.attachContext ?? DEFAULT_LABELS.attachContext, disabled: picking || context.length >= MAX_CONTEXT_ITEMS && images.length >= MAX_IMAGE_ITEMS, onClick: pickContext, children: picking ? /* @__PURE__ */ jsx3("i", { className: "scui-control-spinner" }) : /* @__PURE__ */ jsx3(UiIcon, { name: "attach", size: 17 }) }) : null,
1483
1494
  /* @__PURE__ */ jsx3("textarea", { ref: textarea, rows: 1, "aria-label": `Message ${harnessDisplayName(state.harness) || "agent"}`, placeholder: state.startup !== "ready" ? "Connecting\u2026" : pendingStatus === "failed" ? "Retry or edit the unsent message\u2026" : pendingStatus === "editing" ? "Edit and resend\u2026" : steerAvailable && context.length === 0 && images.length === 0 ? "Redirect the current turn\u2026" : queueBlocked ? "Queue a follow-up\u2026" : labels.askAgent, value: draft, disabled: state.mode !== "control" && !state.canSend, onPaste: pasteImages, onInput: (event) => {
1484
1495
  const value = event.currentTarget.value;
1485
1496
  setDraft(value);
@@ -2789,7 +2800,8 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
2789
2800
  const Readiness = components.HarnessReadiness ?? HarnessReadiness;
2790
2801
  const launchModes = selectedHarness?.launchModes?.length ? selectedHarness.launchModes : ["headless"];
2791
2802
  const rememberedMode = modes[harness];
2792
- const mode = launchModes.includes(rememberedMode) ? rememberedMode : launchModes.includes(selectedHarness?.preferredLaunchMode) ? selectedHarness.preferredLaunchMode : launchModes[0];
2803
+ const requestedMode = launchModes.includes(rememberedMode) ? rememberedMode : launchModes.includes(selectedHarness?.preferredLaunchMode) ? selectedHarness.preferredLaunchMode : launchModes[0];
2804
+ const mode = (context.length > 0 || images.length > 0) && launchModes.includes("headless") ? "headless" : requestedMode;
2793
2805
  const terminalAttachments = mode === "terminal" && (context.length > 0 || images.length > 0);
2794
2806
  const remember = (overrides = {}) => boundedSet(newChatMemory, memoryKey, { harness, draft, context, images, modes, ...overrides });
2795
2807
  useAutosizeTextarea(textarea, draft);
@@ -2831,7 +2843,7 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
2831
2843
  lastComposerCommand.current = composerCommand.id;
2832
2844
  if (composerCommand.action === "attach") {
2833
2845
  if (mode === "terminal" && !launchModes.includes("headless")) {
2834
- setPickerError("Terminal starts currently accept text only. Choose Chat to attach context or images.");
2846
+ setPickerError("Terminal starts currently accept text only. Choose Headless to attach context or images.");
2835
2847
  } else {
2836
2848
  try {
2837
2849
  const attachments = partitionAttachments(composerCommand.attachments);
@@ -2839,12 +2851,10 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
2839
2851
  if (images.length + attachments.images.length > MAX_IMAGE_ITEMS) throw new Error(`Attach at most ${MAX_IMAGE_ITEMS} images.`);
2840
2852
  const nextContext = mergeContext(context, attachments.context);
2841
2853
  const nextImages = mergeImages(images, attachments.images);
2842
- const nextModes = mode === "terminal" ? { ...modes, [harness]: "headless" } : modes;
2843
- if (nextModes !== modes) setModes(nextModes);
2844
2854
  setContext(nextContext);
2845
2855
  setImages(nextImages);
2846
2856
  setPickerError(null);
2847
- remember({ context: nextContext, images: nextImages, modes: nextModes });
2857
+ remember({ context: nextContext, images: nextImages });
2848
2858
  } catch (error) {
2849
2859
  setPickerError(error instanceof Error ? error.message : "Could not attach context.");
2850
2860
  }
@@ -2885,7 +2895,7 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
2885
2895
  const allFiles = Array.from(value ?? []);
2886
2896
  if (!allFiles.length) return false;
2887
2897
  if (mode === "terminal") {
2888
- setPickerError("Terminal starts currently accept text only. Choose Chat to attach context or images.");
2898
+ setPickerError("Terminal starts currently accept text only. Choose Headless to attach context or images.");
2889
2899
  return true;
2890
2900
  }
2891
2901
  const files = allFiles.filter((file) => file.type.startsWith("image/"));
@@ -2950,21 +2960,6 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
2950
2960
  ] }),
2951
2961
  /* @__PURE__ */ jsxs8("div", { className: "scui-compose", children: [
2952
2962
  /* @__PURE__ */ jsx9(Readiness, { harnesses: state.harnesses, state, adapter }),
2953
- launchModes.length > 1 ? /* @__PURE__ */ jsxs8("fieldset", { className: "scui-launch-modes", disabled: Boolean(starting), children: [
2954
- /* @__PURE__ */ jsx9("legend", { children: "Run as" }),
2955
- launchModes.map((item) => /* @__PURE__ */ jsxs8("label", { children: [
2956
- /* @__PURE__ */ jsx9("input", { type: "radio", name: `launch-mode-${memoryKey}`, value: item, checked: mode === item, onChange: () => {
2957
- const next = { ...modes, [harness]: item };
2958
- setModes(next);
2959
- setPickerError(null);
2960
- remember({ modes: next });
2961
- } }),
2962
- /* @__PURE__ */ jsxs8("span", { children: [
2963
- /* @__PURE__ */ jsx9("strong", { children: item === "terminal" ? "Terminal" : "Chat" }),
2964
- /* @__PURE__ */ jsx9("small", { children: item === "terminal" ? "Interactive tmux session" : "Managed here" })
2965
- ] })
2966
- ] }, item))
2967
- ] }) : null,
2968
2963
  mode !== "terminal" ? /* @__PURE__ */ jsx9(ContextCandidates, { items: contextCandidates, context, images, state, adapter, onAttach: attachCandidate, component: components.ContextCandidate }) : null,
2969
2964
  /* @__PURE__ */ jsx9(ImageTray, { items: images, onRemove: (index) => setImages((items) => {
2970
2965
  const next = items.filter((_, itemIndex) => itemIndex !== index);
@@ -2976,7 +2971,7 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
2976
2971
  remember({ context: next });
2977
2972
  return next;
2978
2973
  }) }),
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,
2974
+ terminalAttachments ? /* @__PURE__ */ jsx9("small", { className: "scui-context-error", role: "alert", children: "Terminal starts currently accept text only. Remove attachments or choose Headless." }) : pickerError ? /* @__PURE__ */ jsx9("small", { className: "scui-context-error", role: "alert", children: pickerError }) : null,
2980
2975
  /* @__PURE__ */ jsxs8("div", { className: `scui-envelope scui-new-envelope${dragging ? " scui-drop-target" : ""}`, onDragEnter: (event) => {
2981
2976
  if (Array.from(event.dataTransfer?.types ?? []).includes("Files")) {
2982
2977
  event.preventDefault();
@@ -2998,12 +2993,18 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
2998
2993
  }
2999
2994
  } }),
3000
2995
  /* @__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,
2996
+ adapter.pickContext ? /* @__PURE__ */ jsx9("button", { className: "scui-attach", type: "button", "aria-label": labels.attachContext ?? DEFAULT_LABELS.attachContext, 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
2997
  /* @__PURE__ */ jsx9(Picker, { harnesses: state.harnesses, value: harness, disabled: Boolean(starting), adapter, logo: components.HarnessLogo, onChange: (value) => {
3003
2998
  setHarness(value);
3004
2999
  remember({ harness: value });
3005
3000
  } }),
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 }) }) })
3001
+ /* @__PURE__ */ jsx9(ExecutionModeSelect, { modes: launchModes, value: mode, disabled: Boolean(starting) || mode !== requestedMode, onChange: (item) => {
3002
+ const next = { ...modes, [harness]: item };
3003
+ setModes(next);
3004
+ setPickerError(null);
3005
+ remember({ modes: next });
3006
+ } }),
3007
+ /* @__PURE__ */ jsx9("span", { children: /* @__PURE__ */ jsx9("button", { type: "button", className: "scui-send", "aria-label": mode === "terminal" ? "Start terminal session" : "Start headless session", 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
3008
  ] })
3008
3009
  ] })
3009
3010
  ] })
package/index.d.ts CHANGED
@@ -40,7 +40,7 @@ export interface HarnessOption {
40
40
  };
41
41
  /** Host-supported ways to start this harness. Omitted means headless when startable. */
42
42
  launchModes?: ExecutionMode[];
43
- /** Host preference, used unless this browser has a remembered choice for the harness. */
43
+ /** Host preference, used unless this browser has a remembered choice. Defaults to terminal when advertised. */
44
44
  preferredLaunchMode?: ExecutionMode | null;
45
45
  }
46
46
 
@@ -264,7 +264,7 @@ export interface SupercodeUiState {
264
264
  canSend: boolean;
265
265
  canSteer: boolean;
266
266
  canResume: boolean;
267
- /** Execution strategies the current host can actually provide. Headless is the default. */
267
+ /** Execution transports the current host can actually provide. */
268
268
  continuationModes: ContinuationMode[];
269
269
  canBranch: boolean;
270
270
  canAttach: boolean;
@@ -350,6 +350,7 @@ export interface MessengerLabels {
350
350
  newChat: string;
351
351
  searchChats: string;
352
352
  askAgent: string;
353
+ attachContext?: string;
353
354
  continueHere: string;
354
355
  continueWithTerminal?: string;
355
356
  joinLive: string;
@@ -556,6 +557,7 @@ export function Conversation(props: { state: SupercodeUiState; adapter: UiAdapte
556
557
  export function SessionRow(props: { row: SessionRowModel; state: SupercodeUiState; adapter: UiAdapter; now?: number; onOpen(row: SessionRowModel): void }): VNode;
557
558
  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;
558
559
  export function ContinuationBar(props: { state: SupercodeUiState; adapter: UiAdapter; labels?: MessengerLabels }): VNode | null;
560
+ export function ExecutionModeSelect(props: { modes?: ExecutionMode[]; value: ExecutionMode; onChange?(mode: ExecutionMode): void; disabled?: boolean; label?: string }): VNode | null;
559
561
  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;
560
562
  export function SupercodeMessenger(props: MessengerProps): VNode;
561
563