@volter-ai-dev/supercode-ui 0.1.66 → 0.1.68

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/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,
@@ -70,7 +74,12 @@ var EMPTY_UI_STATE = Object.freeze({
70
74
  subagentInspector: null,
71
75
  attached: null,
72
76
  owned: null,
73
- attachError: null
77
+ attachError: null,
78
+ agentPackage: null,
79
+ contributions: Object.freeze([]),
80
+ agentPackageError: null,
81
+ agentPackageCapabilityState: null,
82
+ agentPackageCapabilityError: null
74
83
  });
75
84
  var TOOL_CATEGORIES = /* @__PURE__ */ new Set(["read", "search", "edit", "command", "test", "web", "agent", "plan", "other"]);
76
85
  var MAX_TOOL_FIELDS = 8;
@@ -530,7 +539,11 @@ function languageLabel(info) {
530
539
  }
531
540
  function frameCode(render, tokens, index, options, env, self) {
532
541
  const label = markdown.utils.escapeHtml(languageLabel(tokens[index]?.info ?? ""));
533
- 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>`;
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>`;
534
547
  }
535
548
  for (const kind of ["fence", "code_block"]) {
536
549
  const render = markdown.renderer.rules[kind];
@@ -588,6 +601,12 @@ function Markdown({ value, copyText }) {
588
601
 
589
602
  // src/memory.js
590
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
+ }
591
610
  function boundedSet(map, key, value) {
592
611
  map.delete(key);
593
612
  map.set(key, value);
@@ -1091,6 +1110,26 @@ function TechnicalDetails({ entry }) {
1091
1110
  ] });
1092
1111
  }
1093
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
+ }
1094
1133
  if (entry.role === "request") return /* @__PURE__ */ jsx4(RequestCard, { entry, adapter, canRespond: state.canRespond });
1095
1134
  if (entry.role === "reasoning") {
1096
1135
  return /* @__PURE__ */ jsxs3("details", { className: "scui-reasoning", open: entry.streaming, children: [
@@ -1220,7 +1259,7 @@ function SessionDetails({ semantics }) {
1220
1259
  ] })
1221
1260
  ] });
1222
1261
  }
