@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 +8 -7
- package/activity.mjs +2 -1
- package/components.d.ts +1 -0
- package/components.mjs +31 -29
- package/composer.d.ts +1 -1
- package/composer.mjs +17 -5
- package/conversation.mjs +1 -0
- package/core.mjs +4 -1
- package/embed.mjs +30 -29
- package/index.d.ts +4 -2
- package/messenger.mjs +30 -29
- package/package.json +1 -1
- package/react/activity.mjs +2 -1
- package/react/components.mjs +31 -29
- package/react/composer.mjs +17 -5
- package/react/conversation.mjs +1 -0
- package/react/messenger.mjs +30 -29
- package/react/sessions.mjs +1 -0
- package/react/settings.mjs +1 -0
- package/sessions.mjs +1 -0
- package/settings.mjs +1 -0
- package/styles.css +2 -2
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(
|
|
1279
|
-
|
|
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":
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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":
|
|
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(
|
|
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
package/react/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/react/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 "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(
|
|
1316
|
-
|
|
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":
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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":
|
|
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(
|
|
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/react/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 "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(
|
|
317
|
-
|
|
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":
|
|
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/react/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/react/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 "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(
|
|
1279
|
-
|
|
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":
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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":
|
|
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(
|
|
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/react/sessions.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",
|