@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/README.md +116 -0
- package/components.mjs +963 -0
- package/composer.mjs +168 -0
- package/controller.d.ts +53 -0
- package/controller.mjs +262 -0
- package/conversation.mjs +375 -0
- package/core.mjs +349 -0
- package/embed.mjs +972 -0
- package/index.d.ts +313 -0
- package/index.mjs +3 -0
- package/logo.mjs +114 -0
- package/messenger.mjs +947 -0
- package/package.json +106 -0
- package/sessions.mjs +196 -0
- package/styles.css +154 -0
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
|
+
};
|