1223
- var conversationMemory = /* @__PURE__ */ new Map();
1262
+ var conversationMemory = uiMemory();
1224
1263
  function ConversationAnnouncements({ state }) {
1225
1264
  const previousBusy = useRef3(state.busy);
1226
1265
  const [announcement, setAnnouncement] = useState2("");
package/core.d.ts CHANGED
@@ -1,6 +1,11 @@
1
1
  export type {
2
2
  AttachedSessionModel,
3
3
  AgentActivityModel,
4
+ AgentContributionModel,
5
+ AgentPackageModel,
6
+ AgentPackageResourceModel,
7
+ AgentPackageResourceStateModel,
8
+ AgentPackageCapabilityStateModel,
4
9
  ContinuationMode,
5
10
  ControlStrategy,
6
11
  HarnessId,
@@ -13,6 +18,10 @@ export type {
13
18
  SessionMode,
14
19
  SessionRowModel,
15
20
  SessionSemanticsModel,
21
+ CrossSurfaceModel,
22
+ HarnessSessionDescriptorModel,
23
+ TriggerKind,
24
+ TriggerModel,
16
25
  StartupPhase,
17
26
  SupercodeUiIntent,
18
27
  SupercodeUiState,
@@ -36,6 +45,7 @@ export {
36
45
  groupConversation,
37
46
  harnessDisplayName,
38
47
  isSendKey,
48
+ normalizeContributions,
39
49
  normalizeUiState,
40
50
  operationLabel,
41
51
  projectAgentActivity,
@@ -43,6 +53,9 @@ export {
43
53
  relativeAge,
44
54
  sessionActivity,
45
55
  sessionDisplayName,
56
+ sessionRowsFromDescriptors,
57
+ surfaceLabel,
58
+ selectContributions,
46
59
  terminalCommand,
47
60
  toolCategory,
48
61
  toolTarget,
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,
@@ -68,6 +72,11 @@ export const EMPTY_UI_STATE = Object.freeze({
68
72
  attached: null,
69
73
  owned: null,
70
74
  attachError: null,
75
+ agentPackage: null,
76
+ contributions: Object.freeze([]),
77
+ agentPackageError: null,
78
+ agentPackageCapabilityState: null,
79
+ agentPackageCapabilityError: null,
71
80
  });
72
81
 
73
82
  const ROLES = new Set(['system', 'user', 'assistant', 'tool', 'reasoning', 'request', 'notice']);
@@ -81,6 +90,10 @@ const AUTHENTICATION_PHASES = new Set(['idle', 'checking', 'required', 'configur
81
90
  const MAX_TOOL_FIELDS = 8;
82
91
  const MAX_TOOL_FIELD_CHARS = 800;
83
92
  const MAX_TOOL_PREVIEW_CHARS = 4_000;
93
+ const MAX_CONTRIBUTIONS = 64;
94
+ const MAX_CONTRIBUTION_JSON_DEPTH = 12;
95
+ const MAX_CONTRIBUTION_COLLECTION = 100;
96
+ const MAX_CONTRIBUTION_STRING_CHARS = 16_000;
84
97
 
85
98
  function record(value) {
86
99
  return value !== null && typeof value === 'object' && !Array.isArray(value) ? value : null;
@@ -121,6 +134,165 @@ function nullableNumber(value) {
121
134
  return typeof value === 'number' && Number.isFinite(value) ? value : null;
122
135
  }
123
136
 
137
+ function relativeContributionPath(value) {
138
+ if (typeof value !== 'string' || value.startsWith('/') || /^[a-z]:[\\/]/i.test(value)) return null;
139
+ return value.replaceAll('\\', '/').split('/').some((segment) => segment === '..')
140
+ ? null
141
+ : boundedString(value, 1_000);
142
+ }
143
+
144
+ function contributionJson(value, depth = 0, seen = new WeakSet()) {
145
+ if (value === null || typeof value === 'boolean' || typeof value === 'string') {
146
+ return typeof value === 'string' ? boundedString(value, MAX_CONTRIBUTION_STRING_CHARS) : value;
147
+ }
148
+ if (typeof value === 'number') return Number.isFinite(value) ? value : undefined;
149
+ if (depth >= MAX_CONTRIBUTION_JSON_DEPTH || !value || typeof value !== 'object' || seen.has(value)) {
150
+ return undefined;
151
+ }
152
+ seen.add(value);
153
+ if (Array.isArray(value)) {
154
+ const result = [];
155
+ for (const item of value.slice(0, MAX_CONTRIBUTION_COLLECTION)) {
156
+ const normalized = contributionJson(item, depth + 1, seen);
157
+ if (normalized !== undefined) result.push(normalized);
158
+ }
159
+ seen.delete(value);
160
+ return result;
161
+ }
162
+ const source = record(value);
163
+ if (!source) return undefined;
164
+ const result = {};
165
+ for (const [key, item] of Object.entries(source).slice(0, MAX_CONTRIBUTION_COLLECTION)) {
166
+ const normalized = contributionJson(item, depth + 1, seen);
167
+ if (normalized !== undefined) result[boundedString(key, 200)] = normalized;
168
+ }
169
+ seen.delete(value);
170
+ return result;
171
+ }
172
+
173
+ export function normalizeContributions(value) {
174
+ if (!Array.isArray(value)) return [];
175
+ return value.slice(0, MAX_CONTRIBUTIONS).flatMap((candidate) => {
176
+ const item = record(candidate);
177
+ const placement = record(item?.placement);
178
+ const source = relativeContributionPath(item?.source);
179
+ if (
180
+ item?.schema !== 'supercode/contribution-v1' ||
181
+ typeof item.id !== 'string' || !item.id.trim() ||
182
+ typeof item.kind !== 'string' || !item.kind.trim() ||
183
+ source === null
184
+ ) return [];
185
+ const data = contributionJson(item.data);
186
+ if (data === undefined) return [];
187
+ return [{
188
+ schema: 'supercode/contribution-v1',
189
+ id: boundedString(item.id, 200),
190
+ kind: boundedString(item.kind, 100),
191
+ ...(typeof item.title === 'string' ? { title: boundedString(item.title, 500) } : {}),
192
+ ...(placement ? { placement: {
193
+ ...(typeof placement.surface === 'string' ? { surface: boundedString(placement.surface, 100) } : {}),
194
+ ...(typeof placement.region === 'string' ? { region: boundedString(placement.region, 100) } : {}),
195
+ } } : {}),
196
+ data,
197
+ source,
198
+ }];
199
+ });
200
+ }
201
+
202
+ export function selectContributions(contributions, query = {}) {
203
+ return normalizeContributions(contributions).filter((contribution) => (
204
+ (query.kind === undefined || contribution.kind === query.kind) &&
205
+ (query.surface === undefined || contribution.placement?.surface === query.surface) &&
206
+ (query.region === undefined || contribution.placement?.region === query.region)
207
+ ));
208
+ }
209
+
210
+ function readAgentPackage(value) {
211
+ const agentPackage = record(value);
212
+ const capabilities = record(agentPackage?.capabilities);
213
+ const storage = record(agentPackage?.storage);
214
+ const relativePath = relativeContributionPath(storage?.relativePath);
215
+ if (
216
+ agentPackage?.schemaVersion !== 1 ||
217
+ typeof agentPackage.id !== 'string' ||
218
+ typeof agentPackage.name !== 'string' ||
219
+ typeof agentPackage.version !== 'string' ||
220
+ !capabilities ||
221
+ !Array.isArray(capabilities.required) ||
222
+ !Array.isArray(capabilities.optional) ||
223
+ !storage ||
224
+ relativePath === null
225
+ ) return null;
226
+ const resources = record(agentPackage.resources);
227
+ return {
228
+ id: boundedString(agentPackage.id, 200),
229
+ name: boundedString(agentPackage.name, 500),
230
+ version: boundedString(agentPackage.version, 100),
231
+ description: typeof agentPackage.description === 'string'
232
+ ? boundedString(agentPackage.description, 2_000)
233
+ : null,
234
+ capabilities: {
235
+ required: capabilities.required.filter((item) => typeof item === 'string').slice(0, 100).map((item) => boundedString(item, 200)),
236
+ optional: capabilities.optional.filter((item) => typeof item === 'string').slice(0, 100).map((item) => boundedString(item, 200)),
237
+ },
238
+ storage: { relativePath },
239
+ resources: resources ? Object.fromEntries(Object.entries(resources).flatMap(([id, candidate]) => {
240
+ const state = record(candidate);
241
+ if (state?.schema !== 'supercode/package-resource-state-v1') return [];
242
+ if (state.status === 'absent' && typeof state.contributionId === 'string') {
243
+ return [[boundedString(id, 200), {
244
+ schema: 'supercode/package-resource-state-v1',
245
+ status: 'absent',
246
+ contributionId: boundedString(state.contributionId, 200),
247
+ }]];
248
+ }
249
+ if (state.status === 'error' && typeof state.contributionId === 'string' && typeof state.message === 'string') {
250
+ return [[boundedString(id, 200), {
251
+ schema: 'supercode/package-resource-state-v1',
252
+ status: 'error',
253
+ contributionId: boundedString(state.contributionId, 200),
254
+ message: boundedString(state.message),
255
+ }]];
256
+ }
257
+ const resource = record(state?.resource);
258
+ const data = contributionJson(resource?.data);
259
+ if (
260
+ state?.status !== 'ready' ||
261
+ resource?.schema !== 'supercode/package-resource-v1' ||
262
+ typeof resource.contributionId !== 'string' ||
263
+ !['json', 'text'].includes(resource.format) ||
264
+ typeof resource.mediaType !== 'string' ||
265
+ data === undefined
266
+ ) return [];
267
+ return [[boundedString(id, 200), {
268
+ schema: 'supercode/package-resource-state-v1',
269
+ status: 'ready',
270
+ resource: {
271
+ schema: 'supercode/package-resource-v1',
272
+ contributionId: boundedString(resource.contributionId, 200),
273
+ format: resource.format,
274
+ mediaType: boundedString(resource.mediaType, 200),
275
+ data,
276
+ },
277
+ }]];
278
+ })) : {},
279
+ };
280
+ }
281
+
282
+ function readAgentPackageCapabilityState(value) {
283
+ const state = record(value);
284
+ if (!state) return null;
285
+ const fields = ['availableRequired', 'missingRequired', 'enabledOptional', 'unavailableOptional'];
286
+ if (!fields.every((field) => Array.isArray(state[field]))) return null;
287
+ return {
288
+ ready: state.ready === true,
289
+ ...Object.fromEntries(fields.map((field) => [
290
+ field,
291
+ state[field].filter((item) => typeof item === 'string').slice(0, 100).map((item) => boundedString(item, 200)),
292
+ ])),
293
+ };
294
+ }
295
+
124
296
  export function relativeAge(updatedAt, now = Date.now()) {
125
297
  if (typeof updatedAt !== 'number' || !Number.isFinite(updatedAt) || updatedAt <= 0) return '';
126
298
  const delta = Math.max(0, now - updatedAt);
@@ -504,12 +676,58 @@ function readToolPresentation(value, entry) {
504
676
  };
505
677
  }
506
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
+
507
721
  function readTranscript(value) {
508
722
  if (!Array.isArray(value)) return [];
509
723
  const result = [];
510
724
  for (const candidate of value) {
511
725
  const item = record(candidate);
512
- if (!item || typeof item.id !== 'string' || typeof item.text !== 'string' || !ROLES.has(item.role)) continue;
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
+ }
513
731
  const entry = {
514
732
  id: item.id,
515
733
  role: item.role,
@@ -574,6 +792,105 @@ function readTranscript(value) {
574
792
  return result;
575
793
  }
576
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
+
577
894
  function readSessions(value) {
578
895
  if (!Array.isArray(value)) return [];
579
896
  return value.flatMap((raw) => {
@@ -596,6 +913,12 @@ function readSessions(value) {
596
913
  runtimeStatus: row.runtimeStatus === 'running' || row.runtimeStatus === 'busy' || row.runtimeStatus === 'idle' ? row.runtimeStatus : null,
597
914
  ...(Number.isSafeInteger(row.subagentCount) && row.subagentCount > 0 ? { subagentCount: row.subagentCount } : {}),
598
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) } : {}),
599
922
  }];
600
923
  });
601
924
  }
@@ -836,6 +1159,10 @@ export function normalizeUiState(value) {
836
1159
  canConfigureSettings: raw.canConfigureSettings === true,
837
1160
  messaging: raw.messaging === 'live_peer' ? 'live_peer' : null,
838
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),
839
1166
  taskPlan: readTaskPlan(raw.taskPlan),
840
1167
  semantics: readSemantics(raw.semantics),
841
1168
  terminalHandoff: readTerminalHandoff(raw.terminalHandoff),
@@ -896,6 +1223,13 @@ export function normalizeUiState(value) {
896
1223
  attached: readAttached(raw.attached),
897
1224
  owned: readAttached(raw.owned),
898
1225
  attachError: attachError && typeof attachError.key === 'string' && typeof attachError.message === 'string' ? { key: attachError.key, message: attachError.message } : null,
1226
+ agentPackage: readAgentPackage(raw.agentPackage),
1227
+ contributions: normalizeContributions(raw.contributions),
1228
+ agentPackageError: typeof raw.agentPackageError === 'string' ? boundedString(raw.agentPackageError) : null,
1229
+ agentPackageCapabilityState: readAgentPackageCapabilityState(raw.agentPackageCapabilityState),
1230
+ agentPackageCapabilityError: typeof raw.agentPackageCapabilityError === 'string'
1231
+ ? boundedString(raw.agentPackageCapabilityError)
1232
+ : null,
899
1233
  };
900
1234
  }
901
1235
 
@@ -903,6 +1237,61 @@ export function harnessDisplayName(id) {
903
1237
  return HARNESS_NAMES[id] ?? id;
904
1238
  }
905
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
+
906
1295
  export function sessionDisplayName(session) {
907
1296
  const title = session.title?.trim();
908
1297
  return title && title !== session.name ? title : session.name || 'Untitled chat';