@volter-ai-dev/supercode-ui 0.1.67 → 0.1.69

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/activity.mjs CHANGED
@@ -29,6 +29,10 @@ var EMPTY_UI_STATE = Object.freeze({
29
29
  harness: "",
30
30
  mode: "none",
31
31
  strategy: null,
32
+ participant: Object.freeze({ kind: "local-user", label: null, origin: null }),
33
+ workspaceRef: Object.freeze({ kind: "none", value: null }),
34
+ mirror: null,
35
+ holder: null,
32
36
  canSend: false,
33
37
  canSteer: false,
34
38
  canResume: false,
@@ -629,12 +633,40 @@ function readToolPresentation(value, entry) {
629
633
  matches: nullableNumber(item.matches) ?? generated.matches
630
634
  };
631
635
  }
636
+ var OPAQUE_RAW_CHARS = 4e3;
637
+ function opaqueEntry(item) {
638
+ const previouslyOpaque = item.role === "opaque";
639
+ let raw;
640
+ if (previouslyOpaque && typeof item.raw === "string") {
641
+ raw = item.raw;
642
+ } else {
643
+ try {
644
+ raw = JSON.stringify(item, null, 2) ?? "";
645
+ } catch {
646
+ raw = "[unserializable entry payload]";
647
+ }
648
+ }
649
+ const kind = previouslyOpaque && typeof item.kind === "string" ? item.kind : typeof item.role === "string" ? item.role : "";
650
+ return {
651
+ id: item.id,
652
+ role: "opaque",
653
+ kind: boundedString(kind, 120) || "unknown",
654
+ text: typeof item.text === "string" ? boundedString(item.text, OPAQUE_RAW_CHARS) : "",
655
+ ts: nullableNumber(item.ts),
656
+ truncated: item.truncated === true || raw.length > OPAQUE_RAW_CHARS,
657
+ raw: boundedString(raw, OPAQUE_RAW_CHARS)
658
+ };
659
+ }
632
660
  function readTranscript(value) {
633
661
  if (!Array.isArray(value)) return [];
634
662
  const result = [];
635
663
  for (const candidate of value) {
636
664
  const item = record(candidate);
637
- if (!item || typeof item.id !== "string" || typeof item.text !== "string" || !ROLES.has(item.role)) continue;
665
+ if (!item || typeof item.id !== "string") continue;
666
+ if (typeof item.text !== "string" || !ROLES.has(item.role)) {
667
+ result.push(opaqueEntry(item));
668
+ continue;
669
+ }
638
670
  const entry = {
639
671
  id: item.id,
640
672
  role: item.role,
@@ -692,6 +724,75 @@ function readTranscript(value) {
692
724
  }
693
725
  return result;
694
726
  }
727
+ function readParticipant(value) {
728
+ const participant = record(value);
729
+ if (!participant) return { kind: "local-user", label: null, origin: null };
730
+ return {
731
+ kind: ["local-user", "foreign", "unknown"].includes(participant.kind) ? participant.kind : "unknown",
732
+ label: typeof participant.label === "string" && participant.label ? boundedString(participant.label, 200) : null,
733
+ origin: typeof participant.origin === "string" && participant.origin ? boundedString(participant.origin, 100) : null
734
+ };
735
+ }
736
+ function readWorkspaceRef(value, fallbackPath = "") {
737
+ const ref = record(value);
738
+ if (ref && ["repo", "none", "channel"].includes(ref.kind)) {
739
+ return {
740
+ kind: ref.kind,
741
+ value: ref.kind === "none" ? null : typeof ref.value === "string" && ref.value ? boundedString(ref.value, 500) : null
742
+ };
743
+ }
744
+ return fallbackPath ? { kind: "repo", value: boundedString(fallbackPath, 500) } : { kind: "none", value: null };
745
+ }
746
+ function readMirror(value) {
747
+ const mirror = record(value);
748
+ if (!mirror || typeof mirror.canonicalHarness !== "string" || !mirror.canonicalHarness) return null;
749
+ return {
750
+ canonicalHarness: boundedString(mirror.canonicalHarness, 100),
751
+ canonicalKey: typeof mirror.canonicalKey === "string" && mirror.canonicalKey ? boundedString(mirror.canonicalKey, 300) : null,
752
+ bounded: mirror.bounded === true,
753
+ truncated: mirror.truncated === true,
754
+ origin: typeof mirror.origin === "string" && mirror.origin ? boundedString(mirror.origin, 200) : null
755
+ };
756
+ }
757
+ function readHolder(value) {
758
+ const holder = record(value);
759
+ if (!holder) return null;
760
+ const surface = typeof holder.surface === "string" && holder.surface ? boundedString(holder.surface, 100) : null;
761
+ return {
762
+ surface,
763
+ canTakeOver: holder.canTakeOver === true && surface !== null && surface !== "supercode"
764
+ };
765
+ }
766
+ var TRIGGER_KINDS = ["human", "channel", "cron", "heartbeat", "webhook", "parent", "api", "unknown"];
767
+ function readTrigger(value) {
768
+ const trigger = record(value);
769
+ if (!trigger) return null;
770
+ const kind = trigger.kind === "manual" ? "human" : trigger.kind;
771
+ return {
772
+ kind: TRIGGER_KINDS.includes(kind) ? kind : "unknown",
773
+ label: typeof trigger.label === "string" && trigger.label ? boundedString(trigger.label, 200) : null,
774
+ surface: typeof trigger.surface === "string" && trigger.surface ? boundedString(trigger.surface, 300) : null
775
+ };
776
+ }
777
+ function readCrossSurface(value) {
778
+ const moved = record(value);
779
+ if (!moved || typeof moved.state !== "string" || !moved.state) return null;
780
+ return {
781
+ state: boundedString(moved.state, 100),
782
+ platform: typeof moved.platform === "string" && moved.platform ? boundedString(moved.platform, 100) : null,
783
+ error: typeof moved.error === "string" && moved.error ? boundedString(moved.error, 500) : null
784
+ };
785
+ }
786
+ function readRecurring(value) {
787
+ const recurring = record(value);
788
+ if (!recurring || typeof recurring.groupKey !== "string" || !recurring.groupKey) return null;
789
+ const runs = Number.isSafeInteger(recurring.runs) && recurring.runs > 0 ? recurring.runs : 1;
790
+ return {
791
+ groupKey: boundedString(recurring.groupKey, 300),
792
+ runs,
793
+ lastStatus: ["ok", "failed", "mixed"].includes(recurring.lastStatus) ? recurring.lastStatus : null
794
+ };
795
+ }
695
796
  function readSessions(value) {
696
797
  if (!Array.isArray(value)) return [];
697
798
  return value.flatMap((raw) => {
@@ -713,7 +814,13 @@ function readSessions(value) {
713
814
  live: row.live === true,
714
815
  runtimeStatus: row.runtimeStatus === "running" || row.runtimeStatus === "busy" || row.runtimeStatus === "idle" ? row.runtimeStatus : null,
715
816
  ...Number.isSafeInteger(row.subagentCount) && row.subagentCount > 0 ? { subagentCount: row.subagentCount } : {},
716
- activity: ACTIVITY_PRIORITY[row.activity] !== void 0 ? row.activity : void 0
817
+ activity: ACTIVITY_PRIORITY[row.activity] !== void 0 ? row.activity : void 0,
818
+ ...row.participant !== void 0 ? { participant: readParticipant(row.participant) } : {},
819
+ workspaceRef: readWorkspaceRef(row.workspaceRef, string(row.cwd)),
820
+ ...readMirror(row.mirror) ? { mirror: readMirror(row.mirror) } : {},
821
+ ...readTrigger(row.trigger) ? { trigger: readTrigger(row.trigger) } : {},
822
+ ...readRecurring(row.recurring) ? { recurring: readRecurring(row.recurring) } : {},
823
+ ...readCrossSurface(row.crossSurface) ? { crossSurface: readCrossSurface(row.crossSurface) } : {}
717
824
  }];
718
825
  });
719
826
  }
@@ -897,6 +1004,10 @@ function normalizeUiState(value) {
897
1004
  canConfigureSettings: raw.canConfigureSettings === true,
898
1005
  messaging: raw.messaging === "live_peer" ? "live_peer" : null,
899
1006
  workspace: string(raw.workspace),
1007
+ workspaceRef: readWorkspaceRef(raw.workspaceRef, string(raw.workspace)),
1008
+ participant: readParticipant(raw.participant),
1009
+ mirror: readMirror(raw.mirror),
1010
+ holder: readHolder(raw.holder),
900
1011
  taskPlan: readTaskPlan(raw.taskPlan),
901
1012
  semantics: readSemantics(raw.semantics),
902
1013
  terminalHandoff: readTerminalHandoff(raw.terminalHandoff),
package/components.d.ts CHANGED
@@ -26,3 +26,6 @@ export {
26
26
  UiIcon,
27
27
  hasHarnessLogo,
28
28
  } from './index.js';
29
+
30
+ /** Clear all session-scoped UI memory stores (drafts, queues, view choices). */
31
+ export declare function resetSupercodeUiMemory(): void;
package/components.mjs CHANGED
@@ -32,6 +32,10 @@ var EMPTY_UI_STATE = Object.freeze({
32
32
  harness: "",
33
33
  mode: "none",
34
34
  strategy: null,
35
+ participant: Object.freeze({ kind: "local-user", label: null, origin: null }),
36
+ workspaceRef: Object.freeze({ kind: "none", value: null }),
37
+ mirror: null,
38
+ holder: null,
35
39
  canSend: false,
36
40
  canSteer: false,
37
41
  canResume: false,
@@ -641,12 +645,40 @@ function readToolPresentation(value, entry) {
641
645
  matches: nullableNumber(item.matches) ?? generated.matches
642
646
  };
643
647
  }
648
+ var OPAQUE_RAW_CHARS = 4e3;
649
+ function opaqueEntry(item) {
650
+ const previouslyOpaque = item.role === "opaque";
651
+ let raw;
652
+ if (previouslyOpaque && typeof item.raw === "string") {
653
+ raw = item.raw;
654
+ } else {
655
+ try {
656
+ raw = JSON.stringify(item, null, 2) ?? "";
657
+ } catch {
658
+ raw = "[unserializable entry payload]";
659
+ }
660
+ }
661
+ const kind = previouslyOpaque && typeof item.kind === "string" ? item.kind : typeof item.role === "string" ? item.role : "";
662
+ return {
663
+ id: item.id,
664
+ role: "opaque",
665
+ kind: boundedString(kind, 120) || "unknown",
666
+ text: typeof item.text === "string" ? boundedString(item.text, OPAQUE_RAW_CHARS) : "",
667
+ ts: nullableNumber(item.ts),
668
+ truncated: item.truncated === true || raw.length > OPAQUE_RAW_CHARS,
669
+ raw: boundedString(raw, OPAQUE_RAW_CHARS)
670
+ };
671
+ }
644
672
  function readTranscript(value) {
645
673
  if (!Array.isArray(value)) return [];
646
674
  const result = [];
647
675
  for (const candidate of value) {
648
676
  const item = record(candidate);
649
- if (!item || typeof item.id !== "string" || typeof item.text !== "string" || !ROLES.has(item.role)) continue;
677
+ if (!item || typeof item.id !== "string") continue;
678
+ if (typeof item.text !== "string" || !ROLES.has(item.role)) {
679
+ result.push(opaqueEntry(item));
680
+ continue;
681
+ }
650
682
  const entry = {
651
683
  id: item.id,
652
684
  role: item.role,
@@ -704,6 +736,75 @@ function readTranscript(value) {
704
736
  }
705
737
  return result;
706
738
  }
739
+ function readParticipant(value) {
740
+ const participant = record(value);
741
+ if (!participant) return { kind: "local-user", label: null, origin: null };
742
+ return {
743
+ kind: ["local-user", "foreign", "unknown"].includes(participant.kind) ? participant.kind : "unknown",
744
+ label: typeof participant.label === "string" && participant.label ? boundedString(participant.label, 200) : null,
745
+ origin: typeof participant.origin === "string" && participant.origin ? boundedString(participant.origin, 100) : null
746
+ };
747
+ }
748
+ function readWorkspaceRef(value, fallbackPath = "") {
749
+ const ref = record(value);
750
+ if (ref && ["repo", "none", "channel"].includes(ref.kind)) {
751
+ return {
752
+ kind: ref.kind,
753
+ value: ref.kind === "none" ? null : typeof ref.value === "string" && ref.value ? boundedString(ref.value, 500) : null
754
+ };
755
+ }
756
+ return fallbackPath ? { kind: "repo", value: boundedString(fallbackPath, 500) } : { kind: "none", value: null };
757
+ }
758
+ function readMirror(value) {
759
+ const mirror = record(value);
760
+ if (!mirror || typeof mirror.canonicalHarness !== "string" || !mirror.canonicalHarness) return null;
761
+ return {
762
+ canonicalHarness: boundedString(mirror.canonicalHarness, 100),
763
+ canonicalKey: typeof mirror.canonicalKey === "string" && mirror.canonicalKey ? boundedString(mirror.canonicalKey, 300) : null,
764
+ bounded: mirror.bounded === true,
765
+ truncated: mirror.truncated === true,
766
+ origin: typeof mirror.origin === "string" && mirror.origin ? boundedString(mirror.origin, 200) : null
767
+ };
768
+ }
769
+ function readHolder(value) {
770
+ const holder = record(value);
771
+ if (!holder) return null;
772
+ const surface = typeof holder.surface === "string" && holder.surface ? boundedString(holder.surface, 100) : null;
773
+ return {
774
+ surface,
775
+ canTakeOver: holder.canTakeOver === true && surface !== null && surface !== "supercode"
776
+ };
777
+ }
778
+ var TRIGGER_KINDS = ["human", "channel", "cron", "heartbeat", "webhook", "parent", "api", "unknown"];
779
+ function readTrigger(value) {
780
+ const trigger = record(value);
781
+ if (!trigger) return null;
782
+ const kind = trigger.kind === "manual" ? "human" : trigger.kind;
783
+ return {
784
+ kind: TRIGGER_KINDS.includes(kind) ? kind : "unknown",
785
+ label: typeof trigger.label === "string" && trigger.label ? boundedString(trigger.label, 200) : null,
786
+ surface: typeof trigger.surface === "string" && trigger.surface ? boundedString(trigger.surface, 300) : null
787
+ };
788
+ }
789
+ function readCrossSurface(value) {
790
+ const moved = record(value);
791
+ if (!moved || typeof moved.state !== "string" || !moved.state) return null;
792
+ return {
793
+ state: boundedString(moved.state, 100),
794
+ platform: typeof moved.platform === "string" && moved.platform ? boundedString(moved.platform, 100) : null,
795
+ error: typeof moved.error === "string" && moved.error ? boundedString(moved.error, 500) : null
796
+ };
797
+ }
798
+ function readRecurring(value) {
799
+ const recurring = record(value);
800
+ if (!recurring || typeof recurring.groupKey !== "string" || !recurring.groupKey) return null;
801
+ const runs = Number.isSafeInteger(recurring.runs) && recurring.runs > 0 ? recurring.runs : 1;
802
+ return {
803
+ groupKey: boundedString(recurring.groupKey, 300),
804
+ runs,
805
+ lastStatus: ["ok", "failed", "mixed"].includes(recurring.lastStatus) ? recurring.lastStatus : null
806
+ };
807
+ }
707
808
  function readSessions(value) {
708
809
  if (!Array.isArray(value)) return [];
709
810
  return value.flatMap((raw) => {
@@ -725,7 +826,13 @@ function readSessions(value) {
725
826
  live: row.live === true,
726
827
  runtimeStatus: row.runtimeStatus === "running" || row.runtimeStatus === "busy" || row.runtimeStatus === "idle" ? row.runtimeStatus : null,
727
828
  ...Number.isSafeInteger(row.subagentCount) && row.subagentCount > 0 ? { subagentCount: row.subagentCount } : {},
728
- activity: ACTIVITY_PRIORITY[row.activity] !== void 0 ? row.activity : void 0
829
+ activity: ACTIVITY_PRIORITY[row.activity] !== void 0 ? row.activity : void 0,
830
+ ...row.participant !== void 0 ? { participant: readParticipant(row.participant) } : {},
831
+ workspaceRef: readWorkspaceRef(row.workspaceRef, string(row.cwd)),
832
+ ...readMirror(row.mirror) ? { mirror: readMirror(row.mirror) } : {},
833
+ ...readTrigger(row.trigger) ? { trigger: readTrigger(row.trigger) } : {},
834
+ ...readRecurring(row.recurring) ? { recurring: readRecurring(row.recurring) } : {},
835
+ ...readCrossSurface(row.crossSurface) ? { crossSurface: readCrossSurface(row.crossSurface) } : {}
729
836
  }];
730
837
  });
731
838
  }
@@ -909,6 +1016,10 @@ function normalizeUiState(value) {
909
1016
  canConfigureSettings: raw.canConfigureSettings === true,
910
1017
  messaging: raw.messaging === "live_peer" ? "live_peer" : null,
911
1018
  workspace: string(raw.workspace),
1019
+ workspaceRef: readWorkspaceRef(raw.workspaceRef, string(raw.workspace)),
1020
+ participant: readParticipant(raw.participant),
1021
+ mirror: readMirror(raw.mirror),
1022
+ holder: readHolder(raw.holder),
912
1023
  taskPlan: readTaskPlan(raw.taskPlan),
913
1024
  semantics: readSemantics(raw.semantics),
914
1025
  terminalHandoff: readTerminalHandoff(raw.terminalHandoff),
@@ -1110,6 +1221,15 @@ function isSendKey(event) {
1110
1221
 
1111
1222
  // src/memory.js
1112
1223
  var MEMORY_LIMIT = 100;
1224
+ var registry = /* @__PURE__ */ new Set();
1225
+ function uiMemory() {
1226
+ const map = /* @__PURE__ */ new Map();
1227
+ registry.add(map);
1228
+ return map;
1229
+ }
1230
+ function resetSupercodeUiMemory() {
1231
+ for (const map of registry) map.clear();
1232
+ }
1113
1233
  function boundedSet(map, key, value) {
1114
1234
  map.delete(key);
1115
1235
  map.set(key, value);
@@ -1463,7 +1583,7 @@ function MessageImages({ items, adapter }) {
1463
1583
  }
1464
1584
 
1465
1585
  // src/intent.js
1466
- var CONFIRMABLE_ACTIONS = /* @__PURE__ */ new Set(["resume", "join", "branch", "reduce", "export"]);
1586
+ var CONFIRMABLE_ACTIONS = /* @__PURE__ */ new Set(["resume", "join", "branch", "reduce", "export", "take_over"]);
1467
1587
  async function dispatchConfirmedIntent(adapter, intent) {
1468
1588
  if (CONFIRMABLE_ACTIONS.has(intent.action) && adapter.confirmIntent) {
1469
1589
  const confirmed = await adapter.confirmIntent(intent);
@@ -1488,7 +1608,7 @@ function useAutosizeTextarea(ref, value) {
1488
1608
 
1489
1609
  // src/composer.jsx
1490
1610
  import { jsx as jsx3, jsxs as jsxs3 } from "preact/jsx-runtime";
1491
- var composerMemory = /* @__PURE__ */ new Map();
1611
+ var composerMemory = uiMemory();
1492
1612
  function ExecutionModeSelect({ modes = [], value, onChange, disabled = false, label = "Run in" }) {
1493
1613
  if (modes.length < 2) return null;
1494
1614
  return /* @__PURE__ */ jsxs3("label", { className: "scui-execution-mode", title: disabled ? void 0 : `${label}: ${value}`, children: [
@@ -1891,7 +2011,11 @@ function languageLabel(info) {
1891
2011
  }
1892
2012
  function frameCode(render, tokens, index, options, env, self) {
1893
2013
  const label = markdown.utils.escapeHtml(languageLabel(tokens[index]?.info ?? ""));
1894
- return `<div class="scui-code-block"><div class="scui-code-head"><span>${label}</span><button class="scui-code-copy" type="button" aria-label="Copy code" title="Copy code">${COPY_ICON}<span>Copy</span></button></div>${render(tokens, index, options, env, self)}</div>`;
2014
+ const body = render(tokens, index, options, env, self).replace(
2015
+ "<pre>",
2016
+ `<pre tabindex="0" role="region" aria-label="${label} code">`
2017
+ );
2018
+ return `<div class="scui-code-block"><div class="scui-code-head"><span>${label}</span><button class="scui-code-copy" type="button" aria-label="Copy code" title="Copy code">${COPY_ICON}<span>Copy</span></button></div>${body}</div>`;
1895
2019
  }
1896
2020
  for (const kind of ["fence", "code_block"]) {
1897
2021
  const render = markdown.renderer.rules[kind];
@@ -2228,6 +2352,26 @@ function TechnicalDetails({ entry }) {
2228
2352
  ] });
2229
2353
  }
2230
2354
  function TranscriptEntry({ entry, state, adapter }) {
2355
+ if (entry.role === "opaque") {
2356
+ return /* @__PURE__ */ jsxs6("article", { className: "scui-message scui-opaque", "data-role": "opaque", "data-kind": entry.kind, "aria-label": "Unrecognized entry", children: [
2357
+ /* @__PURE__ */ jsxs6("div", { className: "scui-notice", "data-code": "opaque-entry", children: [
2358
+ "Unrecognized entry kind ",
2359
+ /* @__PURE__ */ jsx7("code", { children: entry.kind }),
2360
+ " \u2014 kept as-is, nothing dropped"
2361
+ ] }),
2362
+ entry.text ? /* @__PURE__ */ jsx7(Markdown, { value: entry.text, copyText: adapter?.copyText }) : null,
2363
+ entry.raw ? /* @__PURE__ */ jsxs6("details", { className: "scui-tool-technical", children: [
2364
+ /* @__PURE__ */ jsx7("summary", { children: "Technical details" }),
2365
+ /* @__PURE__ */ jsx7("div", { children: /* @__PURE__ */ jsxs6("section", { children: [
2366
+ /* @__PURE__ */ jsx7("strong", { children: "Raw entry" }),
2367
+ /* @__PURE__ */ jsxs6("pre", { children: [
2368
+ entry.raw,
2369
+ entry.truncated ? "\n[truncated]" : ""
2370
+ ] })
2371
+ ] }) })
2372
+ ] }) : null
2373
+ ] });
2374
+ }
2231
2375
  if (entry.role === "request") return /* @__PURE__ */ jsx7(RequestCard, { entry, adapter, canRespond: state.canRespond });
2232
2376
  if (entry.role === "reasoning") {
2233
2377
  return /* @__PURE__ */ jsxs6("details", { className: "scui-reasoning", open: entry.streaming, children: [
@@ -2357,7 +2501,7 @@ function SessionDetails({ semantics }) {
2357
2501
  ] })
2358
2502
  ] });
2359
2503
  }
2360
- var conversationMemory = /* @__PURE__ */ new Map();
2504
+ var conversationMemory = uiMemory();
2361
2505
  function ConversationAnnouncements({ state }) {
2362
2506
  const previousBusy = useRef4(state.busy);
2363
2507
  const [announcement, setAnnouncement] = useState3("");
@@ -2476,7 +2620,7 @@ import { useEffect as useEffect9, useId as useId3, useMemo as useMemo3, useRef a
2476
2620
  // src/sessions.jsx
2477
2621
  import { useEffect as useEffect6, useLayoutEffect as useLayoutEffect3, useRef as useRef5, useState as useState4 } from "preact/hooks";
2478
2622
  import { jsx as jsx8, jsxs as jsxs7 } from "preact/jsx-runtime";
2479
- var sessionListMemory = /* @__PURE__ */ new Map();
2623
+ var sessionListMemory = uiMemory();
2480
2624
  function sessionPathParts(value) {
2481
2625
  const complete = String(value ?? "").replaceAll("\\", "/");
2482
2626
  const boundary = complete.lastIndexOf("/");
@@ -2498,11 +2642,55 @@ function SessionRow({ row, state, onOpen, onOpenSubagents, now = Date.now() }) {
2498
2642
  const unreadCount = attention?.unreadCount ?? 0;
2499
2643
  const unreadLabel = unreadCount > 99 ? "99+" : String(unreadCount);
2500
2644
  const age = relativeAge(row.previewUpdatedAt ?? row.updatedAt, now) || row.age;
2501
- return /* @__PURE__ */ jsxs7("article", { className: "scui-session", "data-active": row.active, "data-activity": activity, "data-writable": row.writable, children: [
2645
+ const nounBadges = [];
2646
+ if (row.trigger && row.trigger.kind !== "manual" && row.trigger.kind !== "human") {
2647
+ const runs = row.recurring?.runs;
2648
+ const detail = row.trigger.label || row.trigger.surface || "";
2649
+ nounBadges.push(
2650
+ /* @__PURE__ */ jsxs7("small", { className: "scui-session-noun", "data-noun": "trigger", "data-kind": row.trigger.kind, "data-surface": row.trigger.surface ?? void 0, "data-status": row.recurring?.lastStatus ?? void 0, children: [
2651
+ row.trigger.kind,
2652
+ detail ? ` \xB7 ${detail}` : "",
2653
+ runs && runs > 1 ? ` \xB7 \xD7${runs}` : ""
2654
+ ] }, "trigger")
2655
+ );
2656
+ }
2657
+ if (row.crossSurface) {
2658
+ nounBadges.push(
2659
+ /* @__PURE__ */ jsxs7("small", { className: "scui-session-noun", "data-noun": "cross-surface", "data-state": row.crossSurface.state, title: row.crossSurface.error ?? void 0, children: [
2660
+ "\u2192",
2661
+ row.crossSurface.platform ?? "elsewhere",
2662
+ " \xB7 ",
2663
+ row.crossSurface.state
2664
+ ] }, "cross-surface")
2665
+ );
2666
+ }
2667
+ if (row.participant && row.participant.kind !== "local-user") {
2668
+ nounBadges.push(
2669
+ /* @__PURE__ */ jsx8("small", { className: "scui-session-noun", "data-noun": "participant", "data-kind": row.participant.kind, children: row.participant.kind === "foreign" ? `${row.participant.label ?? "someone else"}${row.participant.origin ? ` via ${row.participant.origin}` : ""}` : "unknown participant" }, "participant")
2670
+ );
2671
+ }
2672
+ if (row.mirror) {
2673
+ nounBadges.push(
2674
+ /* @__PURE__ */ jsxs7("small", { className: "scui-session-noun", "data-noun": "mirror", "data-truncated": row.mirror.truncated, children: [
2675
+ "mirror of ",
2676
+ harnessDisplayName(row.mirror.canonicalHarness)
2677
+ ] }, "mirror")
2678
+ );
2679
+ }
2680
+ if (row.workspaceRef?.kind === "channel") {
2681
+ nounBadges.push(
2682
+ /* @__PURE__ */ jsxs7("small", { className: "scui-session-noun", "data-noun": "workspace", "data-kind": "channel", children: [
2683
+ "#",
2684
+ row.workspaceRef.value ?? "channel"
2685
+ ] }, "workspace")
2686
+ );
2687
+ }
2688
+ return /* @__PURE__ */ jsxs7("article", { className: "scui-session", "data-active": row.active, "data-activity": activity, "data-writable": row.writable, "data-workspace-kind": row.workspaceRef?.kind, children: [
2502
2689
  /* @__PURE__ */ jsxs7("button", { className: "scui-session-main", "data-session-key": row.key, type: "button", "aria-label": `${title} \xB7 ${harnessDisplayName(row.harness)}${row.writable ? "" : " \xB7 Read-only"}${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: [
2503
2690
  /* @__PURE__ */ jsx8(HarnessLogo, { id: row.harness, activity, size: 34 }),
2504
2691
  /* @__PURE__ */ jsxs7("span", { className: "scui-session-copy", children: [
2505
2692
  /* @__PURE__ */ jsx8("span", { className: "scui-session-title", children: /* @__PURE__ */ jsx8("strong", { children: title }) }),
2693
+ nounBadges.length ? /* @__PURE__ */ jsx8("span", { className: "scui-session-nouns", children: nounBadges }) : null,
2506
2694
  path.complete ? /* @__PURE__ */ jsxs7("small", { className: "scui-session-path", title: row.cwd, children: [
2507
2695
  /* @__PURE__ */ jsx8("span", { className: "scui-session-path-leading", children: path.leading }),
2508
2696
  path.separator ? /* @__PURE__ */ jsx8("span", { className: "scui-session-path-separator", children: path.separator }) : null,
@@ -2917,10 +3105,54 @@ function HarnessPicker({ harnesses, state, value, disabled = false, adapter, onC
2917
3105
 
2918
3106
  // src/messenger.jsx
2919
3107
  import { Fragment as Fragment5, jsx as jsx11, jsxs as jsxs10 } from "preact/jsx-runtime";
2920
- var pendingMessageMemory = /* @__PURE__ */ new Map();
2921
- var messengerViewMemory = /* @__PURE__ */ new Map();
2922
- var newChatMemory = /* @__PURE__ */ new Map();
3108
+ var pendingMessageMemory = uiMemory();
3109
+ var messengerViewMemory = uiMemory();
3110
+ var newChatMemory = uiMemory();
2923
3111
  var TRACKED_ACTIONS = /* @__PURE__ */ new Set(["resume", "join", "detach", "branch", "reduce", "terminal", "export", "steer", "interrupt", "respond", "configureHarness", "refresh", "loadEarlier", "loadSessions"]);
3112
+ function UniversalNouns({ state, adapter }) {
3113
+ const banners = [];
3114
+ if (state.participant && state.participant.kind !== "local-user") {
3115
+ banners.push(
3116
+ /* @__PURE__ */ jsx11("div", { className: "scui-notice scui-noun-banner", "data-noun": "participant", "data-kind": state.participant.kind, children: state.participant.kind === "foreign" ? /* @__PURE__ */ jsxs10(Fragment5, { children: [
3117
+ "This is ",
3118
+ /* @__PURE__ */ jsx11("strong", { children: state.participant.label ?? "someone else's" }),
3119
+ " conversation",
3120
+ state.participant.origin ? /* @__PURE__ */ jsxs10(Fragment5, { children: [
3121
+ " via ",
3122
+ state.participant.origin
3123
+ ] }) : null,
3124
+ "."
3125
+ ] }) : /* @__PURE__ */ jsx11(Fragment5, { children: "Unknown participant \u2014 this conversation was not started here." }) }, "participant")
3126
+ );
3127
+ }
3128
+ if (state.mirror) {
3129
+ banners.push(
3130
+ /* @__PURE__ */ jsxs10("div", { className: "scui-notice scui-noun-banner", "data-noun": "mirror", "data-truncated": state.mirror.truncated, children: [
3131
+ state.mirror.bounded ? "Bounded mirror" : "Mirror",
3132
+ " of a ",
3133
+ /* @__PURE__ */ jsx11("strong", { children: harnessDisplayName(state.mirror.canonicalHarness) }),
3134
+ " session \u2014 the canonical record lives in its own store",
3135
+ state.mirror.truncated ? " (truncated here)" : "",
3136
+ ".",
3137
+ state.mirror.canonicalKey ? /* @__PURE__ */ jsx11("button", { type: "button", onClick: () => adapter.onIntent({ action: "open_session", key: state.mirror.canonicalKey }), children: "Open canonical session" }) : null
3138
+ ] }, "mirror")
3139
+ );
3140
+ }
3141
+ if (state.holder && state.holder.surface !== "supercode") {
3142
+ banners.push(
3143
+ /* @__PURE__ */ jsxs10("div", { className: "scui-notice scui-noun-banner", "data-noun": "holder", "data-surface": state.holder.surface ?? "unheld", children: [
3144
+ state.holder.surface ? /* @__PURE__ */ jsxs10(Fragment5, { children: [
3145
+ "Held by ",
3146
+ /* @__PURE__ */ jsx11("strong", { children: state.holder.surface }),
3147
+ " right now."
3148
+ ] }) : /* @__PURE__ */ jsx11(Fragment5, { children: "No surface holds this session right now." }),
3149
+ state.holder.canTakeOver ? /* @__PURE__ */ jsx11("button", { type: "button", onClick: () => dispatchConfirmedIntent(adapter, { action: "take_over" }), children: "Take over" }) : null
3150
+ ] }, "holder")
3151
+ );
3152
+ }
3153
+ if (!banners.length) return null;
3154
+ return /* @__PURE__ */ jsx11("div", { className: "scui-noun-banners", children: banners });
3155
+ }
2924
3156
  function Receipt({ state, adapter }) {
2925
3157
  const receipt = state.reductionReceipt;
2926
3158
  if (receipt) return /* @__PURE__ */ jsx11("div", { className: "scui-receipt", children: /* @__PURE__ */ jsxs10("span", { children: [
@@ -3184,6 +3416,7 @@ function Chat({ state, adapter, onBack, onNew, onClose, components, slots, label
3184
3416
  state.recoverable ? /* @__PURE__ */ jsx11("button", { type: "button", disabled: Boolean(action), onClick: () => trackedAdapter.onIntent({ action: "refresh" }), children: "Retry" }) : null
3185
3417
  ] }) : null,
3186
3418
  /* @__PURE__ */ jsx11(Receipt, { state, adapter }),
3419
+ /* @__PURE__ */ jsx11(UniversalNouns, { state: actionState, adapter: trackedAdapter }),
3187
3420
  /* @__PURE__ */ jsx11(Conversation, { state: actionState, adapter: trackedAdapter, components, slots, memoryKey, pending: pendingMessage, unreadAfterMessages }, memoryKey),
3188
3421
  /* @__PURE__ */ jsx11(ContinuationBar, { state: actionState, adapter: trackedAdapter, labels }),
3189
3422
  /* @__PURE__ */ jsx11(Advisory, { state: actionState, adapter: trackedAdapter, value: state.interopSettings?.advisories[0] ?? null, onReview: (recommendedChange) => setSettings({ recommendedChange }) }),
@@ -3558,5 +3791,6 @@ export {
3558
3791
  TranscriptEntry,
3559
3792
  UiIcon,
3560
3793
  harnessLogoDataUrl,
3561
- hasHarnessLogo
3794
+ hasHarnessLogo,
3795
+ resetSupercodeUiMemory
3562
3796
  };
package/composer.mjs CHANGED
@@ -32,6 +32,10 @@ var EMPTY_UI_STATE = Object.freeze({
32
32
  harness: "",
33
33
  mode: "none",
34
34
  strategy: null,
35
+ participant: Object.freeze({ kind: "local-user", label: null, origin: null }),
36
+ workspaceRef: Object.freeze({ kind: "none", value: null }),
37
+ mirror: null,
38
+ holder: null,
35
39
  canSend: false,
36
40
  canSteer: false,
37
41
  canResume: false,
@@ -100,6 +104,12 @@ function isSendKey(event) {
100
104
 
101
105
  // src/memory.js
102
106
  var MEMORY_LIMIT = 100;
107
+ var registry = /* @__PURE__ */ new Set();
108
+ function uiMemory() {
109
+ const map = /* @__PURE__ */ new Map();
110
+ registry.add(map);
111
+ return map;
112
+ }
103
113
  function boundedSet(map, key, value) {
104
114
  map.delete(key);
105
115
  map.set(key, value);
@@ -289,7 +299,7 @@ function ImageTray({ items, onRemove }) {
289
299
  }
290
300
 
291
301
  // src/intent.js
292
- var CONFIRMABLE_ACTIONS = /* @__PURE__ */ new Set(["resume", "join", "branch", "reduce", "export"]);
302
+ var CONFIRMABLE_ACTIONS = /* @__PURE__ */ new Set(["resume", "join", "branch", "reduce", "export", "take_over"]);
293
303
  async function dispatchConfirmedIntent(adapter, intent) {
294
304
  if (CONFIRMABLE_ACTIONS.has(intent.action) && adapter.confirmIntent) {
295
305
  const confirmed = await adapter.confirmIntent(intent);
@@ -314,7 +324,7 @@ function useAutosizeTextarea(ref, value) {
314
324
 
315
325
  // src/composer.jsx
316
326
  import { jsx as jsx3, jsxs as jsxs3 } from "preact/jsx-runtime";
317
- var composerMemory = /* @__PURE__ */ new Map();
327
+ var composerMemory = uiMemory();
318
328
  function ExecutionModeSelect({ modes = [], value, onChange, disabled = false, label = "Run in" }) {
319
329
  if (modes.length < 2) return null;
320
330
  return /* @__PURE__ */ jsxs3("label", { className: "scui-execution-mode", title: disabled ? void 0 : `${label}: ${value}`, children: [
package/controller.d.ts CHANGED
@@ -9,6 +9,7 @@ import type {
9
9
  SupercodeController,
10
10
  } from '@volter-ai-dev/supercode-client';
11
11
  import type {
12
+ TranscriptEntryModel,
12
13
  AttachedSessionModel,
13
14
  ContinuationMode,
14
15
  SessionAttention,
@@ -19,6 +20,9 @@ import type {
19
20
  UiAdapter,
20
21
  } from './index.js';
21
22
 
23
+ /** `after`: a conversation entry id (row follows it), `'^'` (before everything), or null/absent (at the end). */
24
+ export type HostTranscriptEntry = TranscriptEntryModel & { after?: string | null };
25
+
22
26
  export interface ClientProjectionOptions {
23
27
  now?: number;
24
28
  /** Maximum rendered conversation rows after hidden context is removed. Default 120. */
@@ -49,6 +53,9 @@ export interface ClientProjectionOptions {
49
53
  continuationModes?: ContinuationMode[];
50
54
  /** Trusted-host inventory and lifecycle overlays (for example a machine-wide session catalog). */
51
55
  sessions?: SessionRowModel[];
56
+ /** Host-owned rows placed inside the conversation: after the conversation entry `after` names,
57
+ * or at the end. The conversation's own order is never changed; ids already present are skipped. */
58
+ hostEntries?: readonly HostTranscriptEntry[];
52
59
  attached?: AttachedSessionModel | null;
53
60
  owned?: AttachedSessionModel | null;
54
61
  attachError?: SupercodeUiState['attachError'];
package/controller.mjs CHANGED
@@ -509,6 +509,39 @@ function projectPill(snapshot) {
509
509
  return { tone: 'live', label: text(`${label(harness)} ready`) };
510
510
  }
511
511
 
512
+ /** A host's own rows — display objects an app printed, notices the host raised —
513
+ * inside the conversation at the position the host names: after a conversation
514
+ * entry by id, or at the end. The conversation's own order is never changed. */
515
+ function withHostEntries(transcript, hostEntries) {
516
+ if (!Array.isArray(hostEntries) || hostEntries.length === 0) return transcript;
517
+ const out = [...transcript];
518
+ const tail = [];
519
+ for (const raw of hostEntries) {
520
+ if (!raw || typeof raw !== 'object' || typeof raw.id !== 'string') continue;
521
+ const { after, ...entry } = raw;
522
+ if (out.some((item) => item.id === entry.id)) continue;
523
+ if (after === '^') {
524
+ // before everything, in the given order
525
+ let at = 0;
526
+ while (at < out.length && out[at].__hostAfter === '^') at += 1;
527
+ out.splice(at, 0, { ...entry, __hostAfter: '^' });
528
+ continue;
529
+ }
530
+ const index = typeof after === 'string' ? out.findIndex((item) => item.id === after) : -1;
531
+ if (index >= 0) {
532
+ // keep host rows that share an anchor in their given order
533
+ let at = index + 1;
534
+ while (at < out.length && out[at].__hostAfter === after) at += 1;
535
+ out.splice(at, 0, { ...entry, __hostAfter: after });
536
+ } else tail.push(entry);
537
+ }
538
+ return [...out, ...tail].map((item) => {
539
+ if (!('__hostAfter' in item)) return item;
540
+ const { __hostAfter: _anchor, ...clean } = item;
541
+ return clean;
542
+ });
543
+ }
544
+
512
545
  function projectClientSnapshotInternal(snapshot, options, imageRegistry) {
513
546
  const now = Number.isFinite(options.now) ? options.now : Date.now();
514
547
  const busy = ['running', 'interrupting', 'reconciling'].includes(snapshot.turn?.state);
@@ -559,7 +592,10 @@ function projectClientSnapshotInternal(snapshot, options, imageRegistry) {
559
592
  ? snapshot.connection?.ownsRuntime ? active : null
560
593
  : options.owned;
561
594
  const maxEntries = positiveInteger(options.maxEntries, DEFAULT_MAX_ENTRIES);
562
- const transcript = projectConversation(snapshot.conversation ?? [], options, imageRegistry);
595
+ const transcript = withHostEntries(
596
+ projectConversation(snapshot.conversation ?? [], options, imageRegistry),
597
+ options.hostEntries,
598
+ );
563
599
  const startup = snapshot.availability === 'loading'
564
600
  ? snapshot.operation === 'start' ? 'starting' : snapshot.operation === 'refresh' ? 'discovering' : 'connecting'
565
601
  : 'ready';