@volter-ai-dev/supercode-ui 0.1.12 → 0.1.14
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 +1 -1
- package/components.d.ts +1 -0
- package/components.mjs +791 -364
- package/composer.d.ts +1 -1
- package/composer.mjs +100 -24
- package/controller.mjs +5 -2
- package/conversation.d.ts +1 -0
- package/conversation.mjs +368 -193
- package/core.mjs +3 -2
- package/embed.mjs +792 -366
- package/icon.d.ts +2 -0
- package/icon.mjs +45 -0
- package/index.d.ts +17 -4
- package/messenger.mjs +790 -364
- package/package.json +8 -2
- package/sessions.mjs +164 -46
- package/styles.css +38 -18
package/composer.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export type { MessengerLabels, SupercodeUiState, UiAdapter } from './index.js';
|
|
1
|
+
export type { MessengerLabels, PendingMessageModel, SupercodeUiState, UiAdapter } from './index.js';
|
|
2
2
|
export { Composer, ContinuationBar } from './index.js';
|
package/composer.mjs
CHANGED
|
@@ -79,8 +79,65 @@ function boundedSet(map, key, value) {
|
|
|
79
79
|
while (map.size > MEMORY_LIMIT) map.delete(map.keys().next().value);
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
// src/icon.jsx
|
|
83
|
+
import { Fragment, jsx, jsxs } from "preact/jsx-runtime";
|
|
84
|
+
var ICONS = {
|
|
85
|
+
back: () => /* @__PURE__ */ jsx("path", { d: "m11.5 4.5-4.5 4.5 4.5 4.5" }),
|
|
86
|
+
check: () => /* @__PURE__ */ jsx("path", { d: "m4 9 3.25 3.25L14 5.5" }),
|
|
87
|
+
chevron: () => /* @__PURE__ */ jsx("path", { d: "m7 4.5 4.5 4.5L7 13.5" }),
|
|
88
|
+
close: () => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
89
|
+
/* @__PURE__ */ jsx("path", { d: "m4.75 4.75 8.5 8.5" }),
|
|
90
|
+
/* @__PURE__ */ jsx("path", { d: "m13.25 4.75-8.5 8.5" })
|
|
91
|
+
] }),
|
|
92
|
+
copy: () => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
93
|
+
/* @__PURE__ */ jsx("rect", { x: "5", y: "5", width: "8", height: "8", rx: "1.5" }),
|
|
94
|
+
/* @__PURE__ */ jsx("path", { d: "M3 10.5V4.25C3 3.56 3.56 3 4.25 3h6.25" })
|
|
95
|
+
] }),
|
|
96
|
+
down: () => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
97
|
+
/* @__PURE__ */ jsx("path", { d: "M9 3.5v10" }),
|
|
98
|
+
/* @__PURE__ */ jsx("path", { d: "m4.75 9.5 4.25 4 4.25-4" })
|
|
99
|
+
] }),
|
|
100
|
+
menu: () => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
101
|
+
/* @__PURE__ */ jsx("circle", { cx: "4", cy: "9", r: "1.25", fill: "currentColor", stroke: "none" }),
|
|
102
|
+
/* @__PURE__ */ jsx("circle", { cx: "9", cy: "9", r: "1.25", fill: "currentColor", stroke: "none" }),
|
|
103
|
+
/* @__PURE__ */ jsx("circle", { cx: "14", cy: "9", r: "1.25", fill: "currentColor", stroke: "none" })
|
|
104
|
+
] }),
|
|
105
|
+
plus: () => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
106
|
+
/* @__PURE__ */ jsx("path", { d: "M9 3.5v11" }),
|
|
107
|
+
/* @__PURE__ */ jsx("path", { d: "M3.5 9h11" })
|
|
108
|
+
] }),
|
|
109
|
+
search: () => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
110
|
+
/* @__PURE__ */ jsx("circle", { cx: "7.75", cy: "7.75", r: "4.25" }),
|
|
111
|
+
/* @__PURE__ */ jsx("path", { d: "m11 11 3.5 3.5" })
|
|
112
|
+
] }),
|
|
113
|
+
send: () => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
114
|
+
/* @__PURE__ */ jsx("path", { d: "M9 14.5v-11" }),
|
|
115
|
+
/* @__PURE__ */ jsx("path", { d: "m4.75 7.75 4.25-4.25 4.25 4.25" })
|
|
116
|
+
] }),
|
|
117
|
+
stop: () => /* @__PURE__ */ jsx("rect", { x: "4.5", y: "4.5", width: "9", height: "9", rx: "1.5", fill: "currentColor", stroke: "none" })
|
|
118
|
+
};
|
|
119
|
+
function UiIcon({ name, size = 16, class: className = "" }) {
|
|
120
|
+
const Glyph = ICONS[name];
|
|
121
|
+
if (!Glyph) return null;
|
|
122
|
+
return /* @__PURE__ */ jsx("svg", { class: `scui-icon ${className}`, style: { "--scui-icon-size": `${size}px` }, viewBox: "0 0 18 18", fill: "none", stroke: "currentColor", "stroke-width": "1.5", "stroke-linecap": "round", "stroke-linejoin": "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx(Glyph, {}) });
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// src/textarea.js
|
|
126
|
+
import { useLayoutEffect } from "preact/hooks";
|
|
127
|
+
function useAutosizeTextarea(ref, value) {
|
|
128
|
+
useLayoutEffect(() => {
|
|
129
|
+
const element = ref.current;
|
|
130
|
+
if (!element) return;
|
|
131
|
+
element.style.height = "auto";
|
|
132
|
+
const maxHeight = Number.parseFloat(getComputedStyle(element).maxHeight) || 150;
|
|
133
|
+
const height = Math.min(element.scrollHeight, maxHeight);
|
|
134
|
+
element.style.height = `${height}px`;
|
|
135
|
+
element.style.overflowY = element.scrollHeight > maxHeight ? "auto" : "hidden";
|
|
136
|
+
}, [ref, value]);
|
|
137
|
+
}
|
|
138
|
+
|
|
82
139
|
// src/composer.jsx
|
|
83
|
-
import { jsx, jsxs } from "preact/jsx-runtime";
|
|
140
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "preact/jsx-runtime";
|
|
84
141
|
var composerMemory = /* @__PURE__ */ new Map();
|
|
85
142
|
function ContinuationBar({ state, adapter, labels = DEFAULT_LABELS }) {
|
|
86
143
|
if (state.mode !== "mirror" || state.canSend) return null;
|
|
@@ -90,38 +147,56 @@ function ContinuationBar({ state, adapter, labels = DEFAULT_LABELS }) {
|
|
|
90
147
|
const resume = canContinueHere(state);
|
|
91
148
|
const join = state.canAttach;
|
|
92
149
|
const branch = state.canBranch;
|
|
93
|
-
if (!resume && !join && !branch) return /* @__PURE__ */
|
|
94
|
-
/* @__PURE__ */
|
|
95
|
-
/* @__PURE__ */
|
|
150
|
+
if (!resume && !join && !branch) return /* @__PURE__ */ jsx2("div", { class: "scui-continuation", children: /* @__PURE__ */ jsxs2("span", { children: [
|
|
151
|
+
/* @__PURE__ */ jsx2("strong", { children: activeElsewhere ? "Active elsewhere" : "Read-only" }),
|
|
152
|
+
/* @__PURE__ */ jsx2("small", { children: "This session cannot be continued by an available harness." })
|
|
96
153
|
] }) });
|
|
97
|
-
return /* @__PURE__ */
|
|
98
|
-
/* @__PURE__ */
|
|
99
|
-
/* @__PURE__ */
|
|
100
|
-
/* @__PURE__ */
|
|
154
|
+
return /* @__PURE__ */ jsxs2("div", { class: "scui-continuation", children: [
|
|
155
|
+
/* @__PURE__ */ jsxs2("span", { children: [
|
|
156
|
+
/* @__PURE__ */ jsx2("strong", { children: activeElsewhere ? "Active elsewhere" : "Read-only" }),
|
|
157
|
+
/* @__PURE__ */ jsx2("small", { children: join ? "Join the proven live runtime without taking it over." : resume ? `Resume this ${harnessDisplayName(state.harness)} session here.` : "Start an independent continuation." })
|
|
101
158
|
] }),
|
|
102
|
-
/* @__PURE__ */
|
|
159
|
+
/* @__PURE__ */ jsx2("button", { type: "button", disabled: Boolean(state.operation), onClick: () => adapter.onIntent(join ? { action: "join" } : resume ? { action: "resume" } : { action: "branch" }), children: join ? labels.joinLive : resume ? labels.continueHere : labels.forkHere })
|
|
103
160
|
] });
|
|
104
161
|
}
|
|
105
|
-
function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`, onPending }) {
|
|
162
|
+
function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`, pendingStatus = null, restoreDraft = null, onPending, onDraftRestored }) {
|
|
106
163
|
const remembered = composerMemory.get(memoryKey) ?? { draft: state.savedDraft, queue: [] };
|
|
107
164
|
const [draft, setDraft] = useState(remembered.draft);
|
|
108
165
|
const [queue, setQueue] = useState(remembered.queue);
|
|
166
|
+
const [dispatching, setDispatching] = useState(false);
|
|
109
167
|
const textarea = useRef(null);
|
|
168
|
+
useAutosizeTextarea(textarea, draft);
|
|
110
169
|
const remember = (nextDraft, nextQueue) => boundedSet(composerMemory, memoryKey, { draft: nextDraft, queue: nextQueue });
|
|
111
170
|
const updateQueue = (update) => setQueue((items) => {
|
|
112
171
|
const next = update(items);
|
|
113
172
|
remember(draft, next);
|
|
114
173
|
return next;
|
|
115
174
|
});
|
|
175
|
+
const queueBlocked = state.busy || pendingStatus !== null || dispatching;
|
|
176
|
+
const queuesNewMessage = state.busy || pendingStatus === "sending" || pendingStatus === "failed" || dispatching;
|
|
116
177
|
useEffect(() => {
|
|
117
|
-
if (!
|
|
178
|
+
if (!queueBlocked && state.canSend && queue.length) {
|
|
118
179
|
const [next, ...rest] = queue;
|
|
180
|
+
setDispatching(true);
|
|
119
181
|
setQueue(rest);
|
|
120
182
|
remember(draft, rest);
|
|
121
183
|
onPending?.(next);
|
|
122
184
|
adapter.onIntent({ action: "send", text: next });
|
|
123
185
|
}
|
|
124
|
-
}, [adapter, draft, memoryKey, onPending, queue,
|
|
186
|
+
}, [adapter, draft, memoryKey, onPending, queue, queueBlocked, state.canSend]);
|
|
187
|
+
useEffect(() => {
|
|
188
|
+
if (pendingStatus !== null || state.busy) setDispatching(false);
|
|
189
|
+
}, [pendingStatus, state.busy]);
|
|
190
|
+
useEffect(() => {
|
|
191
|
+
textarea.current?.focus({ preventScroll: true });
|
|
192
|
+
}, [memoryKey]);
|
|
193
|
+
useEffect(() => {
|
|
194
|
+
if (!restoreDraft) return;
|
|
195
|
+
setDraft(restoreDraft.text);
|
|
196
|
+
remember(restoreDraft.text, queue);
|
|
197
|
+
textarea.current?.focus({ preventScroll: true });
|
|
198
|
+
onDraftRestored?.(restoreDraft.id);
|
|
199
|
+
}, [onDraftRestored, restoreDraft?.id]);
|
|
125
200
|
useEffect(() => {
|
|
126
201
|
const timer = setTimeout(() => adapter.onIntent({ action: "draft", text: draft }), 250);
|
|
127
202
|
return () => clearTimeout(timer);
|
|
@@ -129,27 +204,28 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
|
|
|
129
204
|
const send = () => {
|
|
130
205
|
const text = draft.trim();
|
|
131
206
|
if (!text) return;
|
|
132
|
-
if (
|
|
207
|
+
if (queuesNewMessage) updateQueue((items) => [...items, text]);
|
|
133
208
|
else if (state.canSend) {
|
|
209
|
+
if (onPending) setDispatching(true);
|
|
134
210
|
onPending?.(text);
|
|
135
211
|
adapter.onIntent({ action: "send", text });
|
|
136
212
|
} else return;
|
|
137
213
|
setDraft("");
|
|
138
|
-
remember("",
|
|
214
|
+
remember("", queuesNewMessage ? [...queue, text] : queue);
|
|
139
215
|
};
|
|
140
|
-
return /* @__PURE__ */
|
|
141
|
-
queue.length ? /* @__PURE__ */
|
|
142
|
-
/* @__PURE__ */
|
|
216
|
+
return /* @__PURE__ */ jsxs2("div", { class: "scui-compose", children: [
|
|
217
|
+
queue.length ? /* @__PURE__ */ jsxs2("div", { class: "scui-queue", children: [
|
|
218
|
+
/* @__PURE__ */ jsxs2("strong", { children: [
|
|
143
219
|
queue.length,
|
|
144
220
|
" queued"
|
|
145
221
|
] }),
|
|
146
|
-
queue.map((item, index) => /* @__PURE__ */
|
|
222
|
+
queue.map((item, index) => /* @__PURE__ */ jsxs2("span", { children: [
|
|
147
223
|
item,
|
|
148
|
-
/* @__PURE__ */
|
|
224
|
+
/* @__PURE__ */ jsx2("button", { type: "button", "aria-label": `Remove queued message ${index + 1}`, onClick: () => updateQueue((items) => items.filter((_, itemIndex) => itemIndex !== index)), children: /* @__PURE__ */ jsx2(UiIcon, { name: "close", size: 13 }) })
|
|
149
225
|
] }, `${index}:${item}`))
|
|
150
226
|
] }) : null,
|
|
151
|
-
/* @__PURE__ */
|
|
152
|
-
/* @__PURE__ */
|
|
227
|
+
/* @__PURE__ */ jsxs2("div", { class: "scui-envelope", children: [
|
|
228
|
+
/* @__PURE__ */ jsx2("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" : queueBlocked ? "Queue a follow-up\u2026" : labels.askAgent, value: draft, disabled: state.mode !== "control" && !state.canSend, onInput: (event) => {
|
|
153
229
|
const value = event.currentTarget.value;
|
|
154
230
|
setDraft(value);
|
|
155
231
|
remember(value, queue);
|
|
@@ -159,9 +235,9 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
|
|
|
159
235
|
send();
|
|
160
236
|
}
|
|
161
237
|
} }),
|
|
162
|
-
/* @__PURE__ */
|
|
163
|
-
state.busy ? /* @__PURE__ */
|
|
164
|
-
/* @__PURE__ */
|
|
238
|
+
/* @__PURE__ */ jsxs2("span", { children: [
|
|
239
|
+
state.busy ? /* @__PURE__ */ jsx2("button", { class: "scui-stop", type: "button", "aria-label": "Stop agent", disabled: !state.canInterrupt, onClick: () => adapter.onIntent({ action: "interrupt" }), children: /* @__PURE__ */ jsx2(UiIcon, { name: "stop", size: 15 }) }) : null,
|
|
240
|
+
/* @__PURE__ */ jsx2("button", { class: "scui-send", type: "button", "aria-label": queuesNewMessage ? "Queue message" : "Send message", disabled: !draft.trim() || !queuesNewMessage && !state.canSend, onClick: send, children: /* @__PURE__ */ jsx2(UiIcon, { name: queuesNewMessage ? "plus" : "send", size: 17 }) })
|
|
165
241
|
] })
|
|
166
242
|
] })
|
|
167
243
|
] });
|
package/controller.mjs
CHANGED
|
@@ -116,6 +116,7 @@ function projectConversationEntry(entry, maxEntryChars) {
|
|
|
116
116
|
return {
|
|
117
117
|
id: entry.id,
|
|
118
118
|
role: entry.role,
|
|
119
|
+
...(Number.isSafeInteger(entry.messageIndex) && entry.messageIndex > 0 ? { messageIndex: entry.messageIndex } : {}),
|
|
119
120
|
text: body.text,
|
|
120
121
|
ts: timestampFromMetadata(entry.metadata),
|
|
121
122
|
truncated: body.truncated,
|
|
@@ -129,6 +130,7 @@ function projectConversationEntry(entry, maxEntryChars) {
|
|
|
129
130
|
const projected = {
|
|
130
131
|
id: entry.id,
|
|
131
132
|
role: 'tool',
|
|
133
|
+
...(Number.isSafeInteger(entry.messageIndex) && entry.messageIndex > 0 ? { messageIndex: entry.messageIndex } : {}),
|
|
132
134
|
text: primary.text,
|
|
133
135
|
ts: timestampFromMetadata(entry.metadata),
|
|
134
136
|
truncated: argumentsText.truncated || result.truncated,
|
|
@@ -401,9 +403,10 @@ export function createControllerBinding(controller, options = {}) {
|
|
|
401
403
|
const getState = () => projectClientSnapshot(controller.getSnapshot(), options.projection?.() ?? {});
|
|
402
404
|
const adapter = {
|
|
403
405
|
onIntent(intent) {
|
|
404
|
-
|
|
406
|
+
return Promise.resolve(options.onIntent?.(intent))
|
|
405
407
|
.then(() => dispatchStandard(controller, intent, options))
|
|
406
|
-
.catch((error) => options.onError?.(error, intent))
|
|
408
|
+
.catch((error) => options.onError?.(error, intent))
|
|
409
|
+
.then(() => undefined);
|
|
407
410
|
},
|
|
408
411
|
...(options.copyText ? { copyText: options.copyText } : {}),
|
|
409
412
|
};
|