@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 +113 -2
- package/components.d.ts +3 -0
- package/components.mjs +246 -12
- package/composer.mjs +12 -2
- package/controller.d.ts +7 -0
- package/controller.mjs +37 -1
- package/conversation.mjs +36 -2
- package/core.d.ts +6 -0
- package/core.mjs +215 -1
- package/embed.mjs +241 -11
- package/host.d.ts +1 -1
- package/host.mjs +2 -2
- package/index.d.ts +103 -0
- package/messenger.mjs +241 -11
- package/package.json +1 -1
- package/react/activity.mjs +113 -2
- package/react/components.mjs +246 -12
- package/react/composer.mjs +12 -2
- package/react/conversation.mjs +36 -2
- package/react/messenger.mjs +241 -11
- package/react/sessions.mjs +62 -3
- package/react/settings.mjs +4 -0
- package/react/subagents.mjs +36 -2
- package/sessions.mjs +62 -3
- package/settings.mjs +4 -0
- package/styles.css +11 -1
- package/subagents.mjs +36 -2
package/conversation.mjs
CHANGED
|
@@ -33,6 +33,10 @@ var EMPTY_UI_STATE = Object.freeze({
|
|
|
33
33
|
harness: "",
|
|
34
34
|
mode: "none",
|
|
35
35
|
strategy: null,
|
|
36
|
+
participant: Object.freeze({ kind: "local-user", label: null, origin: null }),
|
|
37
|
+
workspaceRef: Object.freeze({ kind: "none", value: null }),
|
|
38
|
+
mirror: null,
|
|
39
|
+
holder: null,
|
|
36
40
|
canSend: false,
|
|
37
41
|
canSteer: false,
|
|
38
42
|
canResume: false,
|
|
@@ -535,7 +539,11 @@ function languageLabel(info) {
|
|
|
535
539
|
}
|
|
536
540
|
function frameCode(render, tokens, index, options, env, self) {
|
|
537
541
|
const label = markdown.utils.escapeHtml(languageLabel(tokens[index]?.info ?? ""));
|
|
538
|
-
|
|
542
|
+
const body = render(tokens, index, options, env, self).replace(
|
|
543
|
+
"<pre>",
|
|
544
|
+
`<pre tabindex="0" role="region" aria-label="${label} code">`
|
|
545
|
+
);
|
|
546
|
+
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>`;
|
|
539
547
|
}
|
|
540
548
|
for (const kind of ["fence", "code_block"]) {
|
|
541
549
|
const render = markdown.renderer.rules[kind];
|
|
@@ -593,6 +601,12 @@ function Markdown({ value, copyText }) {
|
|
|
593
601
|
|
|
594
602
|
// src/memory.js
|
|
595
603
|
var MEMORY_LIMIT = 100;
|
|
604
|
+
var registry = /* @__PURE__ */ new Set();
|
|
605
|
+
function uiMemory() {
|
|
606
|
+
const map = /* @__PURE__ */ new Map();
|
|
607
|
+
registry.add(map);
|
|
608
|
+
return map;
|
|
609
|
+
}
|
|
596
610
|
function boundedSet(map, key, value) {
|
|
597
611
|
map.delete(key);
|
|
598
612
|
map.set(key, value);
|
|
@@ -1096,6 +1110,26 @@ function TechnicalDetails({ entry }) {
|
|
|
1096
1110
|
] });
|
|
1097
1111
|
}
|
|
1098
1112
|
function TranscriptEntry({ entry, state, adapter }) {
|
|
1113
|
+
if (entry.role === "opaque") {
|
|
1114
|
+
return /* @__PURE__ */ jsxs3("article", { className: "scui-message scui-opaque", "data-role": "opaque", "data-kind": entry.kind, "aria-label": "Unrecognized entry", children: [
|
|
1115
|
+
/* @__PURE__ */ jsxs3("div", { className: "scui-notice", "data-code": "opaque-entry", children: [
|
|
1116
|
+
"Unrecognized entry kind ",
|
|
1117
|
+
/* @__PURE__ */ jsx4("code", { children: entry.kind }),
|
|
1118
|
+
" \u2014 kept as-is, nothing dropped"
|
|
1119
|
+
] }),
|
|
1120
|
+
entry.text ? /* @__PURE__ */ jsx4(Markdown, { value: entry.text, copyText: adapter?.copyText }) : null,
|
|
1121
|
+
entry.raw ? /* @__PURE__ */ jsxs3("details", { className: "scui-tool-technical", children: [
|
|
1122
|
+
/* @__PURE__ */ jsx4("summary", { children: "Technical details" }),
|
|
1123
|
+
/* @__PURE__ */ jsx4("div", { children: /* @__PURE__ */ jsxs3("section", { children: [
|
|
1124
|
+
/* @__PURE__ */ jsx4("strong", { children: "Raw entry" }),
|
|
1125
|
+
/* @__PURE__ */ jsxs3("pre", { children: [
|
|
1126
|
+
entry.raw,
|
|
1127
|
+
entry.truncated ? "\n[truncated]" : ""
|
|
1128
|
+
] })
|
|
1129
|
+
] }) })
|
|
1130
|
+
] }) : null
|
|
1131
|
+
] });
|
|
1132
|
+
}
|
|
1099
1133
|
if (entry.role === "request") return /* @__PURE__ */ jsx4(RequestCard, { entry, adapter, canRespond: state.canRespond });
|
|
1100
1134
|
if (entry.role === "reasoning") {
|
|
1101
1135
|
return /* @__PURE__ */ jsxs3("details", { className: "scui-reasoning", open: entry.streaming, children: [
|
|
@@ -1225,7 +1259,7 @@ function SessionDetails({ semantics }) {
|
|
|
1225
1259
|
] })
|
|
1226
1260
|
] });
|
|
1227
1261
|
}
|
|
1228
|
-
var conversationMemory =
|
|
1262
|
+
var conversationMemory = uiMemory();
|
|
1229
1263
|
function ConversationAnnouncements({ state }) {
|
|
1230
1264
|
const previousBusy = useRef3(state.busy);
|
|
1231
1265
|
const [announcement, setAnnouncement] = useState2("");
|
package/core.d.ts
CHANGED
|
@@ -18,6 +18,10 @@ export type {
|
|
|
18
18
|
SessionMode,
|
|
19
19
|
SessionRowModel,
|
|
20
20
|
SessionSemanticsModel,
|
|
21
|
+
CrossSurfaceModel,
|
|
22
|
+
HarnessSessionDescriptorModel,
|
|
23
|
+
TriggerKind,
|
|
24
|
+
TriggerModel,
|
|
21
25
|
StartupPhase,
|
|
22
26
|
SupercodeUiIntent,
|
|
23
27
|
SupercodeUiState,
|
|
@@ -49,6 +53,8 @@ export {
|
|
|
49
53
|
relativeAge,
|
|
50
54
|
sessionActivity,
|
|
51
55
|
sessionDisplayName,
|
|
56
|
+
sessionRowsFromDescriptors,
|
|
57
|
+
surfaceLabel,
|
|
52
58
|
selectContributions,
|
|
53
59
|
terminalCommand,
|
|
54
60
|
toolCategory,
|
package/core.mjs
CHANGED
|
@@ -30,6 +30,10 @@ export const EMPTY_UI_STATE = Object.freeze({
|
|
|
30
30
|
harness: '',
|
|
31
31
|
mode: 'none',
|
|
32
32
|
strategy: null,
|
|
33
|
+
participant: Object.freeze({ kind: 'local-user', label: null, origin: null }),
|
|
34
|
+
workspaceRef: Object.freeze({ kind: 'none', value: null }),
|
|
35
|
+
mirror: null,
|
|
36
|
+
holder: null,
|
|
33
37
|
canSend: false,
|
|
34
38
|
canSteer: false,
|
|
35
39
|
canResume: false,
|
|
@@ -672,12 +676,58 @@ function readToolPresentation(value, entry) {
|
|
|
672
676
|
};
|
|
673
677
|
}
|
|
674
678
|
|
|
679
|
+
// Fast-churning upstreams will emit entry kinds this messenger has never
|
|
680
|
+
// seen. Dropping them silently misrepresents the session, so an addressable
|
|
681
|
+
// entry (a record with a string id) whose role or shape is unrecognized is
|
|
682
|
+
// kept as a safe OPAQUE entry: the original kind is preserved as a label and
|
|
683
|
+
// the raw payload stays available under technical details (UNI-13, the
|
|
684
|
+
// messenger counterpart of the TUI's opaque-line rule). Only an entry with no
|
|
685
|
+
// usable identity is still skipped — it cannot be keyed stably across
|
|
686
|
+
// re-renders.
|
|
687
|
+
const OPAQUE_RAW_CHARS = 4_000;
|
|
688
|
+
|
|
689
|
+
function opaqueEntry(item) {
|
|
690
|
+
// An entry that is ALREADY opaque re-normalizes to itself (state flows
|
|
691
|
+
// through normalization more than once: host fixtures, the messenger's own
|
|
692
|
+
// read, mirrors), so the original kind and raw capture must survive
|
|
693
|
+
// instead of being re-wrapped under kind "opaque".
|
|
694
|
+
const previouslyOpaque = item.role === 'opaque';
|
|
695
|
+
let raw;
|
|
696
|
+
if (previouslyOpaque && typeof item.raw === 'string') {
|
|
697
|
+
raw = item.raw;
|
|
698
|
+
} else {
|
|
699
|
+
try {
|
|
700
|
+
raw = JSON.stringify(item, null, 2) ?? '';
|
|
701
|
+
} catch {
|
|
702
|
+
raw = '[unserializable entry payload]';
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
const kind = previouslyOpaque && typeof item.kind === 'string'
|
|
706
|
+
? item.kind
|
|
707
|
+
: typeof item.role === 'string'
|
|
708
|
+
? item.role
|
|
709
|
+
: '';
|
|
710
|
+
return {
|
|
711
|
+
id: item.id,
|
|
712
|
+
role: 'opaque',
|
|
713
|
+
kind: boundedString(kind, 120) || 'unknown',
|
|
714
|
+
text: typeof item.text === 'string' ? boundedString(item.text, OPAQUE_RAW_CHARS) : '',
|
|
715
|
+
ts: nullableNumber(item.ts),
|
|
716
|
+
truncated: item.truncated === true || raw.length > OPAQUE_RAW_CHARS,
|
|
717
|
+
raw: boundedString(raw, OPAQUE_RAW_CHARS),
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
|
|
675
721
|
function readTranscript(value) {
|
|
676
722
|
if (!Array.isArray(value)) return [];
|
|
677
723
|
const result = [];
|
|
678
724
|
for (const candidate of value) {
|
|
679
725
|
const item = record(candidate);
|
|
680
|
-
if (!item || typeof item.id !== 'string'
|
|
726
|
+
if (!item || typeof item.id !== 'string') continue;
|
|
727
|
+
if (typeof item.text !== 'string' || !ROLES.has(item.role)) {
|
|
728
|
+
result.push(opaqueEntry(item));
|
|
729
|
+
continue;
|
|
730
|
+
}
|
|
681
731
|
const entry = {
|
|
682
732
|
id: item.id,
|
|
683
733
|
role: item.role,
|
|
@@ -742,6 +792,105 @@ function readTranscript(value) {
|
|
|
742
792
|
return result;
|
|
743
793
|
}
|
|
744
794
|
|
|
795
|
+
// ---- UNI-8..UNI-12: the universal-layer UI nouns ---------------------------
|
|
796
|
+
|
|
797
|
+
/// UNI-8: WHOSE conversation a session is. The messenger's historical implicit
|
|
798
|
+
/// model is you-and-agent-in-a-workspace; Hermes/OpenClaw channel sessions are
|
|
799
|
+
/// someone-else-and-agent. `kind` is the honest tri-state; `label` is the
|
|
800
|
+
/// human ("@jane"), `origin` the surface ("slack", "telegram", "acp").
|
|
801
|
+
function readParticipant(value) {
|
|
802
|
+
const participant = record(value);
|
|
803
|
+
if (!participant) return { kind: 'local-user', label: null, origin: null };
|
|
804
|
+
return {
|
|
805
|
+
kind: ['local-user', 'foreign', 'unknown'].includes(participant.kind) ? participant.kind : 'unknown',
|
|
806
|
+
label: typeof participant.label === 'string' && participant.label ? boundedString(participant.label, 200) : null,
|
|
807
|
+
origin: typeof participant.origin === 'string' && participant.origin ? boundedString(participant.origin, 100) : null,
|
|
808
|
+
};
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
/// UNI-9: a TYPED workspace. `repo` carries a path, `channel` carries a
|
|
812
|
+
/// channel label, `none` is a first-class value (Hermes assistant chats) —
|
|
813
|
+
/// not an empty string pretending to be a path.
|
|
814
|
+
function readWorkspaceRef(value, fallbackPath = '') {
|
|
815
|
+
const ref = record(value);
|
|
816
|
+
if (ref && ['repo', 'none', 'channel'].includes(ref.kind)) {
|
|
817
|
+
return {
|
|
818
|
+
kind: ref.kind,
|
|
819
|
+
value: ref.kind === 'none' ? null : typeof ref.value === 'string' && ref.value ? boundedString(ref.value, 500) : null,
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
// Legacy hosts send only the string path.
|
|
823
|
+
return fallbackPath
|
|
824
|
+
? { kind: 'repo', value: boundedString(fallbackPath, 500) }
|
|
825
|
+
: { kind: 'none', value: null };
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
/// UNI-10: canonical-elsewhere. A bounded/truncated MIRROR of a session whose
|
|
829
|
+
/// canonical record lives in another store (OpenClaw supervising codex, ...).
|
|
830
|
+
function readMirror(value) {
|
|
831
|
+
const mirror = record(value);
|
|
832
|
+
if (!mirror || typeof mirror.canonicalHarness !== 'string' || !mirror.canonicalHarness) return null;
|
|
833
|
+
return {
|
|
834
|
+
canonicalHarness: boundedString(mirror.canonicalHarness, 100),
|
|
835
|
+
canonicalKey: typeof mirror.canonicalKey === 'string' && mirror.canonicalKey ? boundedString(mirror.canonicalKey, 300) : null,
|
|
836
|
+
bounded: mirror.bounded === true,
|
|
837
|
+
truncated: mirror.truncated === true,
|
|
838
|
+
origin: typeof mirror.origin === 'string' && mirror.origin ? boundedString(mirror.origin, 200) : null,
|
|
839
|
+
};
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
/// UNI-11: which surface HOLDS this session now. `surface: 'supercode'` =
|
|
843
|
+
/// held here (writable path); any other string = held by that surface
|
|
844
|
+
/// (take-over is the transition, behind confirmIntent); null holder = unheld.
|
|
845
|
+
function readHolder(value) {
|
|
846
|
+
const holder = record(value);
|
|
847
|
+
if (!holder) return null;
|
|
848
|
+
const surface = typeof holder.surface === 'string' && holder.surface ? boundedString(holder.surface, 100) : null;
|
|
849
|
+
return {
|
|
850
|
+
surface,
|
|
851
|
+
canTakeOver: holder.canTakeOver === true && surface !== null && surface !== 'supercode',
|
|
852
|
+
};
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
/// UNI-12/ORCH-6: WHY a session exists. Trigger provenance for unattended
|
|
856
|
+
/// work, over the server's own `Trigger` enum. `manual` is the pre-ORCH-6
|
|
857
|
+
/// spelling of `human` and stays accepted so existing hosts keep working.
|
|
858
|
+
const TRIGGER_KINDS = ['human', 'channel', 'cron', 'heartbeat', 'webhook', 'parent', 'api', 'unknown'];
|
|
859
|
+
function readTrigger(value) {
|
|
860
|
+
const trigger = record(value);
|
|
861
|
+
if (!trigger) return null;
|
|
862
|
+
const kind = trigger.kind === 'manual' ? 'human' : trigger.kind;
|
|
863
|
+
return {
|
|
864
|
+
kind: TRIGGER_KINDS.includes(kind) ? kind : 'unknown',
|
|
865
|
+
label: typeof trigger.label === 'string' && trigger.label ? boundedString(trigger.label, 200) : null,
|
|
866
|
+
surface: typeof trigger.surface === 'string' && trigger.surface ? boundedString(trigger.surface, 300) : null,
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
/// ORCH-6: a conversation that moved to another surface (Hermes `handoff_*`).
|
|
871
|
+
function readCrossSurface(value) {
|
|
872
|
+
const moved = record(value);
|
|
873
|
+
if (!moved || typeof moved.state !== 'string' || !moved.state) return null;
|
|
874
|
+
return {
|
|
875
|
+
state: boundedString(moved.state, 100),
|
|
876
|
+
platform: typeof moved.platform === 'string' && moved.platform ? boundedString(moved.platform, 100) : null,
|
|
877
|
+
error: typeof moved.error === 'string' && moved.error ? boundedString(moved.error, 500) : null,
|
|
878
|
+
};
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
/// UNI-12: recurring-run grouping. A row that REPRESENTS a group of runs of
|
|
882
|
+
/// the same recurring trigger, so the inventory shows one line, not N.
|
|
883
|
+
function readRecurring(value) {
|
|
884
|
+
const recurring = record(value);
|
|
885
|
+
if (!recurring || typeof recurring.groupKey !== 'string' || !recurring.groupKey) return null;
|
|
886
|
+
const runs = Number.isSafeInteger(recurring.runs) && recurring.runs > 0 ? recurring.runs : 1;
|
|
887
|
+
return {
|
|
888
|
+
groupKey: boundedString(recurring.groupKey, 300),
|
|
889
|
+
runs,
|
|
890
|
+
lastStatus: ['ok', 'failed', 'mixed'].includes(recurring.lastStatus) ? recurring.lastStatus : null,
|
|
891
|
+
};
|
|
892
|
+
}
|
|
893
|
+
|
|
745
894
|
function readSessions(value) {
|
|
746
895
|
if (!Array.isArray(value)) return [];
|
|
747
896
|
return value.flatMap((raw) => {
|
|
@@ -764,6 +913,12 @@ function readSessions(value) {
|
|
|
764
913
|
runtimeStatus: row.runtimeStatus === 'running' || row.runtimeStatus === 'busy' || row.runtimeStatus === 'idle' ? row.runtimeStatus : null,
|
|
765
914
|
...(Number.isSafeInteger(row.subagentCount) && row.subagentCount > 0 ? { subagentCount: row.subagentCount } : {}),
|
|
766
915
|
activity: ACTIVITY_PRIORITY[row.activity] !== undefined ? row.activity : undefined,
|
|
916
|
+
...(row.participant !== undefined ? { participant: readParticipant(row.participant) } : {}),
|
|
917
|
+
workspaceRef: readWorkspaceRef(row.workspaceRef, string(row.cwd)),
|
|
918
|
+
...(readMirror(row.mirror) ? { mirror: readMirror(row.mirror) } : {}),
|
|
919
|
+
...(readTrigger(row.trigger) ? { trigger: readTrigger(row.trigger) } : {}),
|
|
920
|
+
...(readRecurring(row.recurring) ? { recurring: readRecurring(row.recurring) } : {}),
|
|
921
|
+
...(readCrossSurface(row.crossSurface) ? { crossSurface: readCrossSurface(row.crossSurface) } : {}),
|
|
767
922
|
}];
|
|
768
923
|
});
|
|
769
924
|
}
|
|
@@ -1004,6 +1159,10 @@ export function normalizeUiState(value) {
|
|
|
1004
1159
|
canConfigureSettings: raw.canConfigureSettings === true,
|
|
1005
1160
|
messaging: raw.messaging === 'live_peer' ? 'live_peer' : null,
|
|
1006
1161
|
workspace: string(raw.workspace),
|
|
1162
|
+
workspaceRef: readWorkspaceRef(raw.workspaceRef, string(raw.workspace)),
|
|
1163
|
+
participant: readParticipant(raw.participant),
|
|
1164
|
+
mirror: readMirror(raw.mirror),
|
|
1165
|
+
holder: readHolder(raw.holder),
|
|
1007
1166
|
taskPlan: readTaskPlan(raw.taskPlan),
|
|
1008
1167
|
semantics: readSemantics(raw.semantics),
|
|
1009
1168
|
terminalHandoff: readTerminalHandoff(raw.terminalHandoff),
|
|
@@ -1078,6 +1237,61 @@ export function harnessDisplayName(id) {
|
|
|
1078
1237
|
return HARNESS_NAMES[id] ?? id;
|
|
1079
1238
|
}
|
|
1080
1239
|
|
|
1240
|
+
/// ORCH-6: the compact surface key a person reads —
|
|
1241
|
+
/// `telegram:dm:123456`, `slack:channel:C1`, `acp`. Empty for a surface with
|
|
1242
|
+
/// nothing to say (a terminal session).
|
|
1243
|
+
export function surfaceLabel(value) {
|
|
1244
|
+
const surface = record(value);
|
|
1245
|
+
if (!surface) return '';
|
|
1246
|
+
const parts = [surface.platform, surface.kind, surface.chat_id, surface.thread_id]
|
|
1247
|
+
.filter((part) => typeof part === 'string' && part);
|
|
1248
|
+
return parts.length ? boundedString(parts.join(':'), 300) : '';
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
/// ORCH-6: project `harness.v1.sessions.discover` / `sessions.load` rows into
|
|
1252
|
+
/// list rows. This is the ONE client-side mapping from the service's wire
|
|
1253
|
+
/// shape to {@link SessionRowModel}: the conversation nouns
|
|
1254
|
+
/// (`trigger`/`surface`/`profile`/`recurrence`/`cross_surface`/`workspace`)
|
|
1255
|
+
/// are read straight off the server's derivation, never re-derived here.
|
|
1256
|
+
export function sessionRowsFromDescriptors(value) {
|
|
1257
|
+
if (!Array.isArray(value)) return [];
|
|
1258
|
+
return readSessions(value.flatMap((raw) => {
|
|
1259
|
+
const descriptor = record(raw);
|
|
1260
|
+
const locator = record(descriptor?.locator);
|
|
1261
|
+
if (!locator || typeof locator.harness !== 'string' || typeof locator.session_id !== 'string') return [];
|
|
1262
|
+
const surface = record(descriptor.surface);
|
|
1263
|
+
const label = surfaceLabel(surface);
|
|
1264
|
+
const cwd = typeof descriptor.cwd === 'string' ? descriptor.cwd : '';
|
|
1265
|
+
const title = typeof descriptor.title === 'string' ? descriptor.title : '';
|
|
1266
|
+
const recurrence = record(descriptor.recurrence);
|
|
1267
|
+
return [{
|
|
1268
|
+
key: `${locator.harness}:${locator.session_id}`,
|
|
1269
|
+
harness: locator.harness,
|
|
1270
|
+
name: label || cwd.split(/[\\/]/).filter(Boolean).pop() || locator.session_id,
|
|
1271
|
+
cwd,
|
|
1272
|
+
title,
|
|
1273
|
+
updatedAt: descriptor.updated_at_ms ?? null,
|
|
1274
|
+
messages: descriptor.message_count ?? null,
|
|
1275
|
+
active: false,
|
|
1276
|
+
writable: false,
|
|
1277
|
+
live: false,
|
|
1278
|
+
runtimeStatus: null,
|
|
1279
|
+
subagentCount: descriptor.child_session_count,
|
|
1280
|
+
workspaceRef: descriptor.workspace,
|
|
1281
|
+
trigger: { kind: descriptor.trigger ?? 'unknown', label: recurrence?.job_id ?? descriptor.profile ?? null, surface: label || null },
|
|
1282
|
+
// One fire is one row: supercode renders the grouping a harness
|
|
1283
|
+
// publishes, it never invents a run count it has not counted.
|
|
1284
|
+
...(recurrence?.job_id ? { recurring: { groupKey: recurrence.job_id, runs: 1, lastStatus: null } } : {}),
|
|
1285
|
+
...(descriptor.cross_surface ? { crossSurface: descriptor.cross_surface } : {}),
|
|
1286
|
+
// A channel surface names the person who reached the agent; a terminal
|
|
1287
|
+
// session has no platform and stays the local user.
|
|
1288
|
+
...(surface?.platform && surface?.participant_id
|
|
1289
|
+
? { participant: { kind: 'foreign', label: surface.participant_id, origin: surface.platform } }
|
|
1290
|
+
: {}),
|
|
1291
|
+
}];
|
|
1292
|
+
}));
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1081
1295
|
export function sessionDisplayName(session) {
|
|
1082
1296
|
const title = session.title?.trim();
|
|
1083
1297
|
return title && title !== session.name ? title : session.name || 'Untitled chat';
|