@volter-ai-dev/supercode-ui 0.1.44 → 0.1.46
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 +11 -8
- package/activity.mjs +5 -1
- package/components.d.ts +1 -0
- package/components.mjs +33 -28
- package/composer.d.ts +1 -1
- package/composer.mjs +16 -4
- package/controller.d.ts +2 -0
- package/controller.mjs +6 -4
- package/conversation.mjs +1 -0
- package/core.mjs +9 -1
- package/embed.mjs +31 -28
- package/index.d.ts +6 -3
- package/messenger.mjs +31 -28
- package/package.json +1 -1
- package/react/activity.mjs +5 -1
- package/react/components.mjs +33 -28
- package/react/composer.mjs +16 -4
- package/react/conversation.mjs +1 -0
- package/react/messenger.mjs +31 -28
- package/react/sessions.mjs +3 -1
- package/react/settings.mjs +1 -0
- package/sessions.mjs +3 -1
- package/settings.mjs +1 -0
- package/styles.css +3 -2
package/README.md
CHANGED
|
@@ -183,15 +183,16 @@ drafts, and artifact materialization remain explicit callbacks. A
|
|
|
183
183
|
browser should receive projected state from a trusted host rather than instantiate a local
|
|
184
184
|
controller or gain filesystem authority.
|
|
185
185
|
|
|
186
|
-
Native continuation
|
|
187
|
-
`terminal` to `continuationModes` and handle `onResumeTerminal`;
|
|
188
|
-
|
|
189
|
-
handoff, and never presents a terminal
|
|
186
|
+
Native continuation exposes one action and a quiet execution-transport selector. A host with a real
|
|
187
|
+
terminal provider can add `terminal` to `continuationModes` and handle `onResumeTerminal`; Terminal
|
|
188
|
+
is then the initial choice and Headless remains available from the selector. The UI never infers
|
|
189
|
+
terminal support from a generic runtime handoff, and never presents a terminal transport when the
|
|
190
|
+
host cannot create one.
|
|
190
191
|
|
|
191
192
|
New-session execution is advertised per harness. Set `launchModes: ['headless', 'terminal']` and
|
|
192
|
-
`preferredLaunchMode` on a `HarnessOption`; the complete messenger renders
|
|
193
|
-
|
|
194
|
-
|
|
193
|
+
`preferredLaunchMode` on a `HarnessOption`; the complete messenger renders the same compact Terminal
|
|
194
|
+
versus Headless selector in the composer footer and emits that `mode` on the `new` intent. Direct
|
|
195
|
+
controller bindings delegate Terminal only through `onStartTerminal`, so a terminal selection can
|
|
195
196
|
never accidentally create a second headless runtime. Hosts may remember the preference per harness;
|
|
196
197
|
the messenger also retains the current browser draft choice while its New Chat view is open.
|
|
197
198
|
|
|
@@ -204,7 +205,9 @@ and can overlay a machine-wide session inventory, pagination state, attention, d
|
|
|
204
205
|
errors, and owned/attached identities without reimplementing transcript or capability semantics.
|
|
205
206
|
`projectSessionInventory` performs the corresponding title, latest-preview, activity, path, age,
|
|
206
207
|
sorting, and row projection for raw machine-wide descriptors; the trusted host supplies only its
|
|
207
|
-
opaque-key callback and retains the reversible locator map.
|
|
208
|
+
opaque-key callback and retains the reversible locator map. Its `isWritable` capability callback
|
|
209
|
+
is fail-closed: observed sessions remain neutral read-only channels until the host proves a real
|
|
210
|
+
send or terminal-control path.
|
|
208
211
|
|
|
209
212
|
## Modularity contract
|
|
210
213
|
|
package/activity.mjs
CHANGED
|
@@ -554,6 +554,7 @@ function readSessions(value) {
|
|
|
554
554
|
updatedAt: nullableNumber(row.updatedAt),
|
|
555
555
|
messages: nullableNumber(row.messages),
|
|
556
556
|
active: row.active === true,
|
|
557
|
+
writable: row.writable === true,
|
|
557
558
|
live: row.live === true,
|
|
558
559
|
runtimeStatus: row.runtimeStatus === "running" || row.runtimeStatus === "busy" || row.runtimeStatus === "idle" ? row.runtimeStatus : null
|
|
559
560
|
}];
|
|
@@ -738,7 +739,7 @@ function normalizeUiState(value) {
|
|
|
738
739
|
if (!item || typeof item.id !== "string") return [];
|
|
739
740
|
const startable = item.startable === true;
|
|
740
741
|
const launchModes = Array.isArray(item.launchModes) ? [...new Set(item.launchModes.filter((mode) => mode === "headless" || mode === "terminal"))] : startable ? ["headless"] : [];
|
|
741
|
-
const preferredLaunchMode = launchModes.includes(item.preferredLaunchMode) ? item.preferredLaunchMode : launchModes[0] ?? null;
|
|
742
|
+
const preferredLaunchMode = launchModes.includes(item.preferredLaunchMode) ? item.preferredLaunchMode : launchModes.includes("terminal") ? "terminal" : launchModes[0] ?? null;
|
|
742
743
|
const capabilities = record(item.capabilities);
|
|
743
744
|
return [{
|
|
744
745
|
id: item.id,
|
|
@@ -783,6 +784,7 @@ function sessionDisplayName(session) {
|
|
|
783
784
|
return title && title !== session.name ? title : session.name || "Untitled chat";
|
|
784
785
|
}
|
|
785
786
|
function sessionActivity(state, row) {
|
|
787
|
+
if (!row.writable) return "observed";
|
|
786
788
|
if (state.needsInput && row.active) return "needs-input";
|
|
787
789
|
if (state.busy && row.active) return "working";
|
|
788
790
|
if (row.runtimeStatus === "busy") return "working";
|
|
@@ -800,6 +802,7 @@ var ACTIVITY_PRIORITY = Object.freeze({
|
|
|
800
802
|
finished: 30,
|
|
801
803
|
running: 20,
|
|
802
804
|
recent: 10,
|
|
805
|
+
observed: 0,
|
|
803
806
|
idle: 0
|
|
804
807
|
});
|
|
805
808
|
function activityDetail(activity, preview, pill) {
|
|
@@ -810,6 +813,7 @@ function activityDetail(activity, preview, pill) {
|
|
|
810
813
|
if (activity === "finished") return "Finished";
|
|
811
814
|
if (activity === "unseen") return "New reply";
|
|
812
815
|
if (activity === "running") return "Session is running";
|
|
816
|
+
if (activity === "observed") return "Read-only channel";
|
|
813
817
|
return pill || "Agent chats";
|
|
814
818
|
}
|
|
815
819
|
function projectAgentActivity(value) {
|
package/components.d.ts
CHANGED
package/components.mjs
CHANGED
|
@@ -566,6 +566,7 @@ function readSessions(value) {
|
|
|
566
566
|
updatedAt: nullableNumber(row.updatedAt),
|
|
567
567
|
messages: nullableNumber(row.messages),
|
|
568
568
|
active: row.active === true,
|
|
569
|
+
writable: row.writable === true,
|
|
569
570
|
live: row.live === true,
|
|
570
571
|
runtimeStatus: row.runtimeStatus === "running" || row.runtimeStatus === "busy" || row.runtimeStatus === "idle" ? row.runtimeStatus : null
|
|
571
572
|
}];
|
|
@@ -750,7 +751,7 @@ function normalizeUiState(value) {
|
|
|
750
751
|
if (!item || typeof item.id !== "string") return [];
|
|
751
752
|
const startable = item.startable === true;
|
|
752
753
|
const launchModes = Array.isArray(item.launchModes) ? [...new Set(item.launchModes.filter((mode) => mode === "headless" || mode === "terminal"))] : startable ? ["headless"] : [];
|
|
753
|
-
const preferredLaunchMode = launchModes.includes(item.preferredLaunchMode) ? item.preferredLaunchMode : launchModes[0] ?? null;
|
|
754
|
+
const preferredLaunchMode = launchModes.includes(item.preferredLaunchMode) ? item.preferredLaunchMode : launchModes.includes("terminal") ? "terminal" : launchModes[0] ?? null;
|
|
754
755
|
const capabilities = record(item.capabilities);
|
|
755
756
|
return [{
|
|
756
757
|
id: item.id,
|
|
@@ -795,6 +796,7 @@ function sessionDisplayName(session) {
|
|
|
795
796
|
return title && title !== session.name ? title : session.name || "Untitled chat";
|
|
796
797
|
}
|
|
797
798
|
function sessionActivity(state, row) {
|
|
799
|
+
if (!row.writable) return "observed";
|
|
798
800
|
if (state.needsInput && row.active) return "needs-input";
|
|
799
801
|
if (state.busy && row.active) return "working";
|
|
800
802
|
if (row.runtimeStatus === "busy") return "working";
|
|
@@ -812,6 +814,7 @@ var ACTIVITY_PRIORITY = Object.freeze({
|
|
|
812
814
|
finished: 30,
|
|
813
815
|
running: 20,
|
|
814
816
|
recent: 10,
|
|
817
|
+
observed: 0,
|
|
815
818
|
idle: 0
|
|
816
819
|
});
|
|
817
820
|
function activityDetail(activity, preview, pill) {
|
|
@@ -822,6 +825,7 @@ function activityDetail(activity, preview, pill) {
|
|
|
822
825
|
if (activity === "finished") return "Finished";
|
|
823
826
|
if (activity === "unseen") return "New reply";
|
|
824
827
|
if (activity === "running") return "Session is running";
|
|
828
|
+
if (activity === "observed") return "Read-only channel";
|
|
825
829
|
return pill || "Agent chats";
|
|
826
830
|
}
|
|
827
831
|
function projectAgentActivity(value) {
|
|
@@ -1294,13 +1298,23 @@ function useAutosizeTextarea(ref, value) {
|
|
|
1294
1298
|
// src/composer.jsx
|
|
1295
1299
|
import { jsx as jsx3, jsxs as jsxs3 } from "preact/jsx-runtime";
|
|
1296
1300
|
var composerMemory = /* @__PURE__ */ new Map();
|
|
1301
|
+
function ExecutionModeSelect({ modes = [], value, onChange, disabled = false, label = "Run in" }) {
|
|
1302
|
+
if (modes.length < 2) return null;
|
|
1303
|
+
return /* @__PURE__ */ jsxs3("label", { className: "scui-execution-mode", title: disabled ? void 0 : `${label}: ${value}`, children: [
|
|
1304
|
+
/* @__PURE__ */ jsx3("span", { children: label }),
|
|
1305
|
+
/* @__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)) })
|
|
1306
|
+
] });
|
|
1307
|
+
}
|
|
1297
1308
|
function ContinuationBar({ state, adapter, labels = DEFAULT_LABELS }) {
|
|
1309
|
+
const continuationModes = state.continuationModes?.length ? state.continuationModes : ["headless"];
|
|
1310
|
+
const [selection, setSelection] = useState2({ key: null, mode: null });
|
|
1311
|
+
const requestedMode = selection.key === state.attached?.key ? selection.mode : null;
|
|
1312
|
+
const executionMode = continuationModes.includes(requestedMode) ? requestedMode : continuationModes.includes("terminal") ? "terminal" : continuationModes[0];
|
|
1298
1313
|
if (state.mode !== "mirror" || state.canSend) return null;
|
|
1299
1314
|
const attached = state.attached;
|
|
1300
1315
|
const row = attached?.key ? state.sessions.find((item) => item.key === attached.key) : null;
|
|
1301
1316
|
const activeElsewhere = row?.runtimeStatus === "running" || row?.runtimeStatus === "busy" || row?.runtimeStatus === "idle";
|
|
1302
1317
|
const resume = canContinueHere(state);
|
|
1303
|
-
const terminal = resume && state.continuationModes?.includes("terminal");
|
|
1304
1318
|
const join = state.canAttach;
|
|
1305
1319
|
const branch = state.canBranch;
|
|
1306
1320
|
if (!resume && !join && !branch) return /* @__PURE__ */ jsx3("div", { className: "scui-continuation", children: /* @__PURE__ */ jsxs3("span", { children: [
|
|
@@ -1313,8 +1327,8 @@ function ContinuationBar({ state, adapter, labels = DEFAULT_LABELS }) {
|
|
|
1313
1327
|
/* @__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." })
|
|
1314
1328
|
] }),
|
|
1315
1329
|
/* @__PURE__ */ jsxs3("span", { className: "scui-continuation-actions", children: [
|
|
1316
|
-
/* @__PURE__ */ jsx3(
|
|
1317
|
-
|
|
1330
|
+
!join && resume ? /* @__PURE__ */ jsx3(ExecutionModeSelect, { modes: continuationModes, value: executionMode, disabled: Boolean(state.operation), onChange: (mode) => setSelection({ key: state.attached?.key ?? null, mode }) }) : null,
|
|
1331
|
+
/* @__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 })
|
|
1318
1332
|
] })
|
|
1319
1333
|
] });
|
|
1320
1334
|
}
|
|
@@ -2289,7 +2303,7 @@ function SessionRow({ row, state, onOpen, now = Date.now() }) {
|
|
|
2289
2303
|
const unreadCount = attention?.unreadCount ?? 0;
|
|
2290
2304
|
const unreadLabel = unreadCount > 99 ? "99+" : String(unreadCount);
|
|
2291
2305
|
const age = relativeAge(row.previewUpdatedAt ?? row.updatedAt, now) || row.age;
|
|
2292
|
-
return /* @__PURE__ */ jsxs7("button", { className: "scui-session", "data-active": row.active, "data-activity": activity, "data-session-key": row.key, type: "button", "aria-label": `${title} \xB7 ${harnessDisplayName(row.harness)}${working ? " \xB7 Working" : ""}${path.complete ? ` \xB7 ${path.complete}` : ""}${preview ? ` \xB7 ${preview}` : ""}${unreadCount ? ` \xB7 ${unreadCount} unread` : ""}${age ? ` \xB7 ${age}` : ""}`, "aria-current": row.active ? "true" : void 0, onClick: () => onOpen(row), children: [
|
|
2306
|
+
return /* @__PURE__ */ jsxs7("button", { className: "scui-session", "data-active": row.active, "data-activity": activity, "data-writable": row.writable, "data-session-key": row.key, type: "button", "aria-label": `${title} \xB7 ${harnessDisplayName(row.harness)}${row.writable ? "" : " \xB7 Read-only"}${working ? " \xB7 Working" : ""}${path.complete ? ` \xB7 ${path.complete}` : ""}${preview ? ` \xB7 ${preview}` : ""}${unreadCount ? ` \xB7 ${unreadCount} unread` : ""}${age ? ` \xB7 ${age}` : ""}`, "aria-current": row.active ? "true" : void 0, onClick: () => onOpen(row), children: [
|
|
2293
2307
|
/* @__PURE__ */ jsx8(HarnessLogo, { id: row.harness, activity, size: 34 }),
|
|
2294
2308
|
/* @__PURE__ */ jsxs7("span", { className: "scui-session-copy", children: [
|
|
2295
2309
|
/* @__PURE__ */ jsxs7("span", { className: "scui-session-title", children: [
|
|
@@ -2870,7 +2884,8 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2870
2884
|
const Readiness = components.HarnessReadiness ?? HarnessReadiness;
|
|
2871
2885
|
const launchModes = selectedHarness?.launchModes?.length ? selectedHarness.launchModes : ["headless"];
|
|
2872
2886
|
const rememberedMode = modes[harness];
|
|
2873
|
-
const
|
|
2887
|
+
const requestedMode = launchModes.includes(rememberedMode) ? rememberedMode : launchModes.includes(selectedHarness?.preferredLaunchMode) ? selectedHarness.preferredLaunchMode : launchModes[0];
|
|
2888
|
+
const mode = (context.length > 0 || images.length > 0) && launchModes.includes("headless") ? "headless" : requestedMode;
|
|
2874
2889
|
const terminalAttachments = mode === "terminal" && (context.length > 0 || images.length > 0);
|
|
2875
2890
|
const remember = (overrides = {}) => boundedSet(newChatMemory, memoryKey, { harness, draft, context, images, modes, ...overrides });
|
|
2876
2891
|
useAutosizeTextarea(textarea, draft);
|
|
@@ -2912,7 +2927,7 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2912
2927
|
lastComposerCommand.current = composerCommand.id;
|
|
2913
2928
|
if (composerCommand.action === "attach") {
|
|
2914
2929
|
if (mode === "terminal" && !launchModes.includes("headless")) {
|
|
2915
|
-
setPickerError("Terminal starts currently accept text only. Choose
|
|
2930
|
+
setPickerError("Terminal starts currently accept text only. Choose Headless to attach context or images.");
|
|
2916
2931
|
} else {
|
|
2917
2932
|
try {
|
|
2918
2933
|
const attachments = partitionAttachments(composerCommand.attachments);
|
|
@@ -2920,12 +2935,10 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2920
2935
|
if (images.length + attachments.images.length > MAX_IMAGE_ITEMS) throw new Error(`Attach at most ${MAX_IMAGE_ITEMS} images.`);
|
|
2921
2936
|
const nextContext = mergeContext(context, attachments.context);
|
|
2922
2937
|
const nextImages = mergeImages(images, attachments.images);
|
|
2923
|
-
const nextModes = mode === "terminal" ? { ...modes, [harness]: "headless" } : modes;
|
|
2924
|
-
if (nextModes !== modes) setModes(nextModes);
|
|
2925
2938
|
setContext(nextContext);
|
|
2926
2939
|
setImages(nextImages);
|
|
2927
2940
|
setPickerError(null);
|
|
2928
|
-
remember({ context: nextContext, images: nextImages
|
|
2941
|
+
remember({ context: nextContext, images: nextImages });
|
|
2929
2942
|
} catch (error) {
|
|
2930
2943
|
setPickerError(error instanceof Error ? error.message : "Could not attach context.");
|
|
2931
2944
|
}
|
|
@@ -2966,7 +2979,7 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2966
2979
|
const allFiles = Array.from(value ?? []);
|
|
2967
2980
|
if (!allFiles.length) return false;
|
|
2968
2981
|
if (mode === "terminal") {
|
|
2969
|
-
setPickerError("Terminal starts currently accept text only. Choose
|
|
2982
|
+
setPickerError("Terminal starts currently accept text only. Choose Headless to attach context or images.");
|
|
2970
2983
|
return true;
|
|
2971
2984
|
}
|
|
2972
2985
|
const files = allFiles.filter((file) => file.type.startsWith("image/"));
|
|
@@ -3031,21 +3044,6 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
3031
3044
|
] }),
|
|
3032
3045
|
/* @__PURE__ */ jsxs9("div", { className: "scui-compose", children: [
|
|
3033
3046
|
/* @__PURE__ */ jsx10(Readiness, { harnesses: state.harnesses, state, adapter }),
|
|
3034
|
-
launchModes.length > 1 ? /* @__PURE__ */ jsxs9("fieldset", { className: "scui-launch-modes", disabled: Boolean(starting), children: [
|
|
3035
|
-
/* @__PURE__ */ jsx10("legend", { children: "Run as" }),
|
|
3036
|
-
launchModes.map((item) => /* @__PURE__ */ jsxs9("label", { children: [
|
|
3037
|
-
/* @__PURE__ */ jsx10("input", { type: "radio", name: `launch-mode-${memoryKey}`, value: item, checked: mode === item, onChange: () => {
|
|
3038
|
-
const next = { ...modes, [harness]: item };
|
|
3039
|
-
setModes(next);
|
|
3040
|
-
setPickerError(null);
|
|
3041
|
-
remember({ modes: next });
|
|
3042
|
-
} }),
|
|
3043
|
-
/* @__PURE__ */ jsxs9("span", { children: [
|
|
3044
|
-
/* @__PURE__ */ jsx10("strong", { children: item === "terminal" ? "Terminal" : "Chat" }),
|
|
3045
|
-
/* @__PURE__ */ jsx10("small", { children: item === "terminal" ? "Interactive tmux session" : "Managed here" })
|
|
3046
|
-
] })
|
|
3047
|
-
] }, item))
|
|
3048
|
-
] }) : null,
|
|
3049
3047
|
mode !== "terminal" ? /* @__PURE__ */ jsx10(ContextCandidates, { items: contextCandidates, context, images, state, adapter, onAttach: attachCandidate, component: components.ContextCandidate }) : null,
|
|
3050
3048
|
/* @__PURE__ */ jsx10(ImageTray, { items: images, onRemove: (index) => setImages((items) => {
|
|
3051
3049
|
const next = items.filter((_, itemIndex) => itemIndex !== index);
|
|
@@ -3057,7 +3055,7 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
3057
3055
|
remember({ context: next });
|
|
3058
3056
|
return next;
|
|
3059
3057
|
}) }),
|
|
3060
|
-
terminalAttachments ? /* @__PURE__ */ jsx10("small", { className: "scui-context-error", role: "alert", children: "Terminal starts currently accept text only. Remove attachments or choose
|
|
3058
|
+
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,
|
|
3061
3059
|
/* @__PURE__ */ jsxs9("div", { className: `scui-envelope scui-new-envelope${dragging ? " scui-drop-target" : ""}`, onDragEnter: (event) => {
|
|
3062
3060
|
if (Array.from(event.dataTransfer?.types ?? []).includes("Files")) {
|
|
3063
3061
|
event.preventDefault();
|
|
@@ -3084,7 +3082,13 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
3084
3082
|
setHarness(value);
|
|
3085
3083
|
remember({ harness: value });
|
|
3086
3084
|
} }),
|
|
3087
|
-
/* @__PURE__ */ jsx10(
|
|
3085
|
+
/* @__PURE__ */ jsx10(ExecutionModeSelect, { modes: launchModes, value: mode, disabled: Boolean(starting) || mode !== requestedMode, onChange: (item) => {
|
|
3086
|
+
const next = { ...modes, [harness]: item };
|
|
3087
|
+
setModes(next);
|
|
3088
|
+
setPickerError(null);
|
|
3089
|
+
remember({ modes: next });
|
|
3090
|
+
} }),
|
|
3091
|
+
/* @__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 }) }) })
|
|
3088
3092
|
] })
|
|
3089
3093
|
] })
|
|
3090
3094
|
] })
|
|
@@ -3193,6 +3197,7 @@ export {
|
|
|
3193
3197
|
ContextCandidates,
|
|
3194
3198
|
ContinuationBar,
|
|
3195
3199
|
Conversation,
|
|
3200
|
+
ExecutionModeSelect,
|
|
3196
3201
|
HarnessAdvisory,
|
|
3197
3202
|
HarnessLogo,
|
|
3198
3203
|
HarnessPicker,
|
package/composer.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export type { MessengerLabels, PendingMessageModel, SupercodeUiState, UiAdapter } from './index.js';
|
|
2
|
-
export { Composer, ContinuationBar } from './index.js';
|
|
2
|
+
export { Composer, ContinuationBar, ExecutionModeSelect } from './index.js';
|
package/composer.mjs
CHANGED
|
@@ -77,6 +77,7 @@ var ACTIVITY_PRIORITY = Object.freeze({
|
|
|
77
77
|
finished: 30,
|
|
78
78
|
running: 20,
|
|
79
79
|
recent: 10,
|
|
80
|
+
observed: 0,
|
|
80
81
|
idle: 0
|
|
81
82
|
});
|
|
82
83
|
function canContinueHere(state) {
|
|
@@ -295,13 +296,23 @@ function useAutosizeTextarea(ref, value) {
|
|
|
295
296
|
// src/composer.jsx
|
|
296
297
|
import { jsx as jsx3, jsxs as jsxs3 } from "preact/jsx-runtime";
|
|
297
298
|
var composerMemory = /* @__PURE__ */ new Map();
|
|
299
|
+
function ExecutionModeSelect({ modes = [], value, onChange, disabled = false, label = "Run in" }) {
|
|
300
|
+
if (modes.length < 2) return null;
|
|
301
|
+
return /* @__PURE__ */ jsxs3("label", { className: "scui-execution-mode", title: disabled ? void 0 : `${label}: ${value}`, children: [
|
|
302
|
+
/* @__PURE__ */ jsx3("span", { children: label }),
|
|
303
|
+
/* @__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)) })
|
|
304
|
+
] });
|
|
305
|
+
}
|
|
298
306
|
function ContinuationBar({ state, adapter, labels = DEFAULT_LABELS }) {
|
|
307
|
+
const continuationModes = state.continuationModes?.length ? state.continuationModes : ["headless"];
|
|
308
|
+
const [selection, setSelection] = useState2({ key: null, mode: null });
|
|
309
|
+
const requestedMode = selection.key === state.attached?.key ? selection.mode : null;
|
|
310
|
+
const executionMode = continuationModes.includes(requestedMode) ? requestedMode : continuationModes.includes("terminal") ? "terminal" : continuationModes[0];
|
|
299
311
|
if (state.mode !== "mirror" || state.canSend) return null;
|
|
300
312
|
const attached = state.attached;
|
|
301
313
|
const row = attached?.key ? state.sessions.find((item) => item.key === attached.key) : null;
|
|
302
314
|
const activeElsewhere = row?.runtimeStatus === "running" || row?.runtimeStatus === "busy" || row?.runtimeStatus === "idle";
|
|
303
315
|
const resume = canContinueHere(state);
|
|
304
|
-
const terminal = resume && state.continuationModes?.includes("terminal");
|
|
305
316
|
const join = state.canAttach;
|
|
306
317
|
const branch = state.canBranch;
|
|
307
318
|
if (!resume && !join && !branch) return /* @__PURE__ */ jsx3("div", { className: "scui-continuation", children: /* @__PURE__ */ jsxs3("span", { children: [
|
|
@@ -314,8 +325,8 @@ function ContinuationBar({ state, adapter, labels = DEFAULT_LABELS }) {
|
|
|
314
325
|
/* @__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." })
|
|
315
326
|
] }),
|
|
316
327
|
/* @__PURE__ */ jsxs3("span", { className: "scui-continuation-actions", children: [
|
|
317
|
-
/* @__PURE__ */ jsx3(
|
|
318
|
-
|
|
328
|
+
!join && resume ? /* @__PURE__ */ jsx3(ExecutionModeSelect, { modes: continuationModes, value: executionMode, disabled: Boolean(state.operation), onChange: (mode) => setSelection({ key: state.attached?.key ?? null, mode }) }) : null,
|
|
329
|
+
/* @__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 })
|
|
319
330
|
] })
|
|
320
331
|
] });
|
|
321
332
|
}
|
|
@@ -536,5 +547,6 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
|
|
|
536
547
|
}
|
|
537
548
|
export {
|
|
538
549
|
Composer,
|
|
539
|
-
ContinuationBar
|
|
550
|
+
ContinuationBar,
|
|
551
|
+
ExecutionModeSelect
|
|
540
552
|
};
|
package/controller.d.ts
CHANGED
|
@@ -65,6 +65,8 @@ export interface SessionInventoryProjectionOptions {
|
|
|
65
65
|
maxSessions?: number;
|
|
66
66
|
liveWindowMs?: number;
|
|
67
67
|
preserveOrder?: boolean;
|
|
68
|
+
/** Capability check performed by the trusted host; omitted rows are read-only observations. */
|
|
69
|
+
isWritable?(descriptor: SessionDescriptor): boolean;
|
|
68
70
|
}
|
|
69
71
|
|
|
70
72
|
export function sessionConversationUpdatedAt(descriptor: SessionDescriptor): number | null;
|
package/controller.mjs
CHANGED
|
@@ -72,7 +72,7 @@ function descriptorTitle(descriptor) {
|
|
|
72
72
|
return compactTopic(nativeTitle);
|
|
73
73
|
}
|
|
74
74
|
if (descriptor?.locator?.harness !== 'claude-code' && descriptor?.locator?.harness !== 'codex') {
|
|
75
|
-
return compactTopic(nativeTitle || workspace
|
|
75
|
+
return compactTopic(nativeTitle || workspace) || 'Untitled chat';
|
|
76
76
|
}
|
|
77
77
|
const openingUserMessages = (descriptor?.preview_candidates ?? []).filter(
|
|
78
78
|
(candidate) => candidate?.role === undefined || candidate.role === 'user',
|
|
@@ -125,6 +125,7 @@ export function projectSessionInventory(descriptors, options) {
|
|
|
125
125
|
active: active?.sessionId != null
|
|
126
126
|
&& descriptor.locator.harness === active.harness
|
|
127
127
|
&& descriptor.locator.session_id === active.sessionId,
|
|
128
|
+
writable: options.isWritable?.(descriptor) === true,
|
|
128
129
|
live: updatedAt !== null && now - updatedAt <= liveWindowMs,
|
|
129
130
|
runtimeStatus: sessionDescriptorRuntimeStatus(descriptor),
|
|
130
131
|
});
|
|
@@ -378,14 +379,14 @@ function attached(snapshot) {
|
|
|
378
379
|
harness: snapshot.activeHarness,
|
|
379
380
|
name: workspaceName(snapshot.workspace),
|
|
380
381
|
cwd: snapshot.workspace,
|
|
381
|
-
title:
|
|
382
|
+
title: 'Untitled chat',
|
|
382
383
|
} : null;
|
|
383
384
|
return {
|
|
384
385
|
key: session.key,
|
|
385
386
|
harness: session.harness,
|
|
386
387
|
name: workspaceName(session.cwd),
|
|
387
388
|
cwd: session.cwd ?? '',
|
|
388
|
-
title: session.title ??
|
|
389
|
+
title: session.title ?? 'Untitled chat',
|
|
389
390
|
};
|
|
390
391
|
}
|
|
391
392
|
|
|
@@ -442,11 +443,12 @@ function projectClientSnapshotInternal(snapshot, options, imageRegistry) {
|
|
|
442
443
|
harness: session.harness,
|
|
443
444
|
name: workspaceName(session.cwd),
|
|
444
445
|
cwd: session.cwd ?? '',
|
|
445
|
-
title: session.title ??
|
|
446
|
+
title: session.title ?? 'Untitled chat',
|
|
446
447
|
age: relativeAge(session.updatedAt, now),
|
|
447
448
|
updatedAt: session.updatedAt ?? null,
|
|
448
449
|
messages: session.messageCount ?? null,
|
|
449
450
|
active: session.key === snapshot.activeSessionKey,
|
|
451
|
+
writable: session.key === snapshot.activeSessionKey && actions.send === true,
|
|
450
452
|
live: typeof session.updatedAt === 'number' && now - session.updatedAt <= liveWindowMs,
|
|
451
453
|
runtimeStatus: session.liveStatus ?? null,
|
|
452
454
|
}));
|
package/conversation.mjs
CHANGED
package/core.mjs
CHANGED
|
@@ -562,6 +562,7 @@ function readSessions(value) {
|
|
|
562
562
|
updatedAt: nullableNumber(row.updatedAt),
|
|
563
563
|
messages: nullableNumber(row.messages),
|
|
564
564
|
active: row.active === true,
|
|
565
|
+
writable: row.writable === true,
|
|
565
566
|
live: row.live === true,
|
|
566
567
|
runtimeStatus: row.runtimeStatus === 'running' || row.runtimeStatus === 'busy' || row.runtimeStatus === 'idle' ? row.runtimeStatus : null,
|
|
567
568
|
}];
|
|
@@ -806,7 +807,9 @@ export function normalizeUiState(value) {
|
|
|
806
807
|
: startable ? ['headless'] : [];
|
|
807
808
|
const preferredLaunchMode = launchModes.includes(item.preferredLaunchMode)
|
|
808
809
|
? item.preferredLaunchMode
|
|
809
|
-
: launchModes
|
|
810
|
+
: launchModes.includes('terminal')
|
|
811
|
+
? 'terminal'
|
|
812
|
+
: launchModes[0] ?? null;
|
|
810
813
|
const capabilities = record(item.capabilities);
|
|
811
814
|
return [{
|
|
812
815
|
id: item.id,
|
|
@@ -856,6 +859,9 @@ export function sessionDisplayName(session) {
|
|
|
856
859
|
}
|
|
857
860
|
|
|
858
861
|
export function sessionActivity(state, row) {
|
|
862
|
+
// Runtime activity and interaction authority are deliberately independent. A process may be
|
|
863
|
+
// busy in another terminal, but presenting it as this surface's active agent is misleading.
|
|
864
|
+
if (!row.writable) return 'observed';
|
|
859
865
|
if (state.needsInput && row.active) return 'needs-input';
|
|
860
866
|
if (state.busy && row.active) return 'working';
|
|
861
867
|
// Unread/finished attention has its own badge. It must not suppress the
|
|
@@ -876,6 +882,7 @@ const ACTIVITY_PRIORITY = Object.freeze({
|
|
|
876
882
|
finished: 30,
|
|
877
883
|
running: 20,
|
|
878
884
|
recent: 10,
|
|
885
|
+
observed: 0,
|
|
879
886
|
idle: 0,
|
|
880
887
|
});
|
|
881
888
|
|
|
@@ -887,6 +894,7 @@ function activityDetail(activity, preview, pill) {
|
|
|
887
894
|
if (activity === 'finished') return 'Finished';
|
|
888
895
|
if (activity === 'unseen') return 'New reply';
|
|
889
896
|
if (activity === 'running') return 'Session is running';
|
|
897
|
+
if (activity === 'observed') return 'Read-only channel';
|
|
890
898
|
return pill || 'Agent chats';
|
|
891
899
|
}
|
|
892
900
|
|
package/embed.mjs
CHANGED
|
@@ -569,6 +569,7 @@ function readSessions(value) {
|
|
|
569
569
|
updatedAt: nullableNumber(row.updatedAt),
|
|
570
570
|
messages: nullableNumber(row.messages),
|
|
571
571
|
active: row.active === true,
|
|
572
|
+
writable: row.writable === true,
|
|
572
573
|
live: row.live === true,
|
|
573
574
|
runtimeStatus: row.runtimeStatus === "running" || row.runtimeStatus === "busy" || row.runtimeStatus === "idle" ? row.runtimeStatus : null
|
|
574
575
|
}];
|
|
@@ -753,7 +754,7 @@ function normalizeUiState(value) {
|
|
|
753
754
|
if (!item || typeof item.id !== "string") return [];
|
|
754
755
|
const startable = item.startable === true;
|
|
755
756
|
const launchModes = Array.isArray(item.launchModes) ? [...new Set(item.launchModes.filter((mode) => mode === "headless" || mode === "terminal"))] : startable ? ["headless"] : [];
|
|
756
|
-
const preferredLaunchMode = launchModes.includes(item.preferredLaunchMode) ? item.preferredLaunchMode : launchModes[0] ?? null;
|
|
757
|
+
const preferredLaunchMode = launchModes.includes(item.preferredLaunchMode) ? item.preferredLaunchMode : launchModes.includes("terminal") ? "terminal" : launchModes[0] ?? null;
|
|
757
758
|
const capabilities = record(item.capabilities);
|
|
758
759
|
return [{
|
|
759
760
|
id: item.id,
|
|
@@ -798,6 +799,7 @@ function sessionDisplayName(session) {
|
|
|
798
799
|
return title && title !== session.name ? title : session.name || "Untitled chat";
|
|
799
800
|
}
|
|
800
801
|
function sessionActivity(state, row) {
|
|
802
|
+
if (!row.writable) return "observed";
|
|
801
803
|
if (state.needsInput && row.active) return "needs-input";
|
|
802
804
|
if (state.busy && row.active) return "working";
|
|
803
805
|
if (row.runtimeStatus === "busy") return "working";
|
|
@@ -815,6 +817,7 @@ var ACTIVITY_PRIORITY = Object.freeze({
|
|
|
815
817
|
finished: 30,
|
|
816
818
|
running: 20,
|
|
817
819
|
recent: 10,
|
|
820
|
+
observed: 0,
|
|
818
821
|
idle: 0
|
|
819
822
|
});
|
|
820
823
|
function filterSessions(rows, query) {
|
|
@@ -1260,13 +1263,23 @@ function useAutosizeTextarea(ref, value) {
|
|
|
1260
1263
|
// src/composer.jsx
|
|
1261
1264
|
import { jsx as jsx3, jsxs as jsxs3 } from "preact/jsx-runtime";
|
|
1262
1265
|
var composerMemory = /* @__PURE__ */ new Map();
|
|
1266
|
+
function ExecutionModeSelect({ modes = [], value, onChange, disabled = false, label = "Run in" }) {
|
|
1267
|
+
if (modes.length < 2) return null;
|
|
1268
|
+
return /* @__PURE__ */ jsxs3("label", { className: "scui-execution-mode", title: disabled ? void 0 : `${label}: ${value}`, children: [
|
|
1269
|
+
/* @__PURE__ */ jsx3("span", { children: label }),
|
|
1270
|
+
/* @__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)) })
|
|
1271
|
+
] });
|
|
1272
|
+
}
|
|
1263
1273
|
function ContinuationBar({ state, adapter, labels = DEFAULT_LABELS }) {
|
|
1274
|
+
const continuationModes = state.continuationModes?.length ? state.continuationModes : ["headless"];
|
|
1275
|
+
const [selection, setSelection] = useState2({ key: null, mode: null });
|
|
1276
|
+
const requestedMode = selection.key === state.attached?.key ? selection.mode : null;
|
|
1277
|
+
const executionMode = continuationModes.includes(requestedMode) ? requestedMode : continuationModes.includes("terminal") ? "terminal" : continuationModes[0];
|
|
1264
1278
|
if (state.mode !== "mirror" || state.canSend) return null;
|
|
1265
1279
|
const attached = state.attached;
|
|
1266
1280
|
const row = attached?.key ? state.sessions.find((item) => item.key === attached.key) : null;
|
|
1267
1281
|
const activeElsewhere = row?.runtimeStatus === "running" || row?.runtimeStatus === "busy" || row?.runtimeStatus === "idle";
|
|
1268
1282
|
const resume = canContinueHere(state);
|
|
1269
|
-
const terminal = resume && state.continuationModes?.includes("terminal");
|
|
1270
1283
|
const join = state.canAttach;
|
|
1271
1284
|
const branch = state.canBranch;
|
|
1272
1285
|
if (!resume && !join && !branch) return /* @__PURE__ */ jsx3("div", { className: "scui-continuation", children: /* @__PURE__ */ jsxs3("span", { children: [
|
|
@@ -1279,8 +1292,8 @@ function ContinuationBar({ state, adapter, labels = DEFAULT_LABELS }) {
|
|
|
1279
1292
|
/* @__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." })
|
|
1280
1293
|
] }),
|
|
1281
1294
|
/* @__PURE__ */ jsxs3("span", { className: "scui-continuation-actions", children: [
|
|
1282
|
-
/* @__PURE__ */ jsx3(
|
|
1283
|
-
|
|
1295
|
+
!join && resume ? /* @__PURE__ */ jsx3(ExecutionModeSelect, { modes: continuationModes, value: executionMode, disabled: Boolean(state.operation), onChange: (mode) => setSelection({ key: state.attached?.key ?? null, mode }) }) : null,
|
|
1296
|
+
/* @__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 })
|
|
1284
1297
|
] })
|
|
1285
1298
|
] });
|
|
1286
1299
|
}
|
|
@@ -2209,7 +2222,7 @@ function SessionRow({ row, state, onOpen, now = Date.now() }) {
|
|
|
2209
2222
|
const unreadCount = attention?.unreadCount ?? 0;
|
|
2210
2223
|
const unreadLabel = unreadCount > 99 ? "99+" : String(unreadCount);
|
|
2211
2224
|
const age = relativeAge(row.previewUpdatedAt ?? row.updatedAt, now) || row.age;
|
|
2212
|
-
return /* @__PURE__ */ jsxs6("button", { className: "scui-session", "data-active": row.active, "data-activity": activity, "data-session-key": row.key, type: "button", "aria-label": `${title} \xB7 ${harnessDisplayName(row.harness)}${working ? " \xB7 Working" : ""}${path.complete ? ` \xB7 ${path.complete}` : ""}${preview ? ` \xB7 ${preview}` : ""}${unreadCount ? ` \xB7 ${unreadCount} unread` : ""}${age ? ` \xB7 ${age}` : ""}`, "aria-current": row.active ? "true" : void 0, onClick: () => onOpen(row), children: [
|
|
2225
|
+
return /* @__PURE__ */ jsxs6("button", { className: "scui-session", "data-active": row.active, "data-activity": activity, "data-writable": row.writable, "data-session-key": row.key, type: "button", "aria-label": `${title} \xB7 ${harnessDisplayName(row.harness)}${row.writable ? "" : " \xB7 Read-only"}${working ? " \xB7 Working" : ""}${path.complete ? ` \xB7 ${path.complete}` : ""}${preview ? ` \xB7 ${preview}` : ""}${unreadCount ? ` \xB7 ${unreadCount} unread` : ""}${age ? ` \xB7 ${age}` : ""}`, "aria-current": row.active ? "true" : void 0, onClick: () => onOpen(row), children: [
|
|
2213
2226
|
/* @__PURE__ */ jsx7(HarnessLogo, { id: row.harness, activity, size: 34 }),
|
|
2214
2227
|
/* @__PURE__ */ jsxs6("span", { className: "scui-session-copy", children: [
|
|
2215
2228
|
/* @__PURE__ */ jsxs6("span", { className: "scui-session-title", children: [
|
|
@@ -2790,7 +2803,8 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2790
2803
|
const Readiness = components.HarnessReadiness ?? HarnessReadiness;
|
|
2791
2804
|
const launchModes = selectedHarness?.launchModes?.length ? selectedHarness.launchModes : ["headless"];
|
|
2792
2805
|
const rememberedMode = modes[harness];
|
|
2793
|
-
const
|
|
2806
|
+
const requestedMode = launchModes.includes(rememberedMode) ? rememberedMode : launchModes.includes(selectedHarness?.preferredLaunchMode) ? selectedHarness.preferredLaunchMode : launchModes[0];
|
|
2807
|
+
const mode = (context.length > 0 || images.length > 0) && launchModes.includes("headless") ? "headless" : requestedMode;
|
|
2794
2808
|
const terminalAttachments = mode === "terminal" && (context.length > 0 || images.length > 0);
|
|
2795
2809
|
const remember = (overrides = {}) => boundedSet(newChatMemory, memoryKey, { harness, draft, context, images, modes, ...overrides });
|
|
2796
2810
|
useAutosizeTextarea(textarea, draft);
|
|
@@ -2832,7 +2846,7 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2832
2846
|
lastComposerCommand.current = composerCommand.id;
|
|
2833
2847
|
if (composerCommand.action === "attach") {
|
|
2834
2848
|
if (mode === "terminal" && !launchModes.includes("headless")) {
|
|
2835
|
-
setPickerError("Terminal starts currently accept text only. Choose
|
|
2849
|
+
setPickerError("Terminal starts currently accept text only. Choose Headless to attach context or images.");
|
|
2836
2850
|
} else {
|
|
2837
2851
|
try {
|
|
2838
2852
|
const attachments = partitionAttachments(composerCommand.attachments);
|
|
@@ -2840,12 +2854,10 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2840
2854
|
if (images.length + attachments.images.length > MAX_IMAGE_ITEMS) throw new Error(`Attach at most ${MAX_IMAGE_ITEMS} images.`);
|
|
2841
2855
|
const nextContext = mergeContext(context, attachments.context);
|
|
2842
2856
|
const nextImages = mergeImages(images, attachments.images);
|
|
2843
|
-
const nextModes = mode === "terminal" ? { ...modes, [harness]: "headless" } : modes;
|
|
2844
|
-
if (nextModes !== modes) setModes(nextModes);
|
|
2845
2857
|
setContext(nextContext);
|
|
2846
2858
|
setImages(nextImages);
|
|
2847
2859
|
setPickerError(null);
|
|
2848
|
-
remember({ context: nextContext, images: nextImages
|
|
2860
|
+
remember({ context: nextContext, images: nextImages });
|
|
2849
2861
|
} catch (error) {
|
|
2850
2862
|
setPickerError(error instanceof Error ? error.message : "Could not attach context.");
|
|
2851
2863
|
}
|
|
@@ -2886,7 +2898,7 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2886
2898
|
const allFiles = Array.from(value ?? []);
|
|
2887
2899
|
if (!allFiles.length) return false;
|
|
2888
2900
|
if (mode === "terminal") {
|
|
2889
|
-
setPickerError("Terminal starts currently accept text only. Choose
|
|
2901
|
+
setPickerError("Terminal starts currently accept text only. Choose Headless to attach context or images.");
|
|
2890
2902
|
return true;
|
|
2891
2903
|
}
|
|
2892
2904
|
const files = allFiles.filter((file) => file.type.startsWith("image/"));
|
|
@@ -2951,21 +2963,6 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2951
2963
|
] }),
|
|
2952
2964
|
/* @__PURE__ */ jsxs8("div", { className: "scui-compose", children: [
|
|
2953
2965
|
/* @__PURE__ */ jsx9(Readiness, { harnesses: state.harnesses, state, adapter }),
|
|
2954
|
-
launchModes.length > 1 ? /* @__PURE__ */ jsxs8("fieldset", { className: "scui-launch-modes", disabled: Boolean(starting), children: [
|
|
2955
|
-
/* @__PURE__ */ jsx9("legend", { children: "Run as" }),
|
|
2956
|
-
launchModes.map((item) => /* @__PURE__ */ jsxs8("label", { children: [
|
|
2957
|
-
/* @__PURE__ */ jsx9("input", { type: "radio", name: `launch-mode-${memoryKey}`, value: item, checked: mode === item, onChange: () => {
|
|
2958
|
-
const next = { ...modes, [harness]: item };
|
|
2959
|
-
setModes(next);
|
|
2960
|
-
setPickerError(null);
|
|
2961
|
-
remember({ modes: next });
|
|
2962
|
-
} }),
|
|
2963
|
-
/* @__PURE__ */ jsxs8("span", { children: [
|
|
2964
|
-
/* @__PURE__ */ jsx9("strong", { children: item === "terminal" ? "Terminal" : "Chat" }),
|
|
2965
|
-
/* @__PURE__ */ jsx9("small", { children: item === "terminal" ? "Interactive tmux session" : "Managed here" })
|
|
2966
|
-
] })
|
|
2967
|
-
] }, item))
|
|
2968
|
-
] }) : null,
|
|
2969
2966
|
mode !== "terminal" ? /* @__PURE__ */ jsx9(ContextCandidates, { items: contextCandidates, context, images, state, adapter, onAttach: attachCandidate, component: components.ContextCandidate }) : null,
|
|
2970
2967
|
/* @__PURE__ */ jsx9(ImageTray, { items: images, onRemove: (index) => setImages((items) => {
|
|
2971
2968
|
const next = items.filter((_, itemIndex) => itemIndex !== index);
|
|
@@ -2977,7 +2974,7 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2977
2974
|
remember({ context: next });
|
|
2978
2975
|
return next;
|
|
2979
2976
|
}) }),
|
|
2980
|
-
terminalAttachments ? /* @__PURE__ */ jsx9("small", { className: "scui-context-error", role: "alert", children: "Terminal starts currently accept text only. Remove attachments or choose
|
|
2977
|
+
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,
|
|
2981
2978
|
/* @__PURE__ */ jsxs8("div", { className: `scui-envelope scui-new-envelope${dragging ? " scui-drop-target" : ""}`, onDragEnter: (event) => {
|
|
2982
2979
|
if (Array.from(event.dataTransfer?.types ?? []).includes("Files")) {
|
|
2983
2980
|
event.preventDefault();
|
|
@@ -3004,7 +3001,13 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
3004
3001
|
setHarness(value);
|
|
3005
3002
|
remember({ harness: value });
|
|
3006
3003
|
} }),
|
|
3007
|
-
/* @__PURE__ */ jsx9(
|
|
3004
|
+
/* @__PURE__ */ jsx9(ExecutionModeSelect, { modes: launchModes, value: mode, disabled: Boolean(starting) || mode !== requestedMode, onChange: (item) => {
|
|
3005
|
+
const next = { ...modes, [harness]: item };
|
|
3006
|
+
setModes(next);
|
|
3007
|
+
setPickerError(null);
|
|
3008
|
+
remember({ modes: next });
|
|
3009
|
+
} }),
|
|
3010
|
+
/* @__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 }) }) })
|
|
3008
3011
|
] })
|
|
3009
3012
|
] })
|
|
3010
3013
|
] })
|