@volter-ai-dev/supercode-ui 0.1.0

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/embed.mjs ADDED
@@ -0,0 +1,972 @@
1
+ // src/embed.jsx
2
+ import { render } from "preact";
3
+
4
+ // src/components.jsx
5
+ import MarkdownIt from "markdown-it";
6
+ import { useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "preact/hooks";
7
+
8
+ // core.mjs
9
+ var HARNESS_NAMES = Object.freeze({
10
+ "claude-code": "Claude Code",
11
+ codex: "Codex",
12
+ opencode: "OpenCode",
13
+ pi: "Pi",
14
+ grok: "Grok"
15
+ });
16
+ var DEFAULT_LABELS = Object.freeze({
17
+ chats: "Chats",
18
+ newChat: "New chat",
19
+ searchChats: "Search chats",
20
+ askAgent: "Ask your agent\u2026",
21
+ continueHere: "Continue here",
22
+ joinLive: "Join live",
23
+ forkHere: "Fork here"
24
+ });
25
+ var EMPTY_UI_STATE = Object.freeze({
26
+ pill: Object.freeze({ tone: "off", label: "connecting\u2026" }),
27
+ startup: "connecting",
28
+ transcript: Object.freeze([]),
29
+ busy: false,
30
+ operation: null,
31
+ needsInput: false,
32
+ harness: "",
33
+ mode: "none",
34
+ strategy: null,
35
+ canSend: false,
36
+ canResume: false,
37
+ canBranch: false,
38
+ canAttach: false,
39
+ canDetach: false,
40
+ canOpenTerminal: false,
41
+ canExport: false,
42
+ canReduce: false,
43
+ canInterrupt: false,
44
+ canRespond: false,
45
+ messaging: null,
46
+ workspace: "",
47
+ taskPlan: Object.freeze({ source: "none", items: Object.freeze([]), residueCount: 0, observedAt: null }),
48
+ semantics: Object.freeze({ fidelity: null, residue: Object.freeze([]), residueCount: 0, parseErrors: 0, rawRecords: 0, subagents: Object.freeze([]) }),
49
+ terminalHandoff: null,
50
+ exportBackTarget: null,
51
+ exportReceipt: null,
52
+ reductionReceipt: null,
53
+ error: null,
54
+ recoverable: false,
55
+ harnesses: Object.freeze([]),
56
+ history: Object.freeze({ sessionLimit: 0, hasMoreSessions: false, transcriptLimit: 120, hasEarlier: false }),
57
+ savedDraft: "",
58
+ attention: Object.freeze([]),
59
+ sessions: Object.freeze([]),
60
+ attached: null,
61
+ owned: null,
62
+ attachError: null
63
+ });
64
+ var ROLES = /* @__PURE__ */ new Set(["system", "user", "assistant", "tool", "reasoning", "request", "notice"]);
65
+ var MODES = /* @__PURE__ */ new Set(["none", "control", "mirror"]);
66
+ var STRATEGIES = /* @__PURE__ */ new Set(["start", "resume", "attach", "branch"]);
67
+ var STARTUP = /* @__PURE__ */ new Set(["connecting", "starting", "discovering", "ready"]);
68
+ var FIDELITY = /* @__PURE__ */ new Set(["byte_lossless", "value_lossless", "semantic"]);
69
+ function record(value) {
70
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
71
+ }
72
+ function string(value, fallback = "") {
73
+ return typeof value === "string" ? value : fallback;
74
+ }
75
+ function number(value, fallback = 0) {
76
+ return typeof value === "number" && Number.isFinite(value) ? value : fallback;
77
+ }
78
+ function nullableNumber(value) {
79
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
80
+ }
81
+ function readTranscript(value) {
82
+ if (!Array.isArray(value)) return [];
83
+ const result = [];
84
+ for (const candidate of value) {
85
+ const item = record(candidate);
86
+ if (!item || typeof item.id !== "string" || typeof item.text !== "string" || !ROLES.has(item.role)) continue;
87
+ const entry = {
88
+ id: item.id,
89
+ role: item.role,
90
+ text: item.text,
91
+ ts: nullableNumber(item.ts),
92
+ truncated: item.truncated === true
93
+ };
94
+ for (const key of ["label", "arguments", "resultText", "code"]) {
95
+ if (typeof item[key] === "string") entry[key] = item[key];
96
+ }
97
+ if (["pending", "completed", "error"].includes(item.status)) entry.status = item.status;
98
+ if (typeof item.streaming === "boolean") entry.streaming = item.streaming;
99
+ if (Array.isArray(item.context)) {
100
+ entry.context = item.context.flatMap((raw) => {
101
+ const context = record(raw);
102
+ return context && typeof context.label === "string" && typeof context.detail === "string" ? [{
103
+ ...typeof context.id === "string" ? { id: context.id } : {},
104
+ ...typeof context.kind === "string" ? { kind: context.kind } : {},
105
+ label: context.label,
106
+ detail: context.detail
107
+ }] : [];
108
+ });
109
+ }
110
+ const request = record(item.request);
111
+ if (request && typeof request.requestKind === "string" && typeof request.payloadText === "string") {
112
+ entry.request = {
113
+ requestId: request.requestId,
114
+ requestKind: request.requestKind,
115
+ payloadText: request.payloadText,
116
+ options: Array.isArray(request.options) ? request.options.flatMap((raw) => {
117
+ const option = record(raw);
118
+ return option && typeof option.optionId === "string" && typeof option.name === "string" ? [{ optionId: option.optionId, name: option.name, kind: string(option.kind, "other") }] : [];
119
+ }) : [],
120
+ cancellable: request.cancellable === true,
121
+ status: request.status === "responded" ? "responded" : "pending",
122
+ resolution: record(request.resolution)
123
+ };
124
+ }
125
+ result.push(entry);
126
+ }
127
+ return result;
128
+ }
129
+ function readSessions(value) {
130
+ if (!Array.isArray(value)) return [];
131
+ return value.flatMap((raw) => {
132
+ const row = record(raw);
133
+ if (!row || typeof row.key !== "string" || typeof row.harness !== "string") return [];
134
+ return [{
135
+ key: row.key,
136
+ harness: row.harness,
137
+ name: string(row.name),
138
+ cwd: string(row.cwd),
139
+ title: string(row.title),
140
+ age: string(row.age),
141
+ updatedAt: nullableNumber(row.updatedAt),
142
+ messages: nullableNumber(row.messages),
143
+ active: row.active === true,
144
+ live: row.live === true,
145
+ runtimeStatus: row.runtimeStatus === "busy" || row.runtimeStatus === "idle" ? row.runtimeStatus : null
146
+ }];
147
+ });
148
+ }
149
+ function readAttached(value) {
150
+ const item = record(value);
151
+ if (!item || typeof item.harness !== "string") return null;
152
+ return { key: string(item.key), harness: item.harness, name: string(item.name), cwd: string(item.cwd), title: string(item.title) };
153
+ }
154
+ function readTaskPlan(value) {
155
+ const plan = record(value);
156
+ if (!plan) return { ...EMPTY_UI_STATE.taskPlan, items: [] };
157
+ return {
158
+ source: ["codex-update-plan", "claude-tasks", "opencode-todos"].includes(plan.source) ? plan.source : "none",
159
+ items: Array.isArray(plan.items) ? plan.items.flatMap((raw) => {
160
+ const item = record(raw);
161
+ if (!item || typeof item.id !== "string" || typeof item.title !== "string") return [];
162
+ return [{
163
+ id: item.id,
164
+ title: item.title,
165
+ status: ["pending", "in_progress", "completed", "cancelled", "unknown"].includes(item.status) ? item.status : "unknown",
166
+ ...typeof item.nativeStatus === "string" ? { nativeStatus: item.nativeStatus } : {},
167
+ ...Array.isArray(item.blockedBy) ? { blockedBy: item.blockedBy.filter((value2) => typeof value2 === "string") } : {}
168
+ }];
169
+ }) : [],
170
+ residueCount: number(plan.residueCount),
171
+ observedAt: nullableNumber(plan.observedAt)
172
+ };
173
+ }
174
+ function readSemantics(value) {
175
+ const semantics = record(value);
176
+ if (!semantics) return { ...EMPTY_UI_STATE.semantics, residue: [], subagents: [] };
177
+ return {
178
+ fidelity: FIDELITY.has(semantics.fidelity) ? semantics.fidelity : null,
179
+ residue: Array.isArray(semantics.residue) ? semantics.residue.filter((item) => typeof item === "string") : [],
180
+ residueCount: number(semantics.residueCount),
181
+ parseErrors: number(semantics.parseErrors),
182
+ rawRecords: number(semantics.rawRecords),
183
+ subagents: Array.isArray(semantics.subagents) ? semantics.subagents.flatMap((raw) => {
184
+ const child = record(raw);
185
+ if (!child || typeof child.id !== "string" || typeof child.source !== "string") return [];
186
+ return [{ id: child.id, source: child.source, model: typeof child.model === "string" ? child.model : null, messages: number(child.messages), fidelity: FIDELITY.has(child.fidelity) ? child.fidelity : "semantic" }];
187
+ }) : []
188
+ };
189
+ }
190
+ function normalizeUiState(value) {
191
+ const raw = record(value) ?? {};
192
+ const pill = record(raw.pill);
193
+ const history = record(raw.history);
194
+ const attachError = record(raw.attachError);
195
+ return {
196
+ pill: { tone: ["live", "warn", "dead"].includes(pill?.tone) ? pill.tone : "off", label: string(pill?.label, "connecting\u2026") },
197
+ startup: STARTUP.has(raw.startup) ? raw.startup : "connecting",
198
+ transcript: readTranscript(raw.transcript),
199
+ busy: raw.busy === true,
200
+ operation: typeof raw.operation === "string" ? raw.operation : null,
201
+ needsInput: raw.needsInput === true,
202
+ harness: string(raw.harness),
203
+ mode: MODES.has(raw.mode) ? raw.mode : "none",
204
+ strategy: STRATEGIES.has(raw.strategy) ? raw.strategy : null,
205
+ canSend: raw.canSend === true,
206
+ canResume: raw.canResume === true,
207
+ canBranch: raw.canBranch === true,
208
+ canAttach: raw.canAttach === true,
209
+ canDetach: raw.canDetach === true,
210
+ canOpenTerminal: raw.canOpenTerminal === true,
211
+ canExport: raw.canExport === true,
212
+ canReduce: raw.canReduce === true,
213
+ canInterrupt: raw.canInterrupt === true,
214
+ canRespond: raw.canRespond === true,
215
+ messaging: raw.messaging === "live_peer" ? "live_peer" : null,
216
+ workspace: string(raw.workspace),
217
+ taskPlan: readTaskPlan(raw.taskPlan),
218
+ semantics: readSemantics(raw.semantics),
219
+ terminalHandoff: record(raw.terminalHandoff),
220
+ exportBackTarget: typeof raw.exportBackTarget === "string" ? raw.exportBackTarget : null,
221
+ exportReceipt: record(raw.exportReceipt),
222
+ reductionReceipt: record(raw.reductionReceipt),
223
+ error: typeof raw.error === "string" ? raw.error : null,
224
+ recoverable: raw.recoverable === true,
225
+ harnesses: Array.isArray(raw.harnesses) ? raw.harnesses.flatMap((candidate) => {
226
+ const item = record(candidate);
227
+ return item && typeof item.id === "string" ? [{ id: item.id, label: string(item.label, harnessDisplayName(item.id)), installed: item.installed === true, startable: item.startable === true, reason: typeof item.reason === "string" ? item.reason : null }] : [];
228
+ }) : [],
229
+ history: { sessionLimit: number(history?.sessionLimit), hasMoreSessions: history?.hasMoreSessions === true, transcriptLimit: number(history?.transcriptLimit, 120), hasEarlier: history?.hasEarlier === true },
230
+ savedDraft: string(raw.savedDraft),
231
+ attention: Array.isArray(raw.attention) ? raw.attention.flatMap((candidate) => {
232
+ const item = record(candidate);
233
+ return item && typeof item.key === "string" && ["unseen", "finished", "failed"].includes(item.kind) ? [{ key: item.key, kind: item.kind, ...typeof item.preview === "string" ? { preview: item.preview } : {} }] : [];
234
+ }) : [],
235
+ sessions: readSessions(raw.sessions),
236
+ attached: readAttached(raw.attached),
237
+ owned: readAttached(raw.owned),
238
+ attachError: attachError && typeof attachError.key === "string" && typeof attachError.message === "string" ? { key: attachError.key, message: attachError.message } : null
239
+ };
240
+ }
241
+ function harnessDisplayName(id) {
242
+ return HARNESS_NAMES[id] ?? id;
243
+ }
244
+ function sessionDisplayName(session) {
245
+ const title = session.title?.trim();
246
+ return title && title !== session.name ? title : session.name || "Untitled chat";
247
+ }
248
+ function sessionActivity(state, row) {
249
+ if (state.needsInput && row.active) return "needs-input";
250
+ if (state.busy && row.active) return "working";
251
+ const attention = state.attention.find((item) => item.key === row.key)?.kind;
252
+ if (attention) return attention;
253
+ if (row.runtimeStatus === "busy") return "working";
254
+ if (row.live || row.runtimeStatus === "idle") return "recent";
255
+ return "idle";
256
+ }
257
+ function filterSessions(rows, query) {
258
+ const needle = query.trim().toLocaleLowerCase();
259
+ if (!needle) return [...rows];
260
+ return rows.filter((row) => [row.name, row.title, row.cwd, row.harness, harnessDisplayName(row.harness)].some((value) => value.toLocaleLowerCase().includes(needle)));
261
+ }
262
+ function groupConversation(entries) {
263
+ const blocks = [];
264
+ for (const entry of entries) {
265
+ if (entry.role !== "tool") {
266
+ blocks.push({ kind: "entry", id: entry.id, entry });
267
+ continue;
268
+ }
269
+ const previous = blocks.at(-1);
270
+ if (previous?.kind === "activity") previous.entries.push(entry);
271
+ else blocks.push({ kind: "activity", id: `activity:${entry.id}`, entries: [entry] });
272
+ }
273
+ return blocks;
274
+ }
275
+ function toolCategory(entry) {
276
+ const name = entry.label?.toLocaleLowerCase() ?? "";
277
+ if (/read|view|open_file|list_dir/.test(name)) return "read";
278
+ if (/search|find|grep|glob/.test(name)) return "search";
279
+ if (/edit|write|patch|replace|create_file/.test(name)) return "edit";
280
+ if (/test|typecheck|lint|build/.test(name)) return "test";
281
+ if (/terminal|bash|shell|command|exec/.test(name)) return /\b(test|typecheck|lint|build)\b/i.test(entry.arguments ?? "") ? "test" : "command";
282
+ if (/browser|web|fetch|url/.test(name)) return "web";
283
+ if (/subagent|spawn|task/.test(name)) return "agent";
284
+ return "other";
285
+ }
286
+ function toolTarget(argumentsText) {
287
+ if (!argumentsText) return "";
288
+ try {
289
+ const args = JSON.parse(argumentsText);
290
+ for (const key of ["file_path", "target_file", "path", "command", "cmd", "query", "pattern", "url"]) {
291
+ if (typeof args?.[key] === "string") return args[key];
292
+ }
293
+ } catch {
294
+ return argumentsText.length > 120 ? `${argumentsText.slice(0, 117)}\u2026` : argumentsText;
295
+ }
296
+ return "";
297
+ }
298
+ function compactToolTarget(target, workspace) {
299
+ const prefix = workspace && !workspace.endsWith("/") ? `${workspace}/` : workspace;
300
+ return prefix && target.startsWith(prefix) ? target.slice(prefix.length) : target;
301
+ }
302
+ function activitySummary(entries) {
303
+ const counts = /* @__PURE__ */ new Map();
304
+ for (const entry of entries) counts.set(toolCategory(entry), (counts.get(toolCategory(entry)) ?? 0) + 1);
305
+ if ([...counts.keys()].every((key) => key === "read" || key === "search")) return `Explored ${entries.length} ${entries.length === 1 ? "item" : "items"}`;
306
+ const labels = { edit: "changed", test: "tests/builds", command: "commands", read: "reads", search: "searches", web: "web", agent: "agents", other: "other" };
307
+ return `Activity \xB7 ${Object.entries(labels).flatMap(([key, label]) => counts.has(key) ? [`${counts.get(key)} ${label}`] : []).join(" \xB7 ")}`;
308
+ }
309
+ function canContinueHere(state) {
310
+ if (state.mode !== "mirror" || state.canSend) return false;
311
+ const row = state.attached?.key ? state.sessions.find((item) => item.key === state.attached.key) : null;
312
+ return state.canResume && row?.runtimeStatus !== "busy" && row?.runtimeStatus !== "idle";
313
+ }
314
+ function operationLabel(operation) {
315
+ if (!operation) return "";
316
+ const labels = { discover: "Refreshing chats\u2026", attach: "Opening chat\u2026", resume: "Continuing here\u2026", branch: "Starting continuation\u2026", reduce: "Reducing context and verifying reversibility\u2026", terminal: "Preparing terminal handoff\u2026", export: "Exporting losslessly\u2026", refresh: "Retrying\u2026" };
317
+ return labels[operation] ?? `${operation.replaceAll("_", " ")}\u2026`;
318
+ }
319
+ function terminalCommand(handoff) {
320
+ const quote = (value) => /^[A-Za-z0-9_./:@%+=,-]+$/.test(value) ? value : `'${value.replaceAll("'", "'\\''")}'`;
321
+ return [handoff.program, ...handoff.arguments].map(quote).join(" ");
322
+ }
323
+ function isSendKey(event) {
324
+ return event.key === "Enter" && !event.shiftKey && !event.isComposing;
325
+ }
326
+
327
+ // src/components.jsx
328
+ import { jsx, jsxs } from "preact/jsx-runtime";
329
+ var markdown = new MarkdownIt({ html: false, linkify: true, breaks: false });
330
+ var defaultLinkOpen = markdown.renderer.rules.link_open;
331
+ markdown.renderer.rules.link_open = (tokens, index, options, env, self) => {
332
+ tokens[index]?.attrSet("target", "_blank");
333
+ tokens[index]?.attrSet("rel", "noreferrer noopener");
334
+ return defaultLinkOpen ? defaultLinkOpen(tokens, index, options, env, self) : self.renderToken(tokens, index, options);
335
+ };
336
+ var LOGOS = {
337
+ "claude-code": {
338
+ viewBox: "-1 3.5 26 18",
339
+ paths: [
340
+ ["path", { "fill-rule": "evenodd", "clip-rule": "evenodd", d: "M20.998 10.949H24v3.102h-3v3.028h-1.487V20H18v-2.921h-1.487V20H15v-2.921H9V20H7.488v-2.921H6V20H4.487v-2.921H3V14.05H0V10.95h3V5h17.998v5.949zM6 10.949h1.488V8.102H6v2.847zm10.51 0H18V8.102h-1.49v2.847z" }]
341
+ ]
342
+ },
343
+ codex: {
344
+ viewBox: "-1 -1 26 26",
345
+ paths: [
346
+ ["path", { "fill-rule": "evenodd", "clip-rule": "evenodd", d: "M8.086.457a6.105 6.105 0 013.046-.415c1.333.153 2.521.72 3.564 1.7a.117.117 0 00.107.029c1.408-.346 2.762-.224 4.061.366l.063.03.154.076c1.357.703 2.33 1.77 2.918 3.198.278.679.418 1.388.421 2.126a5.655 5.655 0 01-.18 1.631.167.167 0 00.04.155 5.982 5.982 0 011.578 2.891c.385 1.901-.01 3.615-1.183 5.14l-.182.22a6.063 6.063 0 01-2.934 1.851.162.162 0 00-.108.102c-.255.736-.511 1.364-.987 1.992-1.199 1.582-2.962 2.462-4.948 2.451-1.583-.008-2.986-.587-4.21-1.736a.145.145 0 00-.14-.032c-.518.167-1.04.191-1.604.185a5.924 5.924 0 01-2.595-.622 6.058 6.058 0 01-2.146-1.781c-.203-.269-.404-.522-.551-.821a7.74 7.74 0 01-.495-1.283 6.11 6.11 0 01-.017-3.064.166.166 0 00.008-.074.115.115 0 00-.037-.064 5.958 5.958 0 01-1.38-2.202 5.196 5.196 0 01-.333-1.589 6.915 6.915 0 01.188-2.132c.45-1.484 1.309-2.648 2.577-3.493.282-.188.55-.334.802-.438.286-.12.573-.22.861-.304a.129.129 0 00.087-.087A6.016 6.016 0 015.635 2.31C6.315 1.464 7.132.846 8.086.457zm-.804 7.85a.848.848 0 00-1.473.842l1.694 2.965-1.688 2.848a.849.849 0 001.46.864l1.94-3.272a.849.849 0 00.007-.854l-1.94-3.393zm5.446 6.24a.849.849 0 000 1.695h4.848a.849.849 0 000-1.696h-4.848z" }]
347
+ ]
348
+ },
349
+ grok: {
350
+ viewBox: "-1 -1 26 26",
351
+ paths: [["path", { d: "M9.27 15.29l7.978-5.897c.391-.29.95-.177 1.137.272.98 2.369.542 5.215-1.41 7.169-1.951 1.954-4.667 2.382-7.149 1.406l-2.711 1.257c3.889 2.661 8.611 2.003 11.562-.953 2.341-2.344 3.066-5.539 2.388-8.42l.006.007c-.983-4.232.242-5.924 2.75-9.383.06-.082.12-.164.179-.248l-3.301 3.305v-.01L9.267 15.292M7.623 16.723c-2.792-2.67-2.31-6.801.071-9.184 1.761-1.763 4.647-2.483 7.166-1.425l2.705-1.25a7.808 7.808 0 00-1.829-1A8.975 8.975 0 005.984 5.83c-2.533 2.536-3.33 6.436-1.962 9.764 1.022 2.487-.653 4.246-2.34 6.022-.599.63-1.199 1.259-1.682 1.925l7.62-6.815" }]]
352
+ },
353
+ opencode: { viewBox: "2.5 0 19 24", paths: [["path", { d: "M16 6H8v12h8V6zm4 16H4V2h16v20z" }]] },
354
+ pi: {
355
+ viewBox: "0 0 24 24",
356
+ paths: [
357
+ ["path", { "fill-rule": "evenodd", "clip-rule": "evenodd", d: "M1 1h16.5v11H12v5.5H6.5V23H1V1zm5.5 5.5V12H12V6.5H6.5z" }],
358
+ ["path", { d: "M17.5 12H23v11h-5.5V12z" }]
359
+ ]
360
+ }
361
+ };
362
+ function Markdown({ value }) {
363
+ const html = useMemo(() => markdown.render(value), [value]);
364
+ return /* @__PURE__ */ jsx("div", { class: "scui-markdown", dangerouslySetInnerHTML: { __html: html } });
365
+ }
366
+ function HarnessLogo({ id, activity, size = 28, onMissingLogo }) {
367
+ const logo = LOGOS[id];
368
+ useEffect(() => {
369
+ if (!logo) onMissingLogo?.(id);
370
+ }, [id, logo, onMissingLogo]);
371
+ if (!logo) return null;
372
+ return /* @__PURE__ */ jsxs("span", { class: "scui-logo", "data-harness": id, "data-activity": activity, style: `--scui-logo-size:${size}px`, "aria-hidden": "true", children: [
373
+ /* @__PURE__ */ jsx("svg", { viewBox: logo.viewBox, focusable: "false", children: logo.paths.map(([Tag, props], index) => /* @__PURE__ */ jsx(Tag, { ...props }, index)) }),
374
+ activity && activity !== "idle" ? /* @__PURE__ */ jsx("i", {}) : null
375
+ ] });
376
+ }
377
+ function LoadingStatus({ state, compact = false }) {
378
+ const copy = {
379
+ connecting: ["Connecting to coding agents", "Checking installed harnesses and capabilities.", 0],
380
+ starting: [`Starting ${harnessDisplayName(state.harness) || "coding agent"}`, "Opening a controlled session in this workspace.", 1],
381
+ discovering: ["Loading recent sessions", "Scanning native session stores without loading full transcripts.", 2],
382
+ ready: ["Ready", "Coding sessions are up to date.", 3]
383
+ }[state.startup];
384
+ return /* @__PURE__ */ jsxs("div", { class: `scui-loading${compact ? " scui-loading-compact" : ""}`, role: "status", "aria-busy": state.startup !== "ready", children: [
385
+ /* @__PURE__ */ jsx("span", { class: "scui-orbit", "aria-hidden": "true", children: /* @__PURE__ */ jsx("i", {}) }),
386
+ /* @__PURE__ */ jsxs("span", { class: "scui-loading-copy", children: [
387
+ /* @__PURE__ */ jsx("strong", { children: copy[0] }),
388
+ /* @__PURE__ */ jsx("small", { children: copy[1] })
389
+ ] }),
390
+ /* @__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)) })
391
+ ] });
392
+ }
393
+ function RequestCard({ entry, adapter, canRespond }) {
394
+ const request = entry.request;
395
+ if (!request) return null;
396
+ if (request.status === "responded") {
397
+ return /* @__PURE__ */ jsxs("div", { class: "scui-request-done", children: [
398
+ "\u2713 Request answered \xB7 ",
399
+ request.resolution?.name ?? request.requestKind
400
+ ] });
401
+ }
402
+ return /* @__PURE__ */ jsxs("section", { class: "scui-request", "aria-label": `${request.requestKind} needs input`, children: [
403
+ /* @__PURE__ */ jsx("strong", { children: "Agent needs input" }),
404
+ /* @__PURE__ */ jsx(Markdown, { value: request.payloadText || entry.text }),
405
+ /* @__PURE__ */ jsxs("div", { class: "scui-request-actions", children: [
406
+ 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)),
407
+ request.cancellable ? /* @__PURE__ */ jsx("button", { type: "button", disabled: !canRespond, onClick: () => adapter.onIntent({ action: "respond", requestId: request.requestId, optionId: null }), children: "Cancel" }) : null
408
+ ] })
409
+ ] });
410
+ }
411
+ function ContextDisclosure({ context }) {
412
+ if (!context?.length) return null;
413
+ return /* @__PURE__ */ jsxs("details", { class: "scui-context", children: [
414
+ /* @__PURE__ */ jsxs("summary", { children: [
415
+ "Context \xB7 ",
416
+ context.length
417
+ ] }),
418
+ /* @__PURE__ */ jsx("div", { children: context.map((item, index) => /* @__PURE__ */ jsxs("p", { children: [
419
+ /* @__PURE__ */ jsx("strong", { children: item.label }),
420
+ /* @__PURE__ */ jsx("span", { children: item.detail })
421
+ ] }, item.id ?? index)) })
422
+ ] });
423
+ }
424
+ function TranscriptEntry({ entry, state, adapter }) {
425
+ if (entry.role === "request") return /* @__PURE__ */ jsx(RequestCard, { entry, adapter, canRespond: state.canRespond });
426
+ if (entry.role === "reasoning") {
427
+ return /* @__PURE__ */ jsxs("details", { class: "scui-reasoning", open: entry.streaming, children: [
428
+ /* @__PURE__ */ jsx("summary", { children: entry.streaming ? "Reasoning\u2026" : "Reasoning" }),
429
+ /* @__PURE__ */ jsx(Markdown, { value: entry.text })
430
+ ] });
431
+ }
432
+ if (entry.role === "notice" || entry.role === "system") return /* @__PURE__ */ jsx("div", { class: "scui-notice", "data-code": entry.code, children: entry.text });
433
+ return /* @__PURE__ */ jsxs("article", { class: "scui-message", "data-role": entry.role, children: [
434
+ /* @__PURE__ */ jsx(Markdown, { value: entry.text }),
435
+ /* @__PURE__ */ jsx(ContextDisclosure, { context: entry.context }),
436
+ entry.truncated ? /* @__PURE__ */ jsx("small", { class: "scui-truncated", children: "Entry truncated by host \xB7 load native session for the complete value" }) : null
437
+ ] });
438
+ }
439
+ function ToolRow({ entry, workspace }) {
440
+ const [open, setOpen] = useState(entry.status === "pending");
441
+ const detailId = useId();
442
+ const target = compactToolTarget(toolTarget(entry.arguments), workspace);
443
+ const hasDetail = Boolean(entry.arguments || entry.resultText);
444
+ const category = toolCategory(entry);
445
+ return /* @__PURE__ */ jsxs("div", { class: "scui-tool", "data-status": entry.status ?? "completed", "data-category": category, children: [
446
+ /* @__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: [
447
+ /* @__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] }),
448
+ /* @__PURE__ */ jsx("strong", { children: entry.label?.split(/__|\//).at(-1)?.replaceAll("_", " ") || "Tool" }),
449
+ target ? /* @__PURE__ */ jsx("span", { class: "scui-tool-target", title: toolTarget(entry.arguments), children: target }) : null,
450
+ /* @__PURE__ */ jsx("span", { class: "scui-spacer" }),
451
+ /* @__PURE__ */ jsx("span", { "aria-label": entry.status ?? "completed", children: entry.status === "pending" ? "\u25CC" : entry.status === "error" ? "\xD7" : "\u2713" })
452
+ ] }),
453
+ hasDetail && open ? /* @__PURE__ */ jsxs("div", { id: detailId, class: "scui-tool-detail", children: [
454
+ entry.arguments ? /* @__PURE__ */ jsxs("pre", { children: [
455
+ /* @__PURE__ */ jsx("b", { children: "Input" }),
456
+ "\n",
457
+ entry.arguments
458
+ ] }) : null,
459
+ entry.resultText ? /* @__PURE__ */ jsxs("pre", { "data-error": entry.status === "error", children: [
460
+ /* @__PURE__ */ jsx("b", { children: "Output" }),
461
+ "\n",
462
+ entry.resultText,
463
+ entry.truncated ? "\n[truncated]" : ""
464
+ ] }) : null
465
+ ] }) : null
466
+ ] });
467
+ }
468
+ function ActivityGroup({ entries, state }) {
469
+ const active = entries.some((entry) => entry.status === "pending");
470
+ const [open, setOpen] = useState(active);
471
+ const id = useId();
472
+ useEffect(() => {
473
+ if (active) setOpen(true);
474
+ }, [active]);
475
+ return /* @__PURE__ */ jsxs("section", { class: "scui-activity", children: [
476
+ /* @__PURE__ */ jsxs("button", { class: "scui-activity-head", type: "button", "aria-expanded": open, "aria-controls": id, onClick: () => setOpen((value) => !value), children: [
477
+ /* @__PURE__ */ jsx("span", { class: "scui-fold", "data-open": open, children: "\u203A" }),
478
+ /* @__PURE__ */ jsx("strong", { children: activitySummary(entries) }),
479
+ /* @__PURE__ */ jsx("span", { class: "scui-spacer" }),
480
+ /* @__PURE__ */ jsxs("small", { children: [
481
+ entries.filter((entry) => entry.status === "completed").length,
482
+ "/",
483
+ entries.length
484
+ ] })
485
+ ] }),
486
+ open ? /* @__PURE__ */ jsx("div", { id, children: entries.map((entry) => /* @__PURE__ */ jsx(ToolRow, { entry, workspace: state.workspace }, entry.id)) }) : null
487
+ ] });
488
+ }
489
+ function TaskPlan({ plan }) {
490
+ if (!plan.items.length) return null;
491
+ const complete = plan.items.filter((item) => item.status === "completed" || item.status === "cancelled").length;
492
+ return /* @__PURE__ */ jsxs("details", { class: "scui-plan", children: [
493
+ /* @__PURE__ */ jsxs("summary", { children: [
494
+ /* @__PURE__ */ jsx("span", { children: "Plan" }),
495
+ /* @__PURE__ */ jsxs("small", { children: [
496
+ complete,
497
+ "/",
498
+ plan.items.length
499
+ ] })
500
+ ] }),
501
+ /* @__PURE__ */ jsx("ol", { children: plan.items.map((item) => /* @__PURE__ */ jsxs("li", { "data-status": item.status, children: [
502
+ /* @__PURE__ */ jsx("i", { "aria-hidden": "true" }),
503
+ " ",
504
+ /* @__PURE__ */ jsx("span", { children: item.title })
505
+ ] }, item.id)) })
506
+ ] });
507
+ }
508
+ function SessionDetails({ semantics }) {
509
+ if (!semantics.fidelity && !semantics.residueCount && !semantics.parseErrors && !semantics.subagents.length) return null;
510
+ return /* @__PURE__ */ jsxs("details", { class: "scui-details", children: [
511
+ /* @__PURE__ */ jsx("summary", { children: "Session details" }),
512
+ /* @__PURE__ */ jsxs("div", { children: [
513
+ semantics.fidelity ? /* @__PURE__ */ jsxs("p", { children: [
514
+ /* @__PURE__ */ jsx("strong", { children: "Fidelity" }),
515
+ /* @__PURE__ */ jsx("span", { children: semantics.fidelity.replaceAll("_", " ") })
516
+ ] }) : null,
517
+ /* @__PURE__ */ jsxs("p", { children: [
518
+ /* @__PURE__ */ jsx("strong", { children: "Native records" }),
519
+ /* @__PURE__ */ jsx("span", { children: semantics.rawRecords })
520
+ ] }),
521
+ semantics.residueCount ? /* @__PURE__ */ jsxs("p", { children: [
522
+ /* @__PURE__ */ jsx("strong", { children: "Residue" }),
523
+ /* @__PURE__ */ jsxs("span", { children: [
524
+ semantics.residueCount,
525
+ " retained"
526
+ ] })
527
+ ] }) : null,
528
+ semantics.parseErrors ? /* @__PURE__ */ jsxs("p", { children: [
529
+ /* @__PURE__ */ jsx("strong", { children: "Parse diagnostics" }),
530
+ /* @__PURE__ */ jsx("span", { children: semantics.parseErrors })
531
+ ] }) : null,
532
+ semantics.subagents.map((agent) => /* @__PURE__ */ jsxs("p", { children: [
533
+ /* @__PURE__ */ jsxs("strong", { children: [
534
+ agent.source,
535
+ " subagent"
536
+ ] }),
537
+ /* @__PURE__ */ jsxs("span", { children: [
538
+ agent.messages,
539
+ " messages \xB7 ",
540
+ agent.fidelity.replaceAll("_", " ")
541
+ ] })
542
+ ] }, agent.id))
543
+ ] })
544
+ ] });
545
+ }
546
+ var conversationMemory = /* @__PURE__ */ new Map();
547
+ var composerMemory = /* @__PURE__ */ new Map();
548
+ function Conversation({ state, adapter, components = {}, slots = {}, memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`, pending = null }) {
549
+ const scroller = useRef(null);
550
+ const remembered = conversationMemory.get(memoryKey) ?? { top: null, atBottom: true };
551
+ const [atBottom, setAtBottom] = useState(remembered.atBottom);
552
+ const earlierAnchor = useRef(null);
553
+ const restored = useRef(false);
554
+ const blocks = groupConversation(state.transcript);
555
+ const Entry = components.TranscriptEntry ?? TranscriptEntry;
556
+ const Group = components.ActivityGroup ?? ActivityGroup;
557
+ const Before = slots.beforeConversation;
558
+ const After = slots.afterConversation;
559
+ const Empty = slots.emptyConversation;
560
+ const pin = () => {
561
+ if (!scroller.current) return;
562
+ scroller.current.scrollTop = scroller.current.scrollHeight;
563
+ setAtBottom(true);
564
+ conversationMemory.set(memoryKey, { top: scroller.current.scrollTop, atBottom: true });
565
+ };
566
+ useLayoutEffect(() => {
567
+ const element = scroller.current;
568
+ if (!element) return;
569
+ const anchor = earlierAnchor.current;
570
+ if (anchor && state.transcript.length > anchor.entries) {
571
+ element.scrollTop = anchor.top + (element.scrollHeight - anchor.height);
572
+ earlierAnchor.current = null;
573
+ conversationMemory.set(memoryKey, { top: element.scrollTop, atBottom: false });
574
+ return;
575
+ }
576
+ if (!restored.current) {
577
+ restored.current = true;
578
+ if (remembered.top !== null && !remembered.atBottom) element.scrollTop = remembered.top;
579
+ else pin();
580
+ } else if (atBottom) pin();
581
+ }, [memoryKey, state.transcript.length, state.busy]);
582
+ return /* @__PURE__ */ jsxs("div", { class: "scui-conversation-wrap", children: [
583
+ /* @__PURE__ */ jsx("div", { class: "scui-conversation", ref: scroller, tabIndex: 0, "aria-label": "Conversation", onScroll: (event) => {
584
+ const element = event.currentTarget;
585
+ const bottom = element.scrollHeight - element.scrollTop - element.clientHeight <= 64;
586
+ setAtBottom(bottom);
587
+ conversationMemory.set(memoryKey, { top: element.scrollTop, atBottom: bottom });
588
+ }, children: /* @__PURE__ */ jsxs("div", { children: [
589
+ Before ? /* @__PURE__ */ jsx(Before, { state, adapter, value: null }) : null,
590
+ /* @__PURE__ */ jsx(TaskPlan, { plan: state.taskPlan }),
591
+ /* @__PURE__ */ jsx(SessionDetails, { semantics: state.semantics }),
592
+ state.history.hasEarlier ? /* @__PURE__ */ jsx("button", { class: "scui-load", type: "button", disabled: Boolean(state.operation), onClick: () => {
593
+ const element = scroller.current;
594
+ if (element) earlierAnchor.current = { height: element.scrollHeight, top: element.scrollTop, entries: state.transcript.length };
595
+ setAtBottom(false);
596
+ adapter.onIntent({ action: "loadEarlier" });
597
+ }, children: "Load earlier messages" }) : null,
598
+ !blocks.length && state.startup !== "ready" ? /* @__PURE__ */ jsx(LoadingStatus, { state }) : null,
599
+ !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,
600
+ 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)),
601
+ pending ? /* @__PURE__ */ jsxs("article", { class: "scui-message scui-pending", "data-role": "user", children: [
602
+ /* @__PURE__ */ jsx(Markdown, { value: pending }),
603
+ /* @__PURE__ */ jsx("small", { children: state.error && !state.busy ? "Not sent" : "Sending\u2026" })
604
+ ] }) : null,
605
+ state.busy ? /* @__PURE__ */ jsxs("div", { class: "scui-working", role: "status", children: [
606
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\u2726" }),
607
+ /* @__PURE__ */ jsx("i", {}),
608
+ /* @__PURE__ */ jsx("i", {}),
609
+ /* @__PURE__ */ jsx("i", {}),
610
+ /* @__PURE__ */ jsxs("small", { children: [
611
+ harnessDisplayName(state.harness),
612
+ " is working"
613
+ ] })
614
+ ] }) : null,
615
+ After ? /* @__PURE__ */ jsx(After, { state, adapter, value: null }) : null
616
+ ] }) }),
617
+ !atBottom ? /* @__PURE__ */ jsx("button", { class: "scui-latest", type: "button", onClick: pin, children: "\u2193 Latest" }) : null
618
+ ] });
619
+ }
620
+ function SessionRow({ row, state, onOpen }) {
621
+ const activity = sessionActivity(state, row);
622
+ const preview = state.attention.find((item) => item.key === row.key)?.preview;
623
+ return /* @__PURE__ */ jsxs("button", { class: "scui-session", "data-active": row.active, "data-activity": activity, type: "button", onClick: () => onOpen(row), children: [
624
+ /* @__PURE__ */ jsx(HarnessLogo, { id: row.harness, activity, size: 34 }),
625
+ /* @__PURE__ */ jsxs("span", { class: "scui-session-copy", children: [
626
+ /* @__PURE__ */ jsxs("span", { children: [
627
+ /* @__PURE__ */ jsx("strong", { children: sessionDisplayName(row) }),
628
+ /* @__PURE__ */ jsx("small", { children: row.age })
629
+ ] }),
630
+ /* @__PURE__ */ jsxs("span", { children: [
631
+ /* @__PURE__ */ jsx("b", { children: harnessDisplayName(row.harness) }),
632
+ /* @__PURE__ */ jsx("small", { children: preview || row.cwd || row.name })
633
+ ] }),
634
+ state.attachError?.key === row.key ? /* @__PURE__ */ jsx("em", { children: state.attachError.message }) : null
635
+ ] })
636
+ ] });
637
+ }
638
+ function SessionList({ state, adapter, onOpen, onNew, onClose, components = {}, labels = DEFAULT_LABELS }) {
639
+ const [query, setQuery] = useState("");
640
+ const rows = filterSessions(state.sessions, query);
641
+ const Row = components.SessionRow ?? SessionRow;
642
+ return /* @__PURE__ */ jsxs("section", { class: "scui-list", children: [
643
+ /* @__PURE__ */ jsxs("header", { class: "scui-head", children: [
644
+ /* @__PURE__ */ jsxs("span", { children: [
645
+ /* @__PURE__ */ jsx("strong", { children: labels.chats }),
646
+ /* @__PURE__ */ jsxs("small", { children: [
647
+ state.sessions.length,
648
+ " recent conversations"
649
+ ] })
650
+ ] }),
651
+ /* @__PURE__ */ jsx("button", { type: "button", "aria-label": labels.newChat, disabled: !state.harnesses.some((item) => item.startable), onClick: onNew, children: "\uFF0B" }),
652
+ onClose ? /* @__PURE__ */ jsx("button", { type: "button", "aria-label": "Close", onClick: onClose, children: "\xD7" }) : null
653
+ ] }),
654
+ state.startup !== "ready" ? /* @__PURE__ */ jsx(LoadingStatus, { state, compact: rows.length > 0 }) : null,
655
+ state.sessions.length > 4 ? /* @__PURE__ */ jsxs("label", { class: "scui-search", children: [
656
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\u2315" }),
657
+ /* @__PURE__ */ jsx("input", { type: "search", "aria-label": labels.searchChats, placeholder: labels.searchChats, value: query, onInput: (event) => setQuery(event.currentTarget.value) }),
658
+ /* @__PURE__ */ jsx("small", { children: rows.length })
659
+ ] }) : null,
660
+ /* @__PURE__ */ jsxs("div", { class: "scui-session-rows", children: [
661
+ !rows.length && state.startup === "ready" ? /* @__PURE__ */ jsx("div", { class: "scui-empty", children: query ? "No chats match your search." : state.error ?? "No coding chats found." }) : null,
662
+ rows.map((row) => /* @__PURE__ */ jsx(Row, { value: row, row, state, adapter, onOpen }, row.key)),
663
+ state.history.hasMoreSessions ? /* @__PURE__ */ jsx("button", { class: "scui-load", type: "button", onClick: () => adapter.onIntent({ action: "loadSessions" }), children: "Load older chats" }) : null
664
+ ] })
665
+ ] });
666
+ }
667
+ function ContinuationBar({ state, adapter, labels = DEFAULT_LABELS }) {
668
+ if (state.mode !== "mirror" || state.canSend) return null;
669
+ const attached = state.attached;
670
+ const row = attached?.key ? state.sessions.find((item) => item.key === attached.key) : null;
671
+ const activeElsewhere = row?.runtimeStatus === "busy" || row?.runtimeStatus === "idle";
672
+ const resume = canContinueHere(state);
673
+ const join = state.canAttach;
674
+ const branch = state.canBranch;
675
+ if (!resume && !join && !branch) return /* @__PURE__ */ jsx("div", { class: "scui-continuation", children: /* @__PURE__ */ jsxs("span", { children: [
676
+ /* @__PURE__ */ jsx("strong", { children: activeElsewhere ? "Active elsewhere" : "Read-only" }),
677
+ /* @__PURE__ */ jsx("small", { children: "This session cannot be continued by an available harness." })
678
+ ] }) });
679
+ return /* @__PURE__ */ jsxs("div", { class: "scui-continuation", children: [
680
+ /* @__PURE__ */ jsxs("span", { children: [
681
+ /* @__PURE__ */ jsx("strong", { children: activeElsewhere ? "Active elsewhere" : "Read-only" }),
682
+ /* @__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." })
683
+ ] }),
684
+ /* @__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 })
685
+ ] });
686
+ }
687
+ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`, onPending }) {
688
+ const remembered = composerMemory.get(memoryKey) ?? { draft: state.savedDraft, queue: [] };
689
+ const [draft, setDraft] = useState(remembered.draft);
690
+ const [queue, setQueue] = useState(remembered.queue);
691
+ const textarea = useRef(null);
692
+ useEffect(() => {
693
+ if (!state.busy && state.canSend && queue.length) {
694
+ const [next, ...rest] = queue;
695
+ setQueue(rest);
696
+ composerMemory.set(memoryKey, { draft, queue: rest });
697
+ onPending?.(next);
698
+ adapter.onIntent({ action: "send", text: next });
699
+ }
700
+ }, [adapter, draft, memoryKey, onPending, queue, state.busy, state.canSend]);
701
+ useEffect(() => {
702
+ const timer = setTimeout(() => adapter.onIntent({ action: "draft", text: draft }), 250);
703
+ return () => clearTimeout(timer);
704
+ }, [adapter, draft]);
705
+ const send = () => {
706
+ const text = draft.trim();
707
+ if (!text) return;
708
+ if (state.busy) setQueue((items) => {
709
+ const next = [...items, text];
710
+ composerMemory.set(memoryKey, { draft: "", queue: next });
711
+ return next;
712
+ });
713
+ else if (state.canSend) {
714
+ onPending?.(text);
715
+ adapter.onIntent({ action: "send", text });
716
+ } else return;
717
+ setDraft("");
718
+ composerMemory.set(memoryKey, { draft: "", queue: state.busy ? [...queue, text] : queue });
719
+ };
720
+ return /* @__PURE__ */ jsxs("div", { class: "scui-compose", children: [
721
+ queue.length ? /* @__PURE__ */ jsxs("div", { class: "scui-queue", children: [
722
+ /* @__PURE__ */ jsxs("strong", { children: [
723
+ queue.length,
724
+ " queued"
725
+ ] }),
726
+ queue.map((item, index) => /* @__PURE__ */ jsxs("span", { children: [
727
+ item,
728
+ /* @__PURE__ */ jsx("button", { "aria-label": `Remove queued message ${index + 1}`, onClick: () => setQueue((items) => items.filter((_, itemIndex) => itemIndex !== index)), children: "\xD7" })
729
+ ] }, `${index}:${item}`))
730
+ ] }) : null,
731
+ /* @__PURE__ */ jsxs("div", { class: "scui-envelope", children: [
732
+ /* @__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) => {
733
+ const value = event.currentTarget.value;
734
+ setDraft(value);
735
+ composerMemory.set(memoryKey, { draft: value, queue });
736
+ }, onKeyDown: (event) => {
737
+ if (isSendKey(event)) {
738
+ event.preventDefault();
739
+ send();
740
+ }
741
+ } }),
742
+ /* @__PURE__ */ jsxs("span", { children: [
743
+ 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,
744
+ /* @__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" })
745
+ ] })
746
+ ] })
747
+ ] });
748
+ }
749
+ function Receipt({ state, adapter }) {
750
+ const receipt = state.reductionReceipt;
751
+ if (receipt) return /* @__PURE__ */ jsx("div", { class: "scui-receipt", children: /* @__PURE__ */ jsxs("span", { children: [
752
+ /* @__PURE__ */ jsx("strong", { children: "Reduced and verified" }),
753
+ /* @__PURE__ */ jsxs("small", { children: [
754
+ receipt.sourceTokens.toLocaleString(),
755
+ " \u2192 ",
756
+ receipt.reducedTokens.toLocaleString(),
757
+ " tokens \xB7 ",
758
+ receipt.ratio.toFixed(1),
759
+ "\xD7 \xB7 reversible"
760
+ ] })
761
+ ] }) });
762
+ if (state.exportReceipt) return /* @__PURE__ */ jsxs("div", { class: "scui-receipt", children: [
763
+ /* @__PURE__ */ jsxs("span", { children: [
764
+ /* @__PURE__ */ jsxs("strong", { children: [
765
+ "Lossless export ready \xB7 ",
766
+ harnessDisplayName(state.exportReceipt.targetHarness)
767
+ ] }),
768
+ /* @__PURE__ */ jsxs("small", { children: [
769
+ state.exportReceipt.path,
770
+ " \xB7 ",
771
+ state.exportReceipt.files,
772
+ " files"
773
+ ] })
774
+ ] }),
775
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: () => adapter.copyText?.(state.exportReceipt.path), children: "Copy path" })
776
+ ] });
777
+ if (state.terminalHandoff) return /* @__PURE__ */ jsxs("div", { class: "scui-receipt", children: [
778
+ /* @__PURE__ */ jsxs("span", { children: [
779
+ /* @__PURE__ */ jsx("strong", { children: "Terminal handoff ready" }),
780
+ /* @__PURE__ */ jsx("small", { children: state.terminalHandoff.cwd })
781
+ ] }),
782
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: () => adapter.copyText?.(terminalCommand(state.terminalHandoff)), children: "Copy command" })
783
+ ] });
784
+ return null;
785
+ }
786
+ function ChatHeader({ state, adapter, onBack, onNew, onClose }) {
787
+ const harness = state.attached?.harness ?? state.harness;
788
+ const targets = state.harnesses.filter((item) => item.startable);
789
+ const title = state.attached ? sessionDisplayName(state.attached) : harnessDisplayName(harness) || "Agent chat";
790
+ const status = state.needsInput ? "Needs input" : state.busy ? "Working" : state.mode === "mirror" ? "Read-only" : "Ready";
791
+ const menu = state.canDetach || state.canOpenTerminal || state.canBranch || state.canAttach || state.canExport || state.canReduce;
792
+ return /* @__PURE__ */ jsxs("header", { class: "scui-head scui-chat-head", children: [
793
+ /* @__PURE__ */ jsx("button", { type: "button", "aria-label": "Back to chats", onClick: onBack, children: "\u2039" }),
794
+ /* @__PURE__ */ jsx(HarnessLogo, { id: harness, size: 28 }),
795
+ /* @__PURE__ */ jsxs("span", { children: [
796
+ /* @__PURE__ */ jsx("strong", { children: title }),
797
+ /* @__PURE__ */ jsxs("small", { children: [
798
+ harnessDisplayName(harness),
799
+ " \xB7 ",
800
+ status
801
+ ] })
802
+ ] }),
803
+ menu ? /* @__PURE__ */ jsxs("details", { class: "scui-menu", children: [
804
+ /* @__PURE__ */ jsx("summary", { "aria-label": "Conversation actions", children: "\u2022\u2022\u2022" }),
805
+ /* @__PURE__ */ jsxs("div", { children: [
806
+ state.canDetach ? /* @__PURE__ */ jsx("button", { onClick: () => adapter.onIntent({ action: "detach" }), children: "Detach to read-only" }) : null,
807
+ state.canOpenTerminal ? /* @__PURE__ */ jsx("button", { onClick: () => adapter.onIntent({ action: "terminal" }), children: "Prepare terminal handoff" }) : null,
808
+ state.canReduce ? targets.map((target) => /* @__PURE__ */ jsx("button", { onClick: () => adapter.onIntent({ action: "reduce", targetHarness: target.id }), children: target.id === state.harness ? `Reduce and continue in ${target.label}` : `Reduce and switch to ${target.label}` }, `reduce:${target.id}`)) : null,
809
+ state.canBranch ? targets.map((target) => /* @__PURE__ */ jsx("button", { onClick: () => adapter.onIntent({ action: "branch", targetHarness: target.id }), children: target.id === state.harness ? `Fork in ${target.label}` : `Continue with ${target.label}` }, `branch:${target.id}`)) : null,
810
+ state.canExport && state.exportBackTarget ? /* @__PURE__ */ jsxs("button", { onClick: () => adapter.onIntent({ action: "export", targetHarness: state.exportBackTarget }), children: [
811
+ "Export back to ",
812
+ harnessDisplayName(state.exportBackTarget)
813
+ ] }) : null
814
+ ] })
815
+ ] }) : null,
816
+ /* @__PURE__ */ jsx("button", { type: "button", "aria-label": "New chat", onClick: onNew, children: "\uFF0B" }),
817
+ onClose ? /* @__PURE__ */ jsx("button", { type: "button", "aria-label": "Close", onClick: onClose, children: "\xD7" }) : null
818
+ ] });
819
+ }
820
+ function Chat({ state, adapter, onBack, onNew, onClose, components, slots, labels }) {
821
+ const Header = slots.header;
822
+ const memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`;
823
+ const [pending, setPending] = useState(null);
824
+ const acknowledged = useRef(/* @__PURE__ */ new Set());
825
+ useEffect(() => {
826
+ setPending(null);
827
+ }, [memoryKey]);
828
+ useEffect(() => {
829
+ if (pending && state.transcript.some((entry) => entry.role === "user" && entry.text.trim() === pending.trim())) setPending(null);
830
+ }, [pending, state.transcript]);
831
+ useEffect(() => {
832
+ const key = state.attached?.key;
833
+ if (!key || !state.attention.some((item) => item.key === key)) {
834
+ if (key) acknowledged.current.delete(key);
835
+ return;
836
+ }
837
+ if (!acknowledged.current.has(key)) {
838
+ acknowledged.current.add(key);
839
+ adapter.onIntent({ action: "ack", key });
840
+ }
841
+ }, [adapter, state.attached?.key, state.attention]);
842
+ return /* @__PURE__ */ jsxs("section", { class: "scui-chat", children: [
843
+ Header ? /* @__PURE__ */ jsx(Header, { state, adapter, value: null }) : /* @__PURE__ */ jsx(ChatHeader, { state, adapter, onBack, onNew, onClose }),
844
+ operationLabel(state.operation) ? /* @__PURE__ */ jsxs("div", { class: "scui-operation", role: "status", children: [
845
+ /* @__PURE__ */ jsx("i", {}),
846
+ operationLabel(state.operation)
847
+ ] }) : null,
848
+ state.error ? /* @__PURE__ */ jsxs("div", { class: "scui-error", role: "alert", children: [
849
+ /* @__PURE__ */ jsx("span", { children: state.error }),
850
+ state.recoverable ? /* @__PURE__ */ jsx("button", { onClick: () => adapter.onIntent({ action: "refresh" }), children: "Retry" }) : null
851
+ ] }) : null,
852
+ /* @__PURE__ */ jsx(Receipt, { state, adapter }),
853
+ /* @__PURE__ */ jsx(Conversation, { state, adapter, components, slots, memoryKey, pending }, memoryKey),
854
+ /* @__PURE__ */ jsx(ContinuationBar, { state, adapter, labels }),
855
+ state.mode !== "mirror" || state.canSend ? /* @__PURE__ */ jsx(Composer, { state, adapter, labels, memoryKey, onPending: setPending }, memoryKey) : null
856
+ ] });
857
+ }
858
+ function NewChat({ state, adapter, onBack, onClose, onStarted, labels }) {
859
+ const startable = state.harnesses.filter((item) => item.startable);
860
+ const [harness, setHarness] = useState(startable[0]?.id ?? "");
861
+ const [draft, setDraft] = useState("");
862
+ const [starting, setStarting] = useState(false);
863
+ useEffect(() => {
864
+ if (starting && (state.busy || state.transcript.length)) onStarted();
865
+ }, [onStarted, starting, state.busy, state.transcript.length]);
866
+ useEffect(() => {
867
+ if (starting && state.error && !state.busy && !state.operation) setStarting(false);
868
+ }, [starting, state.busy, state.error, state.operation]);
869
+ const send = () => {
870
+ const text = draft.trim();
871
+ if (!text || !harness || starting) return;
872
+ setStarting(true);
873
+ adapter.onIntent({ action: "new", harness, text });
874
+ };
875
+ return /* @__PURE__ */ jsxs("section", { class: "scui-chat", children: [
876
+ /* @__PURE__ */ jsxs("header", { class: "scui-head", children: [
877
+ /* @__PURE__ */ jsx("button", { "aria-label": "Back", onClick: onBack, children: "\u2039" }),
878
+ /* @__PURE__ */ jsxs("span", { children: [
879
+ /* @__PURE__ */ jsx("strong", { children: labels.newChat }),
880
+ /* @__PURE__ */ jsx("small", { children: "No session is created until you send" })
881
+ ] }),
882
+ onClose ? /* @__PURE__ */ jsx("button", { "aria-label": "Close", onClick: onClose, children: "\xD7" }) : null
883
+ ] }),
884
+ /* @__PURE__ */ jsxs("div", { class: "scui-new", children: [
885
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\u2726" }),
886
+ /* @__PURE__ */ jsx("strong", { children: "What should the agent build or fix?" }),
887
+ /* @__PURE__ */ jsx("small", { children: "Choose a coding harness and send the first message." })
888
+ ] }),
889
+ /* @__PURE__ */ jsxs("div", { class: "scui-compose", children: [
890
+ /* @__PURE__ */ jsxs("label", { class: "scui-harness-picker", children: [
891
+ /* @__PURE__ */ jsx(HarnessLogo, { id: harness, size: 24 }),
892
+ /* @__PURE__ */ jsx("span", { children: "Coding harness" }),
893
+ /* @__PURE__ */ jsx("select", { value: harness, disabled: starting, onChange: (event) => setHarness(event.currentTarget.value), children: state.harnesses.map((item) => /* @__PURE__ */ jsxs("option", { value: item.id, disabled: !item.startable, children: [
894
+ item.label,
895
+ item.startable ? "" : " \xB7 unavailable"
896
+ ] }, item.id)) })
897
+ ] }),
898
+ /* @__PURE__ */ jsxs("div", { class: "scui-envelope", children: [
899
+ /* @__PURE__ */ jsx("textarea", { rows: 3, "aria-label": "Message coding agent", placeholder: startable.length ? "What should the agent do?" : "No coding harness is available", value: draft, disabled: !startable.length || starting, onInput: (event) => setDraft(event.currentTarget.value), onKeyDown: (event) => {
900
+ if (isSendKey(event)) {
901
+ event.preventDefault();
902
+ send();
903
+ }
904
+ } }),
905
+ /* @__PURE__ */ jsx("span", { children: /* @__PURE__ */ jsx("button", { class: "scui-send", disabled: !draft.trim() || !harness || starting, onClick: send, children: starting ? "\u25CC" : "\u2191" }) })
906
+ ] })
907
+ ] })
908
+ ] });
909
+ }
910
+ function SupercodeMessenger({ state: stateInput, adapter, class: className = "", initialView, labels, components = {}, slots = {} }) {
911
+ const state = normalizeUiState(stateInput);
912
+ const copy = { ...DEFAULT_LABELS, ...labels };
913
+ const [view, setView] = useState(initialView ?? (state.attention.length ? "list" : state.attached || state.transcript.length ? "chat" : "list"));
914
+ const [opening, setOpening] = useState(null);
915
+ useEffect(() => {
916
+ if (!opening) return;
917
+ if (state.attached?.key === opening.key) {
918
+ setOpening(null);
919
+ setView("chat");
920
+ } else if (state.attachError?.key === opening.key) {
921
+ setOpening(null);
922
+ }
923
+ }, [opening, state.attachError?.key, state.attached?.key]);
924
+ const open = (row) => {
925
+ setOpening(row);
926
+ adapter.onIntent({ action: "ack", key: row.key });
927
+ adapter.onIntent({ action: "attach", key: row.key });
928
+ };
929
+ const close = () => adapter.onClose?.();
930
+ return /* @__PURE__ */ jsxs("main", { class: `scui-root ${className}`, "data-view": view, "data-mode": state.mode, "aria-label": "Supercode messenger", children: [
931
+ view === "list" ? /* @__PURE__ */ jsx(SessionList, { state, adapter, onOpen: open, onNew: () => setView("new"), onClose: adapter.onClose ? close : void 0, components, labels: copy }) : null,
932
+ view === "new" ? /* @__PURE__ */ jsx(NewChat, { state, adapter, onBack: () => setView("list"), onClose: adapter.onClose ? close : void 0, onStarted: () => setView("chat"), labels: copy }) : null,
933
+ view === "chat" ? /* @__PURE__ */ jsx(Chat, { state, adapter, onBack: () => setView("list"), onNew: () => setView("new"), onClose: adapter.onClose ? close : void 0, components, slots, labels: copy }) : null,
934
+ opening ? /* @__PURE__ */ jsxs("div", { class: "scui-opening", role: "status", "aria-busy": "true", children: [
935
+ /* @__PURE__ */ jsx(HarnessLogo, { id: opening.harness, size: 34 }),
936
+ /* @__PURE__ */ jsxs("span", { children: [
937
+ /* @__PURE__ */ jsxs("strong", { children: [
938
+ "Opening ",
939
+ sessionDisplayName(opening)
940
+ ] }),
941
+ /* @__PURE__ */ jsx("small", { children: "Loading the latest transcript window\u2026" })
942
+ ] }),
943
+ /* @__PURE__ */ jsx("i", {})
944
+ ] }) : null,
945
+ slots.footer ? /* @__PURE__ */ jsx(slots.footer, { state, adapter, value: copy }) : null
946
+ ] });
947
+ }
948
+
949
+ // src/embed.jsx
950
+ import { jsx as jsx2 } from "preact/jsx-runtime";
951
+ function mountSupercodeMessenger(element, options) {
952
+ if (!(element instanceof Element)) throw new TypeError("mountSupercodeMessenger requires an Element");
953
+ let state = options.state;
954
+ const draw = () => render(/* @__PURE__ */ jsx2(SupercodeMessenger, { ...options, state }), element);
955
+ options.adapter.onIntent({ action: "mounted" });
956
+ draw();
957
+ return {
958
+ update(nextState) {
959
+ state = nextState;
960
+ draw();
961
+ },
962
+ unmount() {
963
+ render(null, element);
964
+ },
965
+ focus() {
966
+ element.querySelector("textarea, input, button, [tabindex]")?.focus({ preventScroll: true });
967
+ }
968
+ };
969
+ }
970
+ export {
971
+ mountSupercodeMessenger
972
+ };