@volter-ai-dev/supercode-ui 0.1.28 → 0.1.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/embed.mjs CHANGED
@@ -2,7 +2,7 @@
2
2
  import { render } from "preact";
3
3
 
4
4
  // src/messenger.jsx
5
- import { useEffect as useEffect7, useId as useId2, useMemo as useMemo2, useRef as useRef6, useState as useState5 } from "preact/hooks";
5
+ import { useEffect as useEffect8, useId as useId3, useMemo as useMemo3, useRef as useRef7, useState as useState6 } from "preact/hooks";
6
6
 
7
7
  // core.mjs
8
8
  var HARNESS_NAMES = Object.freeze({
@@ -43,6 +43,7 @@ var EMPTY_UI_STATE = Object.freeze({
43
43
  canReduce: false,
44
44
  canInterrupt: false,
45
45
  canRespond: false,
46
+ canConfigureSettings: false,
46
47
  messaging: null,
47
48
  workspace: "",
48
49
  taskPlan: Object.freeze({ source: "none", items: Object.freeze([]), residueCount: 0, observedAt: null }),
@@ -51,6 +52,8 @@ var EMPTY_UI_STATE = Object.freeze({
51
52
  exportBackTarget: null,
52
53
  exportReceipt: null,
53
54
  reductionReceipt: null,
55
+ interopSettings: null,
56
+ interopSettingsError: null,
54
57
  error: null,
55
58
  recoverable: false,
56
59
  harnesses: Object.freeze([]),
@@ -84,7 +87,16 @@ function number(value, fallback = 0) {
84
87
  function nullableNumber(value) {
85
88
  return typeof value === "number" && Number.isFinite(value) ? value : null;
86
89
  }
87
- function boundedString(value, max) {
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) {
88
100
  if (typeof value !== "string") return "";
89
101
  return value.length <= max ? value : `${value.slice(0, max)}\u2026`;
90
102
  }
@@ -549,11 +561,12 @@ function readSessions(value) {
549
561
  title: string(row.title),
550
562
  preview: string(row.preview),
551
563
  age: string(row.age),
564
+ previewUpdatedAt: nullableNumber(row.previewUpdatedAt),
552
565
  updatedAt: nullableNumber(row.updatedAt),
553
566
  messages: nullableNumber(row.messages),
554
567
  active: row.active === true,
555
568
  live: row.live === true,
556
- runtimeStatus: row.runtimeStatus === "busy" || row.runtimeStatus === "idle" ? row.runtimeStatus : null
569
+ runtimeStatus: row.runtimeStatus === "running" || row.runtimeStatus === "busy" || row.runtimeStatus === "idle" ? row.runtimeStatus : null
557
570
  }];
558
571
  });
559
572
  }
@@ -627,6 +640,68 @@ function readReductionReceipt(value) {
627
640
  targetHarness: receipt.targetHarness
628
641
  };
629
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
+ }
630
705
  function normalizeUiState(value) {
631
706
  const raw = record(value) ?? {};
632
707
  const pill = record(raw.pill);
@@ -652,6 +727,7 @@ function normalizeUiState(value) {
652
727
  canReduce: raw.canReduce === true,
653
728
  canInterrupt: raw.canInterrupt === true,
654
729
  canRespond: raw.canRespond === true,
730
+ canConfigureSettings: raw.canConfigureSettings === true,
655
731
  messaging: raw.messaging === "live_peer" ? "live_peer" : null,
656
732
  workspace: string(raw.workspace),
657
733
  taskPlan: readTaskPlan(raw.taskPlan),
@@ -660,6 +736,8 @@ function normalizeUiState(value) {
660
736
  exportBackTarget: typeof raw.exportBackTarget === "string" ? raw.exportBackTarget : null,
661
737
  exportReceipt: readExportReceipt(raw.exportReceipt),
662
738
  reductionReceipt: readReductionReceipt(raw.reductionReceipt),
739
+ interopSettings: readInteropSettings(raw.interopSettings),
740
+ interopSettingsError: typeof raw.interopSettingsError === "string" ? boundedString(raw.interopSettingsError) : null,
663
741
  error: typeof raw.error === "string" ? raw.error : null,
664
742
  recoverable: raw.recoverable === true,
665
743
  harnesses: Array.isArray(raw.harnesses) ? raw.harnesses.flatMap((candidate) => {
@@ -691,6 +769,7 @@ function sessionActivity(state, row) {
691
769
  const attention = state.attention.find((item) => item.key === row.key)?.kind;
692
770
  if (attention) return attention;
693
771
  if (row.runtimeStatus === "busy") return "working";
772
+ if (row.runtimeStatus === "running") return "running";
694
773
  if (row.live || row.runtimeStatus === "idle") return "recent";
695
774
  return "idle";
696
775
  }
@@ -748,11 +827,11 @@ function activitySummary(entries) {
748
827
  function canContinueHere(state) {
749
828
  if (state.mode !== "mirror" || state.canSend) return false;
750
829
  const row = state.attached?.key ? state.sessions.find((item) => item.key === state.attached.key) : null;
751
- return state.canResume && row?.runtimeStatus !== "busy" && row?.runtimeStatus !== "idle";
830
+ return state.canResume && row?.runtimeStatus !== "running" && row?.runtimeStatus !== "busy" && row?.runtimeStatus !== "idle";
752
831
  }
753
832
  function operationLabel(operation) {
754
833
  if (!operation) return "";
755
- 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", interrupt: "Stopping agent\u2026", respond: "Sending response\u2026", loadEarlier: "Loading earlier messages\u2026", loadSessions: "Loading more chats\u2026", refresh: "Retrying\u2026" };
834
+ 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", 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" };
756
835
  return labels[operation] ?? `${operation.replaceAll("_", " ")}\u2026`;
757
836
  }
758
837
  function terminalCommand(handoff) {
@@ -1098,7 +1177,7 @@ function ContinuationBar({ state, adapter, labels = DEFAULT_LABELS }) {
1098
1177
  if (state.mode !== "mirror" || state.canSend) return null;
1099
1178
  const attached = state.attached;
1100
1179
  const row = attached?.key ? state.sessions.find((item) => item.key === attached.key) : null;
1101
- const activeElsewhere = row?.runtimeStatus === "busy" || row?.runtimeStatus === "idle";
1180
+ const activeElsewhere = row?.runtimeStatus === "running" || row?.runtimeStatus === "busy" || row?.runtimeStatus === "idle";
1102
1181
  const resume = canContinueHere(state);
1103
1182
  const join = state.canAttach;
1104
1183
  const branch = state.canBranch;
@@ -1982,28 +2061,35 @@ function sessionPathParts(value) {
1982
2061
  trailing: complete.slice(boundary + 1)
1983
2062
  };
1984
2063
  }
1985
- function SessionRow({ row, state, onOpen }) {
2064
+ function SessionRow({ row, state, onOpen, now = Date.now() }) {
1986
2065
  const activity = sessionActivity(state, row);
2066
+ const working = activity === "working";
1987
2067
  const attention = state.attention.find((item) => item.key === row.key);
1988
2068
  const title = sessionDisplayName(row);
1989
2069
  const path = sessionPathParts(row.cwd);
1990
2070
  const preview = row.preview || attention?.preview || "";
1991
2071
  const unreadCount = attention?.unreadCount ?? 0;
1992
2072
  const unreadLabel = unreadCount > 99 ? "99+" : String(unreadCount);
1993
- return /* @__PURE__ */ jsxs6("button", { class: "scui-session", "data-active": row.active, "data-activity": activity, "data-session-key": row.key, type: "button", "aria-label": `${title} \xB7 ${harnessDisplayName(row.harness)}${path.complete ? ` \xB7 ${path.complete}` : ""}${preview ? ` \xB7 ${preview}` : ""}${unreadCount ? ` \xB7 ${unreadCount} unread` : ""}${row.age ? ` \xB7 ${row.age}` : ""}`, "aria-current": row.active ? "true" : void 0, onClick: () => onOpen(row), children: [
2073
+ const age = relativeAge(row.previewUpdatedAt ?? row.updatedAt, now) || row.age;
2074
+ return /* @__PURE__ */ jsxs6("button", { class: "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: [
1994
2075
  /* @__PURE__ */ jsx7(HarnessLogo, { id: row.harness, activity, size: 34 }),
1995
2076
  /* @__PURE__ */ jsxs6("span", { class: "scui-session-copy", children: [
1996
2077
  /* @__PURE__ */ jsxs6("span", { class: "scui-session-title", children: [
1997
2078
  /* @__PURE__ */ jsx7("strong", { children: title }),
1998
- row.age ? /* @__PURE__ */ jsx7("time", { children: row.age }) : null
2079
+ /* @__PURE__ */ jsx7("span", { class: "scui-session-meta", children: age ? /* @__PURE__ */ jsx7("time", { children: age }) : null })
1999
2080
  ] }),
2000
2081
  path.complete ? /* @__PURE__ */ jsxs6("small", { class: "scui-session-path", title: row.cwd, children: [
2001
2082
  /* @__PURE__ */ jsx7("span", { class: "scui-session-path-leading", children: path.leading }),
2002
2083
  path.separator ? /* @__PURE__ */ jsx7("span", { class: "scui-session-path-separator", children: path.separator }) : null,
2003
2084
  path.trailing ? /* @__PURE__ */ jsx7("span", { class: "scui-session-path-trailing", children: path.trailing }) : null
2004
2085
  ] }) : null,
2005
- preview || unreadCount ? /* @__PURE__ */ jsxs6("span", { class: "scui-session-preview", children: [
2006
- preview ? /* @__PURE__ */ jsx7("small", { children: preview }) : null,
2086
+ working || preview || unreadCount ? /* @__PURE__ */ jsxs6("span", { class: "scui-session-preview", children: [
2087
+ working ? /* @__PURE__ */ jsxs6("span", { class: "scui-session-working", "aria-hidden": "true", children: [
2088
+ /* @__PURE__ */ jsx7("i", {}),
2089
+ /* @__PURE__ */ jsx7("i", {}),
2090
+ /* @__PURE__ */ jsx7("i", {})
2091
+ ] }) : null,
2092
+ preview || working ? /* @__PURE__ */ jsx7("small", { children: preview || "Working\u2026" }) : null,
2007
2093
  unreadCount ? /* @__PURE__ */ jsx7("b", { "aria-label": `${unreadCount} unread messages`, children: unreadLabel }) : null
2008
2094
  ] }) : null,
2009
2095
  state.attachError?.key === row.key ? /* @__PURE__ */ jsx7("em", { children: state.attachError.message }) : null
@@ -2014,6 +2100,7 @@ function SessionList({ state, adapter, onOpen, onNew, onClose, components = {},
2014
2100
  const remembered = sessionListMemory.get(memoryKey) ?? { query: "", top: 0 };
2015
2101
  const [query, setQuery] = useState4(remembered.query);
2016
2102
  const [loadingMore, setLoadingMore] = useState4(false);
2103
+ const [now, setNow] = useState4(() => Date.now());
2017
2104
  const root = useRef5(null);
2018
2105
  const rowScroller = useRef5(null);
2019
2106
  const rows = filterSessions(state.sessions, query);
@@ -2029,6 +2116,10 @@ function SessionList({ state, adapter, onOpen, onNew, onClose, components = {},
2029
2116
  useEffect6(() => {
2030
2117
  if (loadingMore) setLoadingMore(false);
2031
2118
  }, [state.error, state.history.hasMoreSessions, state.sessions.length]);
2119
+ useEffect6(() => {
2120
+ const timer = setInterval(() => setNow(Date.now()), 1e4);
2121
+ return () => clearInterval(timer);
2122
+ }, []);
2032
2123
  const loadMore = () => {
2033
2124
  if (loadingMore) return;
2034
2125
  setLoadingMore(true);
@@ -2062,23 +2153,120 @@ function SessionList({ state, adapter, onOpen, onNew, onClose, components = {},
2062
2153
  ] }) : null,
2063
2154
  /* @__PURE__ */ jsxs6("div", { class: "scui-session-rows", ref: rowScroller, onScroll: (event) => boundedSet(sessionListMemory, memoryKey, { query, top: event.currentTarget.scrollTop }), children: [
2064
2155
  !rows.length && state.startup === "ready" ? /* @__PURE__ */ jsx7("div", { class: "scui-empty", children: query ? "No chats match your search." : state.error ?? "No coding chats found." }) : null,
2065
- rows.map((row) => /* @__PURE__ */ jsx7(Row, { value: row, row, state, adapter, onOpen }, row.key)),
2156
+ rows.map((row) => /* @__PURE__ */ jsx7(Row, { value: row, row, state, adapter, onOpen, now }, row.key)),
2066
2157
  state.history.hasMoreSessions ? /* @__PURE__ */ jsx7("button", { class: "scui-load", type: "button", disabled: loadingMore, onClick: loadMore, children: loadingMore ? "Loading older chats\u2026" : "Load older chats" }) : null
2067
2158
  ] })
2068
2159
  ] });
2069
2160
  }
2070
2161
 
2071
- // src/messenger.jsx
2162
+ // src/settings.jsx
2163
+ import { useEffect as useEffect7, useId as useId2, useMemo as useMemo2, useRef as useRef6, useState as useState5 } from "preact/hooks";
2072
2164
  import { jsx as jsx8, jsxs as jsxs7 } from "preact/jsx-runtime";
2165
+ function HarnessAdvisory({ state, onReview }) {
2166
+ const advisory = state.interopSettings?.advisories[0];
2167
+ if (!advisory && !state.interopSettingsError) return null;
2168
+ return /* @__PURE__ */ jsxs7("aside", { class: "scui-advisory", "data-severity": advisory?.severity ?? "error", role: advisory?.severity === "error" ? "alert" : "status", children: [
2169
+ /* @__PURE__ */ jsx8("span", { "aria-hidden": "true", children: "!" }),
2170
+ /* @__PURE__ */ jsxs7("span", { children: [
2171
+ /* @__PURE__ */ jsx8("strong", { children: advisory?.title ?? "Could not inspect harness settings" }),
2172
+ /* @__PURE__ */ jsx8("small", { children: advisory?.message ?? state.interopSettingsError })
2173
+ ] }),
2174
+ advisory && state.canConfigureSettings ? /* @__PURE__ */ jsx8("button", { type: "button", onClick: () => onReview(advisory.recommendation.change), children: "Review" }) : null
2175
+ ] });
2176
+ }
2177
+ function initialValues(report, recommendedChange) {
2178
+ return Object.fromEntries(report.controls.map((control) => [
2179
+ control.key,
2180
+ recommendedChange?.key === control.key ? recommendedChange.value : control.configuredValue ?? control.effectiveValue ?? control.choices[0]?.value ?? null
2181
+ ]));
2182
+ }
2183
+ function HarnessSettingsPanel({ state, adapter, onClose, recommendedChange = null }) {
2184
+ const report = state.interopSettings;
2185
+ const titleId = useId2();
2186
+ const panel = useRef6(null);
2187
+ const valuesKey = `${report?.revision ?? ""}:${recommendedChange?.key ?? ""}:${recommendedChange?.value ?? ""}`;
2188
+ const defaults = useMemo2(() => report ? initialValues(report, recommendedChange) : {}, [valuesKey]);
2189
+ const [values, setValues] = useState5(defaults);
2190
+ useEffect7(() => setValues(defaults), [defaults]);
2191
+ useEffect7(() => {
2192
+ panel.current?.querySelector("select, button")?.focus({ preventScroll: true });
2193
+ const dismiss = (event) => {
2194
+ if (event.key === "Escape") {
2195
+ event.preventDefault();
2196
+ onClose();
2197
+ }
2198
+ };
2199
+ document.addEventListener("keydown", dismiss);
2200
+ return () => document.removeEventListener("keydown", dismiss);
2201
+ }, [onClose]);
2202
+ if (!report) return /* @__PURE__ */ jsx8("section", { ref: panel, class: "scui-settings", role: "dialog", "aria-modal": "true", "aria-labelledby": titleId, children: /* @__PURE__ */ jsxs7("header", { children: [
2203
+ /* @__PURE__ */ jsx8("button", { type: "button", "aria-label": "Close settings", onClick: onClose, children: /* @__PURE__ */ jsx8(UiIcon, { name: "close", size: 18 }) }),
2204
+ /* @__PURE__ */ jsxs7("span", { children: [
2205
+ /* @__PURE__ */ jsx8("strong", { id: titleId, children: "Harness settings" }),
2206
+ /* @__PURE__ */ jsx8("small", { children: state.interopSettingsError ?? "No interoperability controls are available." })
2207
+ ] })
2208
+ ] }) });
2209
+ 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 }] : []);
2210
+ const submit = (event) => {
2211
+ event.preventDefault();
2212
+ if (!changed.length || !state.canConfigureSettings) return;
2213
+ adapter.onIntent({ action: "configureHarness", harness: report.harness, changes: changed, expectedRevision: report.revision });
2214
+ };
2215
+ return /* @__PURE__ */ jsxs7("section", { ref: panel, class: "scui-settings", role: "dialog", "aria-modal": "true", "aria-labelledby": titleId, children: [
2216
+ /* @__PURE__ */ jsxs7("header", { children: [
2217
+ /* @__PURE__ */ jsx8("button", { type: "button", "aria-label": "Close settings", onClick: onClose, children: /* @__PURE__ */ jsx8(UiIcon, { name: "close", size: 18 }) }),
2218
+ /* @__PURE__ */ jsxs7("span", { children: [
2219
+ /* @__PURE__ */ jsxs7("strong", { id: titleId, children: [
2220
+ harnessDisplayName(report.harness),
2221
+ " interoperability"
2222
+ ] }),
2223
+ /* @__PURE__ */ jsx8("small", { children: "Native harness settings used by Supercode" })
2224
+ ] })
2225
+ ] }),
2226
+ /* @__PURE__ */ jsxs7("form", { onSubmit: submit, children: [
2227
+ report.controls.map((control) => {
2228
+ const choice = control.choices.find((item) => item.value === values[control.key]);
2229
+ const recommendation = report.advisories.map((advisory) => advisory.recommendation).find((item) => item.change.key === control.key && item.change.value === values[control.key]);
2230
+ const consequence = choice?.risk ?? recommendation?.consequence;
2231
+ return /* @__PURE__ */ jsxs7("fieldset", { disabled: !control.writable || Boolean(state.operation), children: [
2232
+ /* @__PURE__ */ jsxs7("label", { for: `scui-setting-${control.key}`, children: [
2233
+ /* @__PURE__ */ jsx8("strong", { children: control.label }),
2234
+ /* @__PURE__ */ jsx8("small", { children: control.description })
2235
+ ] }),
2236
+ /* @__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: [
2237
+ control.resettable ? /* @__PURE__ */ jsx8("option", { value: "@default", children: "Use harness default" }) : null,
2238
+ control.choices.map((item) => /* @__PURE__ */ jsx8("option", { value: item.value, children: item.label }, item.value))
2239
+ ] }),
2240
+ choice ? /* @__PURE__ */ jsx8("p", { children: choice.description }) : null,
2241
+ consequence ? /* @__PURE__ */ jsxs7("p", { class: "scui-setting-risk", children: [
2242
+ /* @__PURE__ */ jsx8("strong", { children: "Security consequence" }),
2243
+ consequence
2244
+ ] }) : null,
2245
+ /* @__PURE__ */ jsxs7("small", { class: "scui-setting-source", children: [
2246
+ control.effectiveNote,
2247
+ control.sourcePath ? ` Source: ${control.sourcePath}` : ""
2248
+ ] })
2249
+ ] }, control.key);
2250
+ }),
2251
+ /* @__PURE__ */ jsxs7("footer", { children: [
2252
+ /* @__PURE__ */ jsx8("button", { type: "button", onClick: onClose, children: "Cancel" }),
2253
+ /* @__PURE__ */ jsx8("button", { type: "submit", disabled: !changed.length || !state.canConfigureSettings || Boolean(state.operation), children: state.operation === "configureHarness" ? "Applying\u2026" : "Apply changes" })
2254
+ ] })
2255
+ ] })
2256
+ ] });
2257
+ }
2258
+
2259
+ // src/messenger.jsx
2260
+ import { jsx as jsx9, jsxs as jsxs8 } from "preact/jsx-runtime";
2073
2261
  var pendingMessageMemory = /* @__PURE__ */ new Map();
2074
2262
  var messengerViewMemory = /* @__PURE__ */ new Map();
2075
2263
  var newChatMemory = /* @__PURE__ */ new Map();
2076
- var TRACKED_ACTIONS = /* @__PURE__ */ new Set(["resume", "join", "detach", "branch", "reduce", "terminal", "export", "interrupt", "respond", "refresh", "loadEarlier", "loadSessions"]);
2264
+ var TRACKED_ACTIONS = /* @__PURE__ */ new Set(["resume", "join", "detach", "branch", "reduce", "terminal", "export", "interrupt", "respond", "configureHarness", "refresh", "loadEarlier", "loadSessions"]);
2077
2265
  function Receipt({ state, adapter }) {
2078
2266
  const receipt = state.reductionReceipt;
2079
- if (receipt) return /* @__PURE__ */ jsx8("div", { class: "scui-receipt", children: /* @__PURE__ */ jsxs7("span", { children: [
2080
- /* @__PURE__ */ jsx8("strong", { children: "Reduced and verified" }),
2081
- /* @__PURE__ */ jsxs7("small", { children: [
2267
+ if (receipt) return /* @__PURE__ */ jsx9("div", { class: "scui-receipt", children: /* @__PURE__ */ jsxs8("span", { children: [
2268
+ /* @__PURE__ */ jsx9("strong", { children: "Reduced and verified" }),
2269
+ /* @__PURE__ */ jsxs8("small", { children: [
2082
2270
  receipt.sourceTokens.toLocaleString(),
2083
2271
  " \u2192 ",
2084
2272
  receipt.reducedTokens.toLocaleString(),
@@ -2087,36 +2275,36 @@ function Receipt({ state, adapter }) {
2087
2275
  "\xD7 \xB7 reversible"
2088
2276
  ] })
2089
2277
  ] }) });
2090
- if (state.exportReceipt) return /* @__PURE__ */ jsxs7("div", { class: "scui-receipt", children: [
2091
- /* @__PURE__ */ jsxs7("span", { children: [
2092
- /* @__PURE__ */ jsxs7("strong", { children: [
2278
+ if (state.exportReceipt) return /* @__PURE__ */ jsxs8("div", { class: "scui-receipt", children: [
2279
+ /* @__PURE__ */ jsxs8("span", { children: [
2280
+ /* @__PURE__ */ jsxs8("strong", { children: [
2093
2281
  "Lossless export ready \xB7 ",
2094
2282
  harnessDisplayName(state.exportReceipt.targetHarness)
2095
2283
  ] }),
2096
- /* @__PURE__ */ jsxs7("small", { children: [
2284
+ /* @__PURE__ */ jsxs8("small", { children: [
2097
2285
  state.exportReceipt.path,
2098
2286
  " \xB7 ",
2099
2287
  state.exportReceipt.files,
2100
2288
  " files"
2101
2289
  ] })
2102
2290
  ] }),
2103
- /* @__PURE__ */ jsx8("button", { type: "button", onClick: () => adapter.copyText?.(state.exportReceipt.path), children: "Copy path" })
2291
+ /* @__PURE__ */ jsx9("button", { type: "button", onClick: () => adapter.copyText?.(state.exportReceipt.path), children: "Copy path" })
2104
2292
  ] });
2105
- if (state.terminalHandoff) return /* @__PURE__ */ jsxs7("div", { class: "scui-receipt", children: [
2106
- /* @__PURE__ */ jsxs7("span", { children: [
2107
- /* @__PURE__ */ jsx8("strong", { children: "Terminal handoff ready" }),
2108
- /* @__PURE__ */ jsx8("small", { children: state.terminalHandoff.cwd })
2293
+ if (state.terminalHandoff) return /* @__PURE__ */ jsxs8("div", { class: "scui-receipt", children: [
2294
+ /* @__PURE__ */ jsxs8("span", { children: [
2295
+ /* @__PURE__ */ jsx9("strong", { children: "Terminal handoff ready" }),
2296
+ /* @__PURE__ */ jsx9("small", { children: state.terminalHandoff.cwd })
2109
2297
  ] }),
2110
- /* @__PURE__ */ jsx8("button", { type: "button", onClick: () => adapter.copyText?.(terminalCommand(state.terminalHandoff)), children: "Copy command" })
2298
+ /* @__PURE__ */ jsx9("button", { type: "button", onClick: () => adapter.copyText?.(terminalCommand(state.terminalHandoff)), children: "Copy command" })
2111
2299
  ] });
2112
2300
  return null;
2113
2301
  }
2114
- function ConversationActions({ state, adapter, actionPending }) {
2115
- const [open, setOpen] = useState5(false);
2116
- const root = useRef6(null);
2117
- const panel = useRef6(null);
2118
- const trigger = useRef6(null);
2119
- const menuId = useId2();
2302
+ function ConversationActions({ state, adapter, actionPending, onSettings }) {
2303
+ const [open, setOpen] = useState6(false);
2304
+ const root = useRef7(null);
2305
+ const panel = useRef7(null);
2306
+ const trigger = useRef7(null);
2307
+ const menuId = useId3();
2120
2308
  const targets = state.harnesses.filter((item) => item.startable);
2121
2309
  const groups = [
2122
2310
  {
@@ -2124,7 +2312,8 @@ function ConversationActions({ state, adapter, actionPending }) {
2124
2312
  items: [
2125
2313
  state.canDetach ? { key: "detach", label: "Detach to read-only", intent: { action: "detach" } } : null,
2126
2314
  state.canOpenTerminal ? { key: "terminal", label: "Prepare terminal handoff", intent: { action: "terminal" } } : null,
2127
- state.canExport && state.exportBackTarget ? { key: "export", label: `Export back to ${harnessDisplayName(state.exportBackTarget)}`, intent: { action: "export", targetHarness: state.exportBackTarget } } : null
2315
+ state.canExport && state.exportBackTarget ? { key: "export", label: `Export back to ${harnessDisplayName(state.exportBackTarget)}`, intent: { action: "export", targetHarness: state.exportBackTarget } } : null,
2316
+ state.interopSettings || state.interopSettingsError ? { key: "settings", label: "Interoperability settings", onSelect: onSettings } : null
2128
2317
  ].filter(Boolean)
2129
2318
  },
2130
2319
  {
@@ -2136,7 +2325,7 @@ function ConversationActions({ state, adapter, actionPending }) {
2136
2325
  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 } })) : []
2137
2326
  }
2138
2327
  ].filter((group) => group.items.length);
2139
- useEffect7(() => {
2328
+ useEffect8(() => {
2140
2329
  if (!open) return;
2141
2330
  panel.current?.querySelector("button:not(:disabled)")?.focus({ preventScroll: true });
2142
2331
  const dismiss = (event) => {
@@ -2155,9 +2344,10 @@ function ConversationActions({ state, adapter, actionPending }) {
2155
2344
  document.removeEventListener("pointerdown", dismiss, true);
2156
2345
  };
2157
2346
  }, [open]);
2158
- const dispatch = (intent) => {
2347
+ const dispatch = (item) => {
2159
2348
  setOpen(false);
2160
- adapter.onIntent(intent);
2349
+ if (item.onSelect) item.onSelect();
2350
+ else adapter.onIntent(item.intent);
2161
2351
  };
2162
2352
  const navigate = (event) => {
2163
2353
  if (event.key === "Escape") {
@@ -2175,62 +2365,63 @@ function ConversationActions({ state, adapter, actionPending }) {
2175
2365
  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;
2176
2366
  items[index].focus({ preventScroll: true });
2177
2367
  };
2178
- return /* @__PURE__ */ jsxs7("div", { class: "scui-menu", ref: root, onBlur: (event) => {
2368
+ return /* @__PURE__ */ jsxs8("div", { class: "scui-menu", ref: root, onBlur: (event) => {
2179
2369
  if (event.relatedTarget && !event.currentTarget.contains(event.relatedTarget)) setOpen(false);
2180
2370
  }, children: [
2181
- /* @__PURE__ */ jsx8("button", { ref: trigger, class: "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__ */ jsx8(UiIcon, { name: "menu", size: 18 }) }),
2182
- open ? /* @__PURE__ */ jsx8("div", { ref: panel, id: menuId, class: "scui-menu-panel", role: "menu", "aria-label": "Conversation actions", onKeyDown: navigate, children: groups.map((group) => /* @__PURE__ */ jsxs7("section", { role: "group", "aria-label": group.label, children: [
2183
- /* @__PURE__ */ jsx8("strong", { children: group.label }),
2184
- group.items.map((item) => /* @__PURE__ */ jsx8("button", { role: "menuitem", type: "button", disabled: actionPending, onClick: () => dispatch(item.intent), children: item.label }, item.key))
2371
+ /* @__PURE__ */ jsx9("button", { ref: trigger, class: "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 }) }),
2372
+ open ? /* @__PURE__ */ jsx9("div", { ref: panel, id: menuId, class: "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: [
2373
+ /* @__PURE__ */ jsx9("strong", { children: group.label }),
2374
+ group.items.map((item) => /* @__PURE__ */ jsx9("button", { role: "menuitem", type: "button", disabled: actionPending, onClick: () => dispatch(item), children: item.label }, item.key))
2185
2375
  ] }, group.label)) }) : null
2186
2376
  ] });
2187
2377
  }
2188
- function ChatHeader({ state, adapter, pendingStatus, actionPending, onBack, onNew, onClose }) {
2189
- const back = useRef6(null);
2378
+ function ChatHeader({ state, adapter, pendingStatus, actionPending, onBack, onNew, onClose, onSettings }) {
2379
+ const back = useRef7(null);
2190
2380
  const harness = state.attached?.harness ?? state.harness;
2191
2381
  const title = state.attached ? sessionDisplayName(state.attached) : harnessDisplayName(harness) || "Agent chat";
2192
2382
  const status = state.needsInput ? "Needs input" : pendingStatus === "failed" ? "Send failed" : state.busy ? "Working" : pendingStatus === "sending" ? "Sending" : pendingStatus === "editing" ? "Editing message" : state.mode === "mirror" ? "Read-only" : "Ready";
2193
- const menu = state.canDetach || state.canOpenTerminal || state.canBranch || state.canAttach || state.canExport || state.canReduce;
2194
- useEffect7(() => {
2383
+ const menu = state.canDetach || state.canOpenTerminal || state.canBranch || state.canAttach || state.canExport || state.canReduce || state.interopSettings || state.interopSettingsError;
2384
+ useEffect8(() => {
2195
2385
  if (state.mode === "mirror" && !state.canSend) back.current?.focus({ preventScroll: true });
2196
2386
  }, []);
2197
- return /* @__PURE__ */ jsxs7("header", { class: "scui-head scui-chat-head", children: [
2198
- /* @__PURE__ */ jsx8("button", { ref: back, type: "button", "aria-label": "Back to chats", onClick: onBack, children: /* @__PURE__ */ jsx8(UiIcon, { name: "back", size: 18 }) }),
2199
- /* @__PURE__ */ jsx8(HarnessLogo, { id: harness, size: 28 }),
2200
- /* @__PURE__ */ jsxs7("span", { class: "scui-head-copy", children: [
2201
- /* @__PURE__ */ jsx8("strong", { children: title }),
2202
- /* @__PURE__ */ jsxs7("small", { children: [
2387
+ return /* @__PURE__ */ jsxs8("header", { class: "scui-head scui-chat-head", children: [
2388
+ /* @__PURE__ */ jsx9("button", { ref: back, type: "button", "aria-label": "Back to chats", onClick: onBack, children: /* @__PURE__ */ jsx9(UiIcon, { name: "back", size: 18 }) }),
2389
+ /* @__PURE__ */ jsx9(HarnessLogo, { id: harness, size: 28 }),
2390
+ /* @__PURE__ */ jsxs8("span", { class: "scui-head-copy", children: [
2391
+ /* @__PURE__ */ jsx9("strong", { children: title }),
2392
+ /* @__PURE__ */ jsxs8("small", { children: [
2203
2393
  harnessDisplayName(harness),
2204
2394
  " \xB7 ",
2205
2395
  status
2206
2396
  ] })
2207
2397
  ] }),
2208
- menu ? /* @__PURE__ */ jsx8(ConversationActions, { state, adapter, actionPending }) : null,
2209
- /* @__PURE__ */ jsx8("button", { type: "button", "aria-label": "New chat", onClick: onNew, children: /* @__PURE__ */ jsx8(UiIcon, { name: "plus", size: 18 }) }),
2210
- onClose ? /* @__PURE__ */ jsx8("button", { type: "button", "aria-label": "Close", onClick: onClose, children: /* @__PURE__ */ jsx8(UiIcon, { name: "close", size: 18 }) }) : null
2398
+ menu ? /* @__PURE__ */ jsx9(ConversationActions, { state, adapter, actionPending, onSettings }) : null,
2399
+ /* @__PURE__ */ jsx9("button", { type: "button", "aria-label": "New chat", onClick: onNew, children: /* @__PURE__ */ jsx9(UiIcon, { name: "plus", size: 18 }) }),
2400
+ onClose ? /* @__PURE__ */ jsx9("button", { type: "button", "aria-label": "Close", onClick: onClose, children: /* @__PURE__ */ jsx9(UiIcon, { name: "close", size: 18 }) }) : null
2211
2401
  ] });
2212
2402
  }
2213
2403
  function Chat({ state, adapter, onBack, onNew, onClose, components, slots, labels }) {
2214
2404
  const Header = slots.header;
2215
2405
  const memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`;
2216
- const [pending, setPendingState] = useState5(() => pendingMessageMemory.get(memoryKey) ?? null);
2217
- const [pendingAction, setPendingAction] = useState5(null);
2218
- const [restoreDraft, setRestoreDraft] = useState5(null);
2219
- const restoreSequence = useRef6(0);
2220
- const actionSequence = useRef6(0);
2221
- const acknowledged = useRef6(/* @__PURE__ */ new Set());
2406
+ const [pending, setPendingState] = useState6(() => pendingMessageMemory.get(memoryKey) ?? null);
2407
+ const [pendingAction, setPendingAction] = useState6(null);
2408
+ const [restoreDraft, setRestoreDraft] = useState6(null);
2409
+ const [settings, setSettings] = useState6(null);
2410
+ const restoreSequence = useRef7(0);
2411
+ const actionSequence = useRef7(0);
2412
+ const acknowledged = useRef7(/* @__PURE__ */ new Set());
2222
2413
  const setPending = (update) => setPendingState((current) => {
2223
2414
  const next = typeof update === "function" ? update(current) : update;
2224
2415
  if (next) boundedSet(pendingMessageMemory, memoryKey, next);
2225
2416
  else pendingMessageMemory.delete(memoryKey);
2226
2417
  return next;
2227
2418
  });
2228
- useEffect7(() => {
2419
+ useEffect8(() => {
2229
2420
  setPendingState(pendingMessageMemory.get(memoryKey) ?? null);
2230
2421
  setPendingAction(null);
2231
2422
  setRestoreDraft(null);
2232
2423
  }, [memoryKey]);
2233
- useEffect7(() => {
2424
+ useEffect8(() => {
2234
2425
  if (!pending) return;
2235
2426
  if (state.transcript.some((entry) => entry.role === "user" && entry.text.trim() === pending.text.trim() && !pending.baselineIds.includes(entry.id))) {
2236
2427
  setPending(null);
@@ -2259,7 +2450,7 @@ function Chat({ state, adapter, onBack, onNew, onClose, components, slots, label
2259
2450
  setRestoreDraft({ id: restoreSequence.current, text: pending.text, context: pending.context, images: pending.images });
2260
2451
  setPending({ ...pending, status: "editing" });
2261
2452
  };
2262
- useEffect7(() => {
2453
+ useEffect8(() => {
2263
2454
  const key = state.attached?.key;
2264
2455
  if (!key || !state.attention.some((item) => item.key === key)) {
2265
2456
  if (key) acknowledged.current.delete(key);
@@ -2270,7 +2461,7 @@ function Chat({ state, adapter, onBack, onNew, onClose, components, slots, label
2270
2461
  adapter.onIntent({ action: "ack", key });
2271
2462
  }
2272
2463
  }, [adapter, state.attached?.key, state.attention]);
2273
- const trackedAdapter = useMemo2(() => ({
2464
+ const trackedAdapter = useMemo3(() => ({
2274
2465
  ...adapter,
2275
2466
  onIntent(intent) {
2276
2467
  if (!TRACKED_ACTIONS.has(intent.action)) return adapter.onIntent(intent);
@@ -2294,7 +2485,7 @@ function Chat({ state, adapter, onBack, onNew, onClose, components, slots, label
2294
2485
  return result;
2295
2486
  }
2296
2487
  }), [adapter, state.error]);
2297
- useEffect7(() => {
2488
+ useEffect8(() => {
2298
2489
  if (!pendingAction) return;
2299
2490
  if (state.operation && !pendingAction.seenOperation) {
2300
2491
  setPendingAction({ ...pendingAction, seenOperation: true });
@@ -2307,44 +2498,48 @@ function Chat({ state, adapter, onBack, onNew, onClose, components, slots, label
2307
2498
  const actionLabel = operationLabel(action);
2308
2499
  const actionState = action ? { ...state, operation: action, canInterrupt: false, canRespond: false } : state;
2309
2500
  const unreadAfterMessages = state.attention.find((item) => item.key === state.attached?.key)?.afterMessages ?? null;
2310
- return /* @__PURE__ */ jsxs7("section", { class: "scui-chat", children: [
2311
- Header ? /* @__PURE__ */ jsx8(Header, { state: actionState, adapter: trackedAdapter, value: null }) : /* @__PURE__ */ jsx8(ChatHeader, { state: actionState, adapter: trackedAdapter, pendingStatus: pending?.status ?? null, actionPending: Boolean(action), onBack, onNew, onClose }),
2312
- actionLabel ? /* @__PURE__ */ jsxs7("div", { class: "scui-operation", role: "status", children: [
2313
- /* @__PURE__ */ jsx8("i", {}),
2501
+ const Advisory = components.HarnessAdvisory ?? HarnessAdvisory;
2502
+ const SettingsPanel = components.HarnessSettingsPanel ?? HarnessSettingsPanel;
2503
+ return /* @__PURE__ */ jsxs8("section", { class: "scui-chat", children: [
2504
+ 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 }) }),
2505
+ actionLabel ? /* @__PURE__ */ jsxs8("div", { class: "scui-operation", role: "status", children: [
2506
+ /* @__PURE__ */ jsx9("i", {}),
2314
2507
  actionLabel
2315
2508
  ] }) : null,
2316
- state.error ? /* @__PURE__ */ jsxs7("div", { class: "scui-error", role: "alert", children: [
2317
- /* @__PURE__ */ jsx8("span", { children: state.error }),
2318
- state.recoverable ? /* @__PURE__ */ jsx8("button", { type: "button", disabled: Boolean(action), onClick: () => trackedAdapter.onIntent({ action: "refresh" }), children: "Retry" }) : null
2509
+ state.error ? /* @__PURE__ */ jsxs8("div", { class: "scui-error", role: "alert", children: [
2510
+ /* @__PURE__ */ jsx9("span", { children: state.error }),
2511
+ state.recoverable ? /* @__PURE__ */ jsx9("button", { type: "button", disabled: Boolean(action), onClick: () => trackedAdapter.onIntent({ action: "refresh" }), children: "Retry" }) : null
2319
2512
  ] }) : null,
2320
- /* @__PURE__ */ jsx8(Receipt, { state, adapter }),
2321
- /* @__PURE__ */ jsx8(Conversation, { state: actionState, adapter: trackedAdapter, components, slots, memoryKey, pending: pendingMessage, unreadAfterMessages }, memoryKey),
2322
- /* @__PURE__ */ jsx8(ContinuationBar, { state: actionState, adapter: trackedAdapter, labels }),
2323
- state.mode !== "mirror" || state.canSend ? /* @__PURE__ */ jsx8(Composer, { state: actionState, adapter: trackedAdapter, labels, memoryKey, pendingStatus: pending?.status ?? null, restoreDraft, onDraftRestored: (id) => setRestoreDraft((value) => value?.id === id ? null : value), onPending: beginPending }, memoryKey) : null
2513
+ /* @__PURE__ */ jsx9(Receipt, { state, adapter }),
2514
+ /* @__PURE__ */ jsx9(Conversation, { state: actionState, adapter: trackedAdapter, components, slots, memoryKey, pending: pendingMessage, unreadAfterMessages }, memoryKey),
2515
+ /* @__PURE__ */ jsx9(ContinuationBar, { state: actionState, adapter: trackedAdapter, labels }),
2516
+ /* @__PURE__ */ jsx9(Advisory, { state: actionState, adapter: trackedAdapter, value: state.interopSettings?.advisories[0] ?? null, onReview: (recommendedChange) => setSettings({ recommendedChange }) }),
2517
+ state.mode !== "mirror" || state.canSend ? /* @__PURE__ */ jsx9(Composer, { state: actionState, adapter: trackedAdapter, labels, memoryKey, pendingStatus: pending?.status ?? null, restoreDraft, onDraftRestored: (id) => setRestoreDraft((value) => value?.id === id ? null : value), onPending: beginPending }, memoryKey) : null,
2518
+ settings ? /* @__PURE__ */ jsx9(SettingsPanel, { state: actionState, adapter: trackedAdapter, value: state.interopSettings, recommendedChange: settings.recommendedChange, onClose: () => setSettings(null) }) : null
2324
2519
  ] });
2325
2520
  }
2326
2521
  function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey }) {
2327
2522
  const startable = state.harnesses.filter((item) => item.startable);
2328
2523
  const startableKey = startable.map((item) => item.id).join("\0");
2329
2524
  const remembered = newChatMemory.get(memoryKey) ?? { harness: startable[0]?.id ?? "", draft: "", context: [], images: [] };
2330
- const [harness, setHarness] = useState5(remembered.harness);
2331
- const [draft, setDraft] = useState5(remembered.draft);
2332
- const [context, setContext] = useState5(remembered.context);
2333
- const [images, setImages] = useState5(remembered.images ?? []);
2334
- const [starting, setStarting] = useState5(null);
2335
- const [picking, setPicking] = useState5(false);
2336
- const [dragging, setDragging] = useState5(false);
2337
- const [pickerError, setPickerError] = useState5(null);
2338
- const startSequence = useRef6(0);
2339
- const textarea = useRef6(null);
2525
+ const [harness, setHarness] = useState6(remembered.harness);
2526
+ const [draft, setDraft] = useState6(remembered.draft);
2527
+ const [context, setContext] = useState6(remembered.context);
2528
+ const [images, setImages] = useState6(remembered.images ?? []);
2529
+ const [starting, setStarting] = useState6(null);
2530
+ const [picking, setPicking] = useState6(false);
2531
+ const [dragging, setDragging] = useState6(false);
2532
+ const [pickerError, setPickerError] = useState6(null);
2533
+ const startSequence = useRef7(0);
2534
+ const textarea = useRef7(null);
2340
2535
  useAutosizeTextarea(textarea, draft);
2341
- useEffect7(() => {
2536
+ useEffect8(() => {
2342
2537
  if (startable.some((item) => item.id === harness)) return;
2343
2538
  const next = startable[0]?.id ?? "";
2344
2539
  setHarness(next);
2345
2540
  boundedSet(newChatMemory, memoryKey, { harness: next, draft, context, images });
2346
2541
  }, [harness, startableKey]);
2347
- useEffect7(() => {
2542
+ useEffect8(() => {
2348
2543
  if (!starting) return;
2349
2544
  const sessionChanged = (state.attached?.key ?? null) !== starting.attachedKey;
2350
2545
  const beganWorking = !starting.busy && state.busy;
@@ -2352,10 +2547,10 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
2352
2547
  newChatMemory.delete(memoryKey);
2353
2548
  onStarted();
2354
2549
  }, [memoryKey, onStarted, starting, state.attached?.key, state.busy]);
2355
- useEffect7(() => {
2550
+ useEffect8(() => {
2356
2551
  if (starting && state.error !== starting.initialError && !state.busy && !state.operation) setStarting(null);
2357
2552
  }, [starting, state.busy, state.error, state.operation]);
2358
- useEffect7(() => {
2553
+ useEffect8(() => {
2359
2554
  textarea.current?.focus({ preventScroll: true });
2360
2555
  }, []);
2361
2556
  const pickContext = () => {
@@ -2417,49 +2612,49 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
2417
2612
  Promise.resolve(result).catch(() => setStarting((current) => current?.id === id ? null : current));
2418
2613
  }
2419
2614
  };
2420
- return /* @__PURE__ */ jsxs7("section", { class: "scui-chat", children: [
2421
- /* @__PURE__ */ jsxs7("header", { class: "scui-head", children: [
2422
- /* @__PURE__ */ jsx8("button", { type: "button", "aria-label": "Back", onClick: onBack, children: /* @__PURE__ */ jsx8(UiIcon, { name: "back", size: 18 }) }),
2423
- /* @__PURE__ */ jsxs7("span", { class: "scui-head-copy", children: [
2424
- /* @__PURE__ */ jsx8("strong", { children: labels.newChat }),
2425
- /* @__PURE__ */ jsx8("small", { children: "No session is created until you send" })
2615
+ return /* @__PURE__ */ jsxs8("section", { class: "scui-chat", children: [
2616
+ /* @__PURE__ */ jsxs8("header", { class: "scui-head", children: [
2617
+ /* @__PURE__ */ jsx9("button", { type: "button", "aria-label": "Back", onClick: onBack, children: /* @__PURE__ */ jsx9(UiIcon, { name: "back", size: 18 }) }),
2618
+ /* @__PURE__ */ jsxs8("span", { class: "scui-head-copy", children: [
2619
+ /* @__PURE__ */ jsx9("strong", { children: labels.newChat }),
2620
+ /* @__PURE__ */ jsx9("small", { children: "No session is created until you send" })
2426
2621
  ] }),
2427
- onClose ? /* @__PURE__ */ jsx8("button", { type: "button", "aria-label": "Close", onClick: onClose, children: /* @__PURE__ */ jsx8(UiIcon, { name: "close", size: 18 }) }) : null
2622
+ onClose ? /* @__PURE__ */ jsx9("button", { type: "button", "aria-label": "Close", onClick: onClose, children: /* @__PURE__ */ jsx9(UiIcon, { name: "close", size: 18 }) }) : null
2428
2623
  ] }),
2429
- starting ? /* @__PURE__ */ jsxs7("div", { class: "scui-operation", role: "status", children: [
2430
- /* @__PURE__ */ jsx8("i", {}),
2624
+ starting ? /* @__PURE__ */ jsxs8("div", { class: "scui-operation", role: "status", children: [
2625
+ /* @__PURE__ */ jsx9("i", {}),
2431
2626
  operationLabel("start")
2432
2627
  ] }) : null,
2433
- /* @__PURE__ */ jsxs7("div", { class: "scui-new", children: [
2434
- /* @__PURE__ */ jsx8("span", { "aria-hidden": "true", children: "\u2726" }),
2435
- /* @__PURE__ */ jsx8("strong", { children: "What should the agent build or fix?" }),
2436
- /* @__PURE__ */ jsx8("small", { children: "Choose a coding harness and send the first message." })
2628
+ /* @__PURE__ */ jsxs8("div", { class: "scui-new", children: [
2629
+ /* @__PURE__ */ jsx9("span", { "aria-hidden": "true", children: "\u2726" }),
2630
+ /* @__PURE__ */ jsx9("strong", { children: "What should the agent build or fix?" }),
2631
+ /* @__PURE__ */ jsx9("small", { children: "Choose a coding harness and send the first message." })
2437
2632
  ] }),
2438
- /* @__PURE__ */ jsxs7("div", { class: "scui-compose", children: [
2439
- /* @__PURE__ */ jsxs7("label", { class: "scui-harness-picker", children: [
2440
- /* @__PURE__ */ jsx8(HarnessLogo, { id: harness, size: 24 }),
2441
- /* @__PURE__ */ jsx8("span", { children: "Coding harness" }),
2442
- /* @__PURE__ */ jsx8("select", { value: harness, disabled: Boolean(starting), onChange: (event) => {
2633
+ /* @__PURE__ */ jsxs8("div", { class: "scui-compose", children: [
2634
+ /* @__PURE__ */ jsxs8("label", { class: "scui-harness-picker", children: [
2635
+ /* @__PURE__ */ jsx9(HarnessLogo, { id: harness, size: 24 }),
2636
+ /* @__PURE__ */ jsx9("span", { children: "Coding harness" }),
2637
+ /* @__PURE__ */ jsx9("select", { value: harness, disabled: Boolean(starting), onChange: (event) => {
2443
2638
  const value = event.currentTarget.value;
2444
2639
  setHarness(value);
2445
2640
  boundedSet(newChatMemory, memoryKey, { harness: value, draft, context, images });
2446
- }, children: state.harnesses.map((item) => /* @__PURE__ */ jsxs7("option", { value: item.id, disabled: !item.startable, children: [
2641
+ }, children: state.harnesses.map((item) => /* @__PURE__ */ jsxs8("option", { value: item.id, disabled: !item.startable, children: [
2447
2642
  item.label,
2448
2643
  item.startable ? "" : " \xB7 unavailable"
2449
2644
  ] }, item.id)) })
2450
2645
  ] }),
2451
- /* @__PURE__ */ jsx8(ImageTray, { items: images, onRemove: (index) => setImages((items) => {
2646
+ /* @__PURE__ */ jsx9(ImageTray, { items: images, onRemove: (index) => setImages((items) => {
2452
2647
  const next = items.filter((_, itemIndex) => itemIndex !== index);
2453
2648
  boundedSet(newChatMemory, memoryKey, { harness, draft, context, images: next });
2454
2649
  return next;
2455
2650
  }) }),
2456
- /* @__PURE__ */ jsx8(ContextTray, { items: context, onRemove: (index) => setContext((items) => {
2651
+ /* @__PURE__ */ jsx9(ContextTray, { items: context, onRemove: (index) => setContext((items) => {
2457
2652
  const next = items.filter((_, itemIndex) => itemIndex !== index);
2458
2653
  boundedSet(newChatMemory, memoryKey, { harness, draft, context: next, images });
2459
2654
  return next;
2460
2655
  }) }),
2461
- pickerError ? /* @__PURE__ */ jsx8("small", { class: "scui-context-error", role: "alert", children: pickerError }) : null,
2462
- /* @__PURE__ */ jsxs7("div", { class: `scui-envelope${dragging ? " scui-drop-target" : ""}`, onDragEnter: (event) => {
2656
+ pickerError ? /* @__PURE__ */ jsx9("small", { class: "scui-context-error", role: "alert", children: pickerError }) : null,
2657
+ /* @__PURE__ */ jsxs8("div", { class: `scui-envelope${dragging ? " scui-drop-target" : ""}`, onDragEnter: (event) => {
2463
2658
  if (Array.from(event.dataTransfer?.types ?? []).includes("Files")) {
2464
2659
  event.preventDefault();
2465
2660
  setDragging(true);
@@ -2469,8 +2664,8 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
2469
2664
  }, onDragLeave: (event) => {
2470
2665
  if (!event.currentTarget.contains(event.relatedTarget)) setDragging(false);
2471
2666
  }, onDrop: dropImages, children: [
2472
- adapter.pickContext ? /* @__PURE__ */ jsx8("button", { class: "scui-attach", type: "button", "aria-label": "Attach files or images", disabled: picking || context.length >= MAX_CONTEXT_ITEMS && images.length >= MAX_IMAGE_ITEMS || Boolean(starting), onClick: pickContext, children: picking ? /* @__PURE__ */ jsx8("i", { class: "scui-control-spinner" }) : /* @__PURE__ */ jsx8(UiIcon, { name: "attach", size: 17 }) }) : null,
2473
- /* @__PURE__ */ jsx8("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) => {
2667
+ adapter.pickContext ? /* @__PURE__ */ jsx9("button", { class: "scui-attach", type: "button", "aria-label": "Attach files or images", disabled: picking || context.length >= MAX_CONTEXT_ITEMS && images.length >= MAX_IMAGE_ITEMS || Boolean(starting), onClick: pickContext, children: picking ? /* @__PURE__ */ jsx9("i", { class: "scui-control-spinner" }) : /* @__PURE__ */ jsx9(UiIcon, { name: "attach", size: 17 }) }) : null,
2668
+ /* @__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) => {
2474
2669
  const value = event.currentTarget.value;
2475
2670
  setDraft(value);
2476
2671
  boundedSet(newChatMemory, memoryKey, { harness, draft: value, context, images });
@@ -2480,23 +2675,23 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
2480
2675
  send();
2481
2676
  }
2482
2677
  } }),
2483
- /* @__PURE__ */ jsx8("span", { children: /* @__PURE__ */ jsx8("button", { type: "button", class: "scui-send", "aria-label": "Start chat", disabled: !draft.trim() && !images.length || !harness || Boolean(starting), onClick: send, children: starting ? /* @__PURE__ */ jsx8("i", { class: "scui-control-spinner" }) : /* @__PURE__ */ jsx8(UiIcon, { name: "send", size: 17 }) }) })
2678
+ /* @__PURE__ */ jsx9("span", { children: /* @__PURE__ */ jsx9("button", { type: "button", class: "scui-send", "aria-label": "Start chat", disabled: !draft.trim() && !images.length || !harness || Boolean(starting), onClick: send, children: starting ? /* @__PURE__ */ jsx9("i", { class: "scui-control-spinner" }) : /* @__PURE__ */ jsx9(UiIcon, { name: "send", size: 17 }) }) })
2484
2679
  ] })
2485
2680
  ] })
2486
2681
  ] });
2487
2682
  }
2488
2683
  function SupercodeMessenger({ state: stateInput, adapter, class: className = "", initialView, labels, components = {}, slots = {} }) {
2489
- const state = useMemo2(() => normalizeUiState(stateInput), [stateInput]);
2684
+ const state = useMemo3(() => normalizeUiState(stateInput), [stateInput]);
2490
2685
  const copy = { ...DEFAULT_LABELS, ...labels };
2491
2686
  const memoryKey = state.workspace || "@default";
2492
- const [view, setViewState] = useState5(initialView ?? (state.attention.length ? "list" : messengerViewMemory.get(memoryKey) ?? (state.attached || state.transcript.length ? "chat" : "list")));
2493
- const [opening, setOpening] = useState5(null);
2494
- const [listFocus, setListFocus] = useState5(null);
2687
+ const [view, setViewState] = useState6(initialView ?? (state.attention.length ? "list" : messengerViewMemory.get(memoryKey) ?? (state.attached || state.transcript.length ? "chat" : "list")));
2688
+ const [opening, setOpening] = useState6(null);
2689
+ const [listFocus, setListFocus] = useState6(null);
2495
2690
  const setView = (next) => {
2496
2691
  boundedSet(messengerViewMemory, memoryKey, next);
2497
2692
  setViewState(next);
2498
2693
  };
2499
- useEffect7(() => {
2694
+ useEffect8(() => {
2500
2695
  if (!opening) return;
2501
2696
  if (state.attached?.key === opening.key) {
2502
2697
  setOpening(null);
@@ -2512,40 +2707,40 @@ function SupercodeMessenger({ state: stateInput, adapter, class: className = "",
2512
2707
  };
2513
2708
  const close = () => adapter.onClose?.();
2514
2709
  const Footer = slots.footer;
2515
- return /* @__PURE__ */ jsxs7("main", { class: `scui-root ${className}`, "data-view": view, "data-mode": state.mode, "aria-label": "Supercode messenger", children: [
2516
- view === "list" ? /* @__PURE__ */ jsx8(SessionList, { state, adapter, focusKey: listFocus, onOpen: open, onNew: () => {
2710
+ return /* @__PURE__ */ jsxs8("main", { class: `scui-root ${className}`, "data-view": view, "data-mode": state.mode, "aria-label": "Supercode messenger", children: [
2711
+ view === "list" ? /* @__PURE__ */ jsx9(SessionList, { state, adapter, focusKey: listFocus, onOpen: open, onNew: () => {
2517
2712
  setListFocus("@new");
2518
2713
  setView("new");
2519
2714
  }, onClose: adapter.onClose ? close : void 0, components, labels: copy, memoryKey }) : null,
2520
- view === "new" ? /* @__PURE__ */ jsx8(NewChat, { state, adapter, onBack: () => setView("list"), onClose: adapter.onClose ? close : void 0, onStarted: () => setView("chat"), labels: copy, memoryKey }) : null,
2521
- view === "chat" ? /* @__PURE__ */ jsx8(Chat, { state, adapter, onBack: () => {
2715
+ view === "new" ? /* @__PURE__ */ jsx9(NewChat, { state, adapter, onBack: () => setView("list"), onClose: adapter.onClose ? close : void 0, onStarted: () => setView("chat"), labels: copy, memoryKey }) : null,
2716
+ view === "chat" ? /* @__PURE__ */ jsx9(Chat, { state, adapter, onBack: () => {
2522
2717
  setListFocus(state.attached?.key ?? listFocus);
2523
2718
  setView("list");
2524
2719
  }, onNew: () => {
2525
2720
  setListFocus("@new");
2526
2721
  setView("new");
2527
2722
  }, onClose: adapter.onClose ? close : void 0, components, slots, labels: copy }) : null,
2528
- opening ? /* @__PURE__ */ jsxs7("div", { class: "scui-opening", role: "status", "aria-busy": "true", children: [
2529
- /* @__PURE__ */ jsx8(HarnessLogo, { id: opening.harness, size: 34 }),
2530
- /* @__PURE__ */ jsxs7("span", { children: [
2531
- /* @__PURE__ */ jsxs7("strong", { children: [
2723
+ opening ? /* @__PURE__ */ jsxs8("div", { class: "scui-opening", role: "status", "aria-busy": "true", children: [
2724
+ /* @__PURE__ */ jsx9(HarnessLogo, { id: opening.harness, size: 34 }),
2725
+ /* @__PURE__ */ jsxs8("span", { children: [
2726
+ /* @__PURE__ */ jsxs8("strong", { children: [
2532
2727
  "Opening ",
2533
2728
  sessionDisplayName(opening)
2534
2729
  ] }),
2535
- /* @__PURE__ */ jsx8("small", { children: "Loading the latest transcript window\u2026" })
2730
+ /* @__PURE__ */ jsx9("small", { children: "Loading the latest transcript window\u2026" })
2536
2731
  ] }),
2537
- /* @__PURE__ */ jsx8("i", {})
2732
+ /* @__PURE__ */ jsx9("i", {})
2538
2733
  ] }) : null,
2539
- Footer ? /* @__PURE__ */ jsx8(Footer, { state, adapter, value: copy }) : null
2734
+ Footer ? /* @__PURE__ */ jsx9(Footer, { state, adapter, value: copy }) : null
2540
2735
  ] });
2541
2736
  }
2542
2737
 
2543
2738
  // src/embed.jsx
2544
- import { jsx as jsx9 } from "preact/jsx-runtime";
2739
+ import { jsx as jsx10 } from "preact/jsx-runtime";
2545
2740
  function mountSupercodeMessenger(element, options) {
2546
2741
  if (!(element instanceof Element)) throw new TypeError("mountSupercodeMessenger requires an Element");
2547
2742
  let state = options.state;
2548
- const draw = () => render(/* @__PURE__ */ jsx9(SupercodeMessenger, { ...options, state }), element);
2743
+ const draw = () => render(/* @__PURE__ */ jsx10(SupercodeMessenger, { ...options, state }), element);
2549
2744
  options.adapter.onIntent({ action: "mounted" });
2550
2745
  draw();
2551
2746
  return {