@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/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__ */ jsx("div", { class: "scui-continuation", children: /* @__PURE__ */ jsxs("span", { children: [
94
- /* @__PURE__ */ jsx("strong", { children: activeElsewhere ? "Active elsewhere" : "Read-only" }),
95
- /* @__PURE__ */ jsx("small", { children: "This session cannot be continued by an available harness." })
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__ */ jsxs("div", { class: "scui-continuation", children: [
98
- /* @__PURE__ */ jsxs("span", { children: [
99
- /* @__PURE__ */ jsx("strong", { children: activeElsewhere ? "Active elsewhere" : "Read-only" }),
100
- /* @__PURE__ */ jsx("small", { children: join ? "Join the proven live runtime without taking it over." : resume ? `Resume this ${harnessDisplayName(state.harness)} session here.` : "Start an independent continuation." })
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__ */ jsx("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 })
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 (!state.busy && state.canSend && queue.length) {
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, state.busy, state.canSend]);
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 (state.busy) updateQueue((items) => [...items, text]);
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("", state.busy ? [...queue, text] : queue);
214
+ remember("", queuesNewMessage ? [...queue, text] : queue);
139
215
  };
140
- return /* @__PURE__ */ jsxs("div", { class: "scui-compose", children: [
141
- queue.length ? /* @__PURE__ */ jsxs("div", { class: "scui-queue", children: [
142
- /* @__PURE__ */ jsxs("strong", { children: [
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__ */ jsxs("span", { children: [
222
+ queue.map((item, index) => /* @__PURE__ */ jsxs2("span", { children: [
147
223
  item,
148
- /* @__PURE__ */ jsx("button", { type: "button", "aria-label": `Remove queued message ${index + 1}`, onClick: () => updateQueue((items) => items.filter((_, itemIndex) => itemIndex !== index)), children: "\xD7" })
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__ */ jsxs("div", { class: "scui-envelope", children: [
152
- /* @__PURE__ */ jsx("textarea", { ref: textarea, rows: 1, "aria-label": `Message ${harnessDisplayName(state.harness) || "agent"}`, placeholder: state.startup !== "ready" ? "Connecting\u2026" : state.busy ? "Queue a follow-up\u2026" : labels.askAgent, value: draft, disabled: state.mode !== "control" && !state.canSend, onInput: (event) => {
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__ */ jsxs("span", { children: [
163
- state.busy ? /* @__PURE__ */ jsx("button", { class: "scui-stop", type: "button", "aria-label": "Stop agent", disabled: !state.canInterrupt, onClick: () => adapter.onIntent({ action: "interrupt" }), children: "\u25A0" }) : null,
164
- /* @__PURE__ */ jsx("button", { class: "scui-send", type: "button", "aria-label": state.busy ? "Queue message" : "Send message", disabled: !draft.trim() || !state.busy && !state.canSend, onClick: send, children: state.busy ? "+" : "\u2191" })
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
- void Promise.resolve(options.onIntent?.(intent))
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
  };
package/conversation.d.ts CHANGED
@@ -2,6 +2,7 @@ export type {
2
2
  ActivityGroupProps,
3
3
  MessengerComponents,
4
4
  MessengerSlots,
5
+ PendingMessageModel,
5
6
  SessionSemanticsModel,
6
7
  SupercodeUiState,
7
8
  TaskPlanProps,