@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/messenger.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,
@@ -1256,13 +1257,23 @@ function useAutosizeTextarea(ref, value) {
1256
1257
  // src/composer.jsx
1257
1258
  import { jsx as jsx3, jsxs as jsxs3 } from "preact/jsx-runtime";
1258
1259
  var composerMemory = /* @__PURE__ */ new Map();
1260
+ function ExecutionModeSelect({ modes = [], value, onChange, disabled = false, label = "Run in" }) {
1261
+ if (modes.length < 2) return null;
1262
+ return /* @__PURE__ */ jsxs3("label", { className: "scui-execution-mode", title: disabled ? void 0 : `${label}: ${value}`, children: [
1263
+ /* @__PURE__ */ jsx3("span", { children: label }),
1264
+ /* @__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)) })
1265
+ ] });
1266
+ }
1259
1267
  function ContinuationBar({ state, adapter, labels = DEFAULT_LABELS }) {
1268
+ const continuationModes = state.continuationModes?.length ? state.continuationModes : ["headless"];
1269
+ const [selection, setSelection] = useState2({ key: null, mode: null });
1270
+ const requestedMode = selection.key === state.attached?.key ? selection.mode : null;
1271
+ const executionMode = continuationModes.includes(requestedMode) ? requestedMode : continuationModes.includes("terminal") ? "terminal" : continuationModes[0];
1260
1272
  if (state.mode !== "mirror" || state.canSend) return null;
1261
1273
  const attached = state.attached;
1262
1274
  const row = attached?.key ? state.sessions.find((item) => item.key === attached.key) : null;
1263
1275
  const activeElsewhere = row?.runtimeStatus === "running" || row?.runtimeStatus === "busy" || row?.runtimeStatus === "idle";
1264
1276
  const resume = canContinueHere(state);
1265
- const terminal = resume && state.continuationModes?.includes("terminal");
1266
1277
  const join = state.canAttach;
1267
1278
  const branch = state.canBranch;
1268
1279
  if (!resume && !join && !branch) return /* @__PURE__ */ jsx3("div", { className: "scui-continuation", children: /* @__PURE__ */ jsxs3("span", { children: [
@@ -1275,8 +1286,8 @@ function ContinuationBar({ state, adapter, labels = DEFAULT_LABELS }) {
1275
1286
  /* @__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." })
1276
1287
  ] }),
1277
1288
  /* @__PURE__ */ jsxs3("span", { className: "scui-continuation-actions", children: [
1278
- /* @__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 }),
1279
- 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
1289
+ !join && resume ? /* @__PURE__ */ jsx3(ExecutionModeSelect, { modes: continuationModes, value: executionMode, disabled: Boolean(state.operation), onChange: (mode) => setSelection({ key: state.attached?.key ?? null, mode }) }) : null,
1290
+ /* @__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 })
1280
1291
  ] })
1281
1292
  ] });
1282
1293
  }
@@ -1476,7 +1487,7 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
1476
1487
  }, onDragLeave: (event) => {
1477
1488
  if (!event.currentTarget.contains(event.relatedTarget)) setDragging(false);
1478
1489
  }, onDrop: dropImages, children: [
1479
- 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,
1490
+ 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,
1480
1491
  /* @__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) => {
1481
1492
  const value = event.currentTarget.value;
1482
1493
  setDraft(value);
@@ -2786,7 +2797,8 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
2786
2797
  const Readiness = components.HarnessReadiness ?? HarnessReadiness;
2787
2798
  const launchModes = selectedHarness?.launchModes?.length ? selectedHarness.launchModes : ["headless"];
2788
2799
  const rememberedMode = modes[harness];
2789
- const mode = launchModes.includes(rememberedMode) ? rememberedMode : launchModes.includes(selectedHarness?.preferredLaunchMode) ? selectedHarness.preferredLaunchMode : launchModes[0];
2800
+ const requestedMode = launchModes.includes(rememberedMode) ? rememberedMode : launchModes.includes(selectedHarness?.preferredLaunchMode) ? selectedHarness.preferredLaunchMode : launchModes[0];
2801
+ const mode = (context.length > 0 || images.length > 0) && launchModes.includes("headless") ? "headless" : requestedMode;
2790
2802
  const terminalAttachments = mode === "terminal" && (context.length > 0 || images.length > 0);
2791
2803
  const remember = (overrides = {}) => boundedSet(newChatMemory, memoryKey, { harness, draft, context, images, modes, ...overrides });
2792
2804
  useAutosizeTextarea(textarea, draft);
@@ -2828,7 +2840,7 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
2828
2840
  lastComposerCommand.current = composerCommand.id;
2829
2841
  if (composerCommand.action === "attach") {
2830
2842
  if (mode === "terminal" && !launchModes.includes("headless")) {
2831
- setPickerError("Terminal starts currently accept text only. Choose Chat to attach context or images.");
2843
+ setPickerError("Terminal starts currently accept text only. Choose Headless to attach context or images.");
2832
2844
  } else {
2833
2845
  try {
2834
2846
  const attachments = partitionAttachments(composerCommand.attachments);
@@ -2836,12 +2848,10 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
2836
2848
  if (images.length + attachments.images.length > MAX_IMAGE_ITEMS) throw new Error(`Attach at most ${MAX_IMAGE_ITEMS} images.`);
2837
2849
  const nextContext = mergeContext(context, attachments.context);
2838
2850
  const nextImages = mergeImages(images, attachments.images);
2839
- const nextModes = mode === "terminal" ? { ...modes, [harness]: "headless" } : modes;
2840
- if (nextModes !== modes) setModes(nextModes);
2841
2851
  setContext(nextContext);
2842
2852
  setImages(nextImages);
2843
2853
  setPickerError(null);
2844
- remember({ context: nextContext, images: nextImages, modes: nextModes });
2854
+ remember({ context: nextContext, images: nextImages });
2845
2855
  } catch (error) {
2846
2856
  setPickerError(error instanceof Error ? error.message : "Could not attach context.");
2847
2857
  }
@@ -2882,7 +2892,7 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
2882
2892
  const allFiles = Array.from(value ?? []);
2883
2893
  if (!allFiles.length) return false;
2884
2894
  if (mode === "terminal") {
2885
- setPickerError("Terminal starts currently accept text only. Choose Chat to attach context or images.");
2895
+ setPickerError("Terminal starts currently accept text only. Choose Headless to attach context or images.");
2886
2896
  return true;
2887
2897
  }
2888
2898
  const files = allFiles.filter((file) => file.type.startsWith("image/"));
@@ -2947,21 +2957,6 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
2947
2957
  ] }),
2948
2958
  /* @__PURE__ */ jsxs8("div", { className: "scui-compose", children: [
2949
2959
  /* @__PURE__ */ jsx9(Readiness, { harnesses: state.harnesses, state, adapter }),
2950
- launchModes.length > 1 ? /* @__PURE__ */ jsxs8("fieldset", { className: "scui-launch-modes", disabled: Boolean(starting), children: [
2951
- /* @__PURE__ */ jsx9("legend", { children: "Run as" }),
2952
- launchModes.map((item) => /* @__PURE__ */ jsxs8("label", { children: [
2953
- /* @__PURE__ */ jsx9("input", { type: "radio", name: `launch-mode-${memoryKey}`, value: item, checked: mode === item, onChange: () => {
2954
- const next = { ...modes, [harness]: item };
2955
- setModes(next);
2956
- setPickerError(null);
2957
- remember({ modes: next });
2958
- } }),
2959
- /* @__PURE__ */ jsxs8("span", { children: [
2960
- /* @__PURE__ */ jsx9("strong", { children: item === "terminal" ? "Terminal" : "Chat" }),
2961
- /* @__PURE__ */ jsx9("small", { children: item === "terminal" ? "Interactive tmux session" : "Managed here" })
2962
- ] })
2963
- ] }, item))
2964
- ] }) : null,
2965
2960
  mode !== "terminal" ? /* @__PURE__ */ jsx9(ContextCandidates, { items: contextCandidates, context, images, state, adapter, onAttach: attachCandidate, component: components.ContextCandidate }) : null,
2966
2961
  /* @__PURE__ */ jsx9(ImageTray, { items: images, onRemove: (index) => setImages((items) => {
2967
2962
  const next = items.filter((_, itemIndex) => itemIndex !== index);
@@ -2973,7 +2968,7 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
2973
2968
  remember({ context: next });
2974
2969
  return next;
2975
2970
  }) }),
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,
2971
+ 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,
2977
2972
  /* @__PURE__ */ jsxs8("div", { className: `scui-envelope scui-new-envelope${dragging ? " scui-drop-target" : ""}`, onDragEnter: (event) => {
2978
2973
  if (Array.from(event.dataTransfer?.types ?? []).includes("Files")) {
2979
2974
  event.preventDefault();
@@ -2995,12 +2990,18 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
2995
2990
  }
2996
2991
  } }),
2997
2992
  /* @__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,
2993
+ 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,
2999
2994
  /* @__PURE__ */ jsx9(Picker, { harnesses: state.harnesses, value: harness, disabled: Boolean(starting), adapter, logo: components.HarnessLogo, onChange: (value) => {
3000
2995
  setHarness(value);
3001
2996
  remember({ harness: value });
3002
2997
  } }),
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 }) }) })
2998
+ /* @__PURE__ */ jsx9(ExecutionModeSelect, { modes: launchModes, value: mode, disabled: Boolean(starting) || mode !== requestedMode, onChange: (item) => {
2999
+ const next = { ...modes, [harness]: item };
3000
+ setModes(next);
3001
+ setPickerError(null);
3002
+ remember({ modes: next });
3003
+ } }),
3004
+ /* @__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 }) }) })
3004
3005
  ] })
3005
3006
  ] })
3006
3007
  ] })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter-ai-dev/supercode-ui",
3
- "version": "0.1.43",
3
+ "version": "0.1.45",
4
4
  "type": "module",
5
5
  "description": "Composable default UI kit for Supercode-powered coding-agent experiences",
6
6
  "exports": {
@@ -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,
@@ -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 "react/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,
@@ -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 "react/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
  };
@@ -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",
@@ -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,
@@ -1256,13 +1257,23 @@ function useAutosizeTextarea(ref, value) {
1256
1257
  // src/composer.jsx
1257
1258
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1258
1259
  var composerMemory = /* @__PURE__ */ new Map();
1260
+ function ExecutionModeSelect({ modes = [], value, onChange, disabled = false, label = "Run in" }) {
1261
+ if (modes.length < 2) return null;
1262
+ return /* @__PURE__ */ jsxs3("label", { className: "scui-execution-mode", title: disabled ? void 0 : `${label}: ${value}`, children: [
1263
+ /* @__PURE__ */ jsx3("span", { children: label }),
1264
+ /* @__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)) })
1265
+ ] });
1266
+ }
1259
1267
  function ContinuationBar({ state, adapter, labels = DEFAULT_LABELS }) {
1268
+ const continuationModes = state.continuationModes?.length ? state.continuationModes : ["headless"];
1269
+ const [selection, setSelection] = useState2({ key: null, mode: null });
1270
+ const requestedMode = selection.key === state.attached?.key ? selection.mode : null;
1271
+ const executionMode = continuationModes.includes(requestedMode) ? requestedMode : continuationModes.includes("terminal") ? "terminal" : continuationModes[0];
1260
1272
  if (state.mode !== "mirror" || state.canSend) return null;
1261
1273
  const attached = state.attached;
1262
1274
  const row = attached?.key ? state.sessions.find((item) => item.key === attached.key) : null;
1263
1275
  const activeElsewhere = row?.runtimeStatus === "running" || row?.runtimeStatus === "busy" || row?.runtimeStatus === "idle";
1264
1276
  const resume = canContinueHere(state);
1265
- const terminal = resume && state.continuationModes?.includes("terminal");
1266
1277
  const join = state.canAttach;
1267
1278
  const branch = state.canBranch;
1268
1279
  if (!resume && !join && !branch) return /* @__PURE__ */ jsx3("div", { className: "scui-continuation", children: /* @__PURE__ */ jsxs3("span", { children: [
@@ -1275,8 +1286,8 @@ function ContinuationBar({ state, adapter, labels = DEFAULT_LABELS }) {
1275
1286
  /* @__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." })
1276
1287
  ] }),
1277
1288
  /* @__PURE__ */ jsxs3("span", { className: "scui-continuation-actions", children: [
1278
- /* @__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 }),
1279
- 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
1289
+ !join && resume ? /* @__PURE__ */ jsx3(ExecutionModeSelect, { modes: continuationModes, value: executionMode, disabled: Boolean(state.operation), onChange: (mode) => setSelection({ key: state.attached?.key ?? null, mode }) }) : null,
1290
+ /* @__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 })
1280
1291
  ] })
1281
1292
  ] });
1282
1293
  }
@@ -1476,7 +1487,7 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
1476
1487
  }, onDragLeave: (event) => {
1477
1488
  if (!event.currentTarget.contains(event.relatedTarget)) setDragging(false);
1478
1489
  }, onDrop: dropImages, children: [
1479
- 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,
1490
+ 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,
1480
1491
  /* @__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) => {
1481
1492
  const value = event.currentTarget.value;
1482
1493
  setDraft(value);
@@ -2786,7 +2797,8 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
2786
2797
  const Readiness = components.HarnessReadiness ?? HarnessReadiness;
2787
2798
  const launchModes = selectedHarness?.launchModes?.length ? selectedHarness.launchModes : ["headless"];
2788
2799
  const rememberedMode = modes[harness];
2789
- const mode = launchModes.includes(rememberedMode) ? rememberedMode : launchModes.includes(selectedHarness?.preferredLaunchMode) ? selectedHarness.preferredLaunchMode : launchModes[0];
2800
+ const requestedMode = launchModes.includes(rememberedMode) ? rememberedMode : launchModes.includes(selectedHarness?.preferredLaunchMode) ? selectedHarness.preferredLaunchMode : launchModes[0];
2801
+ const mode = (context.length > 0 || images.length > 0) && launchModes.includes("headless") ? "headless" : requestedMode;
2790
2802
  const terminalAttachments = mode === "terminal" && (context.length > 0 || images.length > 0);
2791
2803
  const remember = (overrides = {}) => boundedSet(newChatMemory, memoryKey, { harness, draft, context, images, modes, ...overrides });
2792
2804
  useAutosizeTextarea(textarea, draft);
@@ -2828,7 +2840,7 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
2828
2840
  lastComposerCommand.current = composerCommand.id;
2829
2841
  if (composerCommand.action === "attach") {
2830
2842
  if (mode === "terminal" && !launchModes.includes("headless")) {
2831
- setPickerError("Terminal starts currently accept text only. Choose Chat to attach context or images.");
2843
+ setPickerError("Terminal starts currently accept text only. Choose Headless to attach context or images.");
2832
2844
  } else {
2833
2845
  try {
2834
2846
  const attachments = partitionAttachments(composerCommand.attachments);
@@ -2836,12 +2848,10 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
2836
2848
  if (images.length + attachments.images.length > MAX_IMAGE_ITEMS) throw new Error(`Attach at most ${MAX_IMAGE_ITEMS} images.`);
2837
2849
  const nextContext = mergeContext(context, attachments.context);
2838
2850
  const nextImages = mergeImages(images, attachments.images);
2839
- const nextModes = mode === "terminal" ? { ...modes, [harness]: "headless" } : modes;
2840
- if (nextModes !== modes) setModes(nextModes);
2841
2851
  setContext(nextContext);
2842
2852
  setImages(nextImages);
2843
2853
  setPickerError(null);
2844
- remember({ context: nextContext, images: nextImages, modes: nextModes });
2854
+ remember({ context: nextContext, images: nextImages });
2845
2855
  } catch (error) {
2846
2856
  setPickerError(error instanceof Error ? error.message : "Could not attach context.");
2847
2857
  }
@@ -2882,7 +2892,7 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
2882
2892
  const allFiles = Array.from(value ?? []);
2883
2893
  if (!allFiles.length) return false;
2884
2894
  if (mode === "terminal") {
2885
- setPickerError("Terminal starts currently accept text only. Choose Chat to attach context or images.");
2895
+ setPickerError("Terminal starts currently accept text only. Choose Headless to attach context or images.");
2886
2896
  return true;
2887
2897
  }
2888
2898
  const files = allFiles.filter((file) => file.type.startsWith("image/"));
@@ -2947,21 +2957,6 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
2947
2957
  ] }),
2948
2958
  /* @__PURE__ */ jsxs8("div", { className: "scui-compose", children: [
2949
2959
  /* @__PURE__ */ jsx9(Readiness, { harnesses: state.harnesses, state, adapter }),
2950
- launchModes.length > 1 ? /* @__PURE__ */ jsxs8("fieldset", { className: "scui-launch-modes", disabled: Boolean(starting), children: [
2951
- /* @__PURE__ */ jsx9("legend", { children: "Run as" }),
2952
- launchModes.map((item) => /* @__PURE__ */ jsxs8("label", { children: [
2953
- /* @__PURE__ */ jsx9("input", { type: "radio", name: `launch-mode-${memoryKey}`, value: item, checked: mode === item, onChange: () => {
2954
- const next = { ...modes, [harness]: item };
2955
- setModes(next);
2956
- setPickerError(null);
2957
- remember({ modes: next });
2958
- } }),
2959
- /* @__PURE__ */ jsxs8("span", { children: [
2960
- /* @__PURE__ */ jsx9("strong", { children: item === "terminal" ? "Terminal" : "Chat" }),
2961
- /* @__PURE__ */ jsx9("small", { children: item === "terminal" ? "Interactive tmux session" : "Managed here" })
2962
- ] })
2963
- ] }, item))
2964
- ] }) : null,
2965
2960
  mode !== "terminal" ? /* @__PURE__ */ jsx9(ContextCandidates, { items: contextCandidates, context, images, state, adapter, onAttach: attachCandidate, component: components.ContextCandidate }) : null,
2966
2961
  /* @__PURE__ */ jsx9(ImageTray, { items: images, onRemove: (index) => setImages((items) => {
2967
2962
  const next = items.filter((_, itemIndex) => itemIndex !== index);
@@ -2973,7 +2968,7 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
2973
2968
  remember({ context: next });
2974
2969
  return next;
2975
2970
  }) }),
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,
2971
+ 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,
2977
2972
  /* @__PURE__ */ jsxs8("div", { className: `scui-envelope scui-new-envelope${dragging ? " scui-drop-target" : ""}`, onDragEnter: (event) => {
2978
2973
  if (Array.from(event.dataTransfer?.types ?? []).includes("Files")) {
2979
2974
  event.preventDefault();
@@ -2995,12 +2990,18 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
2995
2990
  }
2996
2991
  } }),
2997
2992
  /* @__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,
2993
+ 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,
2999
2994
  /* @__PURE__ */ jsx9(Picker, { harnesses: state.harnesses, value: harness, disabled: Boolean(starting), adapter, logo: components.HarnessLogo, onChange: (value) => {
3000
2995
  setHarness(value);
3001
2996
  remember({ harness: value });
3002
2997
  } }),
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 }) }) })
2998
+ /* @__PURE__ */ jsx9(ExecutionModeSelect, { modes: launchModes, value: mode, disabled: Boolean(starting) || mode !== requestedMode, onChange: (item) => {
2999
+ const next = { ...modes, [harness]: item };
3000
+ setModes(next);
3001
+ setPickerError(null);
3002
+ remember({ modes: next });
3003
+ } }),
3004
+ /* @__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 }) }) })
3004
3005
  ] })
3005
3006
  ] })
3006
3007
  ] })
@@ -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",