@volter-ai-dev/supercode-ui 0.1.1 → 0.1.2

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 ADDED
@@ -0,0 +1,2 @@
1
+ export type { MessengerLabels, SupercodeUiState, UiAdapter } from './index.js';
2
+ export { Composer, ContinuationBar } from './index.js';
package/composer.mjs CHANGED
@@ -1,11 +1,11 @@
1
- // src/components.jsx
2
- import MarkdownIt from "markdown-it";
3
- import { useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "preact/hooks";
1
+ // src/composer.jsx
2
+ import { useEffect, useRef, useState } from "preact/hooks";
4
3
 
5
4
  // core.mjs
6
5
  var HARNESS_NAMES = Object.freeze({
7
6
  "claude-code": "Claude Code",
8
7
  codex: "Codex",
8
+ gemini: "Gemini CLI",
9
9
  opencode: "OpenCode",
10
10
  pi: "Pi",
11
11
  grok: "Grok"
@@ -70,15 +70,16 @@ function isSendKey(event) {
70
70
  return event.key === "Enter" && !event.shiftKey && !event.isComposing;
71
71
  }
72
72
 
73
- // src/components.jsx
73
+ // src/memory.js
74
+ var MEMORY_LIMIT = 100;
75
+ function boundedSet(map, key, value) {
76
+ map.delete(key);
77
+ map.set(key, value);
78
+ while (map.size > MEMORY_LIMIT) map.delete(map.keys().next().value);
79
+ }
80
+
81
+ // src/composer.jsx
74
82
  import { jsx, jsxs } from "preact/jsx-runtime";
75
- var markdown = new MarkdownIt({ html: false, linkify: true, breaks: false });
76
- var defaultLinkOpen = markdown.renderer.rules.link_open;
77
- markdown.renderer.rules.link_open = (tokens, index, options, env, self) => {
78
- tokens[index]?.attrSet("target", "_blank");
79
- tokens[index]?.attrSet("rel", "noreferrer noopener");
80
- return defaultLinkOpen ? defaultLinkOpen(tokens, index, options, env, self) : self.renderToken(tokens, index, options);
81
- };
82
83
  var composerMemory = /* @__PURE__ */ new Map();
83
84
  function ContinuationBar({ state, adapter, labels = DEFAULT_LABELS }) {
84
85
  if (state.mode !== "mirror" || state.canSend) return null;
@@ -105,11 +106,17 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
105
106
  const [draft, setDraft] = useState(remembered.draft);
106
107
  const [queue, setQueue] = useState(remembered.queue);
107
108
  const textarea = useRef(null);
109
+ const remember = (nextDraft, nextQueue) => boundedSet(composerMemory, memoryKey, { draft: nextDraft, queue: nextQueue });
110
+ const updateQueue = (update) => setQueue((items) => {
111
+ const next = update(items);
112
+ remember(draft, next);
113
+ return next;
114
+ });
108
115
  useEffect(() => {
109
116
  if (!state.busy && state.canSend && queue.length) {
110
117
  const [next, ...rest] = queue;
111
118
  setQueue(rest);
112
- composerMemory.set(memoryKey, { draft, queue: rest });
119
+ remember(draft, rest);
113
120
  onPending?.(next);
114
121
  adapter.onIntent({ action: "send", text: next });
115
122
  }
@@ -121,17 +128,13 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
121
128
  const send = () => {
122
129
  const text = draft.trim();
123
130
  if (!text) return;
124
- if (state.busy) setQueue((items) => {
125
- const next = [...items, text];
126
- composerMemory.set(memoryKey, { draft: "", queue: next });
127
- return next;
128
- });
131
+ if (state.busy) updateQueue((items) => [...items, text]);
129
132
  else if (state.canSend) {
130
133
  onPending?.(text);
131
134
  adapter.onIntent({ action: "send", text });
132
135
  } else return;
133
136
  setDraft("");
134
- composerMemory.set(memoryKey, { draft: "", queue: state.busy ? [...queue, text] : queue });
137
+ remember("", state.busy ? [...queue, text] : queue);
135
138
  };
136
139
  return /* @__PURE__ */ jsxs("div", { class: "scui-compose", children: [
137
140
  queue.length ? /* @__PURE__ */ jsxs("div", { class: "scui-queue", children: [
@@ -141,14 +144,14 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
141
144
  ] }),
142
145
  queue.map((item, index) => /* @__PURE__ */ jsxs("span", { children: [
143
146
  item,
144
- /* @__PURE__ */ jsx("button", { "aria-label": `Remove queued message ${index + 1}`, onClick: () => setQueue((items) => items.filter((_, itemIndex) => itemIndex !== index)), children: "\xD7" })
147
+ /* @__PURE__ */ jsx("button", { type: "button", "aria-label": `Remove queued message ${index + 1}`, onClick: () => updateQueue((items) => items.filter((_, itemIndex) => itemIndex !== index)), children: "\xD7" })
145
148
  ] }, `${index}:${item}`))
146
149
  ] }) : null,
147
150
  /* @__PURE__ */ jsxs("div", { class: "scui-envelope", children: [
148
151
  /* @__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) => {
149
152
  const value = event.currentTarget.value;
150
153
  setDraft(value);
151
- composerMemory.set(memoryKey, { draft: value, queue });
154
+ remember(value, queue);
152
155
  }, onKeyDown: (event) => {
153
156
  if (isSendKey(event)) {
154
157
  event.preventDefault();
@@ -0,0 +1,20 @@
1
+ export type {
2
+ ActivityGroupProps,
3
+ MessengerComponents,
4
+ MessengerSlots,
5
+ SessionSemanticsModel,
6
+ SupercodeUiState,
7
+ TaskPlanModel,
8
+ TranscriptEntryModel,
9
+ TranscriptEntryProps,
10
+ UiAdapter,
11
+ } from './index.js';
12
+ export {
13
+ ActivityGroup,
14
+ Conversation,
15
+ LoadingStatus,
16
+ RequestCard,
17
+ SessionDetails,
18
+ TaskPlan,
19
+ TranscriptEntry,
20
+ } from './index.js';
package/conversation.mjs CHANGED
@@ -1,11 +1,11 @@
1
- // src/components.jsx
2
- import MarkdownIt from "markdown-it";
3
- import { useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "preact/hooks";
1
+ // src/conversation.jsx
2
+ import { useEffect, useId, useLayoutEffect, useRef, useState } from "preact/hooks";
4
3
 
5
4
  // core.mjs
6
5
  var HARNESS_NAMES = Object.freeze({
7
6
  "claude-code": "Claude Code",
8
7
  codex: "Codex",
8
+ gemini: "Gemini CLI",
9
9
  opencode: "OpenCode",
10
10
  pi: "Pi",
11
11
  grok: "Grok"
@@ -109,8 +109,10 @@ function activitySummary(entries) {
109
109
  return `Activity \xB7 ${Object.entries(labels).flatMap(([key, label]) => counts.has(key) ? [`${counts.get(key)} ${label}`] : []).join(" \xB7 ")}`;
110
110
  }
111
111
 
112
- // src/components.jsx
113
- import { jsx, jsxs } from "preact/jsx-runtime";
112
+ // src/markdown.jsx
113
+ import MarkdownIt from "markdown-it";
114
+ import { useMemo } from "preact/hooks";
115
+ import { jsx } from "preact/jsx-runtime";
114
116
  var markdown = new MarkdownIt({ html: false, linkify: true, breaks: false });
115
117
  var defaultLinkOpen = markdown.renderer.rules.link_open;
116
118
  markdown.renderer.rules.link_open = (tokens, index, options, env, self) => {
@@ -122,6 +124,17 @@ function Markdown({ value }) {
122
124
  const html = useMemo(() => markdown.render(value), [value]);
123
125
  return /* @__PURE__ */ jsx("div", { class: "scui-markdown", dangerouslySetInnerHTML: { __html: html } });
124
126
  }
127
+
128
+ // src/memory.js
129
+ var MEMORY_LIMIT = 100;
130
+ function boundedSet(map, key, value) {
131
+ map.delete(key);
132
+ map.set(key, value);
133
+ while (map.size > MEMORY_LIMIT) map.delete(map.keys().next().value);
134
+ }
135
+
136
+ // src/conversation.jsx
137
+ import { jsx as jsx2, jsxs } from "preact/jsx-runtime";
125
138
  function LoadingStatus({ state, compact = false }) {
126
139
  const copy = {
127
140
  connecting: ["Connecting to coding agents", "Checking installed harnesses and capabilities.", 0],
@@ -130,12 +143,12 @@ function LoadingStatus({ state, compact = false }) {
130
143
  ready: ["Ready", "Coding sessions are up to date.", 3]
131
144
  }[state.startup];
132
145
  return /* @__PURE__ */ jsxs("div", { class: `scui-loading${compact ? " scui-loading-compact" : ""}`, role: "status", "aria-busy": state.startup !== "ready", children: [
133
- /* @__PURE__ */ jsx("span", { class: "scui-orbit", "aria-hidden": "true", children: /* @__PURE__ */ jsx("i", {}) }),
146
+ /* @__PURE__ */ jsx2("span", { class: "scui-orbit", "aria-hidden": "true", children: /* @__PURE__ */ jsx2("i", {}) }),
134
147
  /* @__PURE__ */ jsxs("span", { class: "scui-loading-copy", children: [
135
- /* @__PURE__ */ jsx("strong", { children: copy[0] }),
136
- /* @__PURE__ */ jsx("small", { children: copy[1] })
148
+ /* @__PURE__ */ jsx2("strong", { children: copy[0] }),
149
+ /* @__PURE__ */ jsx2("small", { children: copy[1] })
137
150
  ] }),
138
- /* @__PURE__ */ jsx("span", { class: "scui-progress", "aria-hidden": "true", children: [1, 2, 3].map((step) => /* @__PURE__ */ jsx("i", { "data-progress": step <= copy[2] ? "done" : step === copy[2] + 1 ? "current" : "waiting" }, step)) })
151
+ /* @__PURE__ */ jsx2("span", { class: "scui-progress", "aria-hidden": "true", children: [1, 2, 3].map((step) => /* @__PURE__ */ jsx2("i", { "data-progress": step <= copy[2] ? "done" : step === copy[2] + 1 ? "current" : "waiting" }, step)) })
139
152
  ] });
140
153
  }
141
154
  function RequestCard({ entry, adapter, canRespond }) {
@@ -148,11 +161,11 @@ function RequestCard({ entry, adapter, canRespond }) {
148
161
  ] });
149
162
  }
150
163
  return /* @__PURE__ */ jsxs("section", { class: "scui-request", "aria-label": `${request.requestKind} needs input`, children: [
151
- /* @__PURE__ */ jsx("strong", { children: "Agent needs input" }),
152
- /* @__PURE__ */ jsx(Markdown, { value: request.payloadText || entry.text }),
164
+ /* @__PURE__ */ jsx2("strong", { children: "Agent needs input" }),
165
+ /* @__PURE__ */ jsx2(Markdown, { value: request.payloadText || entry.text }),
153
166
  /* @__PURE__ */ jsxs("div", { class: "scui-request-actions", children: [
154
- request.options.map((option) => /* @__PURE__ */ jsx("button", { type: "button", disabled: !canRespond, onClick: () => adapter.onIntent({ action: "respond", requestId: request.requestId, optionId: option.optionId }), children: option.name }, option.optionId)),
155
- request.cancellable ? /* @__PURE__ */ jsx("button", { type: "button", disabled: !canRespond, onClick: () => adapter.onIntent({ action: "respond", requestId: request.requestId, optionId: null }), children: "Cancel" }) : null
167
+ request.options.map((option) => /* @__PURE__ */ jsx2("button", { type: "button", disabled: !canRespond, onClick: () => adapter.onIntent({ action: "respond", requestId: request.requestId, optionId: option.optionId }), children: option.name }, option.optionId)),
168
+ request.cancellable ? /* @__PURE__ */ jsx2("button", { type: "button", disabled: !canRespond, onClick: () => adapter.onIntent({ action: "respond", requestId: request.requestId, optionId: null }), children: "Cancel" }) : null
156
169
  ] })
157
170
  ] });
158
171
  }
@@ -163,25 +176,25 @@ function ContextDisclosure({ context }) {
163
176
  "Context \xB7 ",
164
177
  context.length
165
178
  ] }),
166
- /* @__PURE__ */ jsx("div", { children: context.map((item, index) => /* @__PURE__ */ jsxs("p", { children: [
167
- /* @__PURE__ */ jsx("strong", { children: item.label }),
168
- /* @__PURE__ */ jsx("span", { children: item.detail })
179
+ /* @__PURE__ */ jsx2("div", { children: context.map((item, index) => /* @__PURE__ */ jsxs("p", { children: [
180
+ /* @__PURE__ */ jsx2("strong", { children: item.label }),
181
+ /* @__PURE__ */ jsx2("span", { children: item.detail })
169
182
  ] }, item.id ?? index)) })
170
183
  ] });
171
184
  }
172
185
  function TranscriptEntry({ entry, state, adapter }) {
173
- if (entry.role === "request") return /* @__PURE__ */ jsx(RequestCard, { entry, adapter, canRespond: state.canRespond });
186
+ if (entry.role === "request") return /* @__PURE__ */ jsx2(RequestCard, { entry, adapter, canRespond: state.canRespond });
174
187
  if (entry.role === "reasoning") {
175
188
  return /* @__PURE__ */ jsxs("details", { class: "scui-reasoning", open: entry.streaming, children: [
176
- /* @__PURE__ */ jsx("summary", { children: entry.streaming ? "Reasoning\u2026" : "Reasoning" }),
177
- /* @__PURE__ */ jsx(Markdown, { value: entry.text })
189
+ /* @__PURE__ */ jsx2("summary", { children: entry.streaming ? "Reasoning\u2026" : "Reasoning" }),
190
+ /* @__PURE__ */ jsx2(Markdown, { value: entry.text })
178
191
  ] });
179
192
  }
180
- if (entry.role === "notice" || entry.role === "system") return /* @__PURE__ */ jsx("div", { class: "scui-notice", "data-code": entry.code, children: entry.text });
193
+ if (entry.role === "notice" || entry.role === "system") return /* @__PURE__ */ jsx2("div", { class: "scui-notice", "data-code": entry.code, children: entry.text });
181
194
  return /* @__PURE__ */ jsxs("article", { class: "scui-message", "data-role": entry.role, children: [
182
- /* @__PURE__ */ jsx(Markdown, { value: entry.text }),
183
- /* @__PURE__ */ jsx(ContextDisclosure, { context: entry.context }),
184
- entry.truncated ? /* @__PURE__ */ jsx("small", { class: "scui-truncated", children: "Entry truncated by host \xB7 load native session for the complete value" }) : null
195
+ /* @__PURE__ */ jsx2(Markdown, { value: entry.text }),
196
+ /* @__PURE__ */ jsx2(ContextDisclosure, { context: entry.context }),
197
+ entry.truncated ? /* @__PURE__ */ jsx2("small", { class: "scui-truncated", children: "Entry truncated by host \xB7 load native session for the complete value" }) : null
185
198
  ] });
186
199
  }
187
200
  function ToolRow({ entry, workspace }) {
@@ -192,20 +205,20 @@ function ToolRow({ entry, workspace }) {
192
205
  const category = toolCategory(entry);
193
206
  return /* @__PURE__ */ jsxs("div", { class: "scui-tool", "data-status": entry.status ?? "completed", "data-category": category, children: [
194
207
  /* @__PURE__ */ jsxs("button", { type: "button", class: "scui-tool-head", disabled: !hasDetail, "aria-expanded": hasDetail ? open : void 0, "aria-controls": hasDetail ? detailId : void 0, onClick: () => hasDetail && setOpen((value) => !value), children: [
195
- /* @__PURE__ */ jsx("span", { class: "scui-tool-glyph", "aria-hidden": "true", children: { read: "\u25A4", search: "\u2315", edit: "\u270E", command: ">_", test: "\u25C7", web: "\u25CE", agent: "\u2659", other: "\u25C6" }[category] }),
196
- /* @__PURE__ */ jsx("strong", { children: entry.label?.split(/__|\//).at(-1)?.replaceAll("_", " ") || "Tool" }),
197
- target ? /* @__PURE__ */ jsx("span", { class: "scui-tool-target", title: toolTarget(entry.arguments), children: target }) : null,
198
- /* @__PURE__ */ jsx("span", { class: "scui-spacer" }),
199
- /* @__PURE__ */ jsx("span", { "aria-label": entry.status ?? "completed", children: entry.status === "pending" ? "\u25CC" : entry.status === "error" ? "\xD7" : "\u2713" })
208
+ /* @__PURE__ */ jsx2("span", { class: "scui-tool-glyph", "aria-hidden": "true", children: { read: "\u25A4", search: "\u2315", edit: "\u270E", command: ">_", test: "\u25C7", web: "\u25CE", agent: "\u2659", other: "\u25C6" }[category] }),
209
+ /* @__PURE__ */ jsx2("strong", { children: entry.label?.split(/__|\//).at(-1)?.replaceAll("_", " ") || "Tool" }),
210
+ target ? /* @__PURE__ */ jsx2("span", { class: "scui-tool-target", title: toolTarget(entry.arguments), children: target }) : null,
211
+ /* @__PURE__ */ jsx2("span", { class: "scui-spacer" }),
212
+ /* @__PURE__ */ jsx2("span", { "aria-label": entry.status ?? "completed", children: entry.status === "pending" ? "\u25CC" : entry.status === "error" ? "\xD7" : "\u2713" })
200
213
  ] }),
201
214
  hasDetail && open ? /* @__PURE__ */ jsxs("div", { id: detailId, class: "scui-tool-detail", children: [
202
215
  entry.arguments ? /* @__PURE__ */ jsxs("pre", { children: [
203
- /* @__PURE__ */ jsx("b", { children: "Input" }),
216
+ /* @__PURE__ */ jsx2("b", { children: "Input" }),
204
217
  "\n",
205
218
  entry.arguments
206
219
  ] }) : null,
207
220
  entry.resultText ? /* @__PURE__ */ jsxs("pre", { "data-error": entry.status === "error", children: [
208
- /* @__PURE__ */ jsx("b", { children: "Output" }),
221
+ /* @__PURE__ */ jsx2("b", { children: "Output" }),
209
222
  "\n",
210
223
  entry.resultText,
211
224
  entry.truncated ? "\n[truncated]" : ""
@@ -222,16 +235,16 @@ function ActivityGroup({ entries, state }) {
222
235
  }, [active]);
223
236
  return /* @__PURE__ */ jsxs("section", { class: "scui-activity", children: [
224
237
  /* @__PURE__ */ jsxs("button", { class: "scui-activity-head", type: "button", "aria-expanded": open, "aria-controls": id, onClick: () => setOpen((value) => !value), children: [
225
- /* @__PURE__ */ jsx("span", { class: "scui-fold", "data-open": open, children: "\u203A" }),
226
- /* @__PURE__ */ jsx("strong", { children: activitySummary(entries) }),
227
- /* @__PURE__ */ jsx("span", { class: "scui-spacer" }),
238
+ /* @__PURE__ */ jsx2("span", { class: "scui-fold", "data-open": open, children: "\u203A" }),
239
+ /* @__PURE__ */ jsx2("strong", { children: activitySummary(entries) }),
240
+ /* @__PURE__ */ jsx2("span", { class: "scui-spacer" }),
228
241
  /* @__PURE__ */ jsxs("small", { children: [
229
242
  entries.filter((entry) => entry.status === "completed").length,
230
243
  "/",
231
244
  entries.length
232
245
  ] })
233
246
  ] }),
234
- open ? /* @__PURE__ */ jsx("div", { id, children: entries.map((entry) => /* @__PURE__ */ jsx(ToolRow, { entry, workspace: state.workspace }, entry.id)) }) : null
247
+ open ? /* @__PURE__ */ jsx2("div", { id, children: entries.map((entry) => /* @__PURE__ */ jsx2(ToolRow, { entry, workspace: state.workspace }, entry.id)) }) : null
235
248
  ] });
236
249
  }
237
250
  function TaskPlan({ plan }) {
@@ -239,43 +252,43 @@ function TaskPlan({ plan }) {
239
252
  const complete = plan.items.filter((item) => item.status === "completed" || item.status === "cancelled").length;
240
253
  return /* @__PURE__ */ jsxs("details", { class: "scui-plan", children: [
241
254
  /* @__PURE__ */ jsxs("summary", { children: [
242
- /* @__PURE__ */ jsx("span", { children: "Plan" }),
255
+ /* @__PURE__ */ jsx2("span", { children: "Plan" }),
243
256
  /* @__PURE__ */ jsxs("small", { children: [
244
257
  complete,
245
258
  "/",
246
259
  plan.items.length
247
260
  ] })
248
261
  ] }),
249
- /* @__PURE__ */ jsx("ol", { children: plan.items.map((item) => /* @__PURE__ */ jsxs("li", { "data-status": item.status, children: [
250
- /* @__PURE__ */ jsx("i", { "aria-hidden": "true" }),
262
+ /* @__PURE__ */ jsx2("ol", { tabIndex: 0, "aria-label": "Task plan steps", children: plan.items.map((item) => /* @__PURE__ */ jsxs("li", { "data-status": item.status, children: [
263
+ /* @__PURE__ */ jsx2("i", { "aria-hidden": "true" }),
251
264
  " ",
252
- /* @__PURE__ */ jsx("span", { children: item.title })
265
+ /* @__PURE__ */ jsx2("span", { children: item.title })
253
266
  ] }, item.id)) })
254
267
  ] });
255
268
  }
256
269
  function SessionDetails({ semantics }) {
257
270
  if (!semantics.fidelity && !semantics.residueCount && !semantics.parseErrors && !semantics.subagents.length) return null;
258
271
  return /* @__PURE__ */ jsxs("details", { class: "scui-details", children: [
259
- /* @__PURE__ */ jsx("summary", { children: "Session details" }),
272
+ /* @__PURE__ */ jsx2("summary", { children: "Session details" }),
260
273
  /* @__PURE__ */ jsxs("div", { children: [
261
274
  semantics.fidelity ? /* @__PURE__ */ jsxs("p", { children: [
262
- /* @__PURE__ */ jsx("strong", { children: "Fidelity" }),
263
- /* @__PURE__ */ jsx("span", { children: semantics.fidelity.replaceAll("_", " ") })
275
+ /* @__PURE__ */ jsx2("strong", { children: "Fidelity" }),
276
+ /* @__PURE__ */ jsx2("span", { children: semantics.fidelity.replaceAll("_", " ") })
264
277
  ] }) : null,
265
278
  /* @__PURE__ */ jsxs("p", { children: [
266
- /* @__PURE__ */ jsx("strong", { children: "Native records" }),
267
- /* @__PURE__ */ jsx("span", { children: semantics.rawRecords })
279
+ /* @__PURE__ */ jsx2("strong", { children: "Native records" }),
280
+ /* @__PURE__ */ jsx2("span", { children: semantics.rawRecords })
268
281
  ] }),
269
282
  semantics.residueCount ? /* @__PURE__ */ jsxs("p", { children: [
270
- /* @__PURE__ */ jsx("strong", { children: "Residue" }),
283
+ /* @__PURE__ */ jsx2("strong", { children: "Residue" }),
271
284
  /* @__PURE__ */ jsxs("span", { children: [
272
285
  semantics.residueCount,
273
286
  " retained"
274
287
  ] })
275
288
  ] }) : null,
276
289
  semantics.parseErrors ? /* @__PURE__ */ jsxs("p", { children: [
277
- /* @__PURE__ */ jsx("strong", { children: "Parse diagnostics" }),
278
- /* @__PURE__ */ jsx("span", { children: semantics.parseErrors })
290
+ /* @__PURE__ */ jsx2("strong", { children: "Parse diagnostics" }),
291
+ /* @__PURE__ */ jsx2("span", { children: semantics.parseErrors })
279
292
  ] }) : null,
280
293
  semantics.subagents.map((agent) => /* @__PURE__ */ jsxs("p", { children: [
281
294
  /* @__PURE__ */ jsxs("strong", { children: [
@@ -304,11 +317,12 @@ function Conversation({ state, adapter, components = {}, slots = {}, memoryKey =
304
317
  const Before = slots.beforeConversation;
305
318
  const After = slots.afterConversation;
306
319
  const Empty = slots.emptyConversation;
320
+ const remember = (value) => boundedSet(conversationMemory, memoryKey, value);
307
321
  const pin = () => {
308
322
  if (!scroller.current) return;
309
323
  scroller.current.scrollTop = scroller.current.scrollHeight;
310
324
  setAtBottom(true);
311
- conversationMemory.set(memoryKey, { top: scroller.current.scrollTop, atBottom: true });
325
+ remember({ top: scroller.current.scrollTop, atBottom: true });
312
326
  };
313
327
  useLayoutEffect(() => {
314
328
  const element = scroller.current;
@@ -317,7 +331,7 @@ function Conversation({ state, adapter, components = {}, slots = {}, memoryKey =
317
331
  if (anchor && state.transcript.length > anchor.entries) {
318
332
  element.scrollTop = anchor.top + (element.scrollHeight - anchor.height);
319
333
  earlierAnchor.current = null;
320
- conversationMemory.set(memoryKey, { top: element.scrollTop, atBottom: false });
334
+ remember({ top: element.scrollTop, atBottom: false });
321
335
  return;
322
336
  }
323
337
  if (!restored.current) {
@@ -327,41 +341,41 @@ function Conversation({ state, adapter, components = {}, slots = {}, memoryKey =
327
341
  } else if (atBottom) pin();
328
342
  }, [memoryKey, state.transcript.length, state.busy]);
329
343
  return /* @__PURE__ */ jsxs("div", { class: "scui-conversation-wrap", children: [
330
- /* @__PURE__ */ jsx("div", { class: "scui-conversation", ref: scroller, tabIndex: 0, "aria-label": "Conversation", onScroll: (event) => {
344
+ /* @__PURE__ */ jsx2("div", { class: "scui-conversation", ref: scroller, tabIndex: 0, "aria-label": "Conversation", onScroll: (event) => {
331
345
  const element = event.currentTarget;
332
346
  const bottom = element.scrollHeight - element.scrollTop - element.clientHeight <= 64;
333
347
  setAtBottom(bottom);
334
- conversationMemory.set(memoryKey, { top: element.scrollTop, atBottom: bottom });
348
+ remember({ top: element.scrollTop, atBottom: bottom });
335
349
  }, children: /* @__PURE__ */ jsxs("div", { children: [
336
- Before ? /* @__PURE__ */ jsx(Before, { state, adapter, value: null }) : null,
337
- /* @__PURE__ */ jsx(TaskPlan, { plan: state.taskPlan }),
338
- /* @__PURE__ */ jsx(SessionDetails, { semantics: state.semantics }),
339
- state.history.hasEarlier ? /* @__PURE__ */ jsx("button", { class: "scui-load", type: "button", disabled: Boolean(state.operation), onClick: () => {
350
+ Before ? /* @__PURE__ */ jsx2(Before, { state, adapter, value: null }) : null,
351
+ /* @__PURE__ */ jsx2(TaskPlan, { plan: state.taskPlan }),
352
+ /* @__PURE__ */ jsx2(SessionDetails, { semantics: state.semantics }),
353
+ state.history.hasEarlier ? /* @__PURE__ */ jsx2("button", { class: "scui-load", type: "button", disabled: Boolean(state.operation), onClick: () => {
340
354
  const element = scroller.current;
341
355
  if (element) earlierAnchor.current = { height: element.scrollHeight, top: element.scrollTop, entries: state.transcript.length };
342
356
  setAtBottom(false);
343
357
  adapter.onIntent({ action: "loadEarlier" });
344
358
  }, children: "Load earlier messages" }) : null,
345
- !blocks.length && state.startup !== "ready" ? /* @__PURE__ */ jsx(LoadingStatus, { state }) : null,
346
- !blocks.length && state.startup === "ready" ? Empty ? /* @__PURE__ */ jsx(Empty, { state, adapter, value: null }) : /* @__PURE__ */ jsx("div", { class: "scui-empty", children: state.error ?? (state.harness ? `${harnessDisplayName(state.harness)} is listening. Say something.` : "No transcript yet.") }) : null,
347
- blocks.map((block) => block.kind === "activity" ? /* @__PURE__ */ jsx(Group, { value: block.entries, entries: block.entries, state, adapter }, block.id) : /* @__PURE__ */ jsx(Entry, { value: block.entry, entry: block.entry, state, adapter }, block.id)),
359
+ !blocks.length && state.startup !== "ready" ? /* @__PURE__ */ jsx2(LoadingStatus, { state }) : null,
360
+ !blocks.length && state.startup === "ready" ? Empty ? /* @__PURE__ */ jsx2(Empty, { state, adapter, value: null }) : /* @__PURE__ */ jsx2("div", { class: "scui-empty", children: state.error ?? (state.harness ? `${harnessDisplayName(state.harness)} is listening. Say something.` : "No transcript yet.") }) : null,
361
+ blocks.map((block) => block.kind === "activity" ? /* @__PURE__ */ jsx2(Group, { value: block.entries, entries: block.entries, state, adapter }, block.id) : /* @__PURE__ */ jsx2(Entry, { value: block.entry, entry: block.entry, state, adapter }, block.id)),
348
362
  pending ? /* @__PURE__ */ jsxs("article", { class: "scui-message scui-pending", "data-role": "user", children: [
349
- /* @__PURE__ */ jsx(Markdown, { value: pending }),
350
- /* @__PURE__ */ jsx("small", { children: state.error && !state.busy ? "Not sent" : "Sending\u2026" })
363
+ /* @__PURE__ */ jsx2(Markdown, { value: pending }),
364
+ /* @__PURE__ */ jsx2("small", { children: state.error && !state.busy ? "Not sent" : "Sending\u2026" })
351
365
  ] }) : null,
352
366
  state.busy ? /* @__PURE__ */ jsxs("div", { class: "scui-working", role: "status", children: [
353
- /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\u2726" }),
354
- /* @__PURE__ */ jsx("i", {}),
355
- /* @__PURE__ */ jsx("i", {}),
356
- /* @__PURE__ */ jsx("i", {}),
367
+ /* @__PURE__ */ jsx2("span", { "aria-hidden": "true", children: "\u2726" }),
368
+ /* @__PURE__ */ jsx2("i", {}),
369
+ /* @__PURE__ */ jsx2("i", {}),
370
+ /* @__PURE__ */ jsx2("i", {}),
357
371
  /* @__PURE__ */ jsxs("small", { children: [
358
372
  harnessDisplayName(state.harness),
359
373
  " is working"
360
374
  ] })
361
375
  ] }) : null,
362
- After ? /* @__PURE__ */ jsx(After, { state, adapter, value: null }) : null
376
+ After ? /* @__PURE__ */ jsx2(After, { state, adapter, value: null }) : null
363
377
  ] }) }),
364
- !atBottom ? /* @__PURE__ */ jsx("button", { class: "scui-latest", type: "button", onClick: pin, children: "\u2193 Latest" }) : null
378
+ !atBottom ? /* @__PURE__ */ jsx2("button", { class: "scui-latest", type: "button", onClick: pin, children: "\u2193 Latest" }) : null
365
379
  ] });
366
380
  }
367
381
  export {
package/core.d.ts ADDED
@@ -0,0 +1,41 @@
1
+ export type {
2
+ AttachedSessionModel,
3
+ ControlStrategy,
4
+ HarnessId,
5
+ HarnessOption,
6
+ MessengerLabels,
7
+ ReductionReceiptModel,
8
+ RequestOption,
9
+ SessionActivity,
10
+ SessionAttention,
11
+ SessionMode,
12
+ SessionRowModel,
13
+ SessionSemanticsModel,
14
+ StartupPhase,
15
+ SupercodeUiIntent,
16
+ SupercodeUiState,
17
+ TaskPlanItem,
18
+ TaskPlanModel,
19
+ TranscriptContext,
20
+ TranscriptEntryModel,
21
+ TranscriptRequest,
22
+ UiTone,
23
+ } from './index.js';
24
+ export {
25
+ DEFAULT_LABELS,
26
+ EMPTY_UI_STATE,
27
+ activitySummary,
28
+ canContinueHere,
29
+ compactToolTarget,
30
+ filterSessions,
31
+ groupConversation,
32
+ harnessDisplayName,
33
+ isSendKey,
34
+ normalizeUiState,
35
+ operationLabel,
36
+ sessionActivity,
37
+ sessionDisplayName,
38
+ terminalCommand,
39
+ toolCategory,
40
+ toolTarget,
41
+ } from './index.js';
package/core.mjs CHANGED
@@ -1,6 +1,7 @@
1
1
  const HARNESS_NAMES = Object.freeze({
2
2
  'claude-code': 'Claude Code',
3
3
  codex: 'Codex',
4
+ gemini: 'Gemini CLI',
4
5
  opencode: 'OpenCode',
5
6
  pi: 'Pi',
6
7
  grok: 'Grok',
@@ -58,7 +59,7 @@ export const EMPTY_UI_STATE = Object.freeze({
58
59
 
59
60
  const ROLES = new Set(['system', 'user', 'assistant', 'tool', 'reasoning', 'request', 'notice']);
60
61
  const MODES = new Set(['none', 'control', 'mirror']);
61
- const STRATEGIES = new Set(['start', 'resume', 'attach', 'branch']);
62
+ const STRATEGIES = new Set(['start', 'resume', 'attach', 'branch', 'reduce']);
62
63
  const STARTUP = new Set(['connecting', 'starting', 'discovering', 'ready']);
63
64
  const FIDELITY = new Set(['byte_lossless', 'value_lossless', 'semantic']);
64
65
 
@@ -196,6 +197,72 @@ function readSemantics(value) {
196
197
  };
197
198
  }
198
199
 
200
+ function readTerminalHandoff(value) {
201
+ const handoff = record(value);
202
+ if (
203
+ !handoff ||
204
+ typeof handoff.program !== 'string' ||
205
+ !handoff.program.trim() ||
206
+ !Array.isArray(handoff.arguments) ||
207
+ !handoff.arguments.every((argument) => typeof argument === 'string') ||
208
+ typeof handoff.cwd !== 'string'
209
+ ) return null;
210
+ return { program: handoff.program, arguments: [...handoff.arguments], cwd: handoff.cwd };
211
+ }
212
+
213
+ function readExportReceipt(value) {
214
+ const receipt = record(value);
215
+ if (
216
+ !receipt ||
217
+ typeof receipt.targetHarness !== 'string' ||
218
+ !['byte_lossless', 'value_lossless'].includes(receipt.fidelity) ||
219
+ typeof receipt.path !== 'string' ||
220
+ !receipt.path.trim() ||
221
+ !Number.isInteger(receipt.files) ||
222
+ receipt.files < 1 ||
223
+ !Number.isInteger(receipt.residueCount) ||
224
+ receipt.residueCount < 0
225
+ ) return null;
226
+ return {
227
+ targetHarness: receipt.targetHarness,
228
+ fidelity: receipt.fidelity,
229
+ path: receipt.path,
230
+ files: receipt.files,
231
+ residueCount: receipt.residueCount,
232
+ };
233
+ }
234
+
235
+ function readReductionReceipt(value) {
236
+ const receipt = record(value);
237
+ if (
238
+ !receipt ||
239
+ typeof receipt.sourceTokens !== 'number' ||
240
+ !Number.isFinite(receipt.sourceTokens) ||
241
+ typeof receipt.reducedTokens !== 'number' ||
242
+ !Number.isFinite(receipt.reducedTokens) ||
243
+ receipt.sourceTokens <= receipt.reducedTokens ||
244
+ receipt.reducedTokens < 0 ||
245
+ typeof receipt.ratio !== 'number' ||
246
+ !Number.isFinite(receipt.ratio) ||
247
+ receipt.ratio <= 1 ||
248
+ typeof receipt.sidecarId !== 'string' ||
249
+ !receipt.sidecarId.trim() ||
250
+ receipt.verified !== true ||
251
+ receipt.reversible !== true ||
252
+ typeof receipt.targetHarness !== 'string' ||
253
+ !receipt.targetHarness.trim()
254
+ ) return null;
255
+ return {
256
+ sourceTokens: receipt.sourceTokens,
257
+ reducedTokens: receipt.reducedTokens,
258
+ ratio: receipt.ratio,
259
+ sidecarId: receipt.sidecarId,
260
+ verified: true,
261
+ reversible: true,
262
+ targetHarness: receipt.targetHarness,
263
+ };
264
+ }
265
+
199
266
  export function normalizeUiState(value) {
200
267
  const raw = record(value) ?? {};
201
268
  const pill = record(raw.pill);
@@ -225,10 +292,10 @@ export function normalizeUiState(value) {
225
292
  workspace: string(raw.workspace),
226
293
  taskPlan: readTaskPlan(raw.taskPlan),
227
294
  semantics: readSemantics(raw.semantics),
228
- terminalHandoff: record(raw.terminalHandoff),
295
+ terminalHandoff: readTerminalHandoff(raw.terminalHandoff),
229
296
  exportBackTarget: typeof raw.exportBackTarget === 'string' ? raw.exportBackTarget : null,
230
- exportReceipt: record(raw.exportReceipt),
231
- reductionReceipt: record(raw.reductionReceipt),
297
+ exportReceipt: readExportReceipt(raw.exportReceipt),
298
+ reductionReceipt: readReductionReceipt(raw.reductionReceipt),
232
299
  error: typeof raw.error === 'string' ? raw.error : null,
233
300
  recoverable: raw.recoverable === true,
234
301
  harnesses: Array.isArray(raw.harnesses) ? raw.harnesses.flatMap((candidate) => {
package/embed.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export type { MessengerProps, MountedMessenger, SupercodeUiState } from './index.js';
2
+ export { mountSupercodeMessenger } from './index.js';