@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/components.mjs CHANGED
@@ -40,6 +40,7 @@ var EMPTY_UI_STATE = Object.freeze({
40
40
  canReduce: false,
41
41
  canInterrupt: false,
42
42
  canRespond: false,
43
+ canConfigureSettings: false,
43
44
  messaging: null,
44
45
  workspace: "",
45
46
  taskPlan: Object.freeze({ source: "none", items: Object.freeze([]), residueCount: 0, observedAt: null }),
@@ -48,6 +49,8 @@ var EMPTY_UI_STATE = Object.freeze({
48
49
  exportBackTarget: null,
49
50
  exportReceipt: null,
50
51
  reductionReceipt: null,
52
+ interopSettings: null,
53
+ interopSettingsError: null,
51
54
  error: null,
52
55
  recoverable: false,
53
56
  harnesses: Object.freeze([]),
@@ -81,7 +84,16 @@ function number(value, fallback = 0) {
81
84
  function nullableNumber(value) {
82
85
  return typeof value === "number" && Number.isFinite(value) ? value : null;
83
86
  }
84
- function boundedString(value, max) {
87
+ function relativeAge(updatedAt, now = Date.now()) {
88
+ if (typeof updatedAt !== "number" || !Number.isFinite(updatedAt) || updatedAt <= 0) return "";
89
+ const delta = Math.max(0, now - updatedAt);
90
+ if (delta < 6e4) return "now";
91
+ if (delta < 36e5) return `${Math.floor(delta / 6e4)}m ago`;
92
+ if (delta < 864e5) return `${Math.floor(delta / 36e5)}h ago`;
93
+ if (delta < 6048e5) return `${Math.floor(delta / 864e5)}d ago`;
94
+ return `${Math.floor(delta / 6048e5)}w ago`;
95
+ }
96
+ function boundedString(value, max = 2e3) {
85
97
  if (typeof value !== "string") return "";
86
98
  return value.length <= max ? value : `${value.slice(0, max)}\u2026`;
87
99
  }
@@ -546,11 +558,12 @@ function readSessions(value) {
546
558
  title: string(row.title),
547
559
  preview: string(row.preview),
548
560
  age: string(row.age),
561
+ previewUpdatedAt: nullableNumber(row.previewUpdatedAt),
549
562
  updatedAt: nullableNumber(row.updatedAt),
550
563
  messages: nullableNumber(row.messages),
551
564
  active: row.active === true,
552
565
  live: row.live === true,
553
- runtimeStatus: row.runtimeStatus === "busy" || row.runtimeStatus === "idle" ? row.runtimeStatus : null
566
+ runtimeStatus: row.runtimeStatus === "running" || row.runtimeStatus === "busy" || row.runtimeStatus === "idle" ? row.runtimeStatus : null
554
567
  }];
555
568
  });
556
569
  }
@@ -624,6 +637,68 @@ function readReductionReceipt(value) {
624
637
  targetHarness: receipt.targetHarness
625
638
  };
626
639
  }
640
+ function readInteropSettings(value) {
641
+ const report = record(value);
642
+ 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;
643
+ const controls = report.controls.slice(0, 20).flatMap((raw) => {
644
+ const control = record(raw);
645
+ if (!control || typeof control.key !== "string" || typeof control.label !== "string") return [];
646
+ return [{
647
+ key: boundedString(control.key, 200),
648
+ nativeKey: boundedString(control.native_key ?? control.nativeKey, 200),
649
+ label: boundedString(control.label, 300),
650
+ description: boundedString(control.description),
651
+ scope: ["user", "project", "managed", "command_line"].includes(control.scope) ? control.scope : "user",
652
+ sourcePath: boundedString(control.source_path ?? control.sourcePath, 4e3),
653
+ configuredValue: typeof (control.configured_value ?? control.configuredValue) === "string" ? boundedString(control.configured_value ?? control.configuredValue, 500) : null,
654
+ effectiveValue: typeof (control.effective_value ?? control.effectiveValue) === "string" ? boundedString(control.effective_value ?? control.effectiveValue, 500) : null,
655
+ effectiveKnown: (control.effective_known ?? control.effectiveKnown) === true,
656
+ effectiveNote: boundedString(control.effective_note ?? control.effectiveNote),
657
+ choices: Array.isArray(control.choices) ? control.choices.slice(0, 20).flatMap((candidate) => {
658
+ const choice = record(candidate);
659
+ return choice && typeof choice.value === "string" && typeof choice.label === "string" ? [{
660
+ value: boundedString(choice.value, 500),
661
+ label: boundedString(choice.label, 300),
662
+ description: boundedString(choice.description),
663
+ ...typeof choice.risk === "string" ? { risk: boundedString(choice.risk) } : {}
664
+ }] : [];
665
+ }) : [],
666
+ writable: control.writable === true,
667
+ resettable: control.resettable === true,
668
+ requiresRestart: (control.requires_restart ?? control.requiresRestart) === true
669
+ }];
670
+ });
671
+ const advisories = report.advisories.slice(0, 20).flatMap((raw) => {
672
+ const advisory = record(raw);
673
+ const recommendation = record(advisory?.recommendation);
674
+ const change = record(recommendation?.change);
675
+ if (!advisory || !recommendation || !change || typeof advisory.code !== "string" || typeof advisory.title !== "string" || typeof advisory.setting !== "string" || typeof change.key !== "string") return [];
676
+ return [{
677
+ code: boundedString(advisory.code, 200),
678
+ severity: ["info", "warning", "error"].includes(advisory.severity) ? advisory.severity : "warning",
679
+ title: boundedString(advisory.title, 500),
680
+ message: boundedString(advisory.message),
681
+ setting: boundedString(advisory.setting, 200),
682
+ recommendation: {
683
+ label: boundedString(recommendation.label, 500),
684
+ description: boundedString(recommendation.description),
685
+ consequence: boundedString(recommendation.consequence),
686
+ change: {
687
+ key: boundedString(change.key, 200),
688
+ value: typeof change.value === "string" ? boundedString(change.value, 500) : null
689
+ },
690
+ command: boundedString(recommendation.command, 4e3)
691
+ }
692
+ }];
693
+ });
694
+ return {
695
+ schema: "supercode.harness-interop-settings.v1",
696
+ harness: report.harness,
697
+ revision: boundedString(report.revision, 500),
698
+ controls,
699
+ advisories
700
+ };
701
+ }
627
702
  function normalizeUiState(value) {
628
703
  const raw = record(value) ?? {};
629
704
  const pill = record(raw.pill);
@@ -649,6 +724,7 @@ function normalizeUiState(value) {
649
724
  canReduce: raw.canReduce === true,
650
725
  canInterrupt: raw.canInterrupt === true,
651
726
  canRespond: raw.canRespond === true,
727
+ canConfigureSettings: raw.canConfigureSettings === true,
652
728
  messaging: raw.messaging === "live_peer" ? "live_peer" : null,
653
729
  workspace: string(raw.workspace),
654
730
  taskPlan: readTaskPlan(raw.taskPlan),
@@ -657,6 +733,8 @@ function normalizeUiState(value) {
657
733
  exportBackTarget: typeof raw.exportBackTarget === "string" ? raw.exportBackTarget : null,
658
734
  exportReceipt: readExportReceipt(raw.exportReceipt),
659
735
  reductionReceipt: readReductionReceipt(raw.reductionReceipt),
736
+ interopSettings: readInteropSettings(raw.interopSettings),
737
+ interopSettingsError: typeof raw.interopSettingsError === "string" ? boundedString(raw.interopSettingsError) : null,
660
738
  error: typeof raw.error === "string" ? raw.error : null,
661
739
  recoverable: raw.recoverable === true,
662
740
  harnesses: Array.isArray(raw.harnesses) ? raw.harnesses.flatMap((candidate) => {
@@ -688,6 +766,7 @@ function sessionActivity(state, row) {
688
766
  const attention = state.attention.find((item) => item.key === row.key)?.kind;
689
767
  if (attention) return attention;
690
768
  if (row.runtimeStatus === "busy") return "working";
769
+ if (row.runtimeStatus === "running") return "running";
691
770
  if (row.live || row.runtimeStatus === "idle") return "recent";
692
771
  return "idle";
693
772
  }
@@ -745,11 +824,11 @@ function activitySummary(entries) {
745
824
  function canContinueHere(state) {
746
825
  if (state.mode !== "mirror" || state.canSend) return false;
747
826
  const row = state.attached?.key ? state.sessions.find((item) => item.key === state.attached.key) : null;
748
- return state.canResume && row?.runtimeStatus !== "busy" && row?.runtimeStatus !== "idle";
827
+ return state.canResume && row?.runtimeStatus !== "running" && row?.runtimeStatus !== "busy" && row?.runtimeStatus !== "idle";
749
828
  }
750
829
  function operationLabel(operation) {
751
830
  if (!operation) return "";
752
- 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" };
831
+ 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" };
753
832
  return labels[operation] ?? `${operation.replaceAll("_", " ")}\u2026`;
754
833
  }
755
834
  function terminalCommand(handoff) {
@@ -1092,7 +1171,7 @@ function ContinuationBar({ state, adapter, labels = DEFAULT_LABELS }) {
1092
1171
  if (state.mode !== "mirror" || state.canSend) return null;
1093
1172
  const attached = state.attached;
1094
1173
  const row = attached?.key ? state.sessions.find((item) => item.key === attached.key) : null;
1095
- const activeElsewhere = row?.runtimeStatus === "busy" || row?.runtimeStatus === "idle";
1174
+ const activeElsewhere = row?.runtimeStatus === "running" || row?.runtimeStatus === "busy" || row?.runtimeStatus === "idle";
1096
1175
  const resume = canContinueHere(state);
1097
1176
  const join = state.canAttach;
1098
1177
  const branch = state.canBranch;
@@ -1986,7 +2065,7 @@ function HarnessLogo({ id, activity, size = 28, onMissingLogo }) {
1986
2065
  }
1987
2066
 
1988
2067
  // src/messenger.jsx
1989
- import { useEffect as useEffect7, useId as useId2, useMemo as useMemo2, useRef as useRef6, useState as useState5 } from "preact/hooks";
2068
+ import { useEffect as useEffect8, useId as useId3, useMemo as useMemo3, useRef as useRef7, useState as useState6 } from "preact/hooks";
1990
2069
 
1991
2070
  // src/sessions.jsx
1992
2071
  import { useEffect as useEffect6, useLayoutEffect as useLayoutEffect3, useRef as useRef5, useState as useState4 } from "preact/hooks";
@@ -2003,28 +2082,35 @@ function sessionPathParts(value) {
2003
2082
  trailing: complete.slice(boundary + 1)
2004
2083
  };
2005
2084
  }
2006
- function SessionRow({ row, state, onOpen }) {
2085
+ function SessionRow({ row, state, onOpen, now = Date.now() }) {
2007
2086
  const activity = sessionActivity(state, row);
2087
+ const working = activity === "working";
2008
2088
  const attention = state.attention.find((item) => item.key === row.key);
2009
2089
  const title = sessionDisplayName(row);
2010
2090
  const path = sessionPathParts(row.cwd);
2011
2091
  const preview = row.preview || attention?.preview || "";
2012
2092
  const unreadCount = attention?.unreadCount ?? 0;
2013
2093
  const unreadLabel = unreadCount > 99 ? "99+" : String(unreadCount);
2014
- 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: [
2094
+ const age = relativeAge(row.previewUpdatedAt ?? row.updatedAt, now) || row.age;
2095
+ 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: [
2015
2096
  /* @__PURE__ */ jsx7(HarnessLogo, { id: row.harness, activity, size: 34 }),
2016
2097
  /* @__PURE__ */ jsxs6("span", { class: "scui-session-copy", children: [
2017
2098
  /* @__PURE__ */ jsxs6("span", { class: "scui-session-title", children: [
2018
2099
  /* @__PURE__ */ jsx7("strong", { children: title }),
2019
- row.age ? /* @__PURE__ */ jsx7("time", { children: row.age }) : null
2100
+ /* @__PURE__ */ jsx7("span", { class: "scui-session-meta", children: age ? /* @__PURE__ */ jsx7("time", { children: age }) : null })
2020
2101
  ] }),
2021
2102
  path.complete ? /* @__PURE__ */ jsxs6("small", { class: "scui-session-path", title: row.cwd, children: [
2022
2103
  /* @__PURE__ */ jsx7("span", { class: "scui-session-path-leading", children: path.leading }),
2023
2104
  path.separator ? /* @__PURE__ */ jsx7("span", { class: "scui-session-path-separator", children: path.separator }) : null,
2024
2105
  path.trailing ? /* @__PURE__ */ jsx7("span", { class: "scui-session-path-trailing", children: path.trailing }) : null
2025
2106
  ] }) : null,
2026
- preview || unreadCount ? /* @__PURE__ */ jsxs6("span", { class: "scui-session-preview", children: [
2027
- preview ? /* @__PURE__ */ jsx7("small", { children: preview }) : null,
2107
+ working || preview || unreadCount ? /* @__PURE__ */ jsxs6("span", { class: "scui-session-preview", children: [
2108
+ working ? /* @__PURE__ */ jsxs6("span", { class: "scui-session-working", "aria-hidden": "true", children: [
2109
+ /* @__PURE__ */ jsx7("i", {}),
2110
+ /* @__PURE__ */ jsx7("i", {}),
2111
+ /* @__PURE__ */ jsx7("i", {})
2112
+ ] }) : null,
2113
+ preview || working ? /* @__PURE__ */ jsx7("small", { children: preview || "Working\u2026" }) : null,
2028
2114
  unreadCount ? /* @__PURE__ */ jsx7("b", { "aria-label": `${unreadCount} unread messages`, children: unreadLabel }) : null
2029
2115
  ] }) : null,
2030
2116
  state.attachError?.key === row.key ? /* @__PURE__ */ jsx7("em", { children: state.attachError.message }) : null
@@ -2035,6 +2121,7 @@ function SessionList({ state, adapter, onOpen, onNew, onClose, components = {},
2035
2121
  const remembered = sessionListMemory.get(memoryKey) ?? { query: "", top: 0 };
2036
2122
  const [query, setQuery] = useState4(remembered.query);
2037
2123
  const [loadingMore, setLoadingMore] = useState4(false);
2124
+ const [now, setNow] = useState4(() => Date.now());
2038
2125
  const root = useRef5(null);
2039
2126
  const rowScroller = useRef5(null);
2040
2127
  const rows = filterSessions(state.sessions, query);
@@ -2050,6 +2137,10 @@ function SessionList({ state, adapter, onOpen, onNew, onClose, components = {},
2050
2137
  useEffect6(() => {
2051
2138
  if (loadingMore) setLoadingMore(false);
2052
2139
  }, [state.error, state.history.hasMoreSessions, state.sessions.length]);
2140
+ useEffect6(() => {
2141
+ const timer = setInterval(() => setNow(Date.now()), 1e4);
2142
+ return () => clearInterval(timer);
2143
+ }, []);
2053
2144
  const loadMore = () => {
2054
2145
  if (loadingMore) return;
2055
2146
  setLoadingMore(true);
@@ -2083,23 +2174,120 @@ function SessionList({ state, adapter, onOpen, onNew, onClose, components = {},
2083
2174
  ] }) : null,
2084
2175
  /* @__PURE__ */ jsxs6("div", { class: "scui-session-rows", ref: rowScroller, onScroll: (event) => boundedSet(sessionListMemory, memoryKey, { query, top: event.currentTarget.scrollTop }), children: [
2085
2176
  !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,
2086
- rows.map((row) => /* @__PURE__ */ jsx7(Row, { value: row, row, state, adapter, onOpen }, row.key)),
2177
+ rows.map((row) => /* @__PURE__ */ jsx7(Row, { value: row, row, state, adapter, onOpen, now }, row.key)),
2087
2178
  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
2088
2179
  ] })
2089
2180
  ] });
2090
2181
  }
2091
2182
 
2092
- // src/messenger.jsx
2183
+ // src/settings.jsx
2184
+ import { useEffect as useEffect7, useId as useId2, useMemo as useMemo2, useRef as useRef6, useState as useState5 } from "preact/hooks";
2093
2185
  import { jsx as jsx8, jsxs as jsxs7 } from "preact/jsx-runtime";
2186
+ function HarnessAdvisory({ state, onReview }) {
2187
+ const advisory = state.interopSettings?.advisories[0];
2188
+ if (!advisory && !state.interopSettingsError) return null;
2189
+ return /* @__PURE__ */ jsxs7("aside", { class: "scui-advisory", "data-severity": advisory?.severity ?? "error", role: advisory?.severity === "error" ? "alert" : "status", children: [
2190
+ /* @__PURE__ */ jsx8("span", { "aria-hidden": "true", children: "!" }),
2191
+ /* @__PURE__ */ jsxs7("span", { children: [
2192
+ /* @__PURE__ */ jsx8("strong", { children: advisory?.title ?? "Could not inspect harness settings" }),
2193
+ /* @__PURE__ */ jsx8("small", { children: advisory?.message ?? state.interopSettingsError })
2194
+ ] }),
2195
+ advisory && state.canConfigureSettings ? /* @__PURE__ */ jsx8("button", { type: "button", onClick: () => onReview(advisory.recommendation.change), children: "Review" }) : null
2196
+ ] });
2197
+ }
2198
+ function initialValues(report, recommendedChange) {
2199
+ return Object.fromEntries(report.controls.map((control) => [
2200
+ control.key,
2201
+ recommendedChange?.key === control.key ? recommendedChange.value : control.configuredValue ?? control.effectiveValue ?? control.choices[0]?.value ?? null
2202
+ ]));
2203
+ }
2204
+ function HarnessSettingsPanel({ state, adapter, onClose, recommendedChange = null }) {
2205
+ const report = state.interopSettings;
2206
+ const titleId = useId2();
2207
+ const panel = useRef6(null);
2208
+ const valuesKey = `${report?.revision ?? ""}:${recommendedChange?.key ?? ""}:${recommendedChange?.value ?? ""}`;
2209
+ const defaults = useMemo2(() => report ? initialValues(report, recommendedChange) : {}, [valuesKey]);
2210
+ const [values, setValues] = useState5(defaults);
2211
+ useEffect7(() => setValues(defaults), [defaults]);
2212
+ useEffect7(() => {
2213
+ panel.current?.querySelector("select, button")?.focus({ preventScroll: true });
2214
+ const dismiss = (event) => {
2215
+ if (event.key === "Escape") {
2216
+ event.preventDefault();
2217
+ onClose();
2218
+ }
2219
+ };
2220
+ document.addEventListener("keydown", dismiss);
2221
+ return () => document.removeEventListener("keydown", dismiss);
2222
+ }, [onClose]);
2223
+ if (!report) return /* @__PURE__ */ jsx8("section", { ref: panel, class: "scui-settings", role: "dialog", "aria-modal": "true", "aria-labelledby": titleId, children: /* @__PURE__ */ jsxs7("header", { children: [
2224
+ /* @__PURE__ */ jsx8("button", { type: "button", "aria-label": "Close settings", onClick: onClose, children: /* @__PURE__ */ jsx8(UiIcon, { name: "close", size: 18 }) }),
2225
+ /* @__PURE__ */ jsxs7("span", { children: [
2226
+ /* @__PURE__ */ jsx8("strong", { id: titleId, children: "Harness settings" }),
2227
+ /* @__PURE__ */ jsx8("small", { children: state.interopSettingsError ?? "No interoperability controls are available." })
2228
+ ] })
2229
+ ] }) });
2230
+ 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 }] : []);
2231
+ const submit = (event) => {
2232
+ event.preventDefault();
2233
+ if (!changed.length || !state.canConfigureSettings) return;
2234
+ adapter.onIntent({ action: "configureHarness", harness: report.harness, changes: changed, expectedRevision: report.revision });
2235
+ };
2236
+ return /* @__PURE__ */ jsxs7("section", { ref: panel, class: "scui-settings", role: "dialog", "aria-modal": "true", "aria-labelledby": titleId, children: [
2237
+ /* @__PURE__ */ jsxs7("header", { children: [
2238
+ /* @__PURE__ */ jsx8("button", { type: "button", "aria-label": "Close settings", onClick: onClose, children: /* @__PURE__ */ jsx8(UiIcon, { name: "close", size: 18 }) }),
2239
+ /* @__PURE__ */ jsxs7("span", { children: [
2240
+ /* @__PURE__ */ jsxs7("strong", { id: titleId, children: [
2241
+ harnessDisplayName(report.harness),
2242
+ " interoperability"
2243
+ ] }),
2244
+ /* @__PURE__ */ jsx8("small", { children: "Native harness settings used by Supercode" })
2245
+ ] })
2246
+ ] }),
2247
+ /* @__PURE__ */ jsxs7("form", { onSubmit: submit, children: [
2248
+ report.controls.map((control) => {
2249
+ const choice = control.choices.find((item) => item.value === values[control.key]);
2250
+ const recommendation = report.advisories.map((advisory) => advisory.recommendation).find((item) => item.change.key === control.key && item.change.value === values[control.key]);
2251
+ const consequence = choice?.risk ?? recommendation?.consequence;
2252
+ return /* @__PURE__ */ jsxs7("fieldset", { disabled: !control.writable || Boolean(state.operation), children: [
2253
+ /* @__PURE__ */ jsxs7("label", { for: `scui-setting-${control.key}`, children: [
2254
+ /* @__PURE__ */ jsx8("strong", { children: control.label }),
2255
+ /* @__PURE__ */ jsx8("small", { children: control.description })
2256
+ ] }),
2257
+ /* @__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: [
2258
+ control.resettable ? /* @__PURE__ */ jsx8("option", { value: "@default", children: "Use harness default" }) : null,
2259
+ control.choices.map((item) => /* @__PURE__ */ jsx8("option", { value: item.value, children: item.label }, item.value))
2260
+ ] }),
2261
+ choice ? /* @__PURE__ */ jsx8("p", { children: choice.description }) : null,
2262
+ consequence ? /* @__PURE__ */ jsxs7("p", { class: "scui-setting-risk", children: [
2263
+ /* @__PURE__ */ jsx8("strong", { children: "Security consequence" }),
2264
+ consequence
2265
+ ] }) : null,
2266
+ /* @__PURE__ */ jsxs7("small", { class: "scui-setting-source", children: [
2267
+ control.effectiveNote,
2268
+ control.sourcePath ? ` Source: ${control.sourcePath}` : ""
2269
+ ] })
2270
+ ] }, control.key);
2271
+ }),
2272
+ /* @__PURE__ */ jsxs7("footer", { children: [
2273
+ /* @__PURE__ */ jsx8("button", { type: "button", onClick: onClose, children: "Cancel" }),
2274
+ /* @__PURE__ */ jsx8("button", { type: "submit", disabled: !changed.length || !state.canConfigureSettings || Boolean(state.operation), children: state.operation === "configureHarness" ? "Applying\u2026" : "Apply changes" })
2275
+ ] })
2276
+ ] })
2277
+ ] });
2278
+ }
2279
+
2280
+ // src/messenger.jsx
2281
+ import { jsx as jsx9, jsxs as jsxs8 } from "preact/jsx-runtime";
2094
2282
  var pendingMessageMemory = /* @__PURE__ */ new Map();
2095
2283
  var messengerViewMemory = /* @__PURE__ */ new Map();
2096
2284
  var newChatMemory = /* @__PURE__ */ new Map();
2097
- var TRACKED_ACTIONS = /* @__PURE__ */ new Set(["resume", "join", "detach", "branch", "reduce", "terminal", "export", "interrupt", "respond", "refresh", "loadEarlier", "loadSessions"]);
2285
+ var TRACKED_ACTIONS = /* @__PURE__ */ new Set(["resume", "join", "detach", "branch", "reduce", "terminal", "export", "interrupt", "respond", "configureHarness", "refresh", "loadEarlier", "loadSessions"]);
2098
2286
  function Receipt({ state, adapter }) {
2099
2287
  const receipt = state.reductionReceipt;
2100
- if (receipt) return /* @__PURE__ */ jsx8("div", { class: "scui-receipt", children: /* @__PURE__ */ jsxs7("span", { children: [
2101
- /* @__PURE__ */ jsx8("strong", { children: "Reduced and verified" }),
2102
- /* @__PURE__ */ jsxs7("small", { children: [
2288
+ if (receipt) return /* @__PURE__ */ jsx9("div", { class: "scui-receipt", children: /* @__PURE__ */ jsxs8("span", { children: [
2289
+ /* @__PURE__ */ jsx9("strong", { children: "Reduced and verified" }),
2290
+ /* @__PURE__ */ jsxs8("small", { children: [
2103
2291
  receipt.sourceTokens.toLocaleString(),
2104
2292
  " \u2192 ",
2105
2293
  receipt.reducedTokens.toLocaleString(),
@@ -2108,36 +2296,36 @@ function Receipt({ state, adapter }) {
2108
2296
  "\xD7 \xB7 reversible"
2109
2297
  ] })
2110
2298
  ] }) });
2111
- if (state.exportReceipt) return /* @__PURE__ */ jsxs7("div", { class: "scui-receipt", children: [
2112
- /* @__PURE__ */ jsxs7("span", { children: [
2113
- /* @__PURE__ */ jsxs7("strong", { children: [
2299
+ if (state.exportReceipt) return /* @__PURE__ */ jsxs8("div", { class: "scui-receipt", children: [
2300
+ /* @__PURE__ */ jsxs8("span", { children: [
2301
+ /* @__PURE__ */ jsxs8("strong", { children: [
2114
2302
  "Lossless export ready \xB7 ",
2115
2303
  harnessDisplayName(state.exportReceipt.targetHarness)
2116
2304
  ] }),
2117
- /* @__PURE__ */ jsxs7("small", { children: [
2305
+ /* @__PURE__ */ jsxs8("small", { children: [
2118
2306
  state.exportReceipt.path,
2119
2307
  " \xB7 ",
2120
2308
  state.exportReceipt.files,
2121
2309
  " files"
2122
2310
  ] })
2123
2311
  ] }),
2124
- /* @__PURE__ */ jsx8("button", { type: "button", onClick: () => adapter.copyText?.(state.exportReceipt.path), children: "Copy path" })
2312
+ /* @__PURE__ */ jsx9("button", { type: "button", onClick: () => adapter.copyText?.(state.exportReceipt.path), children: "Copy path" })
2125
2313
  ] });
2126
- if (state.terminalHandoff) return /* @__PURE__ */ jsxs7("div", { class: "scui-receipt", children: [
2127
- /* @__PURE__ */ jsxs7("span", { children: [
2128
- /* @__PURE__ */ jsx8("strong", { children: "Terminal handoff ready" }),
2129
- /* @__PURE__ */ jsx8("small", { children: state.terminalHandoff.cwd })
2314
+ if (state.terminalHandoff) return /* @__PURE__ */ jsxs8("div", { class: "scui-receipt", children: [
2315
+ /* @__PURE__ */ jsxs8("span", { children: [
2316
+ /* @__PURE__ */ jsx9("strong", { children: "Terminal handoff ready" }),
2317
+ /* @__PURE__ */ jsx9("small", { children: state.terminalHandoff.cwd })
2130
2318
  ] }),
2131
- /* @__PURE__ */ jsx8("button", { type: "button", onClick: () => adapter.copyText?.(terminalCommand(state.terminalHandoff)), children: "Copy command" })
2319
+ /* @__PURE__ */ jsx9("button", { type: "button", onClick: () => adapter.copyText?.(terminalCommand(state.terminalHandoff)), children: "Copy command" })
2132
2320
  ] });
2133
2321
  return null;
2134
2322
  }
2135
- function ConversationActions({ state, adapter, actionPending }) {
2136
- const [open, setOpen] = useState5(false);
2137
- const root = useRef6(null);
2138
- const panel = useRef6(null);
2139
- const trigger = useRef6(null);
2140
- const menuId = useId2();
2323
+ function ConversationActions({ state, adapter, actionPending, onSettings }) {
2324
+ const [open, setOpen] = useState6(false);
2325
+ const root = useRef7(null);
2326
+ const panel = useRef7(null);
2327
+ const trigger = useRef7(null);
2328
+ const menuId = useId3();
2141
2329
  const targets = state.harnesses.filter((item) => item.startable);
2142
2330
  const groups = [
2143
2331
  {
@@ -2145,7 +2333,8 @@ function ConversationActions({ state, adapter, actionPending }) {
2145
2333
  items: [
2146
2334
  state.canDetach ? { key: "detach", label: "Detach to read-only", intent: { action: "detach" } } : null,
2147
2335
  state.canOpenTerminal ? { key: "terminal", label: "Prepare terminal handoff", intent: { action: "terminal" } } : null,
2148
- state.canExport && state.exportBackTarget ? { key: "export", label: `Export back to ${harnessDisplayName(state.exportBackTarget)}`, intent: { action: "export", targetHarness: state.exportBackTarget } } : null
2336
+ state.canExport && state.exportBackTarget ? { key: "export", label: `Export back to ${harnessDisplayName(state.exportBackTarget)}`, intent: { action: "export", targetHarness: state.exportBackTarget } } : null,
2337
+ state.interopSettings || state.interopSettingsError ? { key: "settings", label: "Interoperability settings", onSelect: onSettings } : null
2149
2338
  ].filter(Boolean)
2150
2339
  },
2151
2340
  {
@@ -2157,7 +2346,7 @@ function ConversationActions({ state, adapter, actionPending }) {
2157
2346
  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 } })) : []
2158
2347
  }
2159
2348
  ].filter((group) => group.items.length);
2160
- useEffect7(() => {
2349
+ useEffect8(() => {
2161
2350
  if (!open) return;
2162
2351
  panel.current?.querySelector("button:not(:disabled)")?.focus({ preventScroll: true });
2163
2352
  const dismiss = (event) => {
@@ -2176,9 +2365,10 @@ function ConversationActions({ state, adapter, actionPending }) {
2176
2365
  document.removeEventListener("pointerdown", dismiss, true);
2177
2366
  };
2178
2367
  }, [open]);
2179
- const dispatch = (intent) => {
2368
+ const dispatch = (item) => {
2180
2369
  setOpen(false);
2181
- adapter.onIntent(intent);
2370
+ if (item.onSelect) item.onSelect();
2371
+ else adapter.onIntent(item.intent);
2182
2372
  };
2183
2373
  const navigate = (event) => {
2184
2374
  if (event.key === "Escape") {
@@ -2196,62 +2386,63 @@ function ConversationActions({ state, adapter, actionPending }) {
2196
2386
  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;
2197
2387
  items[index].focus({ preventScroll: true });
2198
2388
  };
2199
- return /* @__PURE__ */ jsxs7("div", { class: "scui-menu", ref: root, onBlur: (event) => {
2389
+ return /* @__PURE__ */ jsxs8("div", { class: "scui-menu", ref: root, onBlur: (event) => {
2200
2390
  if (event.relatedTarget && !event.currentTarget.contains(event.relatedTarget)) setOpen(false);
2201
2391
  }, children: [
2202
- /* @__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 }) }),
2203
- 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: [
2204
- /* @__PURE__ */ jsx8("strong", { children: group.label }),
2205
- group.items.map((item) => /* @__PURE__ */ jsx8("button", { role: "menuitem", type: "button", disabled: actionPending, onClick: () => dispatch(item.intent), children: item.label }, item.key))
2392
+ /* @__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 }) }),
2393
+ 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: [
2394
+ /* @__PURE__ */ jsx9("strong", { children: group.label }),
2395
+ group.items.map((item) => /* @__PURE__ */ jsx9("button", { role: "menuitem", type: "button", disabled: actionPending, onClick: () => dispatch(item), children: item.label }, item.key))
2206
2396
  ] }, group.label)) }) : null
2207
2397
  ] });
2208
2398
  }
2209
- function ChatHeader({ state, adapter, pendingStatus, actionPending, onBack, onNew, onClose }) {
2210
- const back = useRef6(null);
2399
+ function ChatHeader({ state, adapter, pendingStatus, actionPending, onBack, onNew, onClose, onSettings }) {
2400
+ const back = useRef7(null);
2211
2401
  const harness = state.attached?.harness ?? state.harness;
2212
2402
  const title = state.attached ? sessionDisplayName(state.attached) : harnessDisplayName(harness) || "Agent chat";
2213
2403
  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";
2214
- const menu = state.canDetach || state.canOpenTerminal || state.canBranch || state.canAttach || state.canExport || state.canReduce;
2215
- useEffect7(() => {
2404
+ const menu = state.canDetach || state.canOpenTerminal || state.canBranch || state.canAttach || state.canExport || state.canReduce || state.interopSettings || state.interopSettingsError;
2405
+ useEffect8(() => {
2216
2406
  if (state.mode === "mirror" && !state.canSend) back.current?.focus({ preventScroll: true });
2217
2407
  }, []);
2218
- return /* @__PURE__ */ jsxs7("header", { class: "scui-head scui-chat-head", children: [
2219
- /* @__PURE__ */ jsx8("button", { ref: back, type: "button", "aria-label": "Back to chats", onClick: onBack, children: /* @__PURE__ */ jsx8(UiIcon, { name: "back", size: 18 }) }),
2220
- /* @__PURE__ */ jsx8(HarnessLogo, { id: harness, size: 28 }),
2221
- /* @__PURE__ */ jsxs7("span", { class: "scui-head-copy", children: [
2222
- /* @__PURE__ */ jsx8("strong", { children: title }),
2223
- /* @__PURE__ */ jsxs7("small", { children: [
2408
+ return /* @__PURE__ */ jsxs8("header", { class: "scui-head scui-chat-head", children: [
2409
+ /* @__PURE__ */ jsx9("button", { ref: back, type: "button", "aria-label": "Back to chats", onClick: onBack, children: /* @__PURE__ */ jsx9(UiIcon, { name: "back", size: 18 }) }),
2410
+ /* @__PURE__ */ jsx9(HarnessLogo, { id: harness, size: 28 }),
2411
+ /* @__PURE__ */ jsxs8("span", { class: "scui-head-copy", children: [
2412
+ /* @__PURE__ */ jsx9("strong", { children: title }),
2413
+ /* @__PURE__ */ jsxs8("small", { children: [
2224
2414
  harnessDisplayName(harness),
2225
2415
  " \xB7 ",
2226
2416
  status
2227
2417
  ] })
2228
2418
  ] }),
2229
- menu ? /* @__PURE__ */ jsx8(ConversationActions, { state, adapter, actionPending }) : null,
2230
- /* @__PURE__ */ jsx8("button", { type: "button", "aria-label": "New chat", onClick: onNew, children: /* @__PURE__ */ jsx8(UiIcon, { name: "plus", size: 18 }) }),
2231
- onClose ? /* @__PURE__ */ jsx8("button", { type: "button", "aria-label": "Close", onClick: onClose, children: /* @__PURE__ */ jsx8(UiIcon, { name: "close", size: 18 }) }) : null
2419
+ menu ? /* @__PURE__ */ jsx9(ConversationActions, { state, adapter, actionPending, onSettings }) : null,
2420
+ /* @__PURE__ */ jsx9("button", { type: "button", "aria-label": "New chat", onClick: onNew, children: /* @__PURE__ */ jsx9(UiIcon, { name: "plus", size: 18 }) }),
2421
+ onClose ? /* @__PURE__ */ jsx9("button", { type: "button", "aria-label": "Close", onClick: onClose, children: /* @__PURE__ */ jsx9(UiIcon, { name: "close", size: 18 }) }) : null
2232
2422
  ] });
2233
2423
  }
2234
2424
  function Chat({ state, adapter, onBack, onNew, onClose, components, slots, labels }) {
2235
2425
  const Header = slots.header;
2236
2426
  const memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`;
2237
- const [pending, setPendingState] = useState5(() => pendingMessageMemory.get(memoryKey) ?? null);
2238
- const [pendingAction, setPendingAction] = useState5(null);
2239
- const [restoreDraft, setRestoreDraft] = useState5(null);
2240
- const restoreSequence = useRef6(0);
2241
- const actionSequence = useRef6(0);
2242
- const acknowledged = useRef6(/* @__PURE__ */ new Set());
2427
+ const [pending, setPendingState] = useState6(() => pendingMessageMemory.get(memoryKey) ?? null);
2428
+ const [pendingAction, setPendingAction] = useState6(null);
2429
+ const [restoreDraft, setRestoreDraft] = useState6(null);
2430
+ const [settings, setSettings] = useState6(null);
2431
+ const restoreSequence = useRef7(0);
2432
+ const actionSequence = useRef7(0);
2433
+ const acknowledged = useRef7(/* @__PURE__ */ new Set());
2243
2434
  const setPending = (update) => setPendingState((current) => {
2244
2435
  const next = typeof update === "function" ? update(current) : update;
2245
2436
  if (next) boundedSet(pendingMessageMemory, memoryKey, next);
2246
2437
  else pendingMessageMemory.delete(memoryKey);
2247
2438
  return next;
2248
2439
  });
2249
- useEffect7(() => {
2440
+ useEffect8(() => {
2250
2441
  setPendingState(pendingMessageMemory.get(memoryKey) ?? null);
2251
2442
  setPendingAction(null);
2252
2443
  setRestoreDraft(null);
2253
2444
  }, [memoryKey]);
2254
- useEffect7(() => {
2445
+ useEffect8(() => {
2255
2446
  if (!pending) return;
2256
2447
  if (state.transcript.some((entry) => entry.role === "user" && entry.text.trim() === pending.text.trim() && !pending.baselineIds.includes(entry.id))) {
2257
2448
  setPending(null);
@@ -2280,7 +2471,7 @@ function Chat({ state, adapter, onBack, onNew, onClose, components, slots, label
2280
2471
  setRestoreDraft({ id: restoreSequence.current, text: pending.text, context: pending.context, images: pending.images });
2281
2472
  setPending({ ...pending, status: "editing" });
2282
2473
  };
2283
- useEffect7(() => {
2474
+ useEffect8(() => {
2284
2475
  const key = state.attached?.key;
2285
2476
  if (!key || !state.attention.some((item) => item.key === key)) {
2286
2477
  if (key) acknowledged.current.delete(key);
@@ -2291,7 +2482,7 @@ function Chat({ state, adapter, onBack, onNew, onClose, components, slots, label
2291
2482
  adapter.onIntent({ action: "ack", key });
2292
2483
  }
2293
2484
  }, [adapter, state.attached?.key, state.attention]);
2294
- const trackedAdapter = useMemo2(() => ({
2485
+ const trackedAdapter = useMemo3(() => ({
2295
2486
  ...adapter,
2296
2487
  onIntent(intent) {
2297
2488
  if (!TRACKED_ACTIONS.has(intent.action)) return adapter.onIntent(intent);
@@ -2315,7 +2506,7 @@ function Chat({ state, adapter, onBack, onNew, onClose, components, slots, label
2315
2506
  return result;
2316
2507
  }
2317
2508
  }), [adapter, state.error]);
2318
- useEffect7(() => {
2509
+ useEffect8(() => {
2319
2510
  if (!pendingAction) return;
2320
2511
  if (state.operation && !pendingAction.seenOperation) {
2321
2512
  setPendingAction({ ...pendingAction, seenOperation: true });
@@ -2328,44 +2519,48 @@ function Chat({ state, adapter, onBack, onNew, onClose, components, slots, label
2328
2519
  const actionLabel = operationLabel(action);
2329
2520
  const actionState = action ? { ...state, operation: action, canInterrupt: false, canRespond: false } : state;
2330
2521
  const unreadAfterMessages = state.attention.find((item) => item.key === state.attached?.key)?.afterMessages ?? null;
2331
- return /* @__PURE__ */ jsxs7("section", { class: "scui-chat", children: [
2332
- 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 }),
2333
- actionLabel ? /* @__PURE__ */ jsxs7("div", { class: "scui-operation", role: "status", children: [
2334
- /* @__PURE__ */ jsx8("i", {}),
2522
+ const Advisory = components.HarnessAdvisory ?? HarnessAdvisory;
2523
+ const SettingsPanel = components.HarnessSettingsPanel ?? HarnessSettingsPanel;
2524
+ return /* @__PURE__ */ jsxs8("section", { class: "scui-chat", children: [
2525
+ 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 }) }),
2526
+ actionLabel ? /* @__PURE__ */ jsxs8("div", { class: "scui-operation", role: "status", children: [
2527
+ /* @__PURE__ */ jsx9("i", {}),
2335
2528
  actionLabel
2336
2529
  ] }) : null,
2337
- state.error ? /* @__PURE__ */ jsxs7("div", { class: "scui-error", role: "alert", children: [
2338
- /* @__PURE__ */ jsx8("span", { children: state.error }),
2339
- state.recoverable ? /* @__PURE__ */ jsx8("button", { type: "button", disabled: Boolean(action), onClick: () => trackedAdapter.onIntent({ action: "refresh" }), children: "Retry" }) : null
2530
+ state.error ? /* @__PURE__ */ jsxs8("div", { class: "scui-error", role: "alert", children: [
2531
+ /* @__PURE__ */ jsx9("span", { children: state.error }),
2532
+ state.recoverable ? /* @__PURE__ */ jsx9("button", { type: "button", disabled: Boolean(action), onClick: () => trackedAdapter.onIntent({ action: "refresh" }), children: "Retry" }) : null
2340
2533
  ] }) : null,
2341
- /* @__PURE__ */ jsx8(Receipt, { state, adapter }),
2342
- /* @__PURE__ */ jsx8(Conversation, { state: actionState, adapter: trackedAdapter, components, slots, memoryKey, pending: pendingMessage, unreadAfterMessages }, memoryKey),
2343
- /* @__PURE__ */ jsx8(ContinuationBar, { state: actionState, adapter: trackedAdapter, labels }),
2344
- 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
2534
+ /* @__PURE__ */ jsx9(Receipt, { state, adapter }),
2535
+ /* @__PURE__ */ jsx9(Conversation, { state: actionState, adapter: trackedAdapter, components, slots, memoryKey, pending: pendingMessage, unreadAfterMessages }, memoryKey),
2536
+ /* @__PURE__ */ jsx9(ContinuationBar, { state: actionState, adapter: trackedAdapter, labels }),
2537
+ /* @__PURE__ */ jsx9(Advisory, { state: actionState, adapter: trackedAdapter, value: state.interopSettings?.advisories[0] ?? null, onReview: (recommendedChange) => setSettings({ recommendedChange }) }),
2538
+ 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,
2539
+ settings ? /* @__PURE__ */ jsx9(SettingsPanel, { state: actionState, adapter: trackedAdapter, value: state.interopSettings, recommendedChange: settings.recommendedChange, onClose: () => setSettings(null) }) : null
2345
2540
  ] });
2346
2541
  }
2347
2542
  function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey }) {
2348
2543
  const startable = state.harnesses.filter((item) => item.startable);
2349
2544
  const startableKey = startable.map((item) => item.id).join("\0");
2350
2545
  const remembered = newChatMemory.get(memoryKey) ?? { harness: startable[0]?.id ?? "", draft: "", context: [], images: [] };
2351
- const [harness, setHarness] = useState5(remembered.harness);
2352
- const [draft, setDraft] = useState5(remembered.draft);
2353
- const [context, setContext] = useState5(remembered.context);
2354
- const [images, setImages] = useState5(remembered.images ?? []);
2355
- const [starting, setStarting] = useState5(null);
2356
- const [picking, setPicking] = useState5(false);
2357
- const [dragging, setDragging] = useState5(false);
2358
- const [pickerError, setPickerError] = useState5(null);
2359
- const startSequence = useRef6(0);
2360
- const textarea = useRef6(null);
2546
+ const [harness, setHarness] = useState6(remembered.harness);
2547
+ const [draft, setDraft] = useState6(remembered.draft);
2548
+ const [context, setContext] = useState6(remembered.context);
2549
+ const [images, setImages] = useState6(remembered.images ?? []);
2550
+ const [starting, setStarting] = useState6(null);
2551
+ const [picking, setPicking] = useState6(false);
2552
+ const [dragging, setDragging] = useState6(false);
2553
+ const [pickerError, setPickerError] = useState6(null);
2554
+ const startSequence = useRef7(0);
2555
+ const textarea = useRef7(null);
2361
2556
  useAutosizeTextarea(textarea, draft);
2362
- useEffect7(() => {
2557
+ useEffect8(() => {
2363
2558
  if (startable.some((item) => item.id === harness)) return;
2364
2559
  const next = startable[0]?.id ?? "";
2365
2560
  setHarness(next);
2366
2561
  boundedSet(newChatMemory, memoryKey, { harness: next, draft, context, images });
2367
2562
  }, [harness, startableKey]);
2368
- useEffect7(() => {
2563
+ useEffect8(() => {
2369
2564
  if (!starting) return;
2370
2565
  const sessionChanged = (state.attached?.key ?? null) !== starting.attachedKey;
2371
2566
  const beganWorking = !starting.busy && state.busy;
@@ -2373,10 +2568,10 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
2373
2568
  newChatMemory.delete(memoryKey);
2374
2569
  onStarted();
2375
2570
  }, [memoryKey, onStarted, starting, state.attached?.key, state.busy]);
2376
- useEffect7(() => {
2571
+ useEffect8(() => {
2377
2572
  if (starting && state.error !== starting.initialError && !state.busy && !state.operation) setStarting(null);
2378
2573
  }, [starting, state.busy, state.error, state.operation]);
2379
- useEffect7(() => {
2574
+ useEffect8(() => {
2380
2575
  textarea.current?.focus({ preventScroll: true });
2381
2576
  }, []);
2382
2577
  const pickContext = () => {
@@ -2438,49 +2633,49 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
2438
2633
  Promise.resolve(result).catch(() => setStarting((current) => current?.id === id ? null : current));
2439
2634
  }
2440
2635
  };
2441
- return /* @__PURE__ */ jsxs7("section", { class: "scui-chat", children: [
2442
- /* @__PURE__ */ jsxs7("header", { class: "scui-head", children: [
2443
- /* @__PURE__ */ jsx8("button", { type: "button", "aria-label": "Back", onClick: onBack, children: /* @__PURE__ */ jsx8(UiIcon, { name: "back", size: 18 }) }),
2444
- /* @__PURE__ */ jsxs7("span", { class: "scui-head-copy", children: [
2445
- /* @__PURE__ */ jsx8("strong", { children: labels.newChat }),
2446
- /* @__PURE__ */ jsx8("small", { children: "No session is created until you send" })
2636
+ return /* @__PURE__ */ jsxs8("section", { class: "scui-chat", children: [
2637
+ /* @__PURE__ */ jsxs8("header", { class: "scui-head", children: [
2638
+ /* @__PURE__ */ jsx9("button", { type: "button", "aria-label": "Back", onClick: onBack, children: /* @__PURE__ */ jsx9(UiIcon, { name: "back", size: 18 }) }),
2639
+ /* @__PURE__ */ jsxs8("span", { class: "scui-head-copy", children: [
2640
+ /* @__PURE__ */ jsx9("strong", { children: labels.newChat }),
2641
+ /* @__PURE__ */ jsx9("small", { children: "No session is created until you send" })
2447
2642
  ] }),
2448
- onClose ? /* @__PURE__ */ jsx8("button", { type: "button", "aria-label": "Close", onClick: onClose, children: /* @__PURE__ */ jsx8(UiIcon, { name: "close", size: 18 }) }) : null
2643
+ onClose ? /* @__PURE__ */ jsx9("button", { type: "button", "aria-label": "Close", onClick: onClose, children: /* @__PURE__ */ jsx9(UiIcon, { name: "close", size: 18 }) }) : null
2449
2644
  ] }),
2450
- starting ? /* @__PURE__ */ jsxs7("div", { class: "scui-operation", role: "status", children: [
2451
- /* @__PURE__ */ jsx8("i", {}),
2645
+ starting ? /* @__PURE__ */ jsxs8("div", { class: "scui-operation", role: "status", children: [
2646
+ /* @__PURE__ */ jsx9("i", {}),
2452
2647
  operationLabel("start")
2453
2648
  ] }) : null,
2454
- /* @__PURE__ */ jsxs7("div", { class: "scui-new", children: [
2455
- /* @__PURE__ */ jsx8("span", { "aria-hidden": "true", children: "\u2726" }),
2456
- /* @__PURE__ */ jsx8("strong", { children: "What should the agent build or fix?" }),
2457
- /* @__PURE__ */ jsx8("small", { children: "Choose a coding harness and send the first message." })
2649
+ /* @__PURE__ */ jsxs8("div", { class: "scui-new", children: [
2650
+ /* @__PURE__ */ jsx9("span", { "aria-hidden": "true", children: "\u2726" }),
2651
+ /* @__PURE__ */ jsx9("strong", { children: "What should the agent build or fix?" }),
2652
+ /* @__PURE__ */ jsx9("small", { children: "Choose a coding harness and send the first message." })
2458
2653
  ] }),
2459
- /* @__PURE__ */ jsxs7("div", { class: "scui-compose", children: [
2460
- /* @__PURE__ */ jsxs7("label", { class: "scui-harness-picker", children: [
2461
- /* @__PURE__ */ jsx8(HarnessLogo, { id: harness, size: 24 }),
2462
- /* @__PURE__ */ jsx8("span", { children: "Coding harness" }),
2463
- /* @__PURE__ */ jsx8("select", { value: harness, disabled: Boolean(starting), onChange: (event) => {
2654
+ /* @__PURE__ */ jsxs8("div", { class: "scui-compose", children: [
2655
+ /* @__PURE__ */ jsxs8("label", { class: "scui-harness-picker", children: [
2656
+ /* @__PURE__ */ jsx9(HarnessLogo, { id: harness, size: 24 }),
2657
+ /* @__PURE__ */ jsx9("span", { children: "Coding harness" }),
2658
+ /* @__PURE__ */ jsx9("select", { value: harness, disabled: Boolean(starting), onChange: (event) => {
2464
2659
  const value = event.currentTarget.value;
2465
2660
  setHarness(value);
2466
2661
  boundedSet(newChatMemory, memoryKey, { harness: value, draft, context, images });
2467
- }, children: state.harnesses.map((item) => /* @__PURE__ */ jsxs7("option", { value: item.id, disabled: !item.startable, children: [
2662
+ }, children: state.harnesses.map((item) => /* @__PURE__ */ jsxs8("option", { value: item.id, disabled: !item.startable, children: [
2468
2663
  item.label,
2469
2664
  item.startable ? "" : " \xB7 unavailable"
2470
2665
  ] }, item.id)) })
2471
2666
  ] }),
2472
- /* @__PURE__ */ jsx8(ImageTray, { items: images, onRemove: (index) => setImages((items) => {
2667
+ /* @__PURE__ */ jsx9(ImageTray, { items: images, onRemove: (index) => setImages((items) => {
2473
2668
  const next = items.filter((_, itemIndex) => itemIndex !== index);
2474
2669
  boundedSet(newChatMemory, memoryKey, { harness, draft, context, images: next });
2475
2670
  return next;
2476
2671
  }) }),
2477
- /* @__PURE__ */ jsx8(ContextTray, { items: context, onRemove: (index) => setContext((items) => {
2672
+ /* @__PURE__ */ jsx9(ContextTray, { items: context, onRemove: (index) => setContext((items) => {
2478
2673
  const next = items.filter((_, itemIndex) => itemIndex !== index);
2479
2674
  boundedSet(newChatMemory, memoryKey, { harness, draft, context: next, images });
2480
2675
  return next;
2481
2676
  }) }),
2482
- pickerError ? /* @__PURE__ */ jsx8("small", { class: "scui-context-error", role: "alert", children: pickerError }) : null,
2483
- /* @__PURE__ */ jsxs7("div", { class: `scui-envelope${dragging ? " scui-drop-target" : ""}`, onDragEnter: (event) => {
2677
+ pickerError ? /* @__PURE__ */ jsx9("small", { class: "scui-context-error", role: "alert", children: pickerError }) : null,
2678
+ /* @__PURE__ */ jsxs8("div", { class: `scui-envelope${dragging ? " scui-drop-target" : ""}`, onDragEnter: (event) => {
2484
2679
  if (Array.from(event.dataTransfer?.types ?? []).includes("Files")) {
2485
2680
  event.preventDefault();
2486
2681
  setDragging(true);
@@ -2490,8 +2685,8 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
2490
2685
  }, onDragLeave: (event) => {
2491
2686
  if (!event.currentTarget.contains(event.relatedTarget)) setDragging(false);
2492
2687
  }, onDrop: dropImages, children: [
2493
- 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,
2494
- /* @__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) => {
2688
+ 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,
2689
+ /* @__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) => {
2495
2690
  const value = event.currentTarget.value;
2496
2691
  setDraft(value);
2497
2692
  boundedSet(newChatMemory, memoryKey, { harness, draft: value, context, images });
@@ -2501,23 +2696,23 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
2501
2696
  send();
2502
2697
  }
2503
2698
  } }),
2504
- /* @__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 }) }) })
2699
+ /* @__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 }) }) })
2505
2700
  ] })
2506
2701
  ] })
2507
2702
  ] });
2508
2703
  }
2509
2704
  function SupercodeMessenger({ state: stateInput, adapter, class: className = "", initialView, labels, components = {}, slots = {} }) {
2510
- const state = useMemo2(() => normalizeUiState(stateInput), [stateInput]);
2705
+ const state = useMemo3(() => normalizeUiState(stateInput), [stateInput]);
2511
2706
  const copy = { ...DEFAULT_LABELS, ...labels };
2512
2707
  const memoryKey = state.workspace || "@default";
2513
- const [view, setViewState] = useState5(initialView ?? (state.attention.length ? "list" : messengerViewMemory.get(memoryKey) ?? (state.attached || state.transcript.length ? "chat" : "list")));
2514
- const [opening, setOpening] = useState5(null);
2515
- const [listFocus, setListFocus] = useState5(null);
2708
+ const [view, setViewState] = useState6(initialView ?? (state.attention.length ? "list" : messengerViewMemory.get(memoryKey) ?? (state.attached || state.transcript.length ? "chat" : "list")));
2709
+ const [opening, setOpening] = useState6(null);
2710
+ const [listFocus, setListFocus] = useState6(null);
2516
2711
  const setView = (next) => {
2517
2712
  boundedSet(messengerViewMemory, memoryKey, next);
2518
2713
  setViewState(next);
2519
2714
  };
2520
- useEffect7(() => {
2715
+ useEffect8(() => {
2521
2716
  if (!opening) return;
2522
2717
  if (state.attached?.key === opening.key) {
2523
2718
  setOpening(null);
@@ -2533,31 +2728,31 @@ function SupercodeMessenger({ state: stateInput, adapter, class: className = "",
2533
2728
  };
2534
2729
  const close = () => adapter.onClose?.();
2535
2730
  const Footer = slots.footer;
2536
- return /* @__PURE__ */ jsxs7("main", { class: `scui-root ${className}`, "data-view": view, "data-mode": state.mode, "aria-label": "Supercode messenger", children: [
2537
- view === "list" ? /* @__PURE__ */ jsx8(SessionList, { state, adapter, focusKey: listFocus, onOpen: open, onNew: () => {
2731
+ return /* @__PURE__ */ jsxs8("main", { class: `scui-root ${className}`, "data-view": view, "data-mode": state.mode, "aria-label": "Supercode messenger", children: [
2732
+ view === "list" ? /* @__PURE__ */ jsx9(SessionList, { state, adapter, focusKey: listFocus, onOpen: open, onNew: () => {
2538
2733
  setListFocus("@new");
2539
2734
  setView("new");
2540
2735
  }, onClose: adapter.onClose ? close : void 0, components, labels: copy, memoryKey }) : null,
2541
- view === "new" ? /* @__PURE__ */ jsx8(NewChat, { state, adapter, onBack: () => setView("list"), onClose: adapter.onClose ? close : void 0, onStarted: () => setView("chat"), labels: copy, memoryKey }) : null,
2542
- view === "chat" ? /* @__PURE__ */ jsx8(Chat, { state, adapter, onBack: () => {
2736
+ view === "new" ? /* @__PURE__ */ jsx9(NewChat, { state, adapter, onBack: () => setView("list"), onClose: adapter.onClose ? close : void 0, onStarted: () => setView("chat"), labels: copy, memoryKey }) : null,
2737
+ view === "chat" ? /* @__PURE__ */ jsx9(Chat, { state, adapter, onBack: () => {
2543
2738
  setListFocus(state.attached?.key ?? listFocus);
2544
2739
  setView("list");
2545
2740
  }, onNew: () => {
2546
2741
  setListFocus("@new");
2547
2742
  setView("new");
2548
2743
  }, onClose: adapter.onClose ? close : void 0, components, slots, labels: copy }) : null,
2549
- opening ? /* @__PURE__ */ jsxs7("div", { class: "scui-opening", role: "status", "aria-busy": "true", children: [
2550
- /* @__PURE__ */ jsx8(HarnessLogo, { id: opening.harness, size: 34 }),
2551
- /* @__PURE__ */ jsxs7("span", { children: [
2552
- /* @__PURE__ */ jsxs7("strong", { children: [
2744
+ opening ? /* @__PURE__ */ jsxs8("div", { class: "scui-opening", role: "status", "aria-busy": "true", children: [
2745
+ /* @__PURE__ */ jsx9(HarnessLogo, { id: opening.harness, size: 34 }),
2746
+ /* @__PURE__ */ jsxs8("span", { children: [
2747
+ /* @__PURE__ */ jsxs8("strong", { children: [
2553
2748
  "Opening ",
2554
2749
  sessionDisplayName(opening)
2555
2750
  ] }),
2556
- /* @__PURE__ */ jsx8("small", { children: "Loading the latest transcript window\u2026" })
2751
+ /* @__PURE__ */ jsx9("small", { children: "Loading the latest transcript window\u2026" })
2557
2752
  ] }),
2558
- /* @__PURE__ */ jsx8("i", {})
2753
+ /* @__PURE__ */ jsx9("i", {})
2559
2754
  ] }) : null,
2560
- Footer ? /* @__PURE__ */ jsx8(Footer, { state, adapter, value: copy }) : null
2755
+ Footer ? /* @__PURE__ */ jsx9(Footer, { state, adapter, value: copy }) : null
2561
2756
  ] });
2562
2757
  }
2563
2758
  export {
@@ -2565,7 +2760,9 @@ export {
2565
2760
  Composer,
2566
2761
  ContinuationBar,
2567
2762
  Conversation,
2763
+ HarnessAdvisory,
2568
2764
  HarnessLogo,
2765
+ HarnessSettingsPanel,
2569
2766
  ImageViewer,
2570
2767
  LoadingStatus,
2571
2768
  MessageImages,