@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/index.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ export type ControlStrategy = 'start' | 'resume' | 'attach' | 'branch' | 'reduce
|
|
|
8
8
|
export type ExecutionMode = 'headless' | 'terminal';
|
|
9
9
|
/** @deprecated Use ExecutionMode. */
|
|
10
10
|
export type ContinuationMode = ExecutionMode;
|
|
11
|
-
export type SessionActivity = 'idle' | 'recent' | 'running' | 'working' | 'needs-input' | 'finished' | 'failed' | 'unseen';
|
|
11
|
+
export type SessionActivity = 'idle' | 'observed' | 'recent' | 'running' | 'working' | 'needs-input' | 'finished' | 'failed' | 'unseen';
|
|
12
12
|
|
|
13
13
|
export interface AgentActivityModel {
|
|
14
14
|
harness: HarnessId | '';
|
|
@@ -40,7 +40,7 @@ export interface HarnessOption {
|
|
|
40
40
|
};
|
|
41
41
|
/** Host-supported ways to start this harness. Omitted means headless when startable. */
|
|
42
42
|
launchModes?: ExecutionMode[];
|
|
43
|
-
/** Host preference, used unless this browser has a remembered choice
|
|
43
|
+
/** Host preference, used unless this browser has a remembered choice. Defaults to terminal when advertised. */
|
|
44
44
|
preferredLaunchMode?: ExecutionMode | null;
|
|
45
45
|
}
|
|
46
46
|
|
|
@@ -58,6 +58,8 @@ export interface SessionRowModel {
|
|
|
58
58
|
updatedAt: number | null;
|
|
59
59
|
messages: number | null;
|
|
60
60
|
active: boolean;
|
|
61
|
+
/** This surface currently has a real send or terminal-control path for the session. */
|
|
62
|
+
writable: boolean;
|
|
61
63
|
live: boolean;
|
|
62
64
|
runtimeStatus: 'running' | 'busy' | 'idle' | null;
|
|
63
65
|
}
|
|
@@ -264,7 +266,7 @@ export interface SupercodeUiState {
|
|
|
264
266
|
canSend: boolean;
|
|
265
267
|
canSteer: boolean;
|
|
266
268
|
canResume: boolean;
|
|
267
|
-
/** Execution
|
|
269
|
+
/** Execution transports the current host can actually provide. */
|
|
268
270
|
continuationModes: ContinuationMode[];
|
|
269
271
|
canBranch: boolean;
|
|
270
272
|
canAttach: boolean;
|
|
@@ -557,6 +559,7 @@ export function Conversation(props: { state: SupercodeUiState; adapter: UiAdapte
|
|
|
557
559
|
export function SessionRow(props: { row: SessionRowModel; state: SupercodeUiState; adapter: UiAdapter; now?: number; onOpen(row: SessionRowModel): void }): VNode;
|
|
558
560
|
export function SessionList(props: { state: SupercodeUiState; adapter: UiAdapter; onOpen(row: SessionRowModel): void; onNew?(): void; onClose?(): void; components?: MessengerComponents; slots?: MessengerSlots; labels?: MessengerLabels; focusKey?: string | null; memoryKey?: string; headerActions?: MessengerSlots['headerActions'] }): VNode;
|
|
559
561
|
export function ContinuationBar(props: { state: SupercodeUiState; adapter: UiAdapter; labels?: MessengerLabels }): VNode | null;
|
|
562
|
+
export function ExecutionModeSelect(props: { modes?: ExecutionMode[]; value: ExecutionMode; onChange?(mode: ExecutionMode): void; disabled?: boolean; label?: string }): VNode | null;
|
|
560
563
|
export function Composer(props: { state: SupercodeUiState; adapter: UiAdapter; labels?: MessengerLabels; memoryKey?: string; pendingStatus?: PendingMessageModel['status'] | 'editing' | null; restoreDraft?: { id: string | number; text: string; context?: TranscriptContext[]; images?: TranscriptImage[] } | null; command?: MessengerComposerCommand | null; contextCandidates?: TranscriptAttachment[]; components?: Pick<MessengerComponents, 'ContextCandidate'>; onPending?(text: string, context?: TranscriptContext[], images?: TranscriptImage[]): void; onDraftRestored?(id: string | number): void }): VNode;
|
|
561
564
|
export function SupercodeMessenger(props: MessengerProps): VNode;
|
|
562
565
|
|
package/messenger.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 filterSessions(rows, query) {
|
|
@@ -1257,13 +1260,23 @@ function useAutosizeTextarea(ref, value) {
|
|
|
1257
1260
|
// src/composer.jsx
|
|
1258
1261
|
import { jsx as jsx3, jsxs as jsxs3 } from "preact/jsx-runtime";
|
|
1259
1262
|
var composerMemory = /* @__PURE__ */ new Map();
|
|
1263
|
+
function ExecutionModeSelect({ modes = [], value, onChange, disabled = false, label = "Run in" }) {
|
|
1264
|
+
if (modes.length < 2) return null;
|
|
1265
|
+
return /* @__PURE__ */ jsxs3("label", { className: "scui-execution-mode", title: disabled ? void 0 : `${label}: ${value}`, children: [
|
|
1266
|
+
/* @__PURE__ */ jsx3("span", { children: label }),
|
|
1267
|
+
/* @__PURE__ */ jsx3("select", { "aria-label": label, value, disabled, onChange: (event) => onChange?.(event.currentTarget.value), children: modes.map((mode) => /* @__PURE__ */ jsx3("option", { value: mode, children: mode === "terminal" ? "Terminal" : "Headless" }, mode)) })
|
|
1268
|
+
] });
|
|
1269
|
+
}
|
|
1260
1270
|
function ContinuationBar({ state, adapter, labels = DEFAULT_LABELS }) {
|
|
1271
|
+
const continuationModes = state.continuationModes?.length ? state.continuationModes : ["headless"];
|
|
1272
|
+
const [selection, setSelection] = useState2({ key: null, mode: null });
|
|
1273
|
+
const requestedMode = selection.key === state.attached?.key ? selection.mode : null;
|
|
1274
|
+
const executionMode = continuationModes.includes(requestedMode) ? requestedMode : continuationModes.includes("terminal") ? "terminal" : continuationModes[0];
|
|
1261
1275
|
if (state.mode !== "mirror" || state.canSend) return null;
|
|
1262
1276
|
const attached = state.attached;
|
|
1263
1277
|
const row = attached?.key ? state.sessions.find((item) => item.key === attached.key) : null;
|
|
1264
1278
|
const activeElsewhere = row?.runtimeStatus === "running" || row?.runtimeStatus === "busy" || row?.runtimeStatus === "idle";
|
|
1265
1279
|
const resume = canContinueHere(state);
|
|
1266
|
-
const terminal = resume && state.continuationModes?.includes("terminal");
|
|
1267
1280
|
const join = state.canAttach;
|
|
1268
1281
|
const branch = state.canBranch;
|
|
1269
1282
|
if (!resume && !join && !branch) return /* @__PURE__ */ jsx3("div", { className: "scui-continuation", children: /* @__PURE__ */ jsxs3("span", { children: [
|
|
@@ -1276,8 +1289,8 @@ function ContinuationBar({ state, adapter, labels = DEFAULT_LABELS }) {
|
|
|
1276
1289
|
/* @__PURE__ */ jsx3("small", { children: join ? "Join the proven live runtime without taking it over." : resume ? `Resume this ${harnessDisplayName(state.harness)} session here.` : "Start an independent continuation." })
|
|
1277
1290
|
] }),
|
|
1278
1291
|
/* @__PURE__ */ jsxs3("span", { className: "scui-continuation-actions", children: [
|
|
1279
|
-
/* @__PURE__ */ jsx3(
|
|
1280
|
-
|
|
1292
|
+
!join && resume ? /* @__PURE__ */ jsx3(ExecutionModeSelect, { modes: continuationModes, value: executionMode, disabled: Boolean(state.operation), onChange: (mode) => setSelection({ key: state.attached?.key ?? null, mode }) }) : null,
|
|
1293
|
+
/* @__PURE__ */ jsx3("button", { type: "button", disabled: Boolean(state.operation), onClick: () => dispatchConfirmedIntent(adapter, join ? { action: "join" } : resume ? { action: "resume", mode: executionMode } : { action: "branch" }), children: join ? labels.joinLive : resume ? labels.continueHere : labels.forkHere })
|
|
1281
1294
|
] })
|
|
1282
1295
|
] });
|
|
1283
1296
|
}
|
|
@@ -2206,7 +2219,7 @@ function SessionRow({ row, state, onOpen, now = Date.now() }) {
|
|
|
2206
2219
|
const unreadCount = attention?.unreadCount ?? 0;
|
|
2207
2220
|
const unreadLabel = unreadCount > 99 ? "99+" : String(unreadCount);
|
|
2208
2221
|
const age = relativeAge(row.previewUpdatedAt ?? row.updatedAt, now) || row.age;
|
|
2209
|
-
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: [
|
|
2222
|
+
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: [
|
|
2210
2223
|
/* @__PURE__ */ jsx7(HarnessLogo, { id: row.harness, activity, size: 34 }),
|
|
2211
2224
|
/* @__PURE__ */ jsxs6("span", { className: "scui-session-copy", children: [
|
|
2212
2225
|
/* @__PURE__ */ jsxs6("span", { className: "scui-session-title", children: [
|
|
@@ -2787,7 +2800,8 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2787
2800
|
const Readiness = components.HarnessReadiness ?? HarnessReadiness;
|
|
2788
2801
|
const launchModes = selectedHarness?.launchModes?.length ? selectedHarness.launchModes : ["headless"];
|
|
2789
2802
|
const rememberedMode = modes[harness];
|
|
2790
|
-
const
|
|
2803
|
+
const requestedMode = launchModes.includes(rememberedMode) ? rememberedMode : launchModes.includes(selectedHarness?.preferredLaunchMode) ? selectedHarness.preferredLaunchMode : launchModes[0];
|
|
2804
|
+
const mode = (context.length > 0 || images.length > 0) && launchModes.includes("headless") ? "headless" : requestedMode;
|
|
2791
2805
|
const terminalAttachments = mode === "terminal" && (context.length > 0 || images.length > 0);
|
|
2792
2806
|
const remember = (overrides = {}) => boundedSet(newChatMemory, memoryKey, { harness, draft, context, images, modes, ...overrides });
|
|
2793
2807
|
useAutosizeTextarea(textarea, draft);
|
|
@@ -2829,7 +2843,7 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2829
2843
|
lastComposerCommand.current = composerCommand.id;
|
|
2830
2844
|
if (composerCommand.action === "attach") {
|
|
2831
2845
|
if (mode === "terminal" && !launchModes.includes("headless")) {
|
|
2832
|
-
setPickerError("Terminal starts currently accept text only. Choose
|
|
2846
|
+
setPickerError("Terminal starts currently accept text only. Choose Headless to attach context or images.");
|
|
2833
2847
|
} else {
|
|
2834
2848
|
try {
|
|
2835
2849
|
const attachments = partitionAttachments(composerCommand.attachments);
|
|
@@ -2837,12 +2851,10 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2837
2851
|
if (images.length + attachments.images.length > MAX_IMAGE_ITEMS) throw new Error(`Attach at most ${MAX_IMAGE_ITEMS} images.`);
|
|
2838
2852
|
const nextContext = mergeContext(context, attachments.context);
|
|
2839
2853
|
const nextImages = mergeImages(images, attachments.images);
|
|
2840
|
-
const nextModes = mode === "terminal" ? { ...modes, [harness]: "headless" } : modes;
|
|
2841
|
-
if (nextModes !== modes) setModes(nextModes);
|
|
2842
2854
|
setContext(nextContext);
|
|
2843
2855
|
setImages(nextImages);
|
|
2844
2856
|
setPickerError(null);
|
|
2845
|
-
remember({ context: nextContext, images: nextImages
|
|
2857
|
+
remember({ context: nextContext, images: nextImages });
|
|
2846
2858
|
} catch (error) {
|
|
2847
2859
|
setPickerError(error instanceof Error ? error.message : "Could not attach context.");
|
|
2848
2860
|
}
|
|
@@ -2883,7 +2895,7 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2883
2895
|
const allFiles = Array.from(value ?? []);
|
|
2884
2896
|
if (!allFiles.length) return false;
|
|
2885
2897
|
if (mode === "terminal") {
|
|
2886
|
-
setPickerError("Terminal starts currently accept text only. Choose
|
|
2898
|
+
setPickerError("Terminal starts currently accept text only. Choose Headless to attach context or images.");
|
|
2887
2899
|
return true;
|
|
2888
2900
|
}
|
|
2889
2901
|
const files = allFiles.filter((file) => file.type.startsWith("image/"));
|
|
@@ -2948,21 +2960,6 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2948
2960
|
] }),
|
|
2949
2961
|
/* @__PURE__ */ jsxs8("div", { className: "scui-compose", children: [
|
|
2950
2962
|
/* @__PURE__ */ jsx9(Readiness, { harnesses: state.harnesses, state, adapter }),
|
|
2951
|
-
launchModes.length > 1 ? /* @__PURE__ */ jsxs8("fieldset", { className: "scui-launch-modes", disabled: Boolean(starting), children: [
|
|
2952
|
-
/* @__PURE__ */ jsx9("legend", { children: "Run as" }),
|
|
2953
|
-
launchModes.map((item) => /* @__PURE__ */ jsxs8("label", { children: [
|
|
2954
|
-
/* @__PURE__ */ jsx9("input", { type: "radio", name: `launch-mode-${memoryKey}`, value: item, checked: mode === item, onChange: () => {
|
|
2955
|
-
const next = { ...modes, [harness]: item };
|
|
2956
|
-
setModes(next);
|
|
2957
|
-
setPickerError(null);
|
|
2958
|
-
remember({ modes: next });
|
|
2959
|
-
} }),
|
|
2960
|
-
/* @__PURE__ */ jsxs8("span", { children: [
|
|
2961
|
-
/* @__PURE__ */ jsx9("strong", { children: item === "terminal" ? "Terminal" : "Chat" }),
|
|
2962
|
-
/* @__PURE__ */ jsx9("small", { children: item === "terminal" ? "Interactive tmux session" : "Managed here" })
|
|
2963
|
-
] })
|
|
2964
|
-
] }, item))
|
|
2965
|
-
] }) : null,
|
|
2966
2963
|
mode !== "terminal" ? /* @__PURE__ */ jsx9(ContextCandidates, { items: contextCandidates, context, images, state, adapter, onAttach: attachCandidate, component: components.ContextCandidate }) : null,
|
|
2967
2964
|
/* @__PURE__ */ jsx9(ImageTray, { items: images, onRemove: (index) => setImages((items) => {
|
|
2968
2965
|
const next = items.filter((_, itemIndex) => itemIndex !== index);
|
|
@@ -2974,7 +2971,7 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2974
2971
|
remember({ context: next });
|
|
2975
2972
|
return next;
|
|
2976
2973
|
}) }),
|
|
2977
|
-
terminalAttachments ? /* @__PURE__ */ jsx9("small", { className: "scui-context-error", role: "alert", children: "Terminal starts currently accept text only. Remove attachments or choose
|
|
2974
|
+
terminalAttachments ? /* @__PURE__ */ jsx9("small", { className: "scui-context-error", role: "alert", children: "Terminal starts currently accept text only. Remove attachments or choose Headless." }) : pickerError ? /* @__PURE__ */ jsx9("small", { className: "scui-context-error", role: "alert", children: pickerError }) : null,
|
|
2978
2975
|
/* @__PURE__ */ jsxs8("div", { className: `scui-envelope scui-new-envelope${dragging ? " scui-drop-target" : ""}`, onDragEnter: (event) => {
|
|
2979
2976
|
if (Array.from(event.dataTransfer?.types ?? []).includes("Files")) {
|
|
2980
2977
|
event.preventDefault();
|
|
@@ -3001,7 +2998,13 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
3001
2998
|
setHarness(value);
|
|
3002
2999
|
remember({ harness: value });
|
|
3003
3000
|
} }),
|
|
3004
|
-
/* @__PURE__ */ jsx9(
|
|
3001
|
+
/* @__PURE__ */ jsx9(ExecutionModeSelect, { modes: launchModes, value: mode, disabled: Boolean(starting) || mode !== requestedMode, onChange: (item) => {
|
|
3002
|
+
const next = { ...modes, [harness]: item };
|
|
3003
|
+
setModes(next);
|
|
3004
|
+
setPickerError(null);
|
|
3005
|
+
remember({ modes: next });
|
|
3006
|
+
} }),
|
|
3007
|
+
/* @__PURE__ */ jsx9("span", { children: /* @__PURE__ */ jsx9("button", { type: "button", className: "scui-send", "aria-label": mode === "terminal" ? "Start terminal session" : "Start headless session", disabled: !draft.trim() && !images.length || !harness || Boolean(starting) || terminalAttachments || mode === "terminal" && !draft.trim(), onClick: send, children: starting ? /* @__PURE__ */ jsx9("i", { className: "scui-control-spinner" }) : /* @__PURE__ */ jsx9(UiIcon, { name: "send", size: 17 }) }) })
|
|
3005
3008
|
] })
|
|
3006
3009
|
] })
|
|
3007
3010
|
] })
|
package/package.json
CHANGED
package/react/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/react/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 "react/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/react/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 "react/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
|
};
|