@volter-ai-dev/supercode-ui 0.1.36 → 0.1.38

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.
@@ -0,0 +1,2974 @@
1
+ // src/messenger.jsx
2
+ import { useEffect as useEffect8, useId as useId3, useMemo as useMemo3, useRef as useRef7, useState as useState6 } from "react";
3
+
4
+ // core.mjs
5
+ var HARNESS_NAMES = Object.freeze({
6
+ "claude-code": "Claude Code",
7
+ codex: "Codex",
8
+ gemini: "Gemini CLI",
9
+ goose: "Goose",
10
+ opencode: "OpenCode",
11
+ pi: "Pi",
12
+ grok: "Grok"
13
+ });
14
+ var DEFAULT_LABELS = Object.freeze({
15
+ chats: "Chats",
16
+ newChat: "New chat",
17
+ searchChats: "Search chats",
18
+ askAgent: "Ask your agent\u2026",
19
+ continueHere: "Continue here",
20
+ continueWithTerminal: "Continue with terminal",
21
+ joinLive: "Join live",
22
+ forkHere: "Fork here"
23
+ });
24
+ var EMPTY_UI_STATE = Object.freeze({
25
+ pill: Object.freeze({ tone: "off", label: "connecting\u2026" }),
26
+ startup: "connecting",
27
+ transcript: Object.freeze([]),
28
+ busy: false,
29
+ operation: null,
30
+ needsInput: false,
31
+ harness: "",
32
+ mode: "none",
33
+ strategy: null,
34
+ canSend: false,
35
+ canSteer: false,
36
+ canResume: false,
37
+ continuationModes: Object.freeze([]),
38
+ canBranch: false,
39
+ canAttach: false,
40
+ canDetach: false,
41
+ canOpenTerminal: false,
42
+ canExport: false,
43
+ canReduce: false,
44
+ canInterrupt: false,
45
+ canRespond: false,
46
+ canConfigureSettings: false,
47
+ messaging: null,
48
+ workspace: "",
49
+ taskPlan: Object.freeze({ source: "none", items: Object.freeze([]), residueCount: 0, observedAt: null }),
50
+ semantics: Object.freeze({ fidelity: null, residue: Object.freeze([]), residueCount: 0, parseErrors: 0, rawRecords: 0, subagents: Object.freeze([]) }),
51
+ terminalHandoff: null,
52
+ exportBackTarget: null,
53
+ exportReceipt: null,
54
+ reductionReceipt: null,
55
+ interopSettings: null,
56
+ interopSettingsError: null,
57
+ error: null,
58
+ recoverable: false,
59
+ harnesses: Object.freeze([]),
60
+ history: Object.freeze({ sessionLimit: 0, hasMoreSessions: false, transcriptLimit: 120, hasEarlier: false }),
61
+ savedDraft: "",
62
+ attention: Object.freeze([]),
63
+ sessions: Object.freeze([]),
64
+ attached: null,
65
+ owned: null,
66
+ attachError: null
67
+ });
68
+ var ROLES = /* @__PURE__ */ new Set(["system", "user", "assistant", "tool", "reasoning", "request", "notice"]);
69
+ var MODES = /* @__PURE__ */ new Set(["none", "control", "mirror"]);
70
+ var STRATEGIES = /* @__PURE__ */ new Set(["start", "resume", "attach", "branch", "reduce"]);
71
+ var STARTUP = /* @__PURE__ */ new Set(["connecting", "starting", "discovering", "ready"]);
72
+ var FIDELITY = /* @__PURE__ */ new Set(["byte_lossless", "value_lossless", "semantic"]);
73
+ var TOOL_CATEGORIES = /* @__PURE__ */ new Set(["read", "search", "edit", "command", "test", "web", "agent", "plan", "other"]);
74
+ var TOOL_DETAILS = /* @__PURE__ */ new Set(["file", "matches", "diff", "terminal", "web", "agent", "plan", "fields"]);
75
+ var MAX_TOOL_FIELDS = 8;
76
+ var MAX_TOOL_FIELD_CHARS = 800;
77
+ var MAX_TOOL_PREVIEW_CHARS = 4e3;
78
+ function record(value) {
79
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
80
+ }
81
+ function string(value, fallback = "") {
82
+ return typeof value === "string" ? value : fallback;
83
+ }
84
+ function number(value, fallback = 0) {
85
+ return typeof value === "number" && Number.isFinite(value) ? value : fallback;
86
+ }
87
+ function nullableNumber(value) {
88
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
89
+ }
90
+ function relativeAge(updatedAt, now = Date.now()) {
91
+ if (typeof updatedAt !== "number" || !Number.isFinite(updatedAt) || updatedAt <= 0) return "";
92
+ const delta = Math.max(0, now - updatedAt);
93
+ if (delta < 6e4) return "now";
94
+ if (delta < 36e5) return `${Math.floor(delta / 6e4)}m ago`;
95
+ if (delta < 864e5) return `${Math.floor(delta / 36e5)}h ago`;
96
+ if (delta < 6048e5) return `${Math.floor(delta / 864e5)}d ago`;
97
+ return `${Math.floor(delta / 6048e5)}w ago`;
98
+ }
99
+ function boundedString(value, max = 2e3) {
100
+ if (typeof value !== "string") return "";
101
+ return value.length <= max ? value : `${value.slice(0, max)}\u2026`;
102
+ }
103
+ function argumentValue(argumentsText) {
104
+ if (!argumentsText) return null;
105
+ try {
106
+ return JSON.parse(argumentsText);
107
+ } catch {
108
+ return null;
109
+ }
110
+ }
111
+ function firstString(source, keys) {
112
+ for (const key of keys) {
113
+ if (typeof source?.[key] === "string" && source[key].trim()) return source[key].trim();
114
+ }
115
+ return "";
116
+ }
117
+ function decodedLiteral(value) {
118
+ if (!value) return "";
119
+ if (value.startsWith('"')) {
120
+ try {
121
+ return JSON.parse(value);
122
+ } catch {
123
+ return "";
124
+ }
125
+ }
126
+ return value.slice(1, -1).replaceAll("\\n", "\n").replaceAll("\\t", " ").replaceAll("\\r", "\r").replaceAll("\\`", "`").replaceAll("\\'", "'").replaceAll("\\\\", "\\");
127
+ }
128
+ function sourceString(source, keys) {
129
+ if (!source) return "";
130
+ const names = keys.join("|");
131
+ const match = new RegExp(`(?:^|[,{\\s])["']?(?:${names})["']?\\s*:\\s*("(?:\\\\.|[^"\\\\])*"|'(?:\\\\.|[^'\\\\])*'|\`(?:\\\\.|[^\`\\\\])*\`)`).exec(source);
132
+ return decodedLiteral(match?.[1]);
133
+ }
134
+ function assignedString(source, keys) {
135
+ if (!source) return "";
136
+ const names = keys.join("|");
137
+ const match = new RegExp(`\\b(?:${names})\\s*=\\s*("(?:\\\\.|[^"\\\\])*"|'(?:\\\\.|[^'\\\\])*'|\`(?:\\\\.|[^\`\\\\])*\`)`).exec(source);
138
+ return decodedLiteral(match?.[1]);
139
+ }
140
+ function callArgumentSource(source, open) {
141
+ let depth = 1;
142
+ let quote = "";
143
+ let escaped = false;
144
+ let lineComment = false;
145
+ let blockComment = false;
146
+ for (let index = open + 1; index < source.length; index += 1) {
147
+ const char = source[index];
148
+ const next = source[index + 1];
149
+ if (lineComment) {
150
+ if (char === "\n") lineComment = false;
151
+ continue;
152
+ }
153
+ if (blockComment) {
154
+ if (char === "*" && next === "/") {
155
+ blockComment = false;
156
+ index += 1;
157
+ }
158
+ continue;
159
+ }
160
+ if (quote) {
161
+ if (escaped) escaped = false;
162
+ else if (char === "\\") escaped = true;
163
+ else if (char === quote) quote = "";
164
+ continue;
165
+ }
166
+ if (char === "/" && next === "/") {
167
+ lineComment = true;
168
+ index += 1;
169
+ continue;
170
+ }
171
+ if (char === "/" && next === "*") {
172
+ blockComment = true;
173
+ index += 1;
174
+ continue;
175
+ }
176
+ if (char === '"' || char === "'" || char === "`") {
177
+ quote = char;
178
+ continue;
179
+ }
180
+ if (char === "(") depth += 1;
181
+ else if (char === ")" && (depth -= 1) === 0) return source.slice(open + 1, index);
182
+ }
183
+ return source.slice(open + 1);
184
+ }
185
+ function toolCalls(source) {
186
+ const calls = [];
187
+ let quote = "";
188
+ let escaped = false;
189
+ let lineComment = false;
190
+ let blockComment = false;
191
+ for (let index = 0; index < source.length && calls.length < 8; index += 1) {
192
+ const char = source[index];
193
+ const next = source[index + 1];
194
+ if (lineComment) {
195
+ if (char === "\n") lineComment = false;
196
+ continue;
197
+ }
198
+ if (blockComment) {
199
+ if (char === "*" && next === "/") {
200
+ blockComment = false;
201
+ index += 1;
202
+ }
203
+ continue;
204
+ }
205
+ if (quote) {
206
+ if (escaped) escaped = false;
207
+ else if (char === "\\") escaped = true;
208
+ else if (char === quote) quote = "";
209
+ continue;
210
+ }
211
+ if (char === "/" && next === "/") {
212
+ lineComment = true;
213
+ index += 1;
214
+ continue;
215
+ }
216
+ if (char === "/" && next === "*") {
217
+ blockComment = true;
218
+ index += 1;
219
+ continue;
220
+ }
221
+ if (char === '"' || char === "'" || char === "`") {
222
+ quote = char;
223
+ continue;
224
+ }
225
+ if (!source.startsWith("tools.", index) || /[A-Za-z0-9_$]/.test(source[index - 1] ?? "")) continue;
226
+ const nameStart = index + 6;
227
+ let nameEnd = nameStart;
228
+ while (/[A-Za-z0-9_$]/.test(source[nameEnd] ?? "")) nameEnd += 1;
229
+ let open = nameEnd;
230
+ while (/\s/.test(source[open] ?? "")) open += 1;
231
+ if (nameEnd === nameStart || source[open] !== "(") continue;
232
+ calls.push({ name: source.slice(nameStart, nameEnd), arguments: callArgumentSource(source, open) });
233
+ index = open;
234
+ }
235
+ return calls;
236
+ }
237
+ function toolEnvelope(entry) {
238
+ const value = argumentValue(entry.arguments);
239
+ const source = typeof value === "string" ? value : value === null ? entry.arguments ?? "" : "";
240
+ const calls = source ? toolCalls(source) : [];
241
+ const tools = [...new Set(calls.map((call) => call.name))];
242
+ const name = tools.length === 1 ? tools[0] : entry.label ?? "tool";
243
+ return { args: record(value), source, callSource: calls[0]?.arguments ?? "", tools, name };
244
+ }
245
+ function patchPath(source) {
246
+ return /\*\*\* (?:Update|Add|Delete) File:\s*([^\r\n]*?)(?=\\[nr]|[\r\n]|$)/.exec(source)?.[1]?.trim() ?? "";
247
+ }
248
+ function explicitNumber(sources, keys) {
249
+ for (const source of sources) {
250
+ for (const key of keys) {
251
+ const value = source?.[key];
252
+ const parsed = typeof value === "string" && value.trim() !== "" ? Number(value) : value;
253
+ if (typeof parsed === "number" && Number.isFinite(parsed)) return parsed;
254
+ }
255
+ }
256
+ return null;
257
+ }
258
+ function toolDetail(category) {
259
+ return { read: "file", search: "matches", edit: "diff", command: "terminal", test: "terminal", web: "web", agent: "agent", plan: "plan" }[category] ?? "fields";
260
+ }
261
+ function usefulToolFields(args) {
262
+ if (!args) return [];
263
+ const hidden = /* @__PURE__ */ new Set(["command", "cmd", "file_path", "target_file", "path", "query", "pattern", "url", "patch", "diff", "old_string", "new_string", "content", "prompt", "description", "task", "plan", "todos"]);
264
+ return Object.entries(args).flatMap(([key, value]) => {
265
+ if (hidden.has(key) || value === null || value === void 0) return [];
266
+ const rendered = typeof value === "string" ? value : JSON.stringify(value);
267
+ if (!rendered) return [];
268
+ return [{ label: key.replaceAll(/[_-]+/g, " ").replace(/^./, (letter) => letter.toLocaleUpperCase()), value: boundedString(rendered, MAX_TOOL_FIELD_CHARS) }];
269
+ }).slice(0, MAX_TOOL_FIELDS);
270
+ }
271
+ function planItems(args) {
272
+ const source = Array.isArray(args?.plan) ? args.plan : Array.isArray(args?.todos) ? args.todos : [];
273
+ return source.flatMap((item) => {
274
+ if (typeof item === "string" && item.trim()) return [{ label: boundedString(item.trim(), 300), status: "" }];
275
+ const value = record(item);
276
+ const label = firstString(value, ["step", "title", "content", "text"]);
277
+ if (!label) return [];
278
+ return [{ label: boundedString(label, 300), status: firstString(value, ["status"]) }];
279
+ }).slice(0, 12);
280
+ }
281
+ function agentItems(name, resultText) {
282
+ if (!/list.?agents/i.test(name)) return [];
283
+ return (resultText ?? "").split("\n").flatMap((line) => {
284
+ const parts = line.trim().split(/\s+·\s+/).filter(Boolean);
285
+ return parts.length > 1 ? [{ label: boundedString(parts[0], 120), status: boundedString(parts.slice(1).join(" \xB7 "), 180) }] : [];
286
+ }).slice(0, 12);
287
+ }
288
+ function editPreview(args, resultText, source) {
289
+ const direct = firstString(args, ["patch", "diff"]);
290
+ if (direct) return direct;
291
+ const oldText = firstString(args, ["old_string"]);
292
+ const newText = firstString(args, ["new_string"]);
293
+ if (oldText || newText) {
294
+ return [
295
+ ...oldText.split("\n").map((line) => `- ${line}`),
296
+ ...newText.split("\n").map((line) => `+ ${line}`)
297
+ ].join("\n");
298
+ }
299
+ const patch = /\*\*\* Begin Patch[\s\S]*?\*\*\* End Patch/.exec(source ?? "")?.[0];
300
+ if (patch) return patch;
301
+ return /^(?:diff --git|@@ |--- |\+\+\+ )/m.test(resultText ?? "") ? resultText : "";
302
+ }
303
+ function toolResultEnvelope(resultText) {
304
+ const match = /^Script (?:completed|failed)\r?\nWall time ([0-9.]+) seconds\r?\nOutput:\r?\n([\s\S]*)$/.exec(resultText ?? "");
305
+ if (!match) {
306
+ try {
307
+ const value = JSON.parse(resultText ?? "");
308
+ const object = record(value);
309
+ return {
310
+ preview: typeof value === "string" ? value : firstString(object, ["output", "message", "text", "summary", "result"]) || resultText || "",
311
+ value: object,
312
+ durationMs: null
313
+ };
314
+ } catch {
315
+ return { preview: resultText ?? "", value: null, durationMs: null };
316
+ }
317
+ }
318
+ const durationMs = Number(match[1]) * 1e3;
319
+ try {
320
+ const value = record(JSON.parse(match[2]));
321
+ return {
322
+ preview: typeof value?.output === "string" ? value.output : match[2],
323
+ value,
324
+ durationMs: Number.isFinite(durationMs) ? durationMs : null
325
+ };
326
+ } catch {
327
+ return { preview: match[2], value: null, durationMs: Number.isFinite(durationMs) ? durationMs : null };
328
+ }
329
+ }
330
+ function semanticAgentPreview(name, status, outcome) {
331
+ const normalized = name.toLocaleLowerCase();
332
+ if (status === "error") return outcome.preview;
333
+ if (/^(?:agent|task)$|spawn.?agent/.test(normalized) && outcome.preview) return "Agent is working in the background.";
334
+ if (/send.?message|followup.?task/.test(normalized) && outcome.preview) {
335
+ return /resumed from transcript|resumedAgentId/i.test(outcome.preview) ? "Agent resumed in the background." : "Message delivered.";
336
+ }
337
+ return outcome.preview;
338
+ }
339
+ function verificationCommand(command) {
340
+ let shell = "";
341
+ let quote = "";
342
+ let escaped = false;
343
+ for (const char of command) {
344
+ if (escaped) {
345
+ escaped = false;
346
+ shell += quote ? " " : char;
347
+ continue;
348
+ }
349
+ if (char === "\\") {
350
+ escaped = true;
351
+ shell += quote ? " " : char;
352
+ continue;
353
+ }
354
+ if (quote) {
355
+ if (char === quote) quote = "";
356
+ shell += " ";
357
+ continue;
358
+ }
359
+ if (char === '"' || char === "'" || char === "`") {
360
+ quote = char;
361
+ shell += " ";
362
+ continue;
363
+ }
364
+ shell += char;
365
+ }
366
+ return /(?:^|[;&|]\s*)(?:(?:npm|pnpm|yarn|bun)\s+(?:run\s+)?(?:test|typecheck|lint|build)|cargo\s+(?:test|clippy|build|check|fmt)|npx\s+(?:vitest|tsc|eslint|playwright)|pytest\b|go\s+test\b)/i.test(shell);
367
+ }
368
+ function classifyTool(name, command) {
369
+ const normalized = name.toLocaleLowerCase();
370
+ if (/write_stdin|^wait$/.test(normalized)) return "command";
371
+ if (/update.?plan|todo|checklist|taskcreate|taskupdate|create.?goal|update.?goal/.test(normalized)) return "plan";
372
+ if (/search.?replace|edit|write|patch|replace|create_file|apply_patch/.test(normalized)) return "edit";
373
+ if (/read|view|open_file|list_dir/.test(normalized)) return "read";
374
+ if (/web.?search|web.?fetch|fetch.?url/.test(normalized)) return "web";
375
+ if (/search|find|grep|glob|toolsearch/.test(normalized)) return "search";
376
+ if (/browser|web|fetch|url/.test(normalized)) return "web";
377
+ if (/agent|subagent|send.?message|delegate|followup.?task|taskstop|taskoutput|^task$/.test(normalized)) return "agent";
378
+ if (/test|typecheck|lint|build/.test(normalized)) return "test";
379
+ if (/terminal|bash|shell|command|exec|write_stdin|^wait$/.test(normalized)) return verificationCommand(command) ? "test" : "command";
380
+ return "other";
381
+ }
382
+ function toolAction(status, category, name, tools) {
383
+ const position = status === "pending" ? 0 : status === "error" ? 2 : 1;
384
+ if (tools.length > 1) return [`Running ${tools.length} actions`, `Ran ${tools.length} actions`, `${tools.length} actions failed`][position];
385
+ const normalized = name.toLocaleLowerCase();
386
+ if (/send.?message|followup.?task/.test(normalized)) return ["Messaging agent", "Messaged agent", "Agent message failed"][position];
387
+ if (/taskstop|interrupt.?agent|kill_command_or_subagent/.test(normalized)) return ["Stopping agent", "Stopped agent", "Stop failed"][position];
388
+ if (/list.?agents|taskoutput|wait.?agent/.test(normalized)) return ["Checking agents", "Checked agents", "Agent check failed"][position];
389
+ if (/get_command_or_subagent_output|write_stdin|^wait$/.test(normalized)) return ["Waiting for", "Checked", "Check failed"][position];
390
+ if (/web.?search/.test(normalized)) return ["Searching web", "Searched web", "Web search failed"][position];
391
+ if (/web.?fetch|fetch.?url/.test(normalized)) return ["Fetching page", "Fetched page", "Page fetch failed"][position];
392
+ if (/^skill$|use.?skill|load.?skill/.test(normalized)) return ["Loading skill", "Loaded skill", "Skill load failed"][position];
393
+ if (/taskcreate/.test(normalized)) return ["Adding task", "Added task", "Task creation failed"][position];
394
+ if (/taskupdate/.test(normalized)) return ["Updating task", "Updated task", "Task update failed"][position];
395
+ if (/create.?goal/.test(normalized)) return ["Creating goal", "Created goal", "Goal creation failed"][position];
396
+ if (/update.?goal/.test(normalized)) return ["Updating goal", "Updated goal", "Goal update failed"][position];
397
+ const actions = {
398
+ read: ["Reading", "Read", "Read failed"],
399
+ search: ["Searching", "Searched", "Search failed"],
400
+ edit: ["Editing", "Edited", "Edit failed"],
401
+ command: ["Running command", "Ran command", "Command failed"],
402
+ test: ["Running tests", "Ran tests", "Tests failed"],
403
+ web: ["Browsing", "Browsed", "Browser action failed"],
404
+ agent: ["Starting agent", "Started agent", "Agent failed"],
405
+ plan: ["Updating plan", "Updated plan", "Plan update failed"]
406
+ };
407
+ return actions[category]?.[position] ?? (status === "error" ? `${name} failed` : name.replaceAll(/[_-]+/g, " "));
408
+ }
409
+ function createToolPresentation(entry) {
410
+ const envelope = toolEnvelope(entry);
411
+ const args = envelope.args;
412
+ const outcome = toolResultEnvelope(entry.resultText);
413
+ const patchSource = assignedString(envelope.source, ["patch"]) || envelope.source;
414
+ const command = firstString(args, ["command", "cmd"]) || sourceString(envelope.callSource, ["command", "cmd"]);
415
+ const category = classifyTool(envelope.name, command || envelope.source || entry.arguments || "");
416
+ const path = firstString(args, ["file_path", "target_file", "target_directory", "path"]) || sourceString(envelope.callSource, ["file_path", "target_file", "target_directory", "path"]) || patchPath(patchSource);
417
+ const query = firstString(args, ["query", "pattern"]) || sourceString(envelope.callSource, ["query", "pattern", "q"]);
418
+ const url = firstString(args, ["url"]) || sourceString(envelope.callSource, ["url", "ref_id"]);
419
+ const subject = firstString(args, ["subject", "description", "summary", "task", "objective", "prompt"]) || sourceString(envelope.callSource, ["subject", "description", "summary", "task", "objective", "prompt"]);
420
+ const agentTarget = category === "agent" ? firstString(record(outcome.value), ["command", "name"]) || firstString(args, ["target", "task_name", "taskId", "task_id", "agentId", "agent_id", "resume", "team_name"]) || sourceString(envelope.callSource, ["target", "task_name", "taskId", "task_id", "agentId", "agent_id", "resume", "team_name"]) : "";
421
+ const skillTarget = /^skill$|use.?skill|load.?skill/i.test(envelope.name) ? firstString(args, ["skill", "name"]) || sourceString(envelope.callSource, ["skill", "name"]) : "";
422
+ const background = /get_command_or_subagent_output|kill_command_or_subagent/i.test(envelope.name) ? "background task" : /write_stdin|^wait$/i.test(envelope.name) ? "background command" : "";
423
+ const items = category === "agent" ? agentItems(envelope.name, outcome.preview) : planItems(args);
424
+ const taskId = firstString(args, ["taskId", "task_id"]);
425
+ const planTarget = category === "plan" ? items.length ? `${items.length} ${items.length === 1 ? "item" : "items"}` : taskId ? `task ${taskId}` : "" : "";
426
+ const target = path || command || query || url || subject || agentTarget || skillTarget || background || planTarget || (envelope.tools.length > 1 ? `${envelope.tools.length} coordinated actions` : toolTarget(entry.arguments));
427
+ const previewSource = category === "edit" ? editPreview(args, outcome.preview, patchSource) : category === "agent" ? semanticAgentPreview(envelope.name, entry.status ?? "completed", outcome) : outcome.preview;
428
+ const result = record(entry.resultContent);
429
+ const metadata = record(entry.metadata);
430
+ const resultMetadata = record(result?.metadata);
431
+ return {
432
+ name: boundedString(envelope.name, 120),
433
+ action: boundedString(toolAction(entry.status ?? "completed", category, envelope.name, envelope.tools), 120),
434
+ category,
435
+ detail: toolDetail(category),
436
+ target: boundedString(target, 300),
437
+ command: boundedString(command, MAX_TOOL_FIELD_CHARS),
438
+ path: boundedString(path, MAX_TOOL_FIELD_CHARS),
439
+ query: boundedString(query, MAX_TOOL_FIELD_CHARS),
440
+ url: boundedString(url, MAX_TOOL_FIELD_CHARS),
441
+ subject: boundedString(subject, MAX_TOOL_FIELD_CHARS),
442
+ preview: boundedString(previewSource, MAX_TOOL_PREVIEW_CHARS),
443
+ fields: usefulToolFields(args),
444
+ items,
445
+ tools: envelope.tools,
446
+ exitCode: explicitNumber([outcome.value, result, resultMetadata, metadata], ["exit_code", "exitCode", "pi_bash_exit_code"]),
447
+ durationMs: explicitNumber([outcome.value, result, resultMetadata, metadata], ["duration_ms", "durationMs", "elapsed_ms", "elapsedMs", "totalDurationMs"]) ?? outcome.durationMs,
448
+ additions: explicitNumber([result, resultMetadata, metadata], ["additions", "lines_added"]),
449
+ deletions: explicitNumber([result, resultMetadata, metadata], ["deletions", "lines_removed"]),
450
+ matches: explicitNumber([result, resultMetadata, metadata], ["matches", "match_count", "result_count"])
451
+ };
452
+ }
453
+ function readToolPresentation(value, entry) {
454
+ const generated = createToolPresentation(entry);
455
+ const item = record(value);
456
+ if (!item) return generated;
457
+ const fields = Array.isArray(item.fields) ? item.fields.flatMap((raw) => {
458
+ const field = record(raw);
459
+ return field && typeof field.label === "string" && typeof field.value === "string" ? [{ label: boundedString(field.label, 80), value: boundedString(field.value, MAX_TOOL_FIELD_CHARS) }] : [];
460
+ }).slice(0, MAX_TOOL_FIELDS) : generated.fields;
461
+ const items = Array.isArray(item.items) ? item.items.flatMap((raw) => {
462
+ const planItem = record(raw);
463
+ return planItem && typeof planItem.label === "string" ? [{ label: boundedString(planItem.label, 300), status: boundedString(planItem.status, 40) }] : [];
464
+ }).slice(0, 12) : generated.items;
465
+ const tools = Array.isArray(item.tools) ? item.tools.filter((tool) => typeof tool === "string").map((tool) => boundedString(tool, 120)).slice(0, 8) : generated.tools;
466
+ return {
467
+ name: boundedString(item.name, 120) || generated.name,
468
+ action: boundedString(item.action, 120) || generated.action,
469
+ category: TOOL_CATEGORIES.has(item.category) ? item.category : generated.category,
470
+ detail: TOOL_DETAILS.has(item.detail) ? item.detail : generated.detail,
471
+ target: boundedString(item.target, 300) || generated.target,
472
+ command: boundedString(item.command, MAX_TOOL_FIELD_CHARS) || generated.command,
473
+ path: boundedString(item.path, MAX_TOOL_FIELD_CHARS) || generated.path,
474
+ query: boundedString(item.query, MAX_TOOL_FIELD_CHARS) || generated.query,
475
+ url: boundedString(item.url, MAX_TOOL_FIELD_CHARS) || generated.url,
476
+ subject: boundedString(item.subject, MAX_TOOL_FIELD_CHARS) || generated.subject,
477
+ preview: boundedString(item.preview, MAX_TOOL_PREVIEW_CHARS) || generated.preview,
478
+ fields,
479
+ items,
480
+ tools,
481
+ exitCode: nullableNumber(item.exitCode) ?? generated.exitCode,
482
+ durationMs: nullableNumber(item.durationMs) ?? generated.durationMs,
483
+ additions: nullableNumber(item.additions) ?? generated.additions,
484
+ deletions: nullableNumber(item.deletions) ?? generated.deletions,
485
+ matches: nullableNumber(item.matches) ?? generated.matches
486
+ };
487
+ }
488
+ function readTranscript(value) {
489
+ if (!Array.isArray(value)) return [];
490
+ const result = [];
491
+ for (const candidate of value) {
492
+ const item = record(candidate);
493
+ if (!item || typeof item.id !== "string" || typeof item.text !== "string" || !ROLES.has(item.role)) continue;
494
+ const entry = {
495
+ id: item.id,
496
+ role: item.role,
497
+ text: item.text,
498
+ ts: nullableNumber(item.ts),
499
+ truncated: item.truncated === true,
500
+ ...Number.isSafeInteger(item.messageIndex) && item.messageIndex > 0 ? { messageIndex: item.messageIndex } : {}
501
+ };
502
+ for (const key of ["label", "arguments", "resultText", "code"]) {
503
+ if (typeof item[key] === "string") entry[key] = item[key];
504
+ }
505
+ if (["pending", "completed", "error"].includes(item.status)) entry.status = item.status;
506
+ if (item.role === "tool") entry.presentation = readToolPresentation(item.presentation, entry);
507
+ if (typeof item.streaming === "boolean") entry.streaming = item.streaming;
508
+ if (Array.isArray(item.context)) {
509
+ entry.context = item.context.flatMap((raw) => {
510
+ const context = record(raw);
511
+ return context && typeof context.label === "string" && typeof context.detail === "string" ? [{
512
+ ...typeof context.id === "string" ? { id: context.id } : {},
513
+ ...typeof context.kind === "string" ? { kind: context.kind } : {},
514
+ label: context.label,
515
+ detail: context.detail
516
+ }] : [];
517
+ });
518
+ }
519
+ if (Array.isArray(item.images)) {
520
+ entry.images = item.images.flatMap((raw) => {
521
+ const image = record(raw);
522
+ return image && typeof image.label === "string" ? [{
523
+ ...typeof image.id === "string" ? { id: image.id } : {},
524
+ label: image.label,
525
+ ...typeof image.url === "string" ? { url: image.url } : {},
526
+ ...typeof image.reference === "string" && image.reference.length <= 4e3 ? { reference: image.reference } : {},
527
+ ...typeof image.mediaType === "string" && image.mediaType.startsWith("image/") && image.mediaType.length <= 100 ? { mediaType: image.mediaType } : {},
528
+ ...Number.isSafeInteger(image.byteSize) && image.byteSize >= 0 ? { byteSize: image.byteSize } : {}
529
+ }] : [];
530
+ }).slice(0, 4);
531
+ }
532
+ const request = record(item.request);
533
+ if (request && typeof request.requestKind === "string" && typeof request.payloadText === "string") {
534
+ entry.request = {
535
+ requestId: request.requestId,
536
+ requestKind: request.requestKind,
537
+ payloadText: request.payloadText,
538
+ options: Array.isArray(request.options) ? request.options.flatMap((raw) => {
539
+ const option = record(raw);
540
+ return option && typeof option.optionId === "string" && typeof option.name === "string" ? [{ optionId: option.optionId, name: option.name, kind: string(option.kind, "other") }] : [];
541
+ }) : [],
542
+ cancellable: request.cancellable === true,
543
+ status: request.status === "responded" ? "responded" : "pending",
544
+ resolution: record(request.resolution)
545
+ };
546
+ }
547
+ result.push(entry);
548
+ }
549
+ return result;
550
+ }
551
+ function readSessions(value) {
552
+ if (!Array.isArray(value)) return [];
553
+ return value.flatMap((raw) => {
554
+ const row = record(raw);
555
+ if (!row || typeof row.key !== "string" || typeof row.harness !== "string") return [];
556
+ return [{
557
+ key: row.key,
558
+ harness: row.harness,
559
+ name: string(row.name),
560
+ cwd: string(row.cwd),
561
+ title: string(row.title),
562
+ preview: string(row.preview),
563
+ age: string(row.age),
564
+ previewUpdatedAt: nullableNumber(row.previewUpdatedAt),
565
+ updatedAt: nullableNumber(row.updatedAt),
566
+ messages: nullableNumber(row.messages),
567
+ active: row.active === true,
568
+ live: row.live === true,
569
+ runtimeStatus: row.runtimeStatus === "running" || row.runtimeStatus === "busy" || row.runtimeStatus === "idle" ? row.runtimeStatus : null
570
+ }];
571
+ });
572
+ }
573
+ function readAttached(value) {
574
+ const item = record(value);
575
+ if (!item || typeof item.harness !== "string") return null;
576
+ return { key: string(item.key), harness: item.harness, name: string(item.name), cwd: string(item.cwd), title: string(item.title) };
577
+ }
578
+ function readTaskPlan(value) {
579
+ const plan = record(value);
580
+ if (!plan) return { ...EMPTY_UI_STATE.taskPlan, items: [] };
581
+ return {
582
+ source: ["codex-update-plan", "claude-tasks", "opencode-todos"].includes(plan.source) ? plan.source : "none",
583
+ items: Array.isArray(plan.items) ? plan.items.flatMap((raw) => {
584
+ const item = record(raw);
585
+ if (!item || typeof item.id !== "string" || typeof item.title !== "string") return [];
586
+ return [{
587
+ id: item.id,
588
+ title: item.title,
589
+ status: ["pending", "in_progress", "completed", "cancelled", "unknown"].includes(item.status) ? item.status : "unknown",
590
+ ...typeof item.nativeStatus === "string" ? { nativeStatus: item.nativeStatus } : {},
591
+ ...Array.isArray(item.blockedBy) ? { blockedBy: item.blockedBy.filter((value2) => typeof value2 === "string") } : {}
592
+ }];
593
+ }) : [],
594
+ residueCount: number(plan.residueCount),
595
+ observedAt: nullableNumber(plan.observedAt)
596
+ };
597
+ }
598
+ function readSemantics(value) {
599
+ const semantics = record(value);
600
+ if (!semantics) return { ...EMPTY_UI_STATE.semantics, residue: [], subagents: [] };
601
+ return {
602
+ fidelity: FIDELITY.has(semantics.fidelity) ? semantics.fidelity : null,
603
+ residue: Array.isArray(semantics.residue) ? semantics.residue.filter((item) => typeof item === "string") : [],
604
+ residueCount: number(semantics.residueCount),
605
+ parseErrors: number(semantics.parseErrors),
606
+ rawRecords: number(semantics.rawRecords),
607
+ subagents: Array.isArray(semantics.subagents) ? semantics.subagents.flatMap((raw) => {
608
+ const child = record(raw);
609
+ if (!child || typeof child.id !== "string" || typeof child.source !== "string") return [];
610
+ 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" }];
611
+ }) : []
612
+ };
613
+ }
614
+ function readTerminalHandoff(value) {
615
+ const handoff = record(value);
616
+ if (!handoff || typeof handoff.program !== "string" || !handoff.program.trim() || !Array.isArray(handoff.arguments) || !handoff.arguments.every((argument) => typeof argument === "string") || typeof handoff.cwd !== "string") return null;
617
+ return { program: handoff.program, arguments: [...handoff.arguments], cwd: handoff.cwd };
618
+ }
619
+ function readExportReceipt(value) {
620
+ const receipt = record(value);
621
+ if (!receipt || typeof receipt.targetHarness !== "string" || !["byte_lossless", "value_lossless", "semantic"].includes(receipt.fidelity) || typeof receipt.path !== "string" || !receipt.path.trim() || !Number.isInteger(receipt.files) || receipt.files < 1 || !Number.isInteger(receipt.residueCount) || receipt.residueCount < 0) return null;
622
+ return {
623
+ targetHarness: receipt.targetHarness,
624
+ fidelity: receipt.fidelity,
625
+ path: receipt.path,
626
+ files: receipt.files,
627
+ residueCount: receipt.residueCount
628
+ };
629
+ }
630
+ function readReductionReceipt(value) {
631
+ const receipt = record(value);
632
+ if (!receipt || typeof receipt.sourceTokens !== "number" || !Number.isFinite(receipt.sourceTokens) || typeof receipt.reducedTokens !== "number" || !Number.isFinite(receipt.reducedTokens) || receipt.sourceTokens <= receipt.reducedTokens || receipt.reducedTokens < 0 || typeof receipt.ratio !== "number" || !Number.isFinite(receipt.ratio) || receipt.ratio <= 1 || typeof receipt.sidecarId !== "string" || !receipt.sidecarId.trim() || receipt.verified !== true || receipt.reversible !== true || typeof receipt.targetHarness !== "string" || !receipt.targetHarness.trim()) return null;
633
+ return {
634
+ sourceTokens: receipt.sourceTokens,
635
+ reducedTokens: receipt.reducedTokens,
636
+ ratio: receipt.ratio,
637
+ sidecarId: receipt.sidecarId,
638
+ verified: true,
639
+ reversible: true,
640
+ targetHarness: receipt.targetHarness
641
+ };
642
+ }
643
+ function readInteropSettings(value) {
644
+ const report = record(value);
645
+ if (report?.schema !== "supercode.harness-interop-settings.v1" || typeof report.harness !== "string" || typeof report.revision !== "string" || !Array.isArray(report.controls) || !Array.isArray(report.advisories)) return null;
646
+ const controls = report.controls.slice(0, 20).flatMap((raw) => {
647
+ const control = record(raw);
648
+ if (!control || typeof control.key !== "string" || typeof control.label !== "string") return [];
649
+ return [{
650
+ key: boundedString(control.key, 200),
651
+ nativeKey: boundedString(control.native_key ?? control.nativeKey, 200),
652
+ label: boundedString(control.label, 300),
653
+ description: boundedString(control.description),
654
+ scope: ["user", "project", "managed", "command_line"].includes(control.scope) ? control.scope : "user",
655
+ sourcePath: boundedString(control.source_path ?? control.sourcePath, 4e3),
656
+ configuredValue: typeof (control.configured_value ?? control.configuredValue) === "string" ? boundedString(control.configured_value ?? control.configuredValue, 500) : null,
657
+ effectiveValue: typeof (control.effective_value ?? control.effectiveValue) === "string" ? boundedString(control.effective_value ?? control.effectiveValue, 500) : null,
658
+ effectiveKnown: (control.effective_known ?? control.effectiveKnown) === true,
659
+ effectiveNote: boundedString(control.effective_note ?? control.effectiveNote),
660
+ choices: Array.isArray(control.choices) ? control.choices.slice(0, 20).flatMap((candidate) => {
661
+ const choice = record(candidate);
662
+ return choice && typeof choice.value === "string" && typeof choice.label === "string" ? [{
663
+ value: boundedString(choice.value, 500),
664
+ label: boundedString(choice.label, 300),
665
+ description: boundedString(choice.description),
666
+ ...typeof choice.risk === "string" ? { risk: boundedString(choice.risk) } : {}
667
+ }] : [];
668
+ }) : [],
669
+ writable: control.writable === true,
670
+ resettable: control.resettable === true,
671
+ requiresRestart: (control.requires_restart ?? control.requiresRestart) === true
672
+ }];
673
+ });
674
+ const advisories = report.advisories.slice(0, 20).flatMap((raw) => {
675
+ const advisory = record(raw);
676
+ const recommendation = record(advisory?.recommendation);
677
+ const change = record(recommendation?.change);
678
+ if (!advisory || !recommendation || !change || typeof advisory.code !== "string" || typeof advisory.title !== "string" || typeof advisory.setting !== "string" || typeof change.key !== "string") return [];
679
+ return [{
680
+ code: boundedString(advisory.code, 200),
681
+ severity: ["info", "warning", "error"].includes(advisory.severity) ? advisory.severity : "warning",
682
+ title: boundedString(advisory.title, 500),
683
+ message: boundedString(advisory.message),
684
+ setting: boundedString(advisory.setting, 200),
685
+ recommendation: {
686
+ label: boundedString(recommendation.label, 500),
687
+ description: boundedString(recommendation.description),
688
+ consequence: boundedString(recommendation.consequence),
689
+ change: {
690
+ key: boundedString(change.key, 200),
691
+ value: typeof change.value === "string" ? boundedString(change.value, 500) : null
692
+ },
693
+ command: boundedString(recommendation.command, 4e3)
694
+ }
695
+ }];
696
+ });
697
+ return {
698
+ schema: "supercode.harness-interop-settings.v1",
699
+ harness: report.harness,
700
+ revision: boundedString(report.revision, 500),
701
+ controls,
702
+ advisories
703
+ };
704
+ }
705
+ function normalizeUiState(value) {
706
+ const raw = record(value) ?? {};
707
+ const pill = record(raw.pill);
708
+ const history = record(raw.history);
709
+ const attachError = record(raw.attachError);
710
+ const canResume = raw.canResume === true;
711
+ const continuationModes = Array.isArray(raw.continuationModes) ? [...new Set(raw.continuationModes.filter((mode) => mode === "headless" || mode === "terminal"))] : canResume ? ["headless"] : [];
712
+ return {
713
+ pill: { tone: ["live", "warn", "dead"].includes(pill?.tone) ? pill.tone : "off", label: string(pill?.label, "connecting\u2026") },
714
+ startup: STARTUP.has(raw.startup) ? raw.startup : "connecting",
715
+ transcript: readTranscript(raw.transcript),
716
+ busy: raw.busy === true,
717
+ operation: typeof raw.operation === "string" ? raw.operation : null,
718
+ needsInput: raw.needsInput === true,
719
+ harness: string(raw.harness),
720
+ mode: MODES.has(raw.mode) ? raw.mode : "none",
721
+ strategy: STRATEGIES.has(raw.strategy) ? raw.strategy : null,
722
+ canSend: raw.canSend === true,
723
+ canSteer: raw.canSteer === true,
724
+ canResume,
725
+ continuationModes,
726
+ canBranch: raw.canBranch === true,
727
+ canAttach: raw.canAttach === true,
728
+ canDetach: raw.canDetach === true,
729
+ canOpenTerminal: raw.canOpenTerminal === true,
730
+ canExport: raw.canExport === true,
731
+ canReduce: raw.canReduce === true,
732
+ canInterrupt: raw.canInterrupt === true,
733
+ canRespond: raw.canRespond === true,
734
+ canConfigureSettings: raw.canConfigureSettings === true,
735
+ messaging: raw.messaging === "live_peer" ? "live_peer" : null,
736
+ workspace: string(raw.workspace),
737
+ taskPlan: readTaskPlan(raw.taskPlan),
738
+ semantics: readSemantics(raw.semantics),
739
+ terminalHandoff: readTerminalHandoff(raw.terminalHandoff),
740
+ exportBackTarget: typeof raw.exportBackTarget === "string" ? raw.exportBackTarget : null,
741
+ exportReceipt: readExportReceipt(raw.exportReceipt),
742
+ reductionReceipt: readReductionReceipt(raw.reductionReceipt),
743
+ interopSettings: readInteropSettings(raw.interopSettings),
744
+ interopSettingsError: typeof raw.interopSettingsError === "string" ? boundedString(raw.interopSettingsError) : null,
745
+ error: typeof raw.error === "string" ? raw.error : null,
746
+ recoverable: raw.recoverable === true,
747
+ harnesses: Array.isArray(raw.harnesses) ? raw.harnesses.flatMap((candidate) => {
748
+ const item = record(candidate);
749
+ if (!item || typeof item.id !== "string") return [];
750
+ const startable = item.startable === true;
751
+ const launchModes = Array.isArray(item.launchModes) ? [...new Set(item.launchModes.filter((mode) => mode === "headless" || mode === "terminal"))] : startable ? ["headless"] : [];
752
+ const preferredLaunchMode = launchModes.includes(item.preferredLaunchMode) ? item.preferredLaunchMode : launchModes[0] ?? null;
753
+ const capabilities = record(item.capabilities);
754
+ return [{
755
+ id: item.id,
756
+ label: string(item.label, harnessDisplayName(item.id)),
757
+ installed: item.installed === true,
758
+ startable,
759
+ reason: typeof item.reason === "string" ? boundedString(item.reason) : null,
760
+ auth: ["ready", "configured", "required", "unknown"].includes(item.auth) ? item.auth : "unknown",
761
+ runtime: ["ready", "degraded", "unavailable"].includes(item.runtime) ? item.runtime : startable ? "ready" : "unavailable",
762
+ protocol: boundedString(item.protocol, 100),
763
+ repair: typeof item.repair === "string" ? boundedString(item.repair, 4e3) : null,
764
+ capabilities: {
765
+ start: capabilities?.start === true,
766
+ resume: capabilities?.resume === true,
767
+ attach: capabilities?.attach === true,
768
+ send: capabilities?.send === true,
769
+ interrupt: capabilities?.interrupt === true,
770
+ steer: capabilities?.steer === true,
771
+ respond: capabilities?.respond === true
772
+ },
773
+ launchModes,
774
+ preferredLaunchMode
775
+ }];
776
+ }) : [],
777
+ history: { sessionLimit: number(history?.sessionLimit), hasMoreSessions: history?.hasMoreSessions === true, transcriptLimit: number(history?.transcriptLimit, 120), hasEarlier: history?.hasEarlier === true },
778
+ savedDraft: string(raw.savedDraft),
779
+ attention: Array.isArray(raw.attention) ? raw.attention.flatMap((candidate) => {
780
+ const item = record(candidate);
781
+ 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 } : {}, ...Number.isSafeInteger(item.afterMessages) && item.afterMessages >= 0 ? { afterMessages: item.afterMessages } : {}, ...Number.isSafeInteger(item.unreadCount) && item.unreadCount > 0 ? { unreadCount: item.unreadCount } : {} }] : [];
782
+ }) : [],
783
+ sessions: readSessions(raw.sessions),
784
+ attached: readAttached(raw.attached),
785
+ owned: readAttached(raw.owned),
786
+ attachError: attachError && typeof attachError.key === "string" && typeof attachError.message === "string" ? { key: attachError.key, message: attachError.message } : null
787
+ };
788
+ }
789
+ function harnessDisplayName(id) {
790
+ return HARNESS_NAMES[id] ?? id;
791
+ }
792
+ function sessionDisplayName(session) {
793
+ const title = session.title?.trim();
794
+ return title && title !== session.name ? title : session.name || "Untitled chat";
795
+ }
796
+ function sessionActivity(state, row) {
797
+ if (state.needsInput && row.active) return "needs-input";
798
+ if (state.busy && row.active) return "working";
799
+ if (row.runtimeStatus === "busy") return "working";
800
+ const attention = state.attention.find((item) => item.key === row.key)?.kind;
801
+ if (attention) return attention;
802
+ if (row.runtimeStatus === "running") return "running";
803
+ if (row.live || row.runtimeStatus === "idle") return "recent";
804
+ return "idle";
805
+ }
806
+ var ACTIVITY_PRIORITY = Object.freeze({
807
+ "needs-input": 70,
808
+ failed: 60,
809
+ working: 50,
810
+ unseen: 40,
811
+ finished: 30,
812
+ running: 20,
813
+ recent: 10,
814
+ idle: 0
815
+ });
816
+ function filterSessions(rows, query) {
817
+ const needle = query.trim().toLocaleLowerCase();
818
+ if (!needle) return [...rows];
819
+ return rows.filter((row) => [row.name, row.title, row.preview, row.cwd, row.harness, harnessDisplayName(row.harness)].some((value) => typeof value === "string" && value.toLocaleLowerCase().includes(needle)));
820
+ }
821
+ function groupConversation(entries) {
822
+ const blocks = [];
823
+ for (const entry of entries) {
824
+ if (entry.role !== "tool") {
825
+ blocks.push({ kind: "entry", id: entry.id, entry });
826
+ continue;
827
+ }
828
+ const previous = blocks.at(-1);
829
+ if (previous?.kind === "activity") previous.entries.push(entry);
830
+ else blocks.push({ kind: "activity", id: `activity:${entry.id}`, entries: [entry] });
831
+ }
832
+ return blocks;
833
+ }
834
+ function toolCategory(entry) {
835
+ if (TOOL_CATEGORIES.has(entry.presentation?.category)) return entry.presentation.category;
836
+ const envelope = toolEnvelope(entry);
837
+ const command = firstString(envelope.args, ["command", "cmd"]) || sourceString(envelope.source, ["command", "cmd"]);
838
+ return classifyTool(envelope.name, command || envelope.source || entry.arguments || "");
839
+ }
840
+ function toolTarget(argumentsText) {
841
+ if (!argumentsText) return "";
842
+ try {
843
+ const args = JSON.parse(argumentsText);
844
+ for (const key of ["file_path", "target_file", "path", "command", "cmd", "query", "pattern", "url"]) {
845
+ if (typeof args?.[key] === "string") return args[key];
846
+ }
847
+ } catch {
848
+ return argumentsText.length > 120 ? `${argumentsText.slice(0, 117)}\u2026` : argumentsText;
849
+ }
850
+ return "";
851
+ }
852
+ function compactToolTarget(target, workspace) {
853
+ const prefix = workspace && !workspace.endsWith("/") ? `${workspace}/` : workspace;
854
+ return prefix && target.startsWith(prefix) ? target.slice(prefix.length) : target;
855
+ }
856
+ function activitySummary(entries) {
857
+ const counts = /* @__PURE__ */ new Map();
858
+ for (const entry of entries) counts.set(toolCategory(entry), (counts.get(toolCategory(entry)) ?? 0) + 1);
859
+ const failed = entries.filter((entry) => entry.status === "error").length;
860
+ const pending = entries.filter((entry) => entry.status === "pending").length;
861
+ if (pending) return `${entries.length} actions in progress`;
862
+ if (failed) return `${entries.length} actions \xB7 ${failed} failed`;
863
+ if ([...counts.keys()].every((key) => key === "read" || key === "search")) return `Explored ${entries.length} ${entries.length === 1 ? "item" : "items"}`;
864
+ const nouns = { read: ["read", "reads"], search: ["search", "searches"], edit: ["edit", "edits"], command: ["command", "commands"], test: ["test run", "test runs"], web: ["web action", "web actions"], agent: ["agent action", "agent actions"], plan: ["plan update", "plan updates"], other: ["action", "actions"] };
865
+ return [...counts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3).map(([category, count]) => `${count} ${nouns[category][count === 1 ? 0 : 1]}`).join(" \xB7 ");
866
+ }
867
+ function canContinueHere(state) {
868
+ if (state.mode !== "mirror" || state.canSend) return false;
869
+ const row = state.attached?.key ? state.sessions.find((item) => item.key === state.attached.key) : null;
870
+ return Boolean(row) && state.canResume && row.runtimeStatus !== "running" && row.runtimeStatus !== "busy" && row.runtimeStatus !== "idle";
871
+ }
872
+ function operationLabel(operation) {
873
+ if (!operation) return "";
874
+ const labels = { discover: "Refreshing chats\u2026", observe: "Loading recent messages\u2026", attach: "Opening chat\u2026", start: "Starting new chat\u2026", resume: "Continuing here\u2026", join: "Joining live session\u2026", detach: "Detaching to read-only\u2026", branch: "Starting continuation\u2026", reduce: "Reducing context and verifying reversibility\u2026", terminal: "Preparing terminal handoff\u2026", export: "Exporting losslessly\u2026", steer: "Steering agent\u2026", interrupt: "Stopping agent\u2026", respond: "Sending response\u2026", configureHarness: "Updating harness settings\u2026", loadEarlier: "Loading earlier messages\u2026", loadSessions: "Loading more chats\u2026", refresh: "Retrying\u2026" };
875
+ return labels[operation] ?? `${operation.replaceAll("_", " ")}\u2026`;
876
+ }
877
+ function terminalCommand(handoff) {
878
+ const quote = (value) => /^[A-Za-z0-9_./:@%+=,-]+$/.test(value) ? value : `'${value.replaceAll("'", "'\\''")}'`;
879
+ return [handoff.program, ...handoff.arguments].map(quote).join(" ");
880
+ }
881
+ function isSendKey(event) {
882
+ return event.key === "Enter" && !event.shiftKey && !event.isComposing;
883
+ }
884
+
885
+ // src/composer.jsx
886
+ import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
887
+
888
+ // src/memory.js
889
+ var MEMORY_LIMIT = 100;
890
+ function boundedSet(map, key, value) {
891
+ map.delete(key);
892
+ map.set(key, value);
893
+ while (map.size > MEMORY_LIMIT) map.delete(map.keys().next().value);
894
+ }
895
+
896
+ // src/icon.jsx
897
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
898
+ var ICONS = {
899
+ attach: () => /* @__PURE__ */ jsx("path", { d: "M6.25 9.75 10.6 5.4a2.1 2.1 0 0 1 2.97 2.97l-5.4 5.4a3.3 3.3 0 0 1-4.67-4.66l5.52-5.52" }),
900
+ back: () => /* @__PURE__ */ jsx("path", { d: "m11.5 4.5-4.5 4.5 4.5 4.5" }),
901
+ check: () => /* @__PURE__ */ jsx("path", { d: "m4 9 3.25 3.25L14 5.5" }),
902
+ chevron: () => /* @__PURE__ */ jsx("path", { d: "m7 4.5 4.5 4.5L7 13.5" }),
903
+ close: () => /* @__PURE__ */ jsxs(Fragment, { children: [
904
+ /* @__PURE__ */ jsx("path", { d: "m4.75 4.75 8.5 8.5" }),
905
+ /* @__PURE__ */ jsx("path", { d: "m13.25 4.75-8.5 8.5" })
906
+ ] }),
907
+ copy: () => /* @__PURE__ */ jsxs(Fragment, { children: [
908
+ /* @__PURE__ */ jsx("rect", { x: "5", y: "5", width: "8", height: "8", rx: "1.5" }),
909
+ /* @__PURE__ */ jsx("path", { d: "M3 10.5V4.25C3 3.56 3.56 3 4.25 3h6.25" })
910
+ ] }),
911
+ down: () => /* @__PURE__ */ jsxs(Fragment, { children: [
912
+ /* @__PURE__ */ jsx("path", { d: "M9 3.5v10" }),
913
+ /* @__PURE__ */ jsx("path", { d: "m4.75 9.5 4.25 4 4.25-4" })
914
+ ] }),
915
+ image: () => /* @__PURE__ */ jsxs(Fragment, { children: [
916
+ /* @__PURE__ */ jsx("rect", { x: "3", y: "3.5", width: "12", height: "11", rx: "1.75" }),
917
+ /* @__PURE__ */ jsx("circle", { cx: "6.5", cy: "7", r: "1.25" }),
918
+ /* @__PURE__ */ jsx("path", { d: "m4.5 13 3.25-3 2.1 1.85 1.65-1.5L14 13" })
919
+ ] }),
920
+ menu: () => /* @__PURE__ */ jsxs(Fragment, { children: [
921
+ /* @__PURE__ */ jsx("circle", { cx: "4", cy: "9", r: "1.25", fill: "currentColor", stroke: "none" }),
922
+ /* @__PURE__ */ jsx("circle", { cx: "9", cy: "9", r: "1.25", fill: "currentColor", stroke: "none" }),
923
+ /* @__PURE__ */ jsx("circle", { cx: "14", cy: "9", r: "1.25", fill: "currentColor", stroke: "none" })
924
+ ] }),
925
+ plus: () => /* @__PURE__ */ jsxs(Fragment, { children: [
926
+ /* @__PURE__ */ jsx("path", { d: "M9 3.5v11" }),
927
+ /* @__PURE__ */ jsx("path", { d: "M3.5 9h11" })
928
+ ] }),
929
+ search: () => /* @__PURE__ */ jsxs(Fragment, { children: [
930
+ /* @__PURE__ */ jsx("circle", { cx: "7.75", cy: "7.75", r: "4.25" }),
931
+ /* @__PURE__ */ jsx("path", { d: "m11 11 3.5 3.5" })
932
+ ] }),
933
+ send: () => /* @__PURE__ */ jsxs(Fragment, { children: [
934
+ /* @__PURE__ */ jsx("path", { d: "M9 14.5v-11" }),
935
+ /* @__PURE__ */ jsx("path", { d: "m4.75 7.75 4.25-4.25 4.25 4.25" })
936
+ ] }),
937
+ stop: () => /* @__PURE__ */ jsx("rect", { x: "4.5", y: "4.5", width: "9", height: "9", rx: "1.5", fill: "currentColor", stroke: "none" })
938
+ };
939
+ function UiIcon({ name, size = 16, class: className = "" }) {
940
+ const Glyph = ICONS[name];
941
+ if (!Glyph) return null;
942
+ return /* @__PURE__ */ jsx("svg", { className: `scui-icon ${className}`, style: { "--scui-icon-size": `${size}px` }, viewBox: "0 0 18 18", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx(Glyph, {}) });
943
+ }
944
+
945
+ // src/context.jsx
946
+ import { useEffect, useRef, useState } from "react";
947
+ import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
948
+ var MAX_CONTEXT_ITEMS = 32;
949
+ var MAX_IMAGE_ITEMS = 4;
950
+ var MAX_IMAGE_BYTES = 5 * 1024 * 1024;
951
+ var MAX_RESOLVED_IMAGE_BYTES = 16 * 1024 * 1024;
952
+ var IMAGE_TYPES = /* @__PURE__ */ new Set(["image/png", "image/jpeg", "image/gif", "image/webp"]);
953
+ function normalizeContext(value) {
954
+ return (Array.isArray(value) ? value : value ? [value] : []).flatMap((item) => {
955
+ if (!item || typeof item.label !== "string" || typeof item.detail !== "string") return [];
956
+ const label = item.label.trim().slice(0, 200);
957
+ const detail = item.detail.slice(0, 2e4);
958
+ if (!label || !detail) return [];
959
+ return [{
960
+ ...typeof item.id === "string" && item.id ? { id: item.id.slice(0, 2e3) } : {},
961
+ ...typeof item.kind === "string" && item.kind ? { kind: item.kind.slice(0, 100) } : {},
962
+ label,
963
+ detail
964
+ }];
965
+ }).slice(0, MAX_CONTEXT_ITEMS);
966
+ }
967
+ function mergeContext(current, picked) {
968
+ const next = [...current];
969
+ const seen = new Set(current.map((item) => item.id ? `id:${item.id}` : `value:${item.kind ?? ""}\0${item.label}\0${item.detail}`));
970
+ for (const item of normalizeContext(picked)) {
971
+ const key = item.id ? `id:${item.id}` : `value:${item.kind ?? ""}\0${item.label}\0${item.detail}`;
972
+ if (seen.has(key)) continue;
973
+ seen.add(key);
974
+ next.push(item);
975
+ if (next.length === MAX_CONTEXT_ITEMS) break;
976
+ }
977
+ return next;
978
+ }
979
+ function normalizeImages(value) {
980
+ const seen = /* @__PURE__ */ new Set();
981
+ return (Array.isArray(value) ? value : value ? [value] : []).flatMap((item) => {
982
+ if (!item || typeof item.label !== "string" || typeof item.url !== "string") return [];
983
+ const label = item.label.trim().slice(0, 200);
984
+ const url = item.url;
985
+ if (!label || seen.has(url) || !(url.startsWith("data:image/") || url.startsWith("https://") || url.startsWith("http://"))) return [];
986
+ seen.add(url);
987
+ return [{
988
+ ...typeof item.id === "string" && item.id ? { id: item.id.slice(0, 2e3) } : {},
989
+ label,
990
+ url
991
+ }];
992
+ }).slice(0, MAX_IMAGE_ITEMS);
993
+ }
994
+ function mergeImages(current, picked) {
995
+ const next = [...current];
996
+ const seen = new Set(current.map((item) => item.url));
997
+ for (const item of normalizeImages(picked)) {
998
+ if (seen.has(item.url)) continue;
999
+ seen.add(item.url);
1000
+ next.push(item);
1001
+ if (next.length === MAX_IMAGE_ITEMS) break;
1002
+ }
1003
+ return next;
1004
+ }
1005
+ function partitionAttachments(value) {
1006
+ const values = Array.isArray(value) ? value : value ? [value] : [];
1007
+ const context = [];
1008
+ const images = [];
1009
+ for (const item of values) {
1010
+ if (item && typeof item.label === "string" && item.label.trim() && typeof item.detail === "string" && item.detail) context.push(item);
1011
+ else if (item && typeof item.label === "string" && item.label.trim() && typeof item.url === "string" && (item.url.startsWith("data:image/") || item.url.startsWith("https://") || item.url.startsWith("http://"))) images.push(item);
1012
+ else throw new Error("The attachment picker returned an invalid item.");
1013
+ }
1014
+ return { context, images };
1015
+ }
1016
+ function normalizeAttachmentCandidates(value) {
1017
+ const candidates = [];
1018
+ for (const item of Array.isArray(value) ? value : []) {
1019
+ const context = normalizeContext(item)[0];
1020
+ if (context) {
1021
+ candidates.push(context);
1022
+ continue;
1023
+ }
1024
+ const image = normalizeImages(item)[0];
1025
+ if (image) candidates.push(image);
1026
+ }
1027
+ return candidates.slice(0, MAX_CONTEXT_ITEMS + MAX_IMAGE_ITEMS);
1028
+ }
1029
+ function attachmentKey(item) {
1030
+ if (item.id) return `id:${item.id}`;
1031
+ return "detail" in item ? `context:${item.kind ?? ""}\0${item.label}\0${item.detail}` : `image:${item.url}`;
1032
+ }
1033
+ function ContextCandidate({ attachment, attached, onAttach }) {
1034
+ const image = "url" in attachment;
1035
+ return /* @__PURE__ */ jsxs2("button", { type: "button", disabled: attached, "aria-label": `${attached ? "Attached" : "Attach"} ${attachment.label}`, onClick: () => onAttach(attachment), children: [
1036
+ image ? /* @__PURE__ */ jsx2("img", { src: attachment.url, alt: "" }) : /* @__PURE__ */ jsx2(UiIcon, { name: "attach", size: 13 }),
1037
+ /* @__PURE__ */ jsxs2("span", { children: [
1038
+ /* @__PURE__ */ jsx2("strong", { children: attachment.label }),
1039
+ /* @__PURE__ */ jsx2("small", { children: image ? "Image" : attachment.kind || "Context" })
1040
+ ] }),
1041
+ attached ? /* @__PURE__ */ jsx2(UiIcon, { name: "check", size: 13 }) : /* @__PURE__ */ jsx2(UiIcon, { name: "plus", size: 13 })
1042
+ ] });
1043
+ }
1044
+ function ContextCandidates({ items, context, images, state, adapter, onAttach, component: Candidate = ContextCandidate }) {
1045
+ if (!items.length) return null;
1046
+ const attached = new Set([...context, ...images].map(attachmentKey));
1047
+ return /* @__PURE__ */ jsx2("div", { className: "scui-context-candidates", "aria-label": "Available context", children: items.map((attachment, index) => /* @__PURE__ */ jsx2(Candidate, { value: attachment, attachment, attached: attached.has(attachmentKey(attachment)), state, adapter, onAttach, index }, attachmentKey(attachment))) });
1048
+ }
1049
+ async function imageAttachmentsFromFiles(value) {
1050
+ const files = Array.from(value ?? []).filter((file) => file?.type?.startsWith("image/"));
1051
+ if (files.length > MAX_IMAGE_ITEMS) throw new Error(`Attach at most ${MAX_IMAGE_ITEMS} images at a time.`);
1052
+ return Promise.all(files.map(async (file) => {
1053
+ if (!IMAGE_TYPES.has(file.type)) throw new Error(`${file.name || "That image"} is not PNG, JPEG, GIF, or WebP.`);
1054
+ if (file.size > MAX_IMAGE_BYTES) throw new Error(`${file.name || "That image"} is larger than 5 MB.`);
1055
+ const url = await new Promise((resolve, reject) => {
1056
+ const reader = new FileReader();
1057
+ reader.onload = () => resolve(reader.result);
1058
+ reader.onerror = () => reject(reader.error ?? new Error(`Could not read ${file.name || "image"}.`));
1059
+ reader.readAsDataURL(file);
1060
+ });
1061
+ return { id: `${file.name}:${file.size}:${file.lastModified}`, label: file.name || "Pasted image", url };
1062
+ }));
1063
+ }
1064
+ function ContextTray({ items, onRemove }) {
1065
+ if (!items.length) return null;
1066
+ return /* @__PURE__ */ jsx2("div", { className: "scui-compose-context", "aria-label": "Attached context", children: items.map((item, index) => /* @__PURE__ */ jsxs2("span", { children: [
1067
+ /* @__PURE__ */ jsx2(UiIcon, { name: "attach", size: 12 }),
1068
+ /* @__PURE__ */ jsx2("strong", { children: item.label }),
1069
+ /* @__PURE__ */ jsx2("button", { type: "button", "aria-label": `Remove context ${item.label}`, onClick: () => onRemove(index), children: /* @__PURE__ */ jsx2(UiIcon, { name: "close", size: 12 }) })
1070
+ ] }, item.id ?? `${item.label}:${index}`)) });
1071
+ }
1072
+ function ImageTray({ items, onRemove }) {
1073
+ if (!items.length) return null;
1074
+ return /* @__PURE__ */ jsx2("div", { className: "scui-compose-images", "aria-label": "Attached images", children: items.map((item, index) => /* @__PURE__ */ jsxs2("span", { children: [
1075
+ /* @__PURE__ */ jsx2("img", { src: item.url, alt: "" }),
1076
+ /* @__PURE__ */ jsx2("strong", { children: item.label }),
1077
+ onRemove ? /* @__PURE__ */ jsx2("button", { type: "button", "aria-label": `Remove image ${item.label}`, onClick: () => onRemove(index), children: /* @__PURE__ */ jsx2(UiIcon, { name: "close", size: 12 }) }) : null
1078
+ ] }, item.id ?? `${item.label}:${index}`)) });
1079
+ }
1080
+ function imageFilename(label) {
1081
+ const value = label.trim().replace(/[\\/:*?"<>|]+/g, "-");
1082
+ return value || "image";
1083
+ }
1084
+ function ImageViewer({ items, index, adapter, onChange, onClose }) {
1085
+ const dialog = useRef(null);
1086
+ const reset = useRef(null);
1087
+ const resolutions = useRef(/* @__PURE__ */ new Map());
1088
+ const alive = useRef(true);
1089
+ const [, redraw] = useState(0);
1090
+ const [copyState, setCopyState2] = useState("idle");
1091
+ const item = items[index];
1092
+ const key = item?.reference ?? item?.url ?? `${item?.id ?? ""}:${index}`;
1093
+ const resolution = item?.url ? null : resolutions.current.get(key);
1094
+ const imageUrl = item?.url ?? (resolution?.status === "ready" ? resolution.url : null);
1095
+ const remote = imageUrl?.startsWith("http://") || imageUrl?.startsWith("https://");
1096
+ const resolve = (candidate, force = false) => {
1097
+ if (candidate?.url || !candidate?.reference || !adapter?.resolveImage) return;
1098
+ const candidateKey = candidate.reference;
1099
+ const current = resolutions.current.get(candidateKey);
1100
+ if (!force && (current?.status === "loading" || current?.status === "ready")) return;
1101
+ if (current?.url) URL.revokeObjectURL(current.url);
1102
+ resolutions.current.set(candidateKey, { status: "loading" });
1103
+ redraw((value) => value + 1);
1104
+ Promise.resolve().then(() => adapter.resolveImage(candidate)).then((blob) => {
1105
+ if (!(blob instanceof Blob) || !blob.type.startsWith("image/")) throw new Error("The host returned an invalid image.");
1106
+ if (blob.size > MAX_RESOLVED_IMAGE_BYTES) throw new Error("This image is too large to preview safely.");
1107
+ if (!alive.current) return;
1108
+ const url = URL.createObjectURL(blob);
1109
+ resolutions.current.set(candidateKey, { status: "ready", url });
1110
+ redraw((value) => value + 1);
1111
+ }).catch((error) => {
1112
+ if (!alive.current) return;
1113
+ resolutions.current.set(candidateKey, {
1114
+ status: "error",
1115
+ message: error instanceof Error && error.message ? error.message : "Could not load this image."
1116
+ });
1117
+ redraw((value) => value + 1);
1118
+ });
1119
+ };
1120
+ useEffect(() => {
1121
+ alive.current = true;
1122
+ if (!dialog.current?.open) dialog.current?.showModal();
1123
+ return () => {
1124
+ alive.current = false;
1125
+ clearTimeout(reset.current);
1126
+ for (const value of resolutions.current.values()) if (value.url) URL.revokeObjectURL(value.url);
1127
+ resolutions.current.clear();
1128
+ };
1129
+ }, []);
1130
+ useEffect(() => {
1131
+ clearTimeout(reset.current);
1132
+ setCopyState2("idle");
1133
+ resolve(item);
1134
+ }, [index, item?.reference]);
1135
+ if (!item || !item.url && (!item.reference || !adapter?.resolveImage)) return null;
1136
+ const move = (amount) => onChange((index + amount + items.length) % items.length);
1137
+ const copy = async () => {
1138
+ try {
1139
+ await adapter.copyText(imageUrl);
1140
+ setCopyState2("copied");
1141
+ } catch {
1142
+ setCopyState2("failed");
1143
+ }
1144
+ clearTimeout(reset.current);
1145
+ reset.current = setTimeout(() => setCopyState2("idle"), 1500);
1146
+ };
1147
+ const close = () => dialog.current?.close();
1148
+ return /* @__PURE__ */ jsxs2(
1149
+ "dialog",
1150
+ {
1151
+ ref: dialog,
1152
+ className: "scui-image-viewer",
1153
+ "aria-label": `Image preview: ${item.label}`,
1154
+ onClose,
1155
+ onClick: (event) => {
1156
+ if (event.target === event.currentTarget) close();
1157
+ },
1158
+ onKeyDown: (event) => {
1159
+ if (items.length < 2 || !["ArrowLeft", "ArrowRight"].includes(event.key)) return;
1160
+ event.preventDefault();
1161
+ move(event.key === "ArrowLeft" ? -1 : 1);
1162
+ },
1163
+ children: [
1164
+ /* @__PURE__ */ jsxs2("header", { children: [
1165
+ /* @__PURE__ */ jsxs2("span", { children: [
1166
+ /* @__PURE__ */ jsx2("strong", { children: item.label }),
1167
+ items.length > 1 ? /* @__PURE__ */ jsxs2("small", { children: [
1168
+ index + 1,
1169
+ " of ",
1170
+ items.length
1171
+ ] }) : null
1172
+ ] }),
1173
+ /* @__PURE__ */ jsxs2("nav", { "aria-label": "Image actions", children: [
1174
+ remote && adapter?.copyText ? /* @__PURE__ */ jsx2("button", { type: "button", "aria-label": copyState === "copied" ? "Image link copied" : copyState === "failed" ? "Could not copy image link" : "Copy image link", title: "Copy image link", "data-status": copyState, onClick: copy, children: /* @__PURE__ */ jsx2(UiIcon, { name: copyState === "copied" ? "check" : "copy", size: 16 }) }) : null,
1175
+ imageUrl ? remote ? /* @__PURE__ */ jsx2("a", { href: imageUrl, target: "_blank", rel: "noreferrer", "aria-label": "Open original image", title: "Open original image", children: /* @__PURE__ */ jsx2(UiIcon, { name: "image", size: 16 }) }) : /* @__PURE__ */ jsx2("a", { href: imageUrl, download: imageFilename(item.label), "aria-label": "Download image", title: "Download image", children: /* @__PURE__ */ jsx2(UiIcon, { name: "down", size: 16 }) }) : null,
1176
+ /* @__PURE__ */ jsx2("button", { type: "button", "aria-label": "Close image preview", title: "Close", onClick: close, children: /* @__PURE__ */ jsx2(UiIcon, { name: "close", size: 17 }) })
1177
+ ] })
1178
+ ] }),
1179
+ /* @__PURE__ */ jsxs2("figure", { children: [
1180
+ imageUrl ? /* @__PURE__ */ jsx2("img", { src: imageUrl, alt: item.label }) : resolution?.status === "error" ? /* @__PURE__ */ jsxs2("div", { className: "scui-image-resolution", role: "alert", children: [
1181
+ /* @__PURE__ */ jsx2(UiIcon, { name: "image", size: 28 }),
1182
+ /* @__PURE__ */ jsx2("strong", { children: "Could not load image" }),
1183
+ /* @__PURE__ */ jsx2("small", { children: resolution.message }),
1184
+ /* @__PURE__ */ jsx2("button", { type: "button", onClick: () => resolve(item, true), children: "Retry" })
1185
+ ] }) : /* @__PURE__ */ jsxs2("div", { className: "scui-image-resolution", role: "status", children: [
1186
+ /* @__PURE__ */ jsx2("i", { className: "scui-control-spinner" }),
1187
+ /* @__PURE__ */ jsx2("strong", { children: "Loading image\u2026" }),
1188
+ /* @__PURE__ */ jsx2("small", { children: "The original stays out of the transcript payload." })
1189
+ ] }),
1190
+ items.length > 1 ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
1191
+ /* @__PURE__ */ jsx2("button", { type: "button", className: "scui-image-previous", "aria-label": "Previous image", onClick: () => move(-1), children: /* @__PURE__ */ jsx2(UiIcon, { name: "chevron", size: 19 }) }),
1192
+ /* @__PURE__ */ jsx2("button", { type: "button", className: "scui-image-next", "aria-label": "Next image", onClick: () => move(1), children: /* @__PURE__ */ jsx2(UiIcon, { name: "chevron", size: 19 }) })
1193
+ ] }) : null
1194
+ ] })
1195
+ ]
1196
+ }
1197
+ );
1198
+ }
1199
+ function MessageImages({ items, adapter }) {
1200
+ const [active, setActive] = useState(null);
1201
+ const opener = useRef(null);
1202
+ if (!items?.length) return null;
1203
+ const viewable = items.filter((item) => item.url || item.reference && adapter?.resolveImage);
1204
+ const close = () => {
1205
+ setActive(null);
1206
+ requestAnimationFrame(() => opener.current?.focus({ preventScroll: true }));
1207
+ };
1208
+ return /* @__PURE__ */ jsxs2(Fragment2, { children: [
1209
+ /* @__PURE__ */ jsx2("div", { className: "scui-message-images", "aria-label": "Message images", children: items.map((item, index) => item.url ? /* @__PURE__ */ jsx2("button", { type: "button", "aria-label": `View image ${item.label}`, onClick: (event) => {
1210
+ opener.current = event.currentTarget;
1211
+ setActive(viewable.indexOf(item));
1212
+ }, children: /* @__PURE__ */ jsx2("img", { src: item.url, alt: "" }) }, item.id ?? `${item.label}:${index}`) : item.reference && adapter?.resolveImage ? /* @__PURE__ */ jsxs2("button", { type: "button", "data-lazy": "true", "aria-label": `Load image ${item.label}`, onClick: (event) => {
1213
+ opener.current = event.currentTarget;
1214
+ setActive(viewable.indexOf(item));
1215
+ }, children: [
1216
+ /* @__PURE__ */ jsx2(UiIcon, { name: "image", size: 16 }),
1217
+ /* @__PURE__ */ jsxs2("span", { children: [
1218
+ /* @__PURE__ */ jsx2("strong", { children: item.label }),
1219
+ /* @__PURE__ */ jsx2("small", { children: "Load preview" })
1220
+ ] })
1221
+ ] }, item.id ?? `${item.label}:${index}`) : /* @__PURE__ */ jsxs2("span", { "data-unavailable": "true", children: [
1222
+ /* @__PURE__ */ jsx2(UiIcon, { name: "image", size: 14 }),
1223
+ /* @__PURE__ */ jsxs2("span", { children: [
1224
+ /* @__PURE__ */ jsx2("strong", { children: item.label }),
1225
+ /* @__PURE__ */ jsx2("small", { children: "Preview unavailable" })
1226
+ ] })
1227
+ ] }, item.id ?? `${item.label}:${index}`)) }),
1228
+ active !== null && viewable[active] ? /* @__PURE__ */ jsx2(ImageViewer, { items: viewable, index: active, adapter, onChange: setActive, onClose: close }) : null
1229
+ ] });
1230
+ }
1231
+
1232
+ // src/intent.js
1233
+ var CONFIRMABLE_ACTIONS = /* @__PURE__ */ new Set(["resume", "join", "branch", "reduce", "export"]);
1234
+ async function dispatchConfirmedIntent(adapter, intent) {
1235
+ if (CONFIRMABLE_ACTIONS.has(intent.action) && adapter.confirmIntent) {
1236
+ const confirmed = await adapter.confirmIntent(intent);
1237
+ if (!confirmed) return;
1238
+ }
1239
+ return adapter.onIntent(intent);
1240
+ }
1241
+
1242
+ // src/textarea.js
1243
+ import { useLayoutEffect } from "react";
1244
+ function useAutosizeTextarea(ref, value) {
1245
+ useLayoutEffect(() => {
1246
+ const element = ref.current;
1247
+ if (!element) return;
1248
+ element.style.height = "auto";
1249
+ const maxHeight = Number.parseFloat(getComputedStyle(element).maxHeight) || 150;
1250
+ const height = Math.min(element.scrollHeight, maxHeight);
1251
+ element.style.height = `${height}px`;
1252
+ element.style.overflowY = element.scrollHeight > maxHeight ? "auto" : "hidden";
1253
+ }, [ref, value]);
1254
+ }
1255
+
1256
+ // src/composer.jsx
1257
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1258
+ var composerMemory = /* @__PURE__ */ new Map();
1259
+ function ContinuationBar({ state, adapter, labels = DEFAULT_LABELS }) {
1260
+ if (state.mode !== "mirror" || state.canSend) return null;
1261
+ const attached = state.attached;
1262
+ const row = attached?.key ? state.sessions.find((item) => item.key === attached.key) : null;
1263
+ const activeElsewhere = row?.runtimeStatus === "running" || row?.runtimeStatus === "busy" || row?.runtimeStatus === "idle";
1264
+ const resume = canContinueHere(state);
1265
+ const terminal = resume && state.continuationModes?.includes("terminal");
1266
+ const join = state.canAttach;
1267
+ const branch = state.canBranch;
1268
+ if (!resume && !join && !branch) return /* @__PURE__ */ jsx3("div", { className: "scui-continuation", children: /* @__PURE__ */ jsxs3("span", { children: [
1269
+ /* @__PURE__ */ jsx3("strong", { children: activeElsewhere ? "Active elsewhere" : "Read-only" }),
1270
+ /* @__PURE__ */ jsx3("small", { children: "This session cannot be continued by an available harness." })
1271
+ ] }) });
1272
+ return /* @__PURE__ */ jsxs3("div", { className: "scui-continuation", children: [
1273
+ /* @__PURE__ */ jsxs3("span", { children: [
1274
+ /* @__PURE__ */ jsx3("strong", { children: activeElsewhere ? "Active elsewhere" : "Read-only" }),
1275
+ /* @__PURE__ */ jsx3("small", { children: join ? "Join the proven live runtime without taking it over." : resume ? `Resume this ${harnessDisplayName(state.harness)} session here.` : "Start an independent continuation." })
1276
+ ] }),
1277
+ /* @__PURE__ */ jsxs3("span", { className: "scui-continuation-actions", children: [
1278
+ /* @__PURE__ */ jsx3("button", { type: "button", disabled: Boolean(state.operation), onClick: () => dispatchConfirmedIntent(adapter, join ? { action: "join" } : resume ? { action: "resume", mode: "headless" } : { action: "branch" }), children: join ? labels.joinLive : resume ? labels.continueHere : labels.forkHere }),
1279
+ terminal ? /* @__PURE__ */ jsx3("button", { type: "button", className: "scui-secondary", disabled: Boolean(state.operation), onClick: () => dispatchConfirmedIntent(adapter, { action: "resume", mode: "terminal" }), children: labels.continueWithTerminal ?? DEFAULT_LABELS.continueWithTerminal }) : null
1280
+ ] })
1281
+ ] });
1282
+ }
1283
+ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`, pendingStatus = null, restoreDraft = null, contextCandidates = [], components = {}, onPending, onDraftRestored }) {
1284
+ const remembered = composerMemory.get(memoryKey) ?? { draft: state.savedDraft, context: [], images: [], queue: [] };
1285
+ const [draft, setDraft] = useState2(remembered.draft);
1286
+ const [context, setContext] = useState2(remembered.context ?? []);
1287
+ const [images, setImages] = useState2(remembered.images ?? []);
1288
+ const [queue, setQueue] = useState2((remembered.queue ?? []).map((item) => ({ ...item, context: item.context ?? [], images: item.images ?? [] })));
1289
+ const [dispatching, setDispatching] = useState2(false);
1290
+ const [steering, setSteering] = useState2(false);
1291
+ const [picking, setPicking] = useState2(false);
1292
+ const [dragging, setDragging] = useState2(false);
1293
+ const [pickerError, setPickerError] = useState2(null);
1294
+ const textarea = useRef2(null);
1295
+ useAutosizeTextarea(textarea, draft);
1296
+ const remember = (nextDraft, nextContext, nextImages, nextQueue) => boundedSet(composerMemory, memoryKey, { draft: nextDraft, context: nextContext, images: nextImages, queue: nextQueue });
1297
+ useEffect2(() => {
1298
+ remember(draft, context, images, queue);
1299
+ }, [draft, context, images, memoryKey, queue]);
1300
+ const updateQueue = (update) => setQueue((items) => {
1301
+ const next = update(items);
1302
+ remember(draft, context, images, next);
1303
+ return next;
1304
+ });
1305
+ const queueBlocked = state.busy || pendingStatus !== null || dispatching || steering;
1306
+ const queuesNewMessage = state.busy || pendingStatus === "sending" || pendingStatus === "failed" || dispatching || steering;
1307
+ const steerAvailable = state.busy && state.canSteer && !steering && pendingStatus === null;
1308
+ const canSteerDraft = steerAvailable && Boolean(draft.trim()) && context.length === 0 && images.length === 0;
1309
+ useEffect2(() => {
1310
+ if (!queueBlocked && state.canSend && queue.length) {
1311
+ const [next, ...rest] = queue;
1312
+ setDispatching(true);
1313
+ setQueue(rest);
1314
+ remember(draft, context, images, rest);
1315
+ onPending?.(next.text, next.context, next.images);
1316
+ adapter.onIntent({ action: "send", text: next.text, ...next.context.length ? { context: next.context } : {}, ...next.images.length ? { images: next.images } : {} });
1317
+ }
1318
+ }, [adapter, draft, memoryKey, onPending, queue, queueBlocked, state.canSend]);
1319
+ useEffect2(() => {
1320
+ if (pendingStatus !== null || state.busy) setDispatching(false);
1321
+ }, [pendingStatus, state.busy]);
1322
+ useEffect2(() => {
1323
+ textarea.current?.focus({ preventScroll: true });
1324
+ }, [memoryKey]);
1325
+ useEffect2(() => {
1326
+ if (!restoreDraft) return;
1327
+ setDraft(restoreDraft.text);
1328
+ const restoredContext = normalizeContext(restoreDraft.context);
1329
+ const restoredImages = normalizeImages(restoreDraft.images);
1330
+ setContext(restoredContext);
1331
+ setImages(restoredImages);
1332
+ remember(restoreDraft.text, restoredContext, restoredImages, queue);
1333
+ textarea.current?.focus({ preventScroll: true });
1334
+ onDraftRestored?.(restoreDraft.id);
1335
+ }, [onDraftRestored, restoreDraft?.id]);
1336
+ useEffect2(() => {
1337
+ const timer = setTimeout(() => adapter.onIntent({ action: "draft", text: draft }), 250);
1338
+ return () => clearTimeout(timer);
1339
+ }, [adapter, draft]);
1340
+ const pickContext = () => {
1341
+ if (!adapter.pickContext || state.mode !== "control" || picking || context.length >= MAX_CONTEXT_ITEMS && images.length >= MAX_IMAGE_ITEMS) return;
1342
+ setPicking(true);
1343
+ setPickerError(null);
1344
+ Promise.resolve().then(() => adapter.pickContext()).then((picked) => {
1345
+ const attachments = partitionAttachments(picked);
1346
+ if (context.length + attachments.context.length > MAX_CONTEXT_ITEMS) throw new Error(`Attach at most ${MAX_CONTEXT_ITEMS} text items.`);
1347
+ if (images.length + attachments.images.length > MAX_IMAGE_ITEMS) throw new Error(`Attach at most ${MAX_IMAGE_ITEMS} images.`);
1348
+ setContext((current) => {
1349
+ const next = mergeContext(current, attachments.context);
1350
+ remember(draft, next, images, queue);
1351
+ return next;
1352
+ });
1353
+ setImages((current) => {
1354
+ const next = mergeImages(current, attachments.images);
1355
+ remember(draft, context, next, queue);
1356
+ return next;
1357
+ });
1358
+ }).catch((error) => setPickerError(error instanceof Error ? error.message : "Could not attach context.")).finally(() => setPicking(false));
1359
+ };
1360
+ const attachCandidate = (picked) => {
1361
+ const attachments = partitionAttachments(picked);
1362
+ const nextContext = mergeContext(context, attachments.context);
1363
+ const nextImages = mergeImages(images, attachments.images);
1364
+ setContext(nextContext);
1365
+ setImages(nextImages);
1366
+ remember(draft, nextContext, nextImages, queue);
1367
+ };
1368
+ const addImageFiles = (value, source) => {
1369
+ const allFiles = Array.from(value ?? []);
1370
+ if (!allFiles.length) return false;
1371
+ const files = allFiles.filter((file) => file.type.startsWith("image/"));
1372
+ if (files.length !== allFiles.length) {
1373
+ setPickerError("Drop or paste PNG, JPEG, GIF, or WebP images.");
1374
+ return true;
1375
+ }
1376
+ if (images.length + files.length > MAX_IMAGE_ITEMS) {
1377
+ setPickerError(`Attach at most ${MAX_IMAGE_ITEMS} images.`);
1378
+ return true;
1379
+ }
1380
+ setPicking(true);
1381
+ setPickerError(null);
1382
+ imageAttachmentsFromFiles(files).then((picked) => setImages((current) => {
1383
+ const next = mergeImages(current, picked);
1384
+ remember(draft, context, next, queue);
1385
+ return next;
1386
+ }), (error) => setPickerError(error instanceof Error ? error.message : `Could not ${source} image.`)).finally(() => setPicking(false));
1387
+ return true;
1388
+ };
1389
+ const pasteImages = (event) => {
1390
+ if (addImageFiles(event.clipboardData?.files, "paste")) event.preventDefault();
1391
+ };
1392
+ const dropImages = (event) => {
1393
+ setDragging(false);
1394
+ if (addImageFiles(event.dataTransfer?.files, "drop")) event.preventDefault();
1395
+ };
1396
+ const send = (forceQueue = false) => {
1397
+ const text = draft.trim();
1398
+ if (!text && !images.length) return;
1399
+ if (canSteerDraft && !forceQueue) {
1400
+ setSteering(true);
1401
+ Promise.resolve(adapter.onIntent({ action: "steer", text })).then(() => {
1402
+ setDraft("");
1403
+ remember("", context, images, queue);
1404
+ }, (error) => setPickerError(error instanceof Error ? error.message : "Could not steer the active turn.")).finally(() => setSteering(false));
1405
+ return;
1406
+ }
1407
+ const message = { text, context, images };
1408
+ if (queuesNewMessage) updateQueue((items) => [...items, message]);
1409
+ else if (state.canSend) {
1410
+ if (onPending) setDispatching(true);
1411
+ onPending?.(text, context, images);
1412
+ adapter.onIntent({ action: "send", text, ...context.length ? { context } : {}, ...images.length ? { images } : {} });
1413
+ } else return;
1414
+ setDraft("");
1415
+ setContext([]);
1416
+ setImages([]);
1417
+ remember("", [], [], queuesNewMessage ? [...queue, message] : queue);
1418
+ };
1419
+ return /* @__PURE__ */ jsxs3("div", { className: "scui-compose", children: [
1420
+ queue.length ? /* @__PURE__ */ jsxs3("div", { className: "scui-queue", children: [
1421
+ /* @__PURE__ */ jsxs3("strong", { children: [
1422
+ queue.length,
1423
+ " queued"
1424
+ ] }),
1425
+ queue.map((item, index) => /* @__PURE__ */ jsxs3("span", { children: [
1426
+ /* @__PURE__ */ jsxs3("span", { children: [
1427
+ item.text || "Image attachment",
1428
+ item.context.length + item.images.length ? /* @__PURE__ */ jsxs3("small", { children: [
1429
+ item.context.length + item.images.length,
1430
+ " attached"
1431
+ ] }) : null
1432
+ ] }),
1433
+ /* @__PURE__ */ jsx3("button", { type: "button", "aria-label": `Remove queued message ${index + 1}`, onClick: () => updateQueue((items) => items.filter((_, itemIndex) => itemIndex !== index)), children: /* @__PURE__ */ jsx3(UiIcon, { name: "close", size: 13 }) })
1434
+ ] }, `${index}:${item.text}`))
1435
+ ] }) : null,
1436
+ /* @__PURE__ */ jsx3(ContextCandidates, { items: contextCandidates, context, images, state, adapter, onAttach: attachCandidate, component: components.ContextCandidate }),
1437
+ /* @__PURE__ */ jsx3(ImageTray, { items: images, onRemove: (index) => setImages((items) => {
1438
+ const next = items.filter((_, itemIndex) => itemIndex !== index);
1439
+ remember(draft, context, next, queue);
1440
+ return next;
1441
+ }) }),
1442
+ /* @__PURE__ */ jsx3(ContextTray, { items: context, onRemove: (index) => setContext((items) => {
1443
+ const next = items.filter((_, itemIndex) => itemIndex !== index);
1444
+ remember(draft, next, images, queue);
1445
+ return next;
1446
+ }) }),
1447
+ pickerError ? /* @__PURE__ */ jsx3("small", { className: "scui-context-error", role: "alert", children: pickerError }) : null,
1448
+ /* @__PURE__ */ jsxs3("div", { className: `scui-envelope${dragging ? " scui-drop-target" : ""}`, onDragEnter: (event) => {
1449
+ if (Array.from(event.dataTransfer?.types ?? []).includes("Files")) {
1450
+ event.preventDefault();
1451
+ setDragging(true);
1452
+ }
1453
+ }, onDragOver: (event) => {
1454
+ if (Array.from(event.dataTransfer?.types ?? []).includes("Files")) event.preventDefault();
1455
+ }, onDragLeave: (event) => {
1456
+ if (!event.currentTarget.contains(event.relatedTarget)) setDragging(false);
1457
+ }, onDrop: dropImages, children: [
1458
+ adapter.pickContext && state.mode === "control" ? /* @__PURE__ */ jsx3("button", { className: "scui-attach", type: "button", "aria-label": "Attach files or images", disabled: picking || context.length >= MAX_CONTEXT_ITEMS && images.length >= MAX_IMAGE_ITEMS, onClick: pickContext, children: picking ? /* @__PURE__ */ jsx3("i", { className: "scui-control-spinner" }) : /* @__PURE__ */ jsx3(UiIcon, { name: "attach", size: 17 }) }) : null,
1459
+ /* @__PURE__ */ jsx3("textarea", { ref: textarea, rows: 1, "aria-label": `Message ${harnessDisplayName(state.harness) || "agent"}`, placeholder: state.startup !== "ready" ? "Connecting\u2026" : pendingStatus === "failed" ? "Retry or edit the unsent message\u2026" : pendingStatus === "editing" ? "Edit and resend\u2026" : steerAvailable && context.length === 0 && images.length === 0 ? "Redirect the current turn\u2026" : queueBlocked ? "Queue a follow-up\u2026" : labels.askAgent, value: draft, disabled: state.mode !== "control" && !state.canSend, onPaste: pasteImages, onInput: (event) => {
1460
+ const value = event.currentTarget.value;
1461
+ setDraft(value);
1462
+ remember(value, context, images, queue);
1463
+ }, onKeyDown: (event) => {
1464
+ if (isSendKey(event)) {
1465
+ event.preventDefault();
1466
+ send();
1467
+ }
1468
+ } }),
1469
+ /* @__PURE__ */ jsxs3("span", { children: [
1470
+ state.busy ? /* @__PURE__ */ jsx3("button", { className: "scui-stop", type: "button", "aria-label": "Stop agent", disabled: !state.canInterrupt, onClick: () => adapter.onIntent({ action: "interrupt" }), children: /* @__PURE__ */ jsx3(UiIcon, { name: "stop", size: 15 }) }) : null,
1471
+ canSteerDraft ? /* @__PURE__ */ jsx3("button", { className: "scui-queue-send", type: "button", "aria-label": "Queue follow-up instead", onClick: () => send(true), children: /* @__PURE__ */ jsx3(UiIcon, { name: "plus", size: 15 }) }) : null,
1472
+ /* @__PURE__ */ jsx3("button", { className: "scui-send", type: "button", "aria-label": canSteerDraft ? "Steer current turn" : queuesNewMessage ? "Queue message" : "Send message", disabled: steering || !draft.trim() && !images.length || !queuesNewMessage && !state.canSend, onClick: () => send(), children: steering ? /* @__PURE__ */ jsx3("i", { className: "scui-control-spinner" }) : /* @__PURE__ */ jsx3(UiIcon, { name: canSteerDraft ? "send" : queuesNewMessage ? "plus" : "send", size: 17 }) })
1473
+ ] })
1474
+ ] })
1475
+ ] });
1476
+ }
1477
+
1478
+ // src/conversation.jsx
1479
+ import { Fragment as Fragment3 } from "react";
1480
+ import { useEffect as useEffect4, useId, useLayoutEffect as useLayoutEffect2, useRef as useRef4, useState as useState3 } from "react";
1481
+
1482
+ // src/markdown.jsx
1483
+ import MarkdownIt from "markdown-it";
1484
+ import { useEffect as useEffect3, useMemo, useRef as useRef3 } from "react";
1485
+ import { jsx as jsx4 } from "react/jsx-runtime";
1486
+ var markdown = new MarkdownIt({ html: false, linkify: true, breaks: false });
1487
+ var LANGUAGE_LABELS = {
1488
+ bash: "Shell",
1489
+ css: "CSS",
1490
+ html: "HTML",
1491
+ js: "JavaScript",
1492
+ javascript: "JavaScript",
1493
+ jsx: "JSX",
1494
+ md: "Markdown",
1495
+ markdown: "Markdown",
1496
+ py: "Python",
1497
+ python: "Python",
1498
+ rs: "Rust",
1499
+ rust: "Rust",
1500
+ sh: "Shell",
1501
+ shell: "Shell",
1502
+ ts: "TypeScript",
1503
+ tsx: "TSX",
1504
+ yaml: "YAML",
1505
+ yml: "YAML",
1506
+ json: "JSON",
1507
+ jsonc: "JSONC",
1508
+ sql: "SQL",
1509
+ xml: "XML"
1510
+ };
1511
+ var COPY_ICON = '<svg viewBox="0 0 18 18" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="5" y="5" width="8" height="8" rx="1.5"></rect><path d="M3 10.5V4.25C3 3.56 3.56 3 4.25 3h6.25"></path></svg>';
1512
+ function languageLabel(info) {
1513
+ const language = info.trim().split(/\s+/, 1)[0]?.toLocaleLowerCase() ?? "";
1514
+ if (!language) return "Code";
1515
+ return LANGUAGE_LABELS[language] ?? language.toLocaleUpperCase();
1516
+ }
1517
+ function frameCode(render, tokens, index, options, env, self) {
1518
+ const label = markdown.utils.escapeHtml(languageLabel(tokens[index]?.info ?? ""));
1519
+ return `<div class="scui-code-block"><div class="scui-code-head"><span>${label}</span><button class="scui-code-copy" type="button" aria-label="Copy code" title="Copy code">${COPY_ICON}<span>Copy</span></button></div>${render(tokens, index, options, env, self)}</div>`;
1520
+ }
1521
+ for (const kind of ["fence", "code_block"]) {
1522
+ const render = markdown.renderer.rules[kind];
1523
+ markdown.renderer.rules[kind] = (tokens, index, options, env, self) => frameCode(render, tokens, index, options, env, self);
1524
+ }
1525
+ var defaultLinkOpen = markdown.renderer.rules.link_open;
1526
+ markdown.renderer.rules.link_open = (tokens, index, options, env, self) => {
1527
+ tokens[index]?.attrSet("target", "_blank");
1528
+ tokens[index]?.attrSet("rel", "noreferrer noopener");
1529
+ return defaultLinkOpen ? defaultLinkOpen(tokens, index, options, env, self) : self.renderToken(tokens, index, options);
1530
+ };
1531
+ function setCopyState(button, status) {
1532
+ const labels = {
1533
+ idle: ["Copy code", "Copy"],
1534
+ copied: ["Code copied", "Copied"],
1535
+ failed: ["Copy failed \xB7 retry", "Retry"]
1536
+ };
1537
+ const [label, visible] = labels[status];
1538
+ button.dataset.status = status;
1539
+ button.setAttribute("aria-label", label);
1540
+ button.setAttribute("title", label);
1541
+ const text = button.querySelector("span");
1542
+ if (text) text.textContent = visible;
1543
+ }
1544
+ function Markdown({ value, copyText }) {
1545
+ const html = useMemo(() => markdown.render(value), [value]);
1546
+ const resets = useRef3(/* @__PURE__ */ new Map());
1547
+ useEffect3(() => () => {
1548
+ for (const timer of resets.current.values()) clearTimeout(timer);
1549
+ resets.current.clear();
1550
+ }, []);
1551
+ const copyCode = async (event) => {
1552
+ const button = event.target.closest?.(".scui-code-copy");
1553
+ if (!button || !event.currentTarget.contains(button) || !copyText) return;
1554
+ const code = button.closest(".scui-code-block")?.querySelector("pre code");
1555
+ if (!code) return;
1556
+ button.disabled = true;
1557
+ try {
1558
+ await copyText(code.textContent ?? "");
1559
+ setCopyState(button, "copied");
1560
+ } catch {
1561
+ setCopyState(button, "failed");
1562
+ } finally {
1563
+ button.disabled = false;
1564
+ }
1565
+ clearTimeout(resets.current.get(button));
1566
+ const timer = setTimeout(() => {
1567
+ if (button.isConnected) setCopyState(button, "idle");
1568
+ resets.current.delete(button);
1569
+ }, 1500);
1570
+ resets.current.set(button, timer);
1571
+ };
1572
+ return /* @__PURE__ */ jsx4("div", { className: "scui-markdown", "data-copyable": Boolean(copyText), onClick: copyCode, dangerouslySetInnerHTML: { __html: html } });
1573
+ }
1574
+
1575
+ // src/conversation.jsx
1576
+ import { Fragment as Fragment4, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1577
+ function LoadingStatus({ state, compact = false }) {
1578
+ const copy = {
1579
+ connecting: ["Connecting to coding agents", "Checking installed harnesses and capabilities.", 0],
1580
+ starting: [`Starting ${harnessDisplayName(state.harness) || "coding agent"}`, "Opening a controlled session in this workspace.", 1],
1581
+ discovering: ["Loading recent sessions", "Scanning native session stores without loading full transcripts.", 2],
1582
+ ready: ["Ready", "Coding sessions are up to date.", 3]
1583
+ }[state.startup];
1584
+ return /* @__PURE__ */ jsxs4("div", { className: `scui-loading${compact ? " scui-loading-compact" : ""}`, role: "status", "aria-busy": state.startup !== "ready", children: [
1585
+ /* @__PURE__ */ jsx5("span", { className: "scui-orbit", "aria-hidden": "true", children: /* @__PURE__ */ jsx5("i", {}) }),
1586
+ /* @__PURE__ */ jsxs4("span", { className: "scui-loading-copy", children: [
1587
+ /* @__PURE__ */ jsx5("strong", { children: copy[0] }),
1588
+ /* @__PURE__ */ jsx5("small", { children: copy[1] })
1589
+ ] }),
1590
+ /* @__PURE__ */ jsx5("span", { className: "scui-progress", "aria-hidden": "true", children: [1, 2, 3].map((step) => /* @__PURE__ */ jsx5("i", { "data-progress": step <= copy[2] ? "done" : step === copy[2] + 1 ? "current" : "waiting" }, step)) })
1591
+ ] });
1592
+ }
1593
+ function RequestCard({ entry, adapter, canRespond }) {
1594
+ const request = entry.request;
1595
+ if (!request) return null;
1596
+ if (request.status === "responded") {
1597
+ return /* @__PURE__ */ jsxs4("div", { className: "scui-request-done", children: [
1598
+ "\u2713 Request answered \xB7 ",
1599
+ request.resolution?.name ?? request.requestKind
1600
+ ] });
1601
+ }
1602
+ return /* @__PURE__ */ jsxs4("section", { className: "scui-request", role: "alert", "aria-label": `${request.requestKind} needs input`, children: [
1603
+ /* @__PURE__ */ jsx5("strong", { children: "Agent needs input" }),
1604
+ /* @__PURE__ */ jsx5(Markdown, { value: request.payloadText || entry.text, copyText: adapter?.copyText }),
1605
+ /* @__PURE__ */ jsxs4("div", { className: "scui-request-actions", children: [
1606
+ request.options.map((option) => /* @__PURE__ */ jsx5("button", { type: "button", disabled: !canRespond, onClick: () => adapter.onIntent({ action: "respond", requestId: request.requestId, optionId: option.optionId }), children: option.name }, option.optionId)),
1607
+ request.cancellable ? /* @__PURE__ */ jsx5("button", { type: "button", disabled: !canRespond, onClick: () => adapter.onIntent({ action: "respond", requestId: request.requestId, optionId: null }), children: "Cancel" }) : null
1608
+ ] })
1609
+ ] });
1610
+ }
1611
+ function ContextDisclosure({ context }) {
1612
+ if (!context?.length) return null;
1613
+ return /* @__PURE__ */ jsxs4("details", { className: "scui-context", children: [
1614
+ /* @__PURE__ */ jsxs4("summary", { children: [
1615
+ "Context \xB7 ",
1616
+ context.length
1617
+ ] }),
1618
+ /* @__PURE__ */ jsx5("div", { children: context.map((item, index) => /* @__PURE__ */ jsxs4("p", { children: [
1619
+ /* @__PURE__ */ jsx5("strong", { children: item.label }),
1620
+ /* @__PURE__ */ jsx5("span", { children: item.detail })
1621
+ ] }, item.id ?? index)) })
1622
+ ] });
1623
+ }
1624
+ function MessageMeta({ entry, adapter }) {
1625
+ const [copyState, setCopyState2] = useState3("idle");
1626
+ const reset = useRef4(null);
1627
+ useEffect4(() => () => clearTimeout(reset.current), []);
1628
+ const date = entry.ts === null ? null : new Date(entry.ts);
1629
+ const validDate = date && Number.isFinite(date.valueOf()) ? date : null;
1630
+ if (!validDate && (!adapter?.copyText || !entry.text)) return null;
1631
+ const copy = async () => {
1632
+ try {
1633
+ await adapter.copyText(entry.text);
1634
+ setCopyState2("copied");
1635
+ } catch {
1636
+ setCopyState2("failed");
1637
+ }
1638
+ clearTimeout(reset.current);
1639
+ reset.current = setTimeout(() => setCopyState2("idle"), 1500);
1640
+ };
1641
+ const copyLabel = copyState === "copied" ? "Message copied" : copyState === "failed" ? "Copy failed \xB7 retry" : "Copy message";
1642
+ return /* @__PURE__ */ jsxs4("footer", { className: "scui-message-meta", children: [
1643
+ validDate ? /* @__PURE__ */ jsx5("time", { dateTime: validDate.toISOString(), title: validDate.toLocaleString(), children: validDate.toLocaleTimeString([], { hour: "numeric", minute: "2-digit" }) }) : null,
1644
+ adapter?.copyText && entry.text ? /* @__PURE__ */ jsx5("button", { type: "button", "aria-label": copyLabel, title: copyLabel, onClick: copy, "data-status": copyState, children: /* @__PURE__ */ jsx5(UiIcon, { name: "copy", size: 13 }) }) : null
1645
+ ] });
1646
+ }
1647
+ var TOOL_ICONS = {
1648
+ read: () => /* @__PURE__ */ jsxs4(Fragment4, { children: [
1649
+ /* @__PURE__ */ jsx5("path", { d: "M5 2.75h7.25L15 5.5v7.75A1.75 1.75 0 0 1 13.25 15h-8.5A1.75 1.75 0 0 1 3 13.25v-8.5A2 2 0 0 1 5 2.75Z" }),
1650
+ /* @__PURE__ */ jsx5("path", { d: "M12 2.9v3h2.85M6 9h6M6 12h4" })
1651
+ ] }),
1652
+ search: () => /* @__PURE__ */ jsxs4(Fragment4, { children: [
1653
+ /* @__PURE__ */ jsx5("circle", { cx: "8", cy: "8", r: "4.5" }),
1654
+ /* @__PURE__ */ jsx5("path", { d: "m11.5 11.5 3 3" })
1655
+ ] }),
1656
+ edit: () => /* @__PURE__ */ jsxs4(Fragment4, { children: [
1657
+ /* @__PURE__ */ jsx5("path", { d: "m11.75 3.25 3 3-8.5 8.5-3.75.75.75-3.75 8.5-8.5Z" }),
1658
+ /* @__PURE__ */ jsx5("path", { d: "m10 5 3 3" })
1659
+ ] }),
1660
+ command: () => /* @__PURE__ */ jsx5(Fragment4, { children: /* @__PURE__ */ jsx5("path", { d: "m3 5 3 3-3 3M8 12h6" }) }),
1661
+ test: () => /* @__PURE__ */ jsxs4(Fragment4, { children: [
1662
+ /* @__PURE__ */ jsx5("path", { d: "M6 2.5v3L3 12a2 2 0 0 0 1.8 3h8.4a2 2 0 0 0 1.8-3l-3-6.5v-3M5 9h8" }),
1663
+ /* @__PURE__ */ jsx5("path", { d: "M5 2.5h8" })
1664
+ ] }),
1665
+ web: () => /* @__PURE__ */ jsxs4(Fragment4, { children: [
1666
+ /* @__PURE__ */ jsx5("circle", { cx: "9", cy: "9", r: "6.5" }),
1667
+ /* @__PURE__ */ jsx5("path", { d: "M2.75 9h12.5M9 2.5c2 1.8 3 4 3 6.5s-1 4.7-3 6.5c-2-1.8-3-4-3-6.5s1-4.7 3-6.5Z" })
1668
+ ] }),
1669
+ agent: () => /* @__PURE__ */ jsxs4(Fragment4, { children: [
1670
+ /* @__PURE__ */ jsx5("circle", { cx: "9", cy: "6", r: "2.5" }),
1671
+ /* @__PURE__ */ jsx5("path", { d: "M4 15c.4-3 2-4.5 5-4.5s4.6 1.5 5 4.5" })
1672
+ ] }),
1673
+ plan: () => /* @__PURE__ */ jsx5(Fragment4, { children: /* @__PURE__ */ jsx5("path", { d: "m3 5 1 1 2-2M3 9l1 1 2-2M3 13l1 1 2-2M8 5h7M8 9h7M8 13h7" }) }),
1674
+ other: () => /* @__PURE__ */ jsxs4(Fragment4, { children: [
1675
+ /* @__PURE__ */ jsx5("path", { d: "M9 2.5v3M9 12.5v3M2.5 9h3M12.5 9h3" }),
1676
+ /* @__PURE__ */ jsx5("circle", { cx: "9", cy: "9", r: "3.5" })
1677
+ ] })
1678
+ };
1679
+ function ToolIcon({ category }) {
1680
+ const Glyph = TOOL_ICONS[category] ?? TOOL_ICONS.other;
1681
+ return /* @__PURE__ */ jsx5("svg", { className: "scui-tool-icon", viewBox: "0 0 18 18", fill: "none", stroke: "currentColor", strokeWidth: "1.35", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx5(Glyph, {}) });
1682
+ }
1683
+ function toolAction2(entry, category, presentation) {
1684
+ if (presentation?.action) return presentation.action;
1685
+ const status = entry.status ?? "completed";
1686
+ const actions = {
1687
+ read: ["Reading", "Read", "Read failed"],
1688
+ search: ["Searching", "Searched", "Search failed"],
1689
+ edit: ["Editing", "Edited", "Edit failed"],
1690
+ command: ["Running command", "Ran command", "Command failed"],
1691
+ test: ["Running tests", "Ran tests", "Tests failed"],
1692
+ web: ["Browsing", "Browsed", "Browser action failed"],
1693
+ agent: ["Starting agent", "Started agent", "Agent failed"],
1694
+ plan: ["Updating plan", "Updated plan", "Plan update failed"]
1695
+ };
1696
+ const position = status === "pending" ? 0 : status === "error" ? 2 : 1;
1697
+ if (actions[category]) return actions[category][position];
1698
+ const label = entry.label?.split(/__|\//).at(-1)?.replaceAll(/[_-]+/g, " ") || "Tool";
1699
+ return status === "error" ? `${label} failed` : label;
1700
+ }
1701
+ function argumentLabel(key) {
1702
+ const labels = { cmd: "Command", command: "Command", cwd: "Working directory", file_path: "File", target_file: "File", path: "Path", query: "Query", pattern: "Pattern", url: "URL" };
1703
+ return labels[key] ?? key.replaceAll(/[_-]+/g, " ").replace(/^./, (letter) => letter.toLocaleUpperCase());
1704
+ }
1705
+ function argumentRows(argumentsText) {
1706
+ if (!argumentsText) return [];
1707
+ try {
1708
+ const value = JSON.parse(argumentsText);
1709
+ if (value && typeof value === "object" && !Array.isArray(value)) {
1710
+ return Object.entries(value).map(([key, item]) => ({
1711
+ key,
1712
+ label: argumentLabel(key),
1713
+ value: typeof item === "string" ? item : JSON.stringify(item, null, 2)
1714
+ }));
1715
+ }
1716
+ } catch {
1717
+ }
1718
+ return [{ key: "arguments", label: "Details", value: argumentsText }];
1719
+ }
1720
+ function stripAnsi(value) {
1721
+ return value?.replaceAll(/[\u001B\u009B][[\]()#;?]*(?:(?:(?:[a-zA-Z\d]*(?:;[-a-zA-Z\d\/#&.:=?%@~_]+)*)?\u0007)|(?:(?:\d{1,4}(?:[;:]\d{0,4})*)?[\dA-PR-TZcf-nq-uy=><~]))/g, "") ?? "";
1722
+ }
1723
+ function formatDuration(value) {
1724
+ if (value === null) return "";
1725
+ if (value < 1e3) return `${Math.round(value)}ms`;
1726
+ return `${(value / 1e3).toFixed(value < 1e4 ? 1 : 0)}s`;
1727
+ }
1728
+ function ToolMetrics({ presentation }) {
1729
+ const metrics = [
1730
+ presentation.matches === null ? "" : `${presentation.matches} ${presentation.matches === 1 ? "match" : "matches"}`,
1731
+ presentation.additions === null ? "" : `+${presentation.additions}`,
1732
+ presentation.deletions === null ? "" : `\u2212${presentation.deletions}`,
1733
+ presentation.exitCode === null ? "" : `exit ${presentation.exitCode}`,
1734
+ formatDuration(presentation.durationMs)
1735
+ ].filter(Boolean);
1736
+ return metrics.length ? /* @__PURE__ */ jsx5("div", { className: "scui-tool-metrics", children: metrics.map((metric) => /* @__PURE__ */ jsx5("span", { children: metric }, metric)) }) : null;
1737
+ }
1738
+ function PendingElapsed({ now }) {
1739
+ const clock = now ?? Date.now;
1740
+ const started = useRef4(clock());
1741
+ const [elapsed, setElapsed] = useState3(0);
1742
+ useEffect4(() => {
1743
+ const timer = setInterval(() => setElapsed(Math.max(0, clock() - started.current)), 1e3);
1744
+ return () => clearInterval(timer);
1745
+ }, [clock]);
1746
+ return /* @__PURE__ */ jsx5("small", { className: "scui-tool-elapsed", children: elapsed < 1e3 ? "now" : formatDuration(elapsed) });
1747
+ }
1748
+ function LinePreview({ value, kind }) {
1749
+ if (!value) return null;
1750
+ return /* @__PURE__ */ jsx5("ol", { className: "scui-code-preview", "data-kind": kind, children: stripAnsi(value).split("\n").map((line, index) => {
1751
+ const tone = kind === "diff" ? line.startsWith("+") && !line.startsWith("+++") ? "add" : line.startsWith("-") && !line.startsWith("---") ? "remove" : line.startsWith("@@") ? "hunk" : "plain" : "plain";
1752
+ return /* @__PURE__ */ jsxs4("li", { "data-tone": tone, children: [
1753
+ /* @__PURE__ */ jsx5("span", { children: index + 1 }),
1754
+ /* @__PURE__ */ jsx5("code", { children: line || " " })
1755
+ ] }, index);
1756
+ }) });
1757
+ }
1758
+ function TerminalPreview({ presentation, pending, failed }) {
1759
+ return /* @__PURE__ */ jsxs4("section", { className: "scui-terminal", children: [
1760
+ /* @__PURE__ */ jsxs4("header", { children: [
1761
+ /* @__PURE__ */ jsxs4("span", { "aria-hidden": "true", children: [
1762
+ /* @__PURE__ */ jsx5("i", {}),
1763
+ /* @__PURE__ */ jsx5("i", {}),
1764
+ /* @__PURE__ */ jsx5("i", {})
1765
+ ] }),
1766
+ /* @__PURE__ */ jsx5("code", { children: presentation.command ? `$ ${presentation.command}` : "Terminal" })
1767
+ ] }),
1768
+ presentation.preview ? /* @__PURE__ */ jsx5("pre", { "data-error": failed, children: stripAnsi(presentation.preview) }) : pending ? /* @__PURE__ */ jsxs4("div", { className: "scui-terminal-wait", children: [
1769
+ /* @__PURE__ */ jsx5("i", {}),
1770
+ " Waiting for output"
1771
+ ] }) : /* @__PURE__ */ jsx5("div", { className: "scui-terminal-empty", children: "No output" })
1772
+ ] });
1773
+ }
1774
+ function SearchPreview({ presentation }) {
1775
+ const lines = stripAnsi(presentation.preview).split("\n").filter(Boolean);
1776
+ return /* @__PURE__ */ jsxs4("section", { className: "scui-search-preview", children: [
1777
+ presentation.query ? /* @__PURE__ */ jsxs4("header", { children: [
1778
+ /* @__PURE__ */ jsx5("span", { children: "Search" }),
1779
+ /* @__PURE__ */ jsx5("code", { children: presentation.query })
1780
+ ] }) : null,
1781
+ lines.length ? /* @__PURE__ */ jsx5("ol", { children: lines.map((line, index) => {
1782
+ const match = /^(.*?):(\d+)(?::(\d+))?:(.*)$/.exec(line);
1783
+ return /* @__PURE__ */ jsx5("li", { children: match ? /* @__PURE__ */ jsxs4(Fragment4, { children: [
1784
+ /* @__PURE__ */ jsx5("code", { children: match[1] }),
1785
+ /* @__PURE__ */ jsxs4("small", { children: [
1786
+ match[2],
1787
+ match[3] ? `:${match[3]}` : ""
1788
+ ] }),
1789
+ /* @__PURE__ */ jsx5("span", { children: match[4] })
1790
+ ] }) : /* @__PURE__ */ jsx5("span", { children: line }) }, index);
1791
+ }) }) : /* @__PURE__ */ jsx5("p", { children: "No textual results" })
1792
+ ] });
1793
+ }
1794
+ function ToolPreview({ presentation, entry }) {
1795
+ const pending = entry.status === "pending";
1796
+ const failed = entry.status === "error";
1797
+ if (presentation.detail === "terminal") return /* @__PURE__ */ jsx5(TerminalPreview, { presentation, pending, failed });
1798
+ if (presentation.detail === "diff") return presentation.preview ? /* @__PURE__ */ jsx5(LinePreview, { value: presentation.preview, kind: "diff" }) : /* @__PURE__ */ jsx5("div", { className: "scui-tool-empty", children: "Edit completed without a textual diff" });
1799
+ if (presentation.detail === "file") return presentation.preview ? /* @__PURE__ */ jsx5(LinePreview, { value: presentation.preview, kind: "file" }) : /* @__PURE__ */ jsx5("div", { className: "scui-tool-empty", children: "File contents were not included in this event" });
1800
+ if (presentation.detail === "matches") return /* @__PURE__ */ jsx5(SearchPreview, { presentation });
1801
+ if (presentation.detail === "web") return /* @__PURE__ */ jsxs4("section", { className: "scui-web-preview", children: [
1802
+ presentation.url ? /* @__PURE__ */ jsx5("code", { children: presentation.url }) : null,
1803
+ presentation.preview ? /* @__PURE__ */ jsx5("p", { children: stripAnsi(presentation.preview) }) : /* @__PURE__ */ jsx5("small", { children: pending ? "Waiting for the page" : "No page summary returned" })
1804
+ ] });
1805
+ if (presentation.detail === "agent") return /* @__PURE__ */ jsx5("section", { className: "scui-agent-preview", children: presentation.items?.length ? /* @__PURE__ */ jsx5("ol", { className: "scui-agent-roster", children: presentation.items.map((item, index) => /* @__PURE__ */ jsxs4("li", { children: [
1806
+ /* @__PURE__ */ jsx5("strong", { children: item.label }),
1807
+ /* @__PURE__ */ jsx5("small", { children: item.status })
1808
+ ] }, `${item.label}:${index}`)) }) : presentation.preview ? /* @__PURE__ */ jsx5("pre", { children: stripAnsi(presentation.preview) }) : /* @__PURE__ */ jsx5("small", { children: pending ? "Agent is working" : "No textual handoff returned" }) });
1809
+ if (presentation.detail === "plan") return /* @__PURE__ */ jsx5("ol", { className: "scui-plan-preview", children: presentation.items?.map((item, index) => /* @__PURE__ */ jsxs4("li", { "data-status": item.status, children: [
1810
+ /* @__PURE__ */ jsx5("i", { "aria-hidden": "true" }),
1811
+ /* @__PURE__ */ jsx5("span", { children: item.label })
1812
+ ] }, `${item.label}:${index}`)) });
1813
+ return presentation.preview ? /* @__PURE__ */ jsx5("pre", { className: "scui-tool-output", "data-error": failed, children: stripAnsi(presentation.preview) }) : null;
1814
+ }
1815
+ function ToolActions({ presentation, adapter }) {
1816
+ const [copied, setCopied] = useState3(false);
1817
+ const reset = useRef4(null);
1818
+ useEffect4(() => () => clearTimeout(reset.current), []);
1819
+ if (!adapter?.copyText) return null;
1820
+ const action = presentation.command ? ["Copy command", presentation.command] : presentation.path ? ["Copy path", presentation.path] : presentation.url ? ["Copy URL", presentation.url] : presentation.query ? ["Copy query", presentation.query] : null;
1821
+ const copy = async () => {
1822
+ await adapter.copyText(action[1]);
1823
+ setCopied(true);
1824
+ clearTimeout(reset.current);
1825
+ reset.current = setTimeout(() => setCopied(false), 1500);
1826
+ };
1827
+ return action ? /* @__PURE__ */ jsx5("div", { className: "scui-tool-actions", children: /* @__PURE__ */ jsx5("button", { type: "button", onClick: copy, children: copied ? "Copied" : action[0] }) }) : null;
1828
+ }
1829
+ function ToolStack({ tools }) {
1830
+ if (tools.length < 2) return null;
1831
+ return /* @__PURE__ */ jsx5("div", { className: "scui-tool-stack", "aria-label": "Coordinated tools", children: tools.map((tool) => /* @__PURE__ */ jsx5("span", { children: tool.replaceAll("__", " \xB7 ").replaceAll("_", " ") }, tool)) });
1832
+ }
1833
+ function TechnicalDetails({ entry }) {
1834
+ if (!entry.arguments && !entry.resultText) return null;
1835
+ return /* @__PURE__ */ jsxs4("details", { className: "scui-tool-technical", children: [
1836
+ /* @__PURE__ */ jsx5("summary", { children: "Technical details" }),
1837
+ /* @__PURE__ */ jsxs4("div", { children: [
1838
+ entry.arguments ? /* @__PURE__ */ jsxs4("section", { children: [
1839
+ /* @__PURE__ */ jsx5("strong", { children: "Native arguments" }),
1840
+ /* @__PURE__ */ jsx5("dl", { children: argumentRows(entry.arguments).map((row) => /* @__PURE__ */ jsxs4("div", { children: [
1841
+ /* @__PURE__ */ jsx5("dt", { children: row.label }),
1842
+ /* @__PURE__ */ jsx5("dd", { children: /* @__PURE__ */ jsx5("pre", { children: row.value }) })
1843
+ ] }, row.key)) })
1844
+ ] }) : null,
1845
+ entry.resultText ? /* @__PURE__ */ jsxs4("section", { children: [
1846
+ /* @__PURE__ */ jsx5("strong", { children: "Native result" }),
1847
+ /* @__PURE__ */ jsxs4("pre", { "data-error": entry.status === "error", children: [
1848
+ entry.resultText,
1849
+ entry.truncated ? "\n[truncated]" : ""
1850
+ ] })
1851
+ ] }) : null
1852
+ ] })
1853
+ ] });
1854
+ }
1855
+ function TranscriptEntry({ entry, state, adapter }) {
1856
+ if (entry.role === "request") return /* @__PURE__ */ jsx5(RequestCard, { entry, adapter, canRespond: state.canRespond });
1857
+ if (entry.role === "reasoning") {
1858
+ return /* @__PURE__ */ jsxs4("details", { className: "scui-reasoning", open: entry.streaming, children: [
1859
+ /* @__PURE__ */ jsx5("summary", { children: entry.streaming ? "Reasoning\u2026" : "Reasoning" }),
1860
+ /* @__PURE__ */ jsx5(Markdown, { value: entry.text, copyText: adapter?.copyText })
1861
+ ] });
1862
+ }
1863
+ if (entry.role === "notice" || entry.role === "system") return /* @__PURE__ */ jsx5("div", { className: "scui-notice", "data-code": entry.code, children: entry.text });
1864
+ return /* @__PURE__ */ jsxs4("article", { className: "scui-message", "data-role": entry.role, "aria-label": `${entry.role === "user" ? "Your" : "Assistant"} message`, children: [
1865
+ /* @__PURE__ */ jsx5(MessageImages, { items: entry.images, adapter }),
1866
+ /* @__PURE__ */ jsx5(Markdown, { value: entry.text, copyText: adapter?.copyText }),
1867
+ /* @__PURE__ */ jsx5(ContextDisclosure, { context: entry.context }),
1868
+ entry.truncated ? /* @__PURE__ */ jsx5("small", { className: "scui-truncated", children: "Entry truncated by host \xB7 load native session for the complete value" }) : null,
1869
+ /* @__PURE__ */ jsx5(MessageMeta, { entry, adapter })
1870
+ ] });
1871
+ }
1872
+ function ToolRow({ entry, workspace, open = false, adapter }) {
1873
+ const presentation = entry.presentation ?? createToolPresentation(entry);
1874
+ const [expanded, setExpanded] = useState3(open || entry.status === "pending");
1875
+ useEffect4(() => {
1876
+ if (entry.status === "pending") setExpanded(true);
1877
+ }, [entry.status]);
1878
+ const target = compactToolTarget(presentation.target, workspace);
1879
+ const hasDetail = Boolean(entry.arguments || entry.resultText || presentation.preview || presentation.fields.length);
1880
+ const category = presentation.category ?? toolCategory(entry);
1881
+ const summary = /* @__PURE__ */ jsxs4(Fragment4, { children: [
1882
+ /* @__PURE__ */ jsx5(ToolIcon, { category }),
1883
+ /* @__PURE__ */ jsx5("strong", { children: toolAction2(entry, category, presentation) }),
1884
+ target ? /* @__PURE__ */ jsx5("code", { className: "scui-tool-target", title: presentation.target, children: target }) : null,
1885
+ /* @__PURE__ */ jsx5("span", { className: "scui-spacer" }),
1886
+ entry.status === "pending" ? /* @__PURE__ */ jsx5(PendingElapsed, { now: adapter?.now }) : null,
1887
+ /* @__PURE__ */ jsx5("span", { className: "scui-tool-status", role: "status", "data-status": entry.status ?? "completed", "aria-label": entry.status ?? "completed", children: entry.status === "pending" ? /* @__PURE__ */ jsx5("i", {}) : /* @__PURE__ */ jsx5(UiIcon, { name: entry.status === "error" ? "close" : "check", size: 12 }) }),
1888
+ hasDetail ? /* @__PURE__ */ jsx5(UiIcon, { name: "chevron", size: 14, className: "scui-tool-chevron" }) : null
1889
+ ] });
1890
+ if (!hasDetail) return /* @__PURE__ */ jsx5("div", { className: "scui-tool", "data-status": entry.status ?? "completed", "data-category": category, children: /* @__PURE__ */ jsx5("div", { className: "scui-tool-head", children: summary }) });
1891
+ return /* @__PURE__ */ jsxs4("details", { className: "scui-tool", "data-status": entry.status ?? "completed", "data-category": category, open: expanded, onToggle: (event) => setExpanded(event.currentTarget.open), children: [
1892
+ /* @__PURE__ */ jsx5("summary", { className: "scui-tool-head", children: summary }),
1893
+ /* @__PURE__ */ jsxs4("div", { className: "scui-tool-detail", children: [
1894
+ /* @__PURE__ */ jsx5(ToolMetrics, { presentation }),
1895
+ /* @__PURE__ */ jsx5(ToolStack, { tools: presentation.tools ?? [] }),
1896
+ /* @__PURE__ */ jsx5(ToolPreview, { presentation, entry }),
1897
+ presentation.fields.length ? /* @__PURE__ */ jsx5("dl", { className: "scui-tool-fields", children: presentation.fields.map((field) => /* @__PURE__ */ jsxs4("div", { children: [
1898
+ /* @__PURE__ */ jsx5("dt", { children: field.label }),
1899
+ /* @__PURE__ */ jsx5("dd", { children: field.value })
1900
+ ] }, field.label)) }) : null,
1901
+ /* @__PURE__ */ jsx5(ToolActions, { presentation, adapter }),
1902
+ /* @__PURE__ */ jsx5(TechnicalDetails, { entry })
1903
+ ] })
1904
+ ] });
1905
+ }
1906
+ function ActivityGroup({ entries, state, adapter }) {
1907
+ if (entries.length === 1) return /* @__PURE__ */ jsx5("section", { className: "scui-activity", "data-single": "true", children: /* @__PURE__ */ jsx5(ToolRow, { entry: entries[0], workspace: state.workspace, adapter }) });
1908
+ const active = entries.some((entry) => entry.status === "pending");
1909
+ const [open, setOpen] = useState3(active);
1910
+ const id = useId();
1911
+ useEffect4(() => {
1912
+ if (active) setOpen(true);
1913
+ }, [active]);
1914
+ return /* @__PURE__ */ jsxs4("section", { className: "scui-activity", children: [
1915
+ /* @__PURE__ */ jsxs4("button", { className: "scui-activity-head", type: "button", "aria-expanded": open, "aria-controls": id, onClick: () => setOpen((value) => !value), children: [
1916
+ /* @__PURE__ */ jsx5("span", { className: "scui-fold", "data-open": open, children: /* @__PURE__ */ jsx5(UiIcon, { name: "chevron", size: 14 }) }),
1917
+ /* @__PURE__ */ jsx5("strong", { children: activitySummary(entries) }),
1918
+ /* @__PURE__ */ jsx5("span", { className: "scui-spacer" }),
1919
+ /* @__PURE__ */ jsxs4("small", { children: [
1920
+ entries.filter((entry) => entry.status !== "pending").length,
1921
+ "/",
1922
+ entries.length
1923
+ ] })
1924
+ ] }),
1925
+ open ? /* @__PURE__ */ jsx5("div", { id, children: entries.map((entry) => /* @__PURE__ */ jsx5(ToolRow, { entry, workspace: state.workspace, adapter }, entry.id)) }) : null
1926
+ ] });
1927
+ }
1928
+ function TaskPlan({ plan }) {
1929
+ if (!plan.items.length) return null;
1930
+ const complete = plan.items.filter((item) => item.status === "completed" || item.status === "cancelled").length;
1931
+ return /* @__PURE__ */ jsxs4("details", { className: "scui-plan", children: [
1932
+ /* @__PURE__ */ jsxs4("summary", { children: [
1933
+ /* @__PURE__ */ jsx5("span", { children: "Plan" }),
1934
+ /* @__PURE__ */ jsxs4("small", { children: [
1935
+ complete,
1936
+ "/",
1937
+ plan.items.length
1938
+ ] })
1939
+ ] }),
1940
+ /* @__PURE__ */ jsx5("ol", { tabIndex: 0, "aria-label": "Task plan steps", children: plan.items.map((item) => /* @__PURE__ */ jsxs4("li", { "data-status": item.status, children: [
1941
+ /* @__PURE__ */ jsx5("i", { "aria-hidden": "true" }),
1942
+ " ",
1943
+ /* @__PURE__ */ jsx5("span", { children: item.title })
1944
+ ] }, item.id)) })
1945
+ ] });
1946
+ }
1947
+ function SessionDetails({ semantics }) {
1948
+ if (!semantics.fidelity && !semantics.residueCount && !semantics.parseErrors && !semantics.subagents.length) return null;
1949
+ return /* @__PURE__ */ jsxs4("details", { className: "scui-details", children: [
1950
+ /* @__PURE__ */ jsx5("summary", { children: "Session details" }),
1951
+ /* @__PURE__ */ jsxs4("div", { children: [
1952
+ semantics.fidelity ? /* @__PURE__ */ jsxs4("p", { children: [
1953
+ /* @__PURE__ */ jsx5("strong", { children: "Fidelity" }),
1954
+ /* @__PURE__ */ jsx5("span", { children: semantics.fidelity.replaceAll("_", " ") })
1955
+ ] }) : null,
1956
+ /* @__PURE__ */ jsxs4("p", { children: [
1957
+ /* @__PURE__ */ jsx5("strong", { children: "Native records" }),
1958
+ /* @__PURE__ */ jsx5("span", { children: semantics.rawRecords })
1959
+ ] }),
1960
+ semantics.residueCount ? /* @__PURE__ */ jsxs4("p", { children: [
1961
+ /* @__PURE__ */ jsx5("strong", { children: "Residue" }),
1962
+ /* @__PURE__ */ jsxs4("span", { children: [
1963
+ semantics.residueCount,
1964
+ " retained"
1965
+ ] })
1966
+ ] }) : null,
1967
+ semantics.parseErrors ? /* @__PURE__ */ jsxs4("p", { children: [
1968
+ /* @__PURE__ */ jsx5("strong", { children: "Parse diagnostics" }),
1969
+ /* @__PURE__ */ jsx5("span", { children: semantics.parseErrors })
1970
+ ] }) : null,
1971
+ semantics.subagents.map((agent) => /* @__PURE__ */ jsxs4("p", { children: [
1972
+ /* @__PURE__ */ jsxs4("strong", { children: [
1973
+ agent.source,
1974
+ " subagent"
1975
+ ] }),
1976
+ /* @__PURE__ */ jsxs4("span", { children: [
1977
+ agent.messages,
1978
+ " messages \xB7 ",
1979
+ agent.fidelity.replaceAll("_", " ")
1980
+ ] })
1981
+ ] }, agent.id))
1982
+ ] })
1983
+ ] });
1984
+ }
1985
+ var conversationMemory = /* @__PURE__ */ new Map();
1986
+ function ConversationAnnouncements({ state }) {
1987
+ const previousBusy = useRef4(state.busy);
1988
+ const [announcement, setAnnouncement] = useState3("");
1989
+ useEffect4(() => {
1990
+ if (previousBusy.current && !state.busy && !state.error) {
1991
+ setAnnouncement(`${harnessDisplayName(state.harness) || "Coding agent"} finished working`);
1992
+ }
1993
+ previousBusy.current = state.busy;
1994
+ }, [state.busy, state.error, state.harness]);
1995
+ return /* @__PURE__ */ jsx5("span", { className: "scui-sr-only", role: "status", "aria-live": "polite", "aria-atomic": "true", children: announcement });
1996
+ }
1997
+ function Conversation({ state, adapter, components = {}, slots = {}, memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`, pending = null, unreadAfterMessages = null }) {
1998
+ const scroller = useRef4(null);
1999
+ const remembered = conversationMemory.get(memoryKey) ?? { top: null, atBottom: true };
2000
+ const [atBottom, setAtBottom] = useState3(remembered.atBottom);
2001
+ const earlierAnchor = useRef4(null);
2002
+ const restored = useRef4(false);
2003
+ const blocks = groupConversation(state.transcript);
2004
+ const unreadBoundary = useRef4(Number.isSafeInteger(unreadAfterMessages) && unreadAfterMessages >= 0 ? unreadAfterMessages : null);
2005
+ const unreadBlock = unreadBoundary.current === null ? -1 : blocks.findIndex((block) => {
2006
+ const entries = block.kind === "activity" ? block.entries : [block.entry];
2007
+ return entries.some((entry) => Number.isSafeInteger(entry.messageIndex) && entry.messageIndex > unreadBoundary.current);
2008
+ });
2009
+ const Entry = components.TranscriptEntry ?? TranscriptEntry;
2010
+ const Group = components.ActivityGroup ?? ActivityGroup;
2011
+ const Plan = components.TaskPlan ?? TaskPlan;
2012
+ const Before = slots.beforeConversation;
2013
+ const After = slots.afterConversation;
2014
+ const Empty = slots.emptyConversation;
2015
+ const remember = (value) => boundedSet(conversationMemory, memoryKey, value);
2016
+ const pendingMessage = typeof pending === "string" ? { text: pending, status: "sending" } : pending;
2017
+ const pin = () => {
2018
+ if (!scroller.current) return;
2019
+ scroller.current.scrollTop = scroller.current.scrollHeight;
2020
+ setAtBottom(true);
2021
+ remember({ top: scroller.current.scrollTop, atBottom: true });
2022
+ };
2023
+ useLayoutEffect2(() => {
2024
+ const element = scroller.current;
2025
+ if (!element) return;
2026
+ const anchor = earlierAnchor.current;
2027
+ if (anchor) {
2028
+ if (state.operation === "loadEarlier") anchor.seenOperation = true;
2029
+ const prepended = state.transcript.length > anchor.entries && state.transcript[0]?.id !== anchor.firstId;
2030
+ if (prepended) {
2031
+ element.scrollTop = anchor.top + (element.scrollHeight - anchor.height);
2032
+ earlierAnchor.current = null;
2033
+ remember({ top: element.scrollTop, atBottom: false });
2034
+ return;
2035
+ }
2036
+ if (state.error || anchor.seenOperation && state.operation !== "loadEarlier") earlierAnchor.current = null;
2037
+ }
2038
+ if (!restored.current) {
2039
+ restored.current = true;
2040
+ if (remembered.top !== null && !remembered.atBottom) element.scrollTop = remembered.top;
2041
+ else pin();
2042
+ } else if (atBottom) pin();
2043
+ }, [memoryKey, state.transcript, state.busy, state.operation, state.error, pendingMessage?.text, pendingMessage?.status]);
2044
+ return /* @__PURE__ */ jsxs4("div", { className: "scui-conversation-wrap", children: [
2045
+ /* @__PURE__ */ jsx5(ConversationAnnouncements, { state }),
2046
+ /* @__PURE__ */ jsx5("div", { className: "scui-conversation", ref: scroller, tabIndex: 0, "aria-label": "Conversation", onScroll: (event) => {
2047
+ const element = event.currentTarget;
2048
+ const bottom = element.scrollHeight - element.scrollTop - element.clientHeight <= 64;
2049
+ setAtBottom(bottom);
2050
+ remember({ top: element.scrollTop, atBottom: bottom });
2051
+ }, children: /* @__PURE__ */ jsxs4("div", { children: [
2052
+ Before ? /* @__PURE__ */ jsx5(Before, { state, adapter, value: null }) : null,
2053
+ /* @__PURE__ */ jsx5(Plan, { plan: state.taskPlan, value: state.taskPlan, state, adapter }),
2054
+ /* @__PURE__ */ jsx5(SessionDetails, { semantics: state.semantics }),
2055
+ state.history.hasEarlier ? /* @__PURE__ */ jsx5("button", { className: "scui-load", type: "button", disabled: Boolean(state.operation), onClick: () => {
2056
+ const element = scroller.current;
2057
+ if (element) earlierAnchor.current = { height: element.scrollHeight, top: element.scrollTop, entries: state.transcript.length, firstId: state.transcript[0]?.id, seenOperation: false };
2058
+ setAtBottom(false);
2059
+ adapter.onIntent({ action: "loadEarlier" });
2060
+ }, children: "Load earlier messages" }) : null,
2061
+ !blocks.length && state.startup !== "ready" ? /* @__PURE__ */ jsx5(LoadingStatus, { state }) : null,
2062
+ !blocks.length && state.startup === "ready" ? Empty ? /* @__PURE__ */ jsx5(Empty, { state, adapter, value: null }) : /* @__PURE__ */ jsx5("div", { className: "scui-empty", children: state.error ?? (state.harness ? `${harnessDisplayName(state.harness)} is listening. Say something.` : "No transcript yet.") }) : null,
2063
+ blocks.map((block, index) => /* @__PURE__ */ jsxs4(Fragment3, { children: [
2064
+ index === unreadBlock ? /* @__PURE__ */ jsx5("div", { className: "scui-unread-divider", role: "separator", "aria-label": "New messages", children: /* @__PURE__ */ jsx5("span", { children: "New" }) }) : null,
2065
+ block.kind === "activity" ? /* @__PURE__ */ jsx5(Group, { value: block.entries, entries: block.entries, state, adapter }) : /* @__PURE__ */ jsx5(Entry, { value: block.entry, entry: block.entry, state, adapter })
2066
+ ] }, block.id)),
2067
+ pendingMessage ? /* @__PURE__ */ jsxs4("article", { className: "scui-message scui-pending", "data-role": "user", "data-status": pendingMessage.status, "aria-label": "Your pending message", children: [
2068
+ /* @__PURE__ */ jsx5(MessageImages, { items: pendingMessage.images, adapter }),
2069
+ /* @__PURE__ */ jsx5(Markdown, { value: pendingMessage.text, copyText: adapter?.copyText }),
2070
+ /* @__PURE__ */ jsx5(ContextDisclosure, { context: pendingMessage.context }),
2071
+ /* @__PURE__ */ jsxs4("footer", { children: [
2072
+ /* @__PURE__ */ jsx5("small", { children: pendingMessage.status === "failed" ? "Not sent" : "Sending\u2026" }),
2073
+ pendingMessage.status === "failed" ? /* @__PURE__ */ jsxs4("span", { children: [
2074
+ /* @__PURE__ */ jsx5("button", { type: "button", onClick: pendingMessage.onRetry, children: "Retry" }),
2075
+ /* @__PURE__ */ jsx5("button", { type: "button", onClick: pendingMessage.onEdit, children: "Edit" })
2076
+ ] }) : null
2077
+ ] })
2078
+ ] }) : null,
2079
+ state.busy ? /* @__PURE__ */ jsxs4("div", { className: "scui-working", role: "status", children: [
2080
+ /* @__PURE__ */ jsx5("span", { "aria-hidden": "true", children: "\u2726" }),
2081
+ /* @__PURE__ */ jsx5("i", {}),
2082
+ /* @__PURE__ */ jsx5("i", {}),
2083
+ /* @__PURE__ */ jsx5("i", {}),
2084
+ /* @__PURE__ */ jsxs4("small", { children: [
2085
+ harnessDisplayName(state.harness),
2086
+ " is working"
2087
+ ] })
2088
+ ] }) : null,
2089
+ After ? /* @__PURE__ */ jsx5(After, { state, adapter, value: null }) : null
2090
+ ] }) }),
2091
+ !atBottom ? /* @__PURE__ */ jsxs4("button", { className: "scui-latest", type: "button", onClick: pin, children: [
2092
+ /* @__PURE__ */ jsx5(UiIcon, { name: "down", size: 13 }),
2093
+ " Latest"
2094
+ ] }) : null
2095
+ ] });
2096
+ }
2097
+
2098
+ // src/logo.jsx
2099
+ import { useEffect as useEffect5 } from "react";
2100
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
2101
+ var LOGOS = {
2102
+ "claude-code": {
2103
+ viewBox: "-1 3.5 26 18",
2104
+ paths: [
2105
+ ["path", { fillRule: "evenodd", clipRule: "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" }]
2106
+ ]
2107
+ },
2108
+ codex: {
2109
+ viewBox: "-1 -1 26 26",
2110
+ paths: [
2111
+ ["path", { fillRule: "evenodd", clipRule: "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" }]
2112
+ ]
2113
+ },
2114
+ gemini: {
2115
+ viewBox: "0 0 24 24",
2116
+ paths: [["path", { d: "M12 24C12 17.373 6.627 12 0 12 6.627 12 12 6.627 12 0c0 6.627 5.373 12 12 12-6.627 0-12 5.373-12 12z" }]]
2117
+ },
2118
+ grok: {
2119
+ viewBox: "-1 -1 26 26",
2120
+ 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" }]]
2121
+ },
2122
+ // Canonical mark from block/goose's documentation/static/img/goose.svg.
2123
+ goose: {
2124
+ viewBox: "0 0 45 45",
2125
+ paths: [["path", { d: "M43.2098 39.2492L39.8118 36.4522C37.9646 34.9318 36.3923 33.1051 35.1631 31.0529C33.4647 28.2172 31.1178 25.8243 28.3164 24.071L26.9482 23.2744C26.4794 22.9485 26.1525 22.4405 26.106 21.8675C26.0762 21.4981 26.1647 21.1678 26.3712 20.8771C27.0837 19.873 30.9617 15.6995 31.6365 15.1411C32.5056 14.4228 33.4739 13.8253 34.3724 13.1412C34.5001 13.0438 34.6283 12.9469 34.7545 12.8481C35.0574 12.6088 35.3295 12.3671 35.5458 12.0803C36.5993 10.8604 36.5783 9.82938 36.5783 9.82938C36.4677 9.5534 36.0645 8.69902 35.2449 8.29825C35.9696 8.28406 36.7872 8.55564 37.1582 8.91873C37.6039 8.21947 37.8922 7.76585 38.3404 7.00347C38.4471 6.82241 38.5038 6.48771 38.2998 6.30029C38.1114 6.11287 37.6416 6.01697 37.4606 6.12364C36.4692 6.70693 35.5047 7.33475 34.6351 7.88525C34.6351 7.88525 33.6046 7.86372 32.3842 8.91775C32.097 9.13452 31.8552 9.4066 31.6282 9.69433C31.5171 9.83476 31.4202 9.96297 31.3233 10.0912C30.6387 10.9901 30.0417 11.958 29.3234 12.8271C28.7656 13.5023 24.5915 17.3798 23.5874 18.0923C23.2967 18.2988 22.9669 18.3879 22.597 18.3575C22.0245 18.3115 21.5161 17.9842 21.1902 17.5154L20.3935 16.1472C18.6402 13.3448 16.2474 10.9989 13.4117 9.30041C11.3594 8.0712 9.5332 6.49847 8.01234 4.65172L5.2153 1.25377C5.0773 1.08642 4.81306 1.11382 4.71422 1.30662C4.39615 1.92612 3.78888 3.23411 3.32353 4.99327C3.92786 5.81095 5.26717 7.35089 6.8624 8.65351C6.97348 8.74403 6.88246 8.92313 6.74447 8.88496C5.36846 8.51013 4.03062 7.90825 3.02944 7.39542C2.94772 7.35383 2.84887 7.4057 2.83713 7.49672C2.68152 8.74354 2.64139 10.1127 2.80581 11.5562C4.02572 12.1596 5.85143 12.8804 7.76816 13.3389C7.90713 13.3722 7.90273 13.5723 7.76229 13.5992C6.26982 13.8801 4.70688 13.9427 3.43656 13.9261C3.3475 13.9251 3.28193 14.0093 3.30591 14.0949C3.56085 14.9958 3.90632 15.9118 4.3619 16.8342C4.55029 17.2487 4.75532 17.6553 4.97504 18.0532C6.21207 18.107 7.47946 17.9964 8.75369 17.7713C8.97095 17.7331 9.08741 18.0189 8.90391 18.1417C8.03877 18.7201 7.11588 19.2183 6.25758 19.6283C6.14259 19.6836 6.1054 19.8304 6.18125 19.9327C6.69114 20.6236 7.25191 21.2774 7.86114 21.8866C7.86114 21.8866 10.8608 24.9763 10.9425 25.2185C12.6928 23.4261 15.5701 21.4394 18.7474 19.7047C14.4921 23.1672 12.1198 25.7235 10.9258 27.1758L10.0935 28.3439C9.66091 28.9507 9.2856 29.5951 8.97193 30.2709C7.92231 32.5292 6.19201 37.0966 6.19201 37.0966C6.0594 37.4543 6.16755 37.8072 6.39753 38.0371C6.66031 38.2999 7.01263 38.4076 7.37033 38.275C7.37033 38.275 11.9368 36.5447 14.1961 35.495C14.8719 35.1814 15.5168 34.8056 16.1231 34.3735L17.4095 33.457C18.0907 32.9715 19.0234 33.0489 19.615 33.6405L22.5804 36.6058C23.1896 37.2151 23.8433 37.7758 24.5343 38.2857C24.637 38.3611 24.7833 38.3244 24.8386 38.2094C25.2492 37.3516 25.7473 36.4277 26.3252 35.5631C26.4481 35.3796 26.7343 35.4965 26.6957 35.7133C26.4701 36.988 26.3605 38.2554 26.3086 39.3035C27.2178 39.9172 28.5551 40.5606 30.3721 41.1611C30.4577 41.185 30.5419 41.1195 30.5409 41.0304C30.5237 39.7596 30.5864 38.1967 30.8677 36.7047C30.8942 36.5638 31.0943 36.5593 31.1281 36.6988C31.5861 38.616 32.3069 40.4417 32.8041 41.5829C34.3543 41.8256 35.7234 41.7855 36.9703 41.6298C37.0618 41.6186 37.1136 41.5197 37.0715 41.4375C36.5587 40.4364 35.9568 39.0975 35.582 37.7225C35.5443 37.584 35.7229 37.4935 35.8135 37.6046C37.1161 39.1998 38.656 40.5391 39.3548 41.1175C41.2333 40.6786 42.5413 40.0713 43.1604 39.7528C43.3532 39.6539 43.3806 39.3897 43.2132 39.2517Z" }]]
2126
+ },
2127
+ opencode: { viewBox: "2.5 0 19 24", paths: [["path", { d: "M16 6H8v12h8V6zm4 16H4V2h16v20z" }]] },
2128
+ pi: {
2129
+ viewBox: "0 0 24 24",
2130
+ paths: [
2131
+ ["path", { fillRule: "evenodd", clipRule: "evenodd", d: "M1 1h16.5v11H12v5.5H6.5V23H1V1zm5.5 5.5V12H12V6.5H6.5z" }],
2132
+ ["path", { d: "M17.5 12H23v11h-5.5V12z" }]
2133
+ ]
2134
+ },
2135
+ // Product mark shared with the first-party browser frontend favicon.
2136
+ supercode: {
2137
+ viewBox: "0 0 64 64",
2138
+ paths: [
2139
+ ["rect", { width: "64", height: "64", rx: "14", fill: "#111923" }],
2140
+ ["path", { d: "M18 21h28v6H18zm0 13h18v6H18z", fill: "#5f8fff" }],
2141
+ ["circle", { cx: "46", cy: "37", r: "7", fill: "none", stroke: "#8ce0aa", strokeWidth: "4" }]
2142
+ ]
2143
+ }
2144
+ };
2145
+ var SVG_ATTRIBUTE_NAMES = Object.freeze({
2146
+ clipRule: "clip-rule",
2147
+ fillRule: "fill-rule",
2148
+ strokeWidth: "stroke-width"
2149
+ });
2150
+ function HarnessLogo({ id, activity, size = 28, onMissingLogo }) {
2151
+ const logo = LOGOS[id];
2152
+ useEffect5(() => {
2153
+ if (!logo) onMissingLogo?.(id);
2154
+ }, [id, logo, onMissingLogo]);
2155
+ if (!logo) return null;
2156
+ return /* @__PURE__ */ jsxs5("span", { className: "scui-logo", "data-harness": id, "data-activity": activity, style: { "--scui-logo-size": `${size}px` }, "aria-hidden": "true", children: [
2157
+ /* @__PURE__ */ jsx6("svg", { viewBox: logo.viewBox, preserveAspectRatio: "xMidYMid meet", focusable: "false", children: logo.paths.map(([Tag, props], index) => /* @__PURE__ */ jsx6(Tag, { ...props }, index)) }),
2158
+ activity && activity !== "idle" ? /* @__PURE__ */ jsx6("i", {}) : null
2159
+ ] });
2160
+ }
2161
+
2162
+ // src/sessions.jsx
2163
+ import { useEffect as useEffect6, useLayoutEffect as useLayoutEffect3, useRef as useRef5, useState as useState4 } from "react";
2164
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
2165
+ var sessionListMemory = /* @__PURE__ */ new Map();
2166
+ function sessionPathParts(value) {
2167
+ const complete = String(value ?? "").replaceAll("\\", "/");
2168
+ const boundary = complete.lastIndexOf("/");
2169
+ if (boundary < 0) return { complete, leading: complete, separator: "", trailing: "" };
2170
+ return {
2171
+ complete,
2172
+ leading: complete.slice(0, boundary),
2173
+ separator: "/",
2174
+ trailing: complete.slice(boundary + 1)
2175
+ };
2176
+ }
2177
+ function SessionRow({ row, state, onOpen, now = Date.now() }) {
2178
+ const activity = sessionActivity(state, row);
2179
+ const working = activity === "working";
2180
+ const attention = state.attention.find((item) => item.key === row.key);
2181
+ const title = sessionDisplayName(row);
2182
+ const path = sessionPathParts(row.cwd);
2183
+ const preview = row.preview || attention?.preview || "";
2184
+ const unreadCount = attention?.unreadCount ?? 0;
2185
+ const unreadLabel = unreadCount > 99 ? "99+" : String(unreadCount);
2186
+ const age = relativeAge(row.previewUpdatedAt ?? row.updatedAt, now) || row.age;
2187
+ return /* @__PURE__ */ jsxs6("button", { className: "scui-session", "data-active": row.active, "data-activity": activity, "data-session-key": row.key, type: "button", "aria-label": `${title} \xB7 ${harnessDisplayName(row.harness)}${working ? " \xB7 Working" : ""}${path.complete ? ` \xB7 ${path.complete}` : ""}${preview ? ` \xB7 ${preview}` : ""}${unreadCount ? ` \xB7 ${unreadCount} unread` : ""}${age ? ` \xB7 ${age}` : ""}`, "aria-current": row.active ? "true" : void 0, onClick: () => onOpen(row), children: [
2188
+ /* @__PURE__ */ jsx7(HarnessLogo, { id: row.harness, activity, size: 34 }),
2189
+ /* @__PURE__ */ jsxs6("span", { className: "scui-session-copy", children: [
2190
+ /* @__PURE__ */ jsxs6("span", { className: "scui-session-title", children: [
2191
+ /* @__PURE__ */ jsx7("strong", { children: title }),
2192
+ /* @__PURE__ */ jsx7("span", { className: "scui-session-meta", children: age ? /* @__PURE__ */ jsx7("time", { children: age }) : null })
2193
+ ] }),
2194
+ path.complete ? /* @__PURE__ */ jsxs6("small", { className: "scui-session-path", title: row.cwd, children: [
2195
+ /* @__PURE__ */ jsx7("span", { className: "scui-session-path-leading", children: path.leading }),
2196
+ path.separator ? /* @__PURE__ */ jsx7("span", { className: "scui-session-path-separator", children: path.separator }) : null,
2197
+ path.trailing ? /* @__PURE__ */ jsx7("span", { className: "scui-session-path-trailing", children: path.trailing }) : null
2198
+ ] }) : null,
2199
+ working || preview || unreadCount ? /* @__PURE__ */ jsxs6("span", { className: "scui-session-preview", children: [
2200
+ working ? /* @__PURE__ */ jsxs6("span", { className: "scui-session-working", "aria-hidden": "true", children: [
2201
+ /* @__PURE__ */ jsx7("i", {}),
2202
+ /* @__PURE__ */ jsx7("i", {}),
2203
+ /* @__PURE__ */ jsx7("i", {})
2204
+ ] }) : null,
2205
+ preview || working ? /* @__PURE__ */ jsx7("small", { children: preview || "Working\u2026" }) : null,
2206
+ unreadCount ? /* @__PURE__ */ jsx7("b", { "aria-label": `${unreadCount} unread messages`, children: unreadLabel }) : null
2207
+ ] }) : null,
2208
+ state.attachError?.key === row.key ? /* @__PURE__ */ jsx7("em", { children: state.attachError.message }) : null
2209
+ ] })
2210
+ ] });
2211
+ }
2212
+ function SessionList({ state, adapter, onOpen, onNew, onClose, components = {}, slots = {}, labels = DEFAULT_LABELS, focusKey = null, memoryKey = state.workspace || "@default", headerActions: HeaderActions }) {
2213
+ const remembered = sessionListMemory.get(memoryKey) ?? { query: "", top: 0 };
2214
+ const [query, setQuery] = useState4(remembered.query);
2215
+ const [loadingMore, setLoadingMore] = useState4(false);
2216
+ const [now, setNow] = useState4(() => Date.now());
2217
+ const root = useRef5(null);
2218
+ const rowScroller = useRef5(null);
2219
+ const rows = filterSessions(state.sessions, query);
2220
+ const Row = components.SessionRow ?? SessionRow;
2221
+ const BeforeSessions = slots.beforeSessions;
2222
+ const AfterSessions = slots.afterSessions;
2223
+ useLayoutEffect3(() => {
2224
+ if (rowScroller.current) rowScroller.current.scrollTop = remembered.top;
2225
+ }, [memoryKey]);
2226
+ useEffect6(() => {
2227
+ if (!focusKey || !root.current) return;
2228
+ const target = focusKey === "@new" ? root.current.querySelector('[data-list-focus="new"]') : [...root.current.querySelectorAll("[data-session-key]")].find((element) => element.dataset.sessionKey === focusKey);
2229
+ (target ?? root.current.querySelector("input,button"))?.focus({ preventScroll: true });
2230
+ }, [focusKey, rows.length]);
2231
+ useEffect6(() => {
2232
+ if (loadingMore) setLoadingMore(false);
2233
+ }, [state.error, state.history.hasMoreSessions, state.sessions.length]);
2234
+ useEffect6(() => {
2235
+ const timer = setInterval(() => setNow(Date.now()), 1e4);
2236
+ return () => clearInterval(timer);
2237
+ }, []);
2238
+ const loadMore = () => {
2239
+ if (loadingMore) return;
2240
+ setLoadingMore(true);
2241
+ const result = adapter.onIntent({ action: "loadSessions" });
2242
+ if (result && typeof result.then === "function") {
2243
+ Promise.resolve(result).then(() => setLoadingMore(false), () => setLoadingMore(false));
2244
+ }
2245
+ };
2246
+ return /* @__PURE__ */ jsxs6("section", { className: "scui-list", ref: root, children: [
2247
+ /* @__PURE__ */ jsxs6("header", { className: "scui-head", children: [
2248
+ /* @__PURE__ */ jsxs6("span", { className: "scui-head-copy", children: [
2249
+ /* @__PURE__ */ jsx7("strong", { children: labels.chats }),
2250
+ /* @__PURE__ */ jsxs6("small", { children: [
2251
+ state.sessions.length,
2252
+ " recent conversations"
2253
+ ] })
2254
+ ] }),
2255
+ HeaderActions ? /* @__PURE__ */ jsx7(HeaderActions, { state, adapter, value: "list" }) : null,
2256
+ /* @__PURE__ */ jsx7("button", { type: "button", "data-list-focus": "new", "aria-label": labels.newChat, disabled: !state.harnesses.some((item) => item.startable), onClick: onNew, children: /* @__PURE__ */ jsx7(UiIcon, { name: "plus", size: 18 }) }),
2257
+ onClose ? /* @__PURE__ */ jsx7("button", { type: "button", "aria-label": "Close", onClick: onClose, children: /* @__PURE__ */ jsx7(UiIcon, { name: "close", size: 18 }) }) : null
2258
+ ] }),
2259
+ state.startup !== "ready" ? /* @__PURE__ */ jsx7(LoadingStatus, { state, compact: rows.length > 0 }) : null,
2260
+ state.sessions.length > 4 ? /* @__PURE__ */ jsxs6("label", { className: "scui-search", children: [
2261
+ /* @__PURE__ */ jsx7(UiIcon, { name: "search", size: 15 }),
2262
+ /* @__PURE__ */ jsx7("input", { type: "search", "aria-label": labels.searchChats, placeholder: labels.searchChats, value: query, onInput: (event) => {
2263
+ const value = event.currentTarget.value;
2264
+ setQuery(value);
2265
+ boundedSet(sessionListMemory, memoryKey, { query: value, top: 0 });
2266
+ if (rowScroller.current) rowScroller.current.scrollTop = 0;
2267
+ } }),
2268
+ /* @__PURE__ */ jsx7("small", { children: rows.length })
2269
+ ] }) : null,
2270
+ /* @__PURE__ */ jsxs6("div", { className: "scui-session-rows", ref: rowScroller, onScroll: (event) => boundedSet(sessionListMemory, memoryKey, { query, top: event.currentTarget.scrollTop }), children: [
2271
+ BeforeSessions ? /* @__PURE__ */ jsx7(BeforeSessions, { state, adapter, value: { query, rows } }) : null,
2272
+ !rows.length && state.startup === "ready" ? /* @__PURE__ */ jsx7("div", { className: "scui-empty", children: query ? "No chats match your search." : state.error ?? "No coding chats found." }) : null,
2273
+ rows.map((row) => /* @__PURE__ */ jsx7(Row, { value: row, row, state, adapter, onOpen, now }, row.key)),
2274
+ state.history.hasMoreSessions ? /* @__PURE__ */ jsx7("button", { className: "scui-load", type: "button", disabled: loadingMore, onClick: loadMore, children: loadingMore ? "Loading older chats\u2026" : "Load older chats" }) : null,
2275
+ AfterSessions ? /* @__PURE__ */ jsx7(AfterSessions, { state, adapter, value: { query, rows } }) : null
2276
+ ] })
2277
+ ] });
2278
+ }
2279
+
2280
+ // src/settings.jsx
2281
+ import { useEffect as useEffect7, useId as useId2, useMemo as useMemo2, useRef as useRef6, useState as useState5 } from "react";
2282
+ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
2283
+ function HarnessAdvisory({ state, onReview }) {
2284
+ const advisory = state.interopSettings?.advisories[0];
2285
+ if (!advisory && !state.interopSettingsError) return null;
2286
+ return /* @__PURE__ */ jsxs7("div", { className: "scui-advisory", "data-severity": advisory?.severity ?? "error", role: advisory?.severity === "error" || !advisory ? "alert" : "status", children: [
2287
+ /* @__PURE__ */ jsx8("span", { "aria-hidden": "true", children: "!" }),
2288
+ /* @__PURE__ */ jsxs7("span", { children: [
2289
+ /* @__PURE__ */ jsx8("strong", { children: advisory?.title ?? "Could not inspect harness settings" }),
2290
+ /* @__PURE__ */ jsx8("small", { children: advisory?.message ?? state.interopSettingsError })
2291
+ ] }),
2292
+ advisory && state.canConfigureSettings ? /* @__PURE__ */ jsx8("button", { type: "button", onClick: () => onReview(advisory.recommendation.change), children: "Review" }) : null
2293
+ ] });
2294
+ }
2295
+ function initialValues(report, recommendedChange) {
2296
+ return Object.fromEntries(report.controls.map((control) => [
2297
+ control.key,
2298
+ recommendedChange?.key === control.key ? recommendedChange.value : control.configuredValue ?? control.effectiveValue ?? control.choices[0]?.value ?? null
2299
+ ]));
2300
+ }
2301
+ function HarnessSettingsPanel({ state, adapter, onClose, recommendedChange = null }) {
2302
+ const report = state.interopSettings;
2303
+ const titleId = useId2();
2304
+ const panel = useRef6(null);
2305
+ const valuesKey = `${report?.revision ?? ""}:${recommendedChange?.key ?? ""}:${recommendedChange?.value ?? ""}`;
2306
+ const defaults = useMemo2(() => report ? initialValues(report, recommendedChange) : {}, [valuesKey]);
2307
+ const [values, setValues] = useState5(defaults);
2308
+ useEffect7(() => setValues(defaults), [defaults]);
2309
+ useEffect7(() => {
2310
+ panel.current?.querySelector("select, button")?.focus({ preventScroll: true });
2311
+ const dismiss = (event) => {
2312
+ if (event.key === "Escape") {
2313
+ event.preventDefault();
2314
+ onClose();
2315
+ }
2316
+ };
2317
+ document.addEventListener("keydown", dismiss);
2318
+ return () => document.removeEventListener("keydown", dismiss);
2319
+ }, [onClose]);
2320
+ if (!report) return /* @__PURE__ */ jsx8("section", { ref: panel, className: "scui-settings", role: "dialog", "aria-modal": "true", "aria-labelledby": titleId, children: /* @__PURE__ */ jsxs7("header", { children: [
2321
+ /* @__PURE__ */ jsx8("button", { type: "button", "aria-label": "Close settings", onClick: onClose, children: /* @__PURE__ */ jsx8(UiIcon, { name: "close", size: 18 }) }),
2322
+ /* @__PURE__ */ jsxs7("span", { children: [
2323
+ /* @__PURE__ */ jsx8("strong", { id: titleId, children: "Harness settings" }),
2324
+ /* @__PURE__ */ jsx8("small", { children: state.interopSettingsError ?? "No interoperability controls are available." })
2325
+ ] })
2326
+ ] }) });
2327
+ const changed = report.controls.flatMap((control) => values[control.key] !== (control.configuredValue ?? control.effectiveValue ?? control.choices[0]?.value ?? null) ? [{ key: control.key, value: values[control.key] ?? null }] : []);
2328
+ const submit = (event) => {
2329
+ event.preventDefault();
2330
+ if (!changed.length || !state.canConfigureSettings) return;
2331
+ adapter.onIntent({ action: "configureHarness", harness: report.harness, changes: changed, expectedRevision: report.revision });
2332
+ };
2333
+ return /* @__PURE__ */ jsxs7("section", { ref: panel, className: "scui-settings", role: "dialog", "aria-modal": "true", "aria-labelledby": titleId, children: [
2334
+ /* @__PURE__ */ jsxs7("header", { children: [
2335
+ /* @__PURE__ */ jsx8("button", { type: "button", "aria-label": "Close settings", onClick: onClose, children: /* @__PURE__ */ jsx8(UiIcon, { name: "close", size: 18 }) }),
2336
+ /* @__PURE__ */ jsxs7("span", { children: [
2337
+ /* @__PURE__ */ jsxs7("strong", { id: titleId, children: [
2338
+ harnessDisplayName(report.harness),
2339
+ " interoperability"
2340
+ ] }),
2341
+ /* @__PURE__ */ jsx8("small", { children: "Native harness settings used by Supercode" })
2342
+ ] })
2343
+ ] }),
2344
+ /* @__PURE__ */ jsxs7("form", { onSubmit: submit, children: [
2345
+ report.controls.map((control) => {
2346
+ const choice = control.choices.find((item) => item.value === values[control.key]);
2347
+ const recommendation = report.advisories.map((advisory) => advisory.recommendation).find((item) => item.change.key === control.key && item.change.value === values[control.key]);
2348
+ const consequence = choice?.risk ?? recommendation?.consequence;
2349
+ return /* @__PURE__ */ jsxs7("fieldset", { disabled: !control.writable || Boolean(state.operation), children: [
2350
+ /* @__PURE__ */ jsxs7("label", { htmlFor: `scui-setting-${control.key}`, children: [
2351
+ /* @__PURE__ */ jsx8("strong", { children: control.label }),
2352
+ /* @__PURE__ */ jsx8("small", { children: control.description })
2353
+ ] }),
2354
+ /* @__PURE__ */ jsxs7("select", { id: `scui-setting-${control.key}`, value: values[control.key] ?? "@default", onChange: (event) => setValues((current) => ({ ...current, [control.key]: event.currentTarget.value === "@default" ? null : event.currentTarget.value })), children: [
2355
+ control.resettable ? /* @__PURE__ */ jsx8("option", { value: "@default", children: "Use harness default" }) : null,
2356
+ control.choices.map((item) => /* @__PURE__ */ jsx8("option", { value: item.value, children: item.label }, item.value))
2357
+ ] }),
2358
+ choice ? /* @__PURE__ */ jsx8("p", { children: choice.description }) : null,
2359
+ consequence ? /* @__PURE__ */ jsxs7("p", { className: "scui-setting-risk", children: [
2360
+ /* @__PURE__ */ jsx8("strong", { children: "Security consequence" }),
2361
+ consequence
2362
+ ] }) : null,
2363
+ /* @__PURE__ */ jsxs7("small", { className: "scui-setting-source", children: [
2364
+ control.effectiveNote,
2365
+ control.sourcePath ? ` Source: ${control.sourcePath}` : ""
2366
+ ] })
2367
+ ] }, control.key);
2368
+ }),
2369
+ /* @__PURE__ */ jsxs7("footer", { children: [
2370
+ /* @__PURE__ */ jsx8("button", { type: "button", onClick: onClose, children: "Cancel" }),
2371
+ /* @__PURE__ */ jsx8("button", { type: "submit", disabled: !changed.length || !state.canConfigureSettings || Boolean(state.operation), children: state.operation === "configureHarness" ? "Applying\u2026" : "Apply changes" })
2372
+ ] })
2373
+ ] })
2374
+ ] });
2375
+ }
2376
+ function HarnessReadiness({ harnesses, adapter }) {
2377
+ const blocked = harnesses.filter((item) => !item.startable);
2378
+ if (!blocked.length) return null;
2379
+ return /* @__PURE__ */ jsxs7("details", { className: "scui-readiness", open: !harnesses.some((item) => item.startable), children: [
2380
+ /* @__PURE__ */ jsxs7("summary", { children: [
2381
+ blocked.length,
2382
+ " ",
2383
+ blocked.length === 1 ? "harness needs" : "harnesses need",
2384
+ " setup"
2385
+ ] }),
2386
+ /* @__PURE__ */ jsx8("div", { children: blocked.map((item) => /* @__PURE__ */ jsxs7("section", { children: [
2387
+ /* @__PURE__ */ jsx8(HarnessLogo, { id: item.id, size: 25 }),
2388
+ /* @__PURE__ */ jsxs7("span", { children: [
2389
+ /* @__PURE__ */ jsx8("strong", { children: item.label }),
2390
+ /* @__PURE__ */ jsx8("small", { children: item.reason || (item.auth === "required" ? "Authentication required" : "Unavailable") }),
2391
+ item.protocol ? /* @__PURE__ */ jsxs7("em", { children: [
2392
+ item.protocol,
2393
+ " \xB7 ",
2394
+ item.runtime
2395
+ ] }) : null
2396
+ ] }),
2397
+ item.repair ? /* @__PURE__ */ jsx8("button", { type: "button", onClick: () => adapter.copyText?.(item.repair), children: "Copy fix" }) : null
2398
+ ] }, item.id)) })
2399
+ ] });
2400
+ }
2401
+
2402
+ // src/messenger.jsx
2403
+ import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
2404
+ var pendingMessageMemory = /* @__PURE__ */ new Map();
2405
+ var messengerViewMemory = /* @__PURE__ */ new Map();
2406
+ var newChatMemory = /* @__PURE__ */ new Map();
2407
+ var TRACKED_ACTIONS = /* @__PURE__ */ new Set(["resume", "join", "detach", "branch", "reduce", "terminal", "export", "steer", "interrupt", "respond", "configureHarness", "refresh", "loadEarlier", "loadSessions"]);
2408
+ function Receipt({ state, adapter }) {
2409
+ const receipt = state.reductionReceipt;
2410
+ if (receipt) return /* @__PURE__ */ jsx9("div", { className: "scui-receipt", children: /* @__PURE__ */ jsxs8("span", { children: [
2411
+ /* @__PURE__ */ jsx9("strong", { children: "Reduced and verified" }),
2412
+ /* @__PURE__ */ jsxs8("small", { children: [
2413
+ receipt.sourceTokens.toLocaleString(),
2414
+ " \u2192 ",
2415
+ receipt.reducedTokens.toLocaleString(),
2416
+ " tokens \xB7 ",
2417
+ receipt.ratio.toFixed(1),
2418
+ "\xD7 \xB7 reversible"
2419
+ ] })
2420
+ ] }) });
2421
+ if (state.exportReceipt) return /* @__PURE__ */ jsxs8("div", { className: "scui-receipt", children: [
2422
+ /* @__PURE__ */ jsxs8("span", { children: [
2423
+ /* @__PURE__ */ jsxs8("strong", { children: [
2424
+ "Lossless export ready \xB7 ",
2425
+ harnessDisplayName(state.exportReceipt.targetHarness)
2426
+ ] }),
2427
+ /* @__PURE__ */ jsxs8("small", { children: [
2428
+ state.exportReceipt.path,
2429
+ " \xB7 ",
2430
+ state.exportReceipt.files,
2431
+ " files"
2432
+ ] })
2433
+ ] }),
2434
+ /* @__PURE__ */ jsx9("button", { type: "button", onClick: () => adapter.copyText?.(state.exportReceipt.path), children: "Copy path" })
2435
+ ] });
2436
+ if (state.terminalHandoff) return /* @__PURE__ */ jsxs8("div", { className: "scui-receipt", children: [
2437
+ /* @__PURE__ */ jsxs8("span", { children: [
2438
+ /* @__PURE__ */ jsx9("strong", { children: "Terminal handoff ready" }),
2439
+ /* @__PURE__ */ jsx9("small", { children: state.terminalHandoff.cwd })
2440
+ ] }),
2441
+ /* @__PURE__ */ jsx9("button", { type: "button", onClick: () => adapter.copyText?.(terminalCommand(state.terminalHandoff)), children: "Copy command" })
2442
+ ] });
2443
+ return null;
2444
+ }
2445
+ function ConversationActions({ state, adapter, actionPending, onSettings }) {
2446
+ const [open, setOpen] = useState6(false);
2447
+ const root = useRef7(null);
2448
+ const panel = useRef7(null);
2449
+ const trigger = useRef7(null);
2450
+ const menuId = useId3();
2451
+ const targets = state.harnesses.filter((item) => item.startable);
2452
+ const groups = [
2453
+ {
2454
+ label: "Session",
2455
+ items: [
2456
+ state.canDetach ? { key: "detach", label: "Detach to read-only", intent: { action: "detach" } } : null,
2457
+ state.canOpenTerminal ? { key: "terminal", label: "Prepare terminal handoff", intent: { action: "terminal" } } : null,
2458
+ state.canExport && state.exportBackTarget ? { key: "export", label: `Export back to ${harnessDisplayName(state.exportBackTarget)}`, intent: { action: "export", targetHarness: state.exportBackTarget } } : null,
2459
+ state.interopSettings || state.interopSettingsError ? { key: "settings", label: "Interoperability settings", onSelect: onSettings } : null
2460
+ ].filter(Boolean)
2461
+ },
2462
+ {
2463
+ label: "Lower-cost continuation",
2464
+ items: state.canReduce ? targets.map((target) => ({ key: `reduce:${target.id}`, label: target.id === state.harness ? `Reduce and continue in ${target.label}` : `Reduce and switch to ${target.label}`, intent: { action: "reduce", targetHarness: target.id } })) : []
2465
+ },
2466
+ {
2467
+ label: "Independent continuation",
2468
+ items: state.canBranch ? targets.map((target) => ({ key: `branch:${target.id}`, label: target.id === state.harness ? `Fork in ${target.label}` : `Continue with ${target.label}`, intent: { action: "branch", targetHarness: target.id } })) : []
2469
+ }
2470
+ ].filter((group) => group.items.length);
2471
+ useEffect8(() => {
2472
+ if (!open) return;
2473
+ panel.current?.querySelector("button:not(:disabled)")?.focus({ preventScroll: true });
2474
+ const dismiss = (event) => {
2475
+ if (event.type === "keydown" && event.key === "Escape") {
2476
+ event.preventDefault();
2477
+ setOpen(false);
2478
+ trigger.current?.focus({ preventScroll: true });
2479
+ } else if (event.type === "pointerdown" && !root.current?.contains(event.target)) {
2480
+ setOpen(false);
2481
+ }
2482
+ };
2483
+ document.addEventListener("keydown", dismiss);
2484
+ document.addEventListener("pointerdown", dismiss, true);
2485
+ return () => {
2486
+ document.removeEventListener("keydown", dismiss);
2487
+ document.removeEventListener("pointerdown", dismiss, true);
2488
+ };
2489
+ }, [open]);
2490
+ const dispatch = (item) => {
2491
+ setOpen(false);
2492
+ if (item.onSelect) item.onSelect();
2493
+ else dispatchConfirmedIntent(adapter, item.intent);
2494
+ };
2495
+ const navigate = (event) => {
2496
+ if (event.key === "Escape") {
2497
+ event.preventDefault();
2498
+ event.stopPropagation();
2499
+ setOpen(false);
2500
+ trigger.current?.focus({ preventScroll: true });
2501
+ return;
2502
+ }
2503
+ if (!["ArrowDown", "ArrowUp", "Home", "End"].includes(event.key)) return;
2504
+ const items = [...panel.current.querySelectorAll("button:not(:disabled)")];
2505
+ if (!items.length) return;
2506
+ event.preventDefault();
2507
+ const current = items.indexOf(document.activeElement);
2508
+ const index = event.key === "Home" ? 0 : event.key === "End" ? items.length - 1 : event.key === "ArrowDown" ? (current + 1) % items.length : (current <= 0 ? items.length : current) - 1;
2509
+ items[index].focus({ preventScroll: true });
2510
+ };
2511
+ return /* @__PURE__ */ jsxs8("div", { className: "scui-menu", ref: root, onBlur: (event) => {
2512
+ if (event.relatedTarget && !event.currentTarget.contains(event.relatedTarget)) setOpen(false);
2513
+ }, children: [
2514
+ /* @__PURE__ */ jsx9("button", { ref: trigger, className: "scui-menu-trigger", type: "button", "aria-label": "Conversation actions", "aria-haspopup": "menu", "aria-expanded": open, "aria-controls": open ? menuId : void 0, onClick: () => setOpen((value) => !value), children: /* @__PURE__ */ jsx9(UiIcon, { name: "menu", size: 18 }) }),
2515
+ open ? /* @__PURE__ */ jsx9("div", { ref: panel, id: menuId, className: "scui-menu-panel", role: "menu", "aria-label": "Conversation actions", onKeyDown: navigate, children: groups.map((group) => /* @__PURE__ */ jsxs8("section", { role: "group", "aria-label": group.label, children: [
2516
+ /* @__PURE__ */ jsx9("strong", { children: group.label }),
2517
+ group.items.map((item) => /* @__PURE__ */ jsx9("button", { role: "menuitem", type: "button", disabled: actionPending, onClick: () => dispatch(item), children: item.label }, item.key))
2518
+ ] }, group.label)) }) : null
2519
+ ] });
2520
+ }
2521
+ function ChatHeader({ state, adapter, pendingStatus, actionPending, onBack, onNew, onClose, onSettings, headerActions: HeaderActions }) {
2522
+ const back = useRef7(null);
2523
+ const harness = state.attached?.harness ?? state.harness;
2524
+ const title = state.attached ? sessionDisplayName(state.attached) : harnessDisplayName(harness) || "Agent chat";
2525
+ const status = state.needsInput ? "Needs input" : pendingStatus === "failed" ? "Send failed" : state.busy ? "Working" : pendingStatus === "sending" ? "Sending" : pendingStatus === "editing" ? "Editing message" : state.messaging === "live_peer" ? "Live" : state.mode === "mirror" ? "Read-only" : "Ready";
2526
+ const menu = state.canDetach || state.canOpenTerminal || state.canBranch || state.canAttach || state.canExport || state.canReduce || state.interopSettings || state.interopSettingsError;
2527
+ useEffect8(() => {
2528
+ if (state.mode === "mirror" && !state.canSend) back.current?.focus({ preventScroll: true });
2529
+ }, []);
2530
+ return /* @__PURE__ */ jsxs8("header", { className: "scui-head scui-chat-head", children: [
2531
+ /* @__PURE__ */ jsx9("button", { ref: back, type: "button", "aria-label": "Back to chats", onClick: onBack, children: /* @__PURE__ */ jsx9(UiIcon, { name: "back", size: 18 }) }),
2532
+ /* @__PURE__ */ jsx9(HarnessLogo, { id: harness, size: 28 }),
2533
+ /* @__PURE__ */ jsxs8("span", { className: "scui-head-copy", children: [
2534
+ /* @__PURE__ */ jsx9("strong", { children: title }),
2535
+ /* @__PURE__ */ jsxs8("small", { children: [
2536
+ harnessDisplayName(harness),
2537
+ " \xB7 ",
2538
+ status
2539
+ ] })
2540
+ ] }),
2541
+ HeaderActions ? /* @__PURE__ */ jsx9(HeaderActions, { state, adapter, value: "chat" }) : null,
2542
+ menu ? /* @__PURE__ */ jsx9(ConversationActions, { state, adapter, actionPending, onSettings }) : null,
2543
+ /* @__PURE__ */ jsx9("button", { type: "button", "aria-label": "New chat", onClick: onNew, children: /* @__PURE__ */ jsx9(UiIcon, { name: "plus", size: 18 }) }),
2544
+ onClose ? /* @__PURE__ */ jsx9("button", { type: "button", "aria-label": "Close", onClick: onClose, children: /* @__PURE__ */ jsx9(UiIcon, { name: "close", size: 18 }) }) : null
2545
+ ] });
2546
+ }
2547
+ function Chat({ state, adapter, onBack, onNew, onClose, components, slots, labels, contextCandidates }) {
2548
+ const Header = slots.header;
2549
+ const HeaderActions = slots.headerActions;
2550
+ const memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`;
2551
+ const [pending, setPendingState] = useState6(() => pendingMessageMemory.get(memoryKey) ?? null);
2552
+ const [pendingAction, setPendingAction] = useState6(null);
2553
+ const [restoreDraft, setRestoreDraft] = useState6(null);
2554
+ const [settings, setSettings] = useState6(null);
2555
+ const restoreSequence = useRef7(0);
2556
+ const actionSequence = useRef7(0);
2557
+ const acknowledged = useRef7(/* @__PURE__ */ new Set());
2558
+ const setPending = (update) => setPendingState((current) => {
2559
+ const next = typeof update === "function" ? update(current) : update;
2560
+ if (next) boundedSet(pendingMessageMemory, memoryKey, next);
2561
+ else pendingMessageMemory.delete(memoryKey);
2562
+ return next;
2563
+ });
2564
+ useEffect8(() => {
2565
+ setPendingState(pendingMessageMemory.get(memoryKey) ?? null);
2566
+ setPendingAction(null);
2567
+ setRestoreDraft(null);
2568
+ }, [memoryKey]);
2569
+ useEffect8(() => {
2570
+ if (!pending) return;
2571
+ if (state.transcript.some((entry) => entry.role === "user" && entry.text.trim() === pending.text.trim() && !pending.baselineIds.includes(entry.id))) {
2572
+ setPending(null);
2573
+ return;
2574
+ }
2575
+ if (pending.status === "sending" && state.busy && !pending.seenBusy) setPending({ ...pending, seenBusy: true });
2576
+ else if (pending.status === "sending" && state.error && !state.busy && !state.operation && (pending.seenBusy || state.error !== pending.initialError)) setPending({ ...pending, status: "failed" });
2577
+ }, [pending, state.busy, state.error, state.operation, state.transcript]);
2578
+ const beginPending = (text, context = [], images = []) => setPending({
2579
+ text,
2580
+ context,
2581
+ images,
2582
+ status: "sending",
2583
+ initialError: state.error,
2584
+ seenBusy: state.busy,
2585
+ baselineIds: state.transcript.filter((entry) => entry.role === "user" && entry.text.trim() === text.trim()).map((entry) => entry.id)
2586
+ });
2587
+ const retryPending = () => {
2588
+ if (!pending) return;
2589
+ adapter.onIntent({ action: "send", text: pending.text, ...pending.context?.length ? { context: pending.context } : {}, ...pending.images?.length ? { images: pending.images } : {} });
2590
+ setPending({ ...pending, status: "sending", initialError: state.error, seenBusy: state.busy });
2591
+ };
2592
+ const editPending = () => {
2593
+ if (!pending) return;
2594
+ restoreSequence.current += 1;
2595
+ setRestoreDraft({ id: restoreSequence.current, text: pending.text, context: pending.context, images: pending.images });
2596
+ setPending({ ...pending, status: "editing" });
2597
+ };
2598
+ useEffect8(() => {
2599
+ const key = state.attached?.key;
2600
+ if (!key || !state.attention.some((item) => item.key === key)) {
2601
+ if (key) acknowledged.current.delete(key);
2602
+ return;
2603
+ }
2604
+ if (!acknowledged.current.has(key)) {
2605
+ acknowledged.current.add(key);
2606
+ adapter.onIntent({ action: "ack", key });
2607
+ }
2608
+ }, [adapter, state.attached?.key, state.attention]);
2609
+ const trackedAdapter = useMemo3(() => ({
2610
+ ...adapter,
2611
+ onIntent(intent) {
2612
+ if (!TRACKED_ACTIONS.has(intent.action)) return adapter.onIntent(intent);
2613
+ const id = actionSequence.current + 1;
2614
+ actionSequence.current = id;
2615
+ const action2 = intent.action;
2616
+ setPendingAction({ id, action: action2, initialError: state.error, seenOperation: false });
2617
+ let result;
2618
+ try {
2619
+ result = adapter.onIntent(intent);
2620
+ } catch (error) {
2621
+ setPendingAction((current) => current?.id === id ? null : current);
2622
+ throw error;
2623
+ }
2624
+ if (result && typeof result.then === "function") {
2625
+ Promise.resolve(result).then(
2626
+ () => setPendingAction((current) => current?.id === id ? null : current),
2627
+ () => setPendingAction((current) => current?.id === id ? null : current)
2628
+ );
2629
+ }
2630
+ return result;
2631
+ }
2632
+ }), [adapter, state.error]);
2633
+ useEffect8(() => {
2634
+ if (!pendingAction) return;
2635
+ if (state.operation && !pendingAction.seenOperation) {
2636
+ setPendingAction({ ...pendingAction, seenOperation: true });
2637
+ } else if (!state.operation && (pendingAction.seenOperation || state.error !== pendingAction.initialError)) {
2638
+ setPendingAction(null);
2639
+ }
2640
+ }, [pendingAction, state.error, state.operation]);
2641
+ const pendingMessage = pending && pending.status !== "editing" ? { text: pending.text, context: pending.context, images: pending.images, status: pending.status, onRetry: retryPending, onEdit: editPending } : null;
2642
+ const action = state.operation || pendingAction?.action || null;
2643
+ const actionLabel = operationLabel(action);
2644
+ const actionState = action ? { ...state, operation: action, canSteer: false, canInterrupt: false, canRespond: false } : state;
2645
+ const unreadAfterMessages = state.attention.find((item) => item.key === state.attached?.key)?.afterMessages ?? null;
2646
+ const Advisory = components.HarnessAdvisory ?? HarnessAdvisory;
2647
+ const SettingsPanel = components.HarnessSettingsPanel ?? HarnessSettingsPanel;
2648
+ return /* @__PURE__ */ jsxs8("section", { className: "scui-chat", children: [
2649
+ Header ? /* @__PURE__ */ jsx9(Header, { state: actionState, adapter: trackedAdapter, value: null }) : /* @__PURE__ */ jsx9(ChatHeader, { state: actionState, adapter: trackedAdapter, pendingStatus: pending?.status ?? null, actionPending: Boolean(action), onBack, onNew, onClose, onSettings: () => setSettings({ recommendedChange: null }), headerActions: HeaderActions }),
2650
+ actionLabel ? /* @__PURE__ */ jsxs8("div", { className: "scui-operation", role: "status", children: [
2651
+ /* @__PURE__ */ jsx9("i", {}),
2652
+ actionLabel
2653
+ ] }) : null,
2654
+ state.error ? /* @__PURE__ */ jsxs8("div", { className: "scui-error", role: "alert", children: [
2655
+ /* @__PURE__ */ jsx9("span", { children: state.error }),
2656
+ state.recoverable ? /* @__PURE__ */ jsx9("button", { type: "button", disabled: Boolean(action), onClick: () => trackedAdapter.onIntent({ action: "refresh" }), children: "Retry" }) : null
2657
+ ] }) : null,
2658
+ /* @__PURE__ */ jsx9(Receipt, { state, adapter }),
2659
+ /* @__PURE__ */ jsx9(Conversation, { state: actionState, adapter: trackedAdapter, components, slots, memoryKey, pending: pendingMessage, unreadAfterMessages }, memoryKey),
2660
+ /* @__PURE__ */ jsx9(ContinuationBar, { state: actionState, adapter: trackedAdapter, labels }),
2661
+ /* @__PURE__ */ jsx9(Advisory, { state: actionState, adapter: trackedAdapter, value: state.interopSettings?.advisories[0] ?? null, onReview: (recommendedChange) => setSettings({ recommendedChange }) }),
2662
+ state.mode !== "mirror" || state.canSend ? /* @__PURE__ */ jsx9(Composer, { state: actionState, adapter: trackedAdapter, labels, memoryKey, pendingStatus: pending?.status ?? null, restoreDraft, contextCandidates, components, onDraftRestored: (id) => setRestoreDraft((value) => value?.id === id ? null : value), onPending: beginPending }, memoryKey) : null,
2663
+ settings ? /* @__PURE__ */ jsx9(SettingsPanel, { state: actionState, adapter: trackedAdapter, value: state.interopSettings, recommendedChange: settings.recommendedChange, onClose: () => setSettings(null) }) : null
2664
+ ] });
2665
+ }
2666
+ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey, headerActions: HeaderActions, contextCandidates, components, navigation }) {
2667
+ const startable = state.harnesses.filter((item) => item.startable);
2668
+ const startableKey = startable.map((item) => `${item.id}:${item.launchModes?.join(",")}:${item.preferredLaunchMode ?? ""}`).join("\0");
2669
+ const remembered = newChatMemory.get(memoryKey) ?? { harness: startable[0]?.id ?? "", draft: "", context: [], images: [], modes: {} };
2670
+ const [harness, setHarness] = useState6(remembered.harness);
2671
+ const [draft, setDraft] = useState6(remembered.draft);
2672
+ const [context, setContext] = useState6(remembered.context);
2673
+ const [images, setImages] = useState6(remembered.images ?? []);
2674
+ const [modes, setModes] = useState6(remembered.modes ?? {});
2675
+ const [starting, setStarting] = useState6(null);
2676
+ const [picking, setPicking] = useState6(false);
2677
+ const [dragging, setDragging] = useState6(false);
2678
+ const [pickerError, setPickerError] = useState6(null);
2679
+ const startSequence = useRef7(0);
2680
+ const textarea = useRef7(null);
2681
+ const selectedHarness = startable.find((item) => item.id === harness) ?? null;
2682
+ const Readiness = components.HarnessReadiness ?? HarnessReadiness;
2683
+ const launchModes = selectedHarness?.launchModes?.length ? selectedHarness.launchModes : ["headless"];
2684
+ const rememberedMode = modes[harness];
2685
+ const mode = launchModes.includes(rememberedMode) ? rememberedMode : launchModes.includes(selectedHarness?.preferredLaunchMode) ? selectedHarness.preferredLaunchMode : launchModes[0];
2686
+ const terminalAttachments = mode === "terminal" && (context.length > 0 || images.length > 0);
2687
+ const remember = (overrides = {}) => boundedSet(newChatMemory, memoryKey, { harness, draft, context, images, modes, ...overrides });
2688
+ useAutosizeTextarea(textarea, draft);
2689
+ useEffect8(() => {
2690
+ if (startable.some((item) => item.id === harness)) return;
2691
+ const next = startable[0]?.id ?? "";
2692
+ setHarness(next);
2693
+ remember({ harness: next });
2694
+ }, [harness, startableKey]);
2695
+ useEffect8(() => {
2696
+ if (!starting || starting.mode === "terminal") return;
2697
+ const sessionChanged = (state.attached?.key ?? null) !== starting.attachedKey;
2698
+ const beganWorking = !starting.busy && state.busy;
2699
+ if (!sessionChanged && !beganWorking) return;
2700
+ newChatMemory.delete(memoryKey);
2701
+ onStarted("headless");
2702
+ }, [memoryKey, onStarted, starting, state.attached?.key, state.busy]);
2703
+ useEffect8(() => {
2704
+ if (starting && state.error !== starting.initialError && !state.busy && !state.operation) setStarting(null);
2705
+ }, [starting, state.busy, state.error, state.operation]);
2706
+ useEffect8(() => {
2707
+ textarea.current?.focus({ preventScroll: true });
2708
+ }, []);
2709
+ useEffect8(() => {
2710
+ if (!navigation) return;
2711
+ const nextHarness = startable.some((item) => item.id === navigation.harness) ? navigation.harness : harness;
2712
+ const nextDraft = typeof navigation.draft === "string" ? navigation.draft : draft;
2713
+ const nextContext = mergeContext([], navigation.context ?? []);
2714
+ const nextImages = mergeImages([], navigation.images ?? []);
2715
+ setHarness(nextHarness);
2716
+ setDraft(nextDraft);
2717
+ setContext(nextContext);
2718
+ setImages(nextImages);
2719
+ remember({ harness: nextHarness, draft: nextDraft, context: nextContext, images: nextImages });
2720
+ textarea.current?.focus({ preventScroll: true });
2721
+ }, [navigation?.id]);
2722
+ const pickContext = () => {
2723
+ if (mode === "terminal" || !adapter.pickContext || picking || context.length >= MAX_CONTEXT_ITEMS && images.length >= MAX_IMAGE_ITEMS) return;
2724
+ setPicking(true);
2725
+ setPickerError(null);
2726
+ Promise.resolve().then(() => adapter.pickContext()).then((picked) => {
2727
+ const attachments = partitionAttachments(picked);
2728
+ if (context.length + attachments.context.length > MAX_CONTEXT_ITEMS) throw new Error(`Attach at most ${MAX_CONTEXT_ITEMS} text items.`);
2729
+ if (images.length + attachments.images.length > MAX_IMAGE_ITEMS) throw new Error(`Attach at most ${MAX_IMAGE_ITEMS} images.`);
2730
+ setContext((current) => {
2731
+ const next = mergeContext(current, attachments.context);
2732
+ remember({ context: next });
2733
+ return next;
2734
+ });
2735
+ setImages((current) => {
2736
+ const next = mergeImages(current, attachments.images);
2737
+ remember({ images: next });
2738
+ return next;
2739
+ });
2740
+ }).catch((error) => setPickerError(error instanceof Error ? error.message : "Could not attach context.")).finally(() => setPicking(false));
2741
+ };
2742
+ const attachCandidate = (picked) => {
2743
+ if (mode === "terminal") return;
2744
+ const attachments = partitionAttachments(picked);
2745
+ const nextContext = mergeContext(context, attachments.context);
2746
+ const nextImages = mergeImages(images, attachments.images);
2747
+ setContext(nextContext);
2748
+ setImages(nextImages);
2749
+ remember({ context: nextContext, images: nextImages });
2750
+ };
2751
+ const addImageFiles = (value, source) => {
2752
+ const allFiles = Array.from(value ?? []);
2753
+ if (!allFiles.length) return false;
2754
+ if (mode === "terminal") {
2755
+ setPickerError("Terminal starts currently accept text only. Choose Chat to attach context or images.");
2756
+ return true;
2757
+ }
2758
+ const files = allFiles.filter((file) => file.type.startsWith("image/"));
2759
+ if (files.length !== allFiles.length) {
2760
+ setPickerError("Drop or paste PNG, JPEG, GIF, or WebP images.");
2761
+ return true;
2762
+ }
2763
+ if (images.length + files.length > MAX_IMAGE_ITEMS) {
2764
+ setPickerError(`Attach at most ${MAX_IMAGE_ITEMS} images.`);
2765
+ return true;
2766
+ }
2767
+ setPicking(true);
2768
+ setPickerError(null);
2769
+ imageAttachmentsFromFiles(files).then((picked) => setImages((current) => {
2770
+ const next = mergeImages(current, picked);
2771
+ remember({ images: next });
2772
+ return next;
2773
+ }), (error) => setPickerError(error instanceof Error ? error.message : `Could not ${source} image.`)).finally(() => setPicking(false));
2774
+ return true;
2775
+ };
2776
+ const pasteImages = (event) => {
2777
+ if (addImageFiles(event.clipboardData?.files, "paste")) event.preventDefault();
2778
+ };
2779
+ const dropImages = (event) => {
2780
+ setDragging(false);
2781
+ if (addImageFiles(event.dataTransfer?.files, "drop")) event.preventDefault();
2782
+ };
2783
+ const send = () => {
2784
+ const text = draft.trim();
2785
+ if (!text && !images.length || !harness || starting || terminalAttachments || mode === "terminal" && !text) return;
2786
+ const id = startSequence.current + 1;
2787
+ startSequence.current = id;
2788
+ setStarting({ id, mode, attachedKey: state.attached?.key ?? null, busy: state.busy, initialError: state.error });
2789
+ const result = adapter.onIntent({ action: "new", harness, mode, text, ...context.length ? { context } : {}, ...images.length ? { images } : {} });
2790
+ if (mode === "terminal") {
2791
+ Promise.resolve(result).then(() => {
2792
+ newChatMemory.delete(memoryKey);
2793
+ onStarted("terminal");
2794
+ }, () => setStarting((current) => current?.id === id ? null : current));
2795
+ } else if (result && typeof result.then === "function") {
2796
+ Promise.resolve(result).catch(() => setStarting((current) => current?.id === id ? null : current));
2797
+ }
2798
+ };
2799
+ return /* @__PURE__ */ jsxs8("section", { className: "scui-chat", children: [
2800
+ /* @__PURE__ */ jsxs8("header", { className: "scui-head", children: [
2801
+ /* @__PURE__ */ jsx9("button", { type: "button", "aria-label": "Back", onClick: onBack, children: /* @__PURE__ */ jsx9(UiIcon, { name: "back", size: 18 }) }),
2802
+ /* @__PURE__ */ jsxs8("span", { className: "scui-head-copy", children: [
2803
+ /* @__PURE__ */ jsx9("strong", { children: labels.newChat }),
2804
+ /* @__PURE__ */ jsx9("small", { children: "No session is created until you send" })
2805
+ ] }),
2806
+ HeaderActions ? /* @__PURE__ */ jsx9(HeaderActions, { state, adapter, value: "new" }) : null,
2807
+ onClose ? /* @__PURE__ */ jsx9("button", { type: "button", "aria-label": "Close", onClick: onClose, children: /* @__PURE__ */ jsx9(UiIcon, { name: "close", size: 18 }) }) : null
2808
+ ] }),
2809
+ starting ? /* @__PURE__ */ jsxs8("div", { className: "scui-operation", role: "status", children: [
2810
+ /* @__PURE__ */ jsx9("i", {}),
2811
+ mode === "terminal" ? "Opening terminal\u2026" : operationLabel("start")
2812
+ ] }) : null,
2813
+ /* @__PURE__ */ jsxs8("div", { className: "scui-new", children: [
2814
+ /* @__PURE__ */ jsx9("span", { "aria-hidden": "true", children: "\u2726" }),
2815
+ /* @__PURE__ */ jsx9("strong", { children: "What should the agent build or fix?" }),
2816
+ /* @__PURE__ */ jsx9("small", { children: "Choose a coding harness and send the first message." })
2817
+ ] }),
2818
+ /* @__PURE__ */ jsxs8("div", { className: "scui-compose", children: [
2819
+ /* @__PURE__ */ jsxs8("label", { className: "scui-harness-picker", children: [
2820
+ /* @__PURE__ */ jsx9(HarnessLogo, { id: harness, size: 24 }),
2821
+ /* @__PURE__ */ jsx9("span", { children: "Coding harness" }),
2822
+ /* @__PURE__ */ jsx9("select", { value: harness, disabled: Boolean(starting), onChange: (event) => {
2823
+ const value = event.currentTarget.value;
2824
+ setHarness(value);
2825
+ remember({ harness: value });
2826
+ }, children: state.harnesses.map((item) => /* @__PURE__ */ jsxs8("option", { value: item.id, disabled: !item.startable, children: [
2827
+ item.label,
2828
+ item.startable ? "" : " \xB7 unavailable"
2829
+ ] }, item.id)) })
2830
+ ] }),
2831
+ /* @__PURE__ */ jsx9(Readiness, { harnesses: state.harnesses, state, adapter }),
2832
+ launchModes.length > 1 ? /* @__PURE__ */ jsxs8("fieldset", { className: "scui-launch-modes", disabled: Boolean(starting), children: [
2833
+ /* @__PURE__ */ jsx9("legend", { children: "Run as" }),
2834
+ launchModes.map((item) => /* @__PURE__ */ jsxs8("label", { children: [
2835
+ /* @__PURE__ */ jsx9("input", { type: "radio", name: `launch-mode-${memoryKey}`, value: item, checked: mode === item, onChange: () => {
2836
+ const next = { ...modes, [harness]: item };
2837
+ setModes(next);
2838
+ setPickerError(null);
2839
+ remember({ modes: next });
2840
+ } }),
2841
+ /* @__PURE__ */ jsxs8("span", { children: [
2842
+ /* @__PURE__ */ jsx9("strong", { children: item === "terminal" ? "Terminal" : "Chat" }),
2843
+ /* @__PURE__ */ jsx9("small", { children: item === "terminal" ? "Interactive tmux session" : "Managed here" })
2844
+ ] })
2845
+ ] }, item))
2846
+ ] }) : null,
2847
+ mode !== "terminal" ? /* @__PURE__ */ jsx9(ContextCandidates, { items: contextCandidates, context, images, state, adapter, onAttach: attachCandidate, component: components.ContextCandidate }) : null,
2848
+ /* @__PURE__ */ jsx9(ImageTray, { items: images, onRemove: (index) => setImages((items) => {
2849
+ const next = items.filter((_, itemIndex) => itemIndex !== index);
2850
+ remember({ images: next });
2851
+ return next;
2852
+ }) }),
2853
+ /* @__PURE__ */ jsx9(ContextTray, { items: context, onRemove: (index) => setContext((items) => {
2854
+ const next = items.filter((_, itemIndex) => itemIndex !== index);
2855
+ remember({ context: next });
2856
+ return next;
2857
+ }) }),
2858
+ terminalAttachments ? /* @__PURE__ */ jsx9("small", { className: "scui-context-error", role: "alert", children: "Terminal starts currently accept text only. Remove attachments or choose Chat." }) : pickerError ? /* @__PURE__ */ jsx9("small", { className: "scui-context-error", role: "alert", children: pickerError }) : null,
2859
+ /* @__PURE__ */ jsxs8("div", { className: `scui-envelope${dragging ? " scui-drop-target" : ""}`, onDragEnter: (event) => {
2860
+ if (Array.from(event.dataTransfer?.types ?? []).includes("Files")) {
2861
+ event.preventDefault();
2862
+ setDragging(true);
2863
+ }
2864
+ }, onDragOver: (event) => {
2865
+ if (Array.from(event.dataTransfer?.types ?? []).includes("Files")) event.preventDefault();
2866
+ }, onDragLeave: (event) => {
2867
+ if (!event.currentTarget.contains(event.relatedTarget)) setDragging(false);
2868
+ }, onDrop: dropImages, children: [
2869
+ adapter.pickContext ? /* @__PURE__ */ jsx9("button", { className: "scui-attach", type: "button", "aria-label": "Attach files or images", disabled: mode === "terminal" || picking || context.length >= MAX_CONTEXT_ITEMS && images.length >= MAX_IMAGE_ITEMS || Boolean(starting), onClick: pickContext, children: picking ? /* @__PURE__ */ jsx9("i", { className: "scui-control-spinner" }) : /* @__PURE__ */ jsx9(UiIcon, { name: "attach", size: 17 }) }) : null,
2870
+ /* @__PURE__ */ jsx9("textarea", { ref: 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 || Boolean(starting), onPaste: pasteImages, onInput: (event) => {
2871
+ const value = event.currentTarget.value;
2872
+ setDraft(value);
2873
+ remember({ draft: value });
2874
+ }, onKeyDown: (event) => {
2875
+ if (isSendKey(event)) {
2876
+ event.preventDefault();
2877
+ send();
2878
+ }
2879
+ } }),
2880
+ /* @__PURE__ */ jsx9("span", { children: /* @__PURE__ */ jsx9("button", { type: "button", className: "scui-send", "aria-label": mode === "terminal" ? "Start terminal session" : "Start chat", disabled: !draft.trim() && !images.length || !harness || Boolean(starting) || terminalAttachments || mode === "terminal" && !draft.trim(), onClick: send, children: starting ? /* @__PURE__ */ jsx9("i", { className: "scui-control-spinner" }) : /* @__PURE__ */ jsx9(UiIcon, { name: "send", size: 17 }) }) })
2881
+ ] })
2882
+ ] })
2883
+ ] });
2884
+ }
2885
+ function SupercodeMessenger({ state: stateInput, adapter, class: className = "", initialView, navigation = null, onViewChange, contextCandidates: candidatesInput = [], labels, components = {}, slots = {} }) {
2886
+ const state = useMemo3(() => normalizeUiState(stateInput), [stateInput]);
2887
+ const contextCandidates = useMemo3(() => normalizeAttachmentCandidates(candidatesInput), [candidatesInput]);
2888
+ const copy = { ...DEFAULT_LABELS, ...labels };
2889
+ const memoryKey = state.workspace || "@default";
2890
+ const [view, setViewState] = useState6(initialView ?? (state.attention.length ? "list" : messengerViewMemory.get(memoryKey) ?? (state.attached || state.transcript.length ? "chat" : "list")));
2891
+ const [opening, setOpening] = useState6(null);
2892
+ const [newNavigation, setNewNavigation] = useState6(null);
2893
+ const [listFocus, setListFocus] = useState6(null);
2894
+ const lastNavigation = useRef7(null);
2895
+ const setView = (next) => {
2896
+ boundedSet(messengerViewMemory, memoryKey, next);
2897
+ setViewState(next);
2898
+ onViewChange?.(next);
2899
+ };
2900
+ useEffect8(() => {
2901
+ if (!navigation || navigation.id === lastNavigation.current) return;
2902
+ lastNavigation.current = navigation.id;
2903
+ if (navigation.view === "list") {
2904
+ setOpening(null);
2905
+ setView("list");
2906
+ return;
2907
+ }
2908
+ if (navigation.view === "new") {
2909
+ setListFocus("@new");
2910
+ setNewNavigation(navigation);
2911
+ setView("new");
2912
+ return;
2913
+ }
2914
+ if (navigation.view === "chat" && typeof navigation.sessionKey === "string" && navigation.sessionKey) {
2915
+ setListFocus(navigation.sessionKey);
2916
+ if (state.attached?.key === navigation.sessionKey) {
2917
+ setOpening(null);
2918
+ setView("chat");
2919
+ return;
2920
+ }
2921
+ const row = state.sessions.find((item) => item.key === navigation.sessionKey);
2922
+ setOpening(row ?? { key: navigation.sessionKey, harness: "supercode", name: "Chat", title: "Chat" });
2923
+ adapter.onIntent({ action: "attach", key: navigation.sessionKey });
2924
+ }
2925
+ }, [adapter, navigation, state.attached?.key, state.sessions]);
2926
+ useEffect8(() => {
2927
+ if (!opening) return;
2928
+ if (state.attached?.key === opening.key) {
2929
+ setOpening(null);
2930
+ setView("chat");
2931
+ } else if (state.attachError?.key === opening.key || state.error && !state.operation) {
2932
+ setOpening(null);
2933
+ }
2934
+ }, [opening, state.attachError?.key, state.attached?.key, state.error, state.operation]);
2935
+ const open = (row) => {
2936
+ setListFocus(row.key);
2937
+ setOpening(row);
2938
+ adapter.onIntent({ action: "attach", key: row.key });
2939
+ };
2940
+ const close = () => adapter.onClose?.();
2941
+ const Footer = slots.footer;
2942
+ const HeaderActions = slots.headerActions;
2943
+ return /* @__PURE__ */ jsxs8("main", { className: `scui-root ${className}`, "data-view": view, "data-mode": state.mode, "aria-label": "Supercode messenger", children: [
2944
+ view === "list" ? /* @__PURE__ */ jsx9(SessionList, { state, adapter, focusKey: listFocus, onOpen: open, onNew: () => {
2945
+ setListFocus("@new");
2946
+ setNewNavigation(null);
2947
+ setView("new");
2948
+ }, onClose: adapter.onClose ? close : void 0, components, labels: copy, memoryKey, slots, headerActions: HeaderActions }) : null,
2949
+ view === "new" ? /* @__PURE__ */ jsx9(NewChat, { state, adapter, onBack: () => setView("list"), onClose: adapter.onClose ? close : void 0, onStarted: (mode) => setView(mode === "terminal" ? "list" : "chat"), labels: copy, memoryKey, headerActions: HeaderActions, contextCandidates, components, navigation: newNavigation }) : null,
2950
+ view === "chat" ? /* @__PURE__ */ jsx9(Chat, { state, adapter, onBack: () => {
2951
+ setListFocus(state.attached?.key ?? listFocus);
2952
+ setView("list");
2953
+ }, onNew: () => {
2954
+ setListFocus("@new");
2955
+ setNewNavigation(null);
2956
+ setView("new");
2957
+ }, onClose: adapter.onClose ? close : void 0, components, slots, labels: copy, contextCandidates }) : null,
2958
+ opening ? /* @__PURE__ */ jsxs8("div", { className: "scui-opening", role: "status", "aria-busy": "true", children: [
2959
+ /* @__PURE__ */ jsx9(HarnessLogo, { id: opening.harness, size: 34 }),
2960
+ /* @__PURE__ */ jsxs8("span", { children: [
2961
+ /* @__PURE__ */ jsxs8("strong", { children: [
2962
+ "Opening ",
2963
+ sessionDisplayName(opening)
2964
+ ] }),
2965
+ /* @__PURE__ */ jsx9("small", { children: "Loading the latest transcript window\u2026" })
2966
+ ] }),
2967
+ /* @__PURE__ */ jsx9("i", {})
2968
+ ] }) : null,
2969
+ Footer ? /* @__PURE__ */ jsx9(Footer, { state, adapter, value: copy }) : null
2970
+ ] });
2971
+ }
2972
+ export {
2973
+ SupercodeMessenger
2974
+ };