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