@volter-ai-dev/supercode-ui 0.1.67 → 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/activity.mjs +113 -2
- package/components.d.ts +3 -0
- package/components.mjs +246 -12
- package/composer.mjs +12 -2
- package/conversation.mjs +36 -2
- package/core.d.ts +6 -0
- package/core.mjs +215 -1
- package/embed.mjs +241 -11
- 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/index.d.ts
CHANGED
|
@@ -90,6 +90,66 @@ export interface HarnessOption {
|
|
|
90
90
|
preferredLaunchMode?: ExecutionMode | null;
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
/** UNI-8: whose conversation a session is. */
|
|
94
|
+
export interface ParticipantModel {
|
|
95
|
+
kind: 'local-user' | 'foreign' | 'unknown';
|
|
96
|
+
/** The human ("@jane"), when known. */
|
|
97
|
+
label: string | null;
|
|
98
|
+
/** The surface the participant reached the agent through ("slack", "acp", ...). */
|
|
99
|
+
origin: string | null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** UNI-9: a typed workspace. `none` is a first-class value, not an empty path. */
|
|
103
|
+
export interface WorkspaceRefModel {
|
|
104
|
+
kind: 'repo' | 'none' | 'channel';
|
|
105
|
+
/** Path for `repo`, channel label for `channel`, null for `none`. */
|
|
106
|
+
value: string | null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** UNI-10: a bounded/truncated mirror of a session canonical in another store. */
|
|
110
|
+
export interface MirrorModel {
|
|
111
|
+
canonicalHarness: HarnessId;
|
|
112
|
+
/** Session key to cross-link to, when the host can resolve it. */
|
|
113
|
+
canonicalKey: string | null;
|
|
114
|
+
bounded: boolean;
|
|
115
|
+
truncated: boolean;
|
|
116
|
+
origin: string | null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** UNI-11: which surface holds this session now; take-over is the transition. */
|
|
120
|
+
export interface HolderModel {
|
|
121
|
+
/** 'supercode' = held here; any other string = that surface; null = unheld. */
|
|
122
|
+
surface: string | null;
|
|
123
|
+
/** Take-over offered (flows through confirmIntent with action 'take_over'). */
|
|
124
|
+
canTakeOver: boolean;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** UNI-12/ORCH-6: why a session exists — the server's own `Trigger` enum.
|
|
128
|
+
* `manual` is the pre-ORCH-6 spelling of `human`, accepted on input and
|
|
129
|
+
* normalized to `human`. */
|
|
130
|
+
export type TriggerKind = 'human' | 'channel' | 'cron' | 'heartbeat' | 'webhook' | 'parent' | 'api' | 'unknown';
|
|
131
|
+
export interface TriggerModel {
|
|
132
|
+
kind: TriggerKind;
|
|
133
|
+
label: string | null;
|
|
134
|
+
/** ORCH-6: the compact surface key this conversation is reached on
|
|
135
|
+
* (`telegram:dm:123456`), when it has one. */
|
|
136
|
+
surface?: string | null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** ORCH-6: a conversation that moved to another surface (Hermes `handoff_*`). */
|
|
140
|
+
export interface CrossSurfaceModel {
|
|
141
|
+
state: string;
|
|
142
|
+
platform: string | null;
|
|
143
|
+
error: string | null;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** UNI-12: one row representing N runs of a recurring trigger. */
|
|
147
|
+
export interface RecurringModel {
|
|
148
|
+
groupKey: string;
|
|
149
|
+
runs: number;
|
|
150
|
+
lastStatus: 'ok' | 'failed' | 'mixed' | null;
|
|
151
|
+
}
|
|
152
|
+
|
|
93
153
|
export interface SessionRowModel {
|
|
94
154
|
key: string;
|
|
95
155
|
harness: HarnessId;
|
|
@@ -112,6 +172,18 @@ export interface SessionRowModel {
|
|
|
112
172
|
subagentCount?: number;
|
|
113
173
|
/** Explicit activity used for child rows in the on-demand inspector. */
|
|
114
174
|
activity?: SessionActivity;
|
|
175
|
+
/** UNI-8: whose conversation this is (absent = local user). */
|
|
176
|
+
participant?: ParticipantModel;
|
|
177
|
+
/** UNI-9: typed workspace (derived from `cwd` when a host omits it). */
|
|
178
|
+
workspaceRef?: WorkspaceRefModel;
|
|
179
|
+
/** UNI-10: canonical-elsewhere provenance. */
|
|
180
|
+
mirror?: MirrorModel;
|
|
181
|
+
/** UNI-12: trigger provenance. */
|
|
182
|
+
trigger?: TriggerModel;
|
|
183
|
+
/** UNI-12: recurring-run grouping. */
|
|
184
|
+
recurring?: RecurringModel;
|
|
185
|
+
/** ORCH-6: moved-to-another-surface state, when the harness publishes one. */
|
|
186
|
+
crossSurface?: CrossSurfaceModel;
|
|
115
187
|
}
|
|
116
188
|
|
|
117
189
|
export interface SubagentInspectorModel {
|
|
@@ -360,6 +432,14 @@ export interface SupercodeUiState {
|
|
|
360
432
|
canConfigureSettings: boolean;
|
|
361
433
|
messaging: 'live_peer' | null;
|
|
362
434
|
workspace: string;
|
|
435
|
+
/** UNI-9: typed workspace for the attached conversation (always present after normalization). */
|
|
436
|
+
workspaceRef?: WorkspaceRefModel;
|
|
437
|
+
/** UNI-8: whose conversation the attached session is (defaults to local-user). */
|
|
438
|
+
participant?: ParticipantModel;
|
|
439
|
+
/** UNI-10: set when the attached session is a mirror. */
|
|
440
|
+
mirror?: MirrorModel | null;
|
|
441
|
+
/** UNI-11: which surface holds the attached session now. */
|
|
442
|
+
holder?: HolderModel | null;
|
|
363
443
|
taskPlan: TaskPlanModel;
|
|
364
444
|
semantics: SessionSemanticsModel;
|
|
365
445
|
terminalHandoff: { program: string; arguments: string[]; cwd: string } | null;
|
|
@@ -619,6 +699,29 @@ export function selectContributions(
|
|
|
619
699
|
): AgentContributionModel[];
|
|
620
700
|
export function harnessDisplayName(id: string): string;
|
|
621
701
|
export function sessionDisplayName(session: Pick<SessionRowModel, 'name' | 'title'>): string;
|
|
702
|
+
/** ORCH-6: one `harness.v1.sessions.discover` / `sessions.load` row, as the
|
|
703
|
+
* service publishes it. Structurally the SDK's `SessionDescriptor`; declared
|
|
704
|
+
* here so the UI package stays dependency-free. */
|
|
705
|
+
export interface HarnessSessionDescriptorModel {
|
|
706
|
+
locator: { harness: string; session_id: string; storage?: unknown };
|
|
707
|
+
cwd?: string | null;
|
|
708
|
+
title?: string | null;
|
|
709
|
+
updated_at_ms?: number | null;
|
|
710
|
+
message_count?: number | null;
|
|
711
|
+
model?: string | null;
|
|
712
|
+
child_session_count?: number;
|
|
713
|
+
trigger?: TriggerKind | 'manual';
|
|
714
|
+
surface?: { key?: string; platform?: string; kind?: string; chat_id?: string; thread_id?: string; participant_id?: string };
|
|
715
|
+
profile?: string;
|
|
716
|
+
recurrence?: { job_id: string; kind: string };
|
|
717
|
+
cross_surface?: { state: string; platform?: string; error?: string };
|
|
718
|
+
workspace?: WorkspaceRefModel | { kind: 'repo' | 'channel' | 'none'; value?: string };
|
|
719
|
+
}
|
|
720
|
+
/** ORCH-6: `telegram:dm:123456` — the compact surface key a person reads. */
|
|
721
|
+
export function surfaceLabel(surface: unknown): string;
|
|
722
|
+
/** ORCH-6: project service rows into list rows. The conversation nouns are
|
|
723
|
+
* read off the server's derivation, never re-derived on the client. */
|
|
724
|
+
export function sessionRowsFromDescriptors(descriptors: readonly HarnessSessionDescriptorModel[] | unknown): SessionRowModel[];
|
|
622
725
|
export function relativeAge(updatedAt: number | null | undefined, now?: number): string;
|
|
623
726
|
export function sessionActivity(state: SupercodeUiState, row: SessionRowModel): SessionActivity;
|
|
624
727
|
export function projectAgentActivity(state: SupercodeUiState | unknown): AgentActivityModel;
|
package/messenger.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"
|
|
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),
|
|
@@ -1070,6 +1181,12 @@ import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } fro
|
|
|
1070
1181
|
|
|
1071
1182
|
// src/memory.js
|
|
1072
1183
|
var MEMORY_LIMIT = 100;
|
|
1184
|
+
var registry = /* @__PURE__ */ new Set();
|
|
1185
|
+
function uiMemory() {
|
|
1186
|
+
const map = /* @__PURE__ */ new Map();
|
|
1187
|
+
registry.add(map);
|
|
1188
|
+
return map;
|
|
1189
|
+
}
|
|
1073
1190
|
function boundedSet(map, key, value) {
|
|
1074
1191
|
map.delete(key);
|
|
1075
1192
|
map.set(key, value);
|
|
@@ -1423,7 +1540,7 @@ function MessageImages({ items, adapter }) {
|
|
|
1423
1540
|
}
|
|
1424
1541
|
|
|
1425
1542
|
// src/intent.js
|
|
1426
|
-
var CONFIRMABLE_ACTIONS = /* @__PURE__ */ new Set(["resume", "join", "branch", "reduce", "export"]);
|
|
1543
|
+
var CONFIRMABLE_ACTIONS = /* @__PURE__ */ new Set(["resume", "join", "branch", "reduce", "export", "take_over"]);
|
|
1427
1544
|
async function dispatchConfirmedIntent(adapter, intent) {
|
|
1428
1545
|
if (CONFIRMABLE_ACTIONS.has(intent.action) && adapter.confirmIntent) {
|
|
1429
1546
|
const confirmed = await adapter.confirmIntent(intent);
|
|
@@ -1448,7 +1565,7 @@ function useAutosizeTextarea(ref, value) {
|
|
|
1448
1565
|
|
|
1449
1566
|
// src/composer.jsx
|
|
1450
1567
|
import { jsx as jsx3, jsxs as jsxs3 } from "preact/jsx-runtime";
|
|
1451
|
-
var composerMemory =
|
|
1568
|
+
var composerMemory = uiMemory();
|
|
1452
1569
|
function ExecutionModeSelect({ modes = [], value, onChange, disabled = false, label = "Run in" }) {
|
|
1453
1570
|
if (modes.length < 2) return null;
|
|
1454
1571
|
return /* @__PURE__ */ jsxs3("label", { className: "scui-execution-mode", title: disabled ? void 0 : `${label}: ${value}`, children: [
|
|
@@ -1744,7 +1861,11 @@ function languageLabel(info) {
|
|
|
1744
1861
|
}
|
|
1745
1862
|
function frameCode(render, tokens, index, options, env, self) {
|
|
1746
1863
|
const label = markdown.utils.escapeHtml(languageLabel(tokens[index]?.info ?? ""));
|
|
1747
|
-
|
|
1864
|
+
const body = render(tokens, index, options, env, self).replace(
|
|
1865
|
+
"<pre>",
|
|
1866
|
+
`<pre tabindex="0" role="region" aria-label="${label} code">`
|
|
1867
|
+
);
|
|
1868
|
+
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>`;
|
|
1748
1869
|
}
|
|
1749
1870
|
for (const kind of ["fence", "code_block"]) {
|
|
1750
1871
|
const render = markdown.renderer.rules[kind];
|
|
@@ -2081,6 +2202,26 @@ function TechnicalDetails({ entry }) {
|
|
|
2081
2202
|
] });
|
|
2082
2203
|
}
|
|
2083
2204
|
function TranscriptEntry({ entry, state, adapter }) {
|
|
2205
|
+
if (entry.role === "opaque") {
|
|
2206
|
+
return /* @__PURE__ */ jsxs4("article", { className: "scui-message scui-opaque", "data-role": "opaque", "data-kind": entry.kind, "aria-label": "Unrecognized entry", children: [
|
|
2207
|
+
/* @__PURE__ */ jsxs4("div", { className: "scui-notice", "data-code": "opaque-entry", children: [
|
|
2208
|
+
"Unrecognized entry kind ",
|
|
2209
|
+
/* @__PURE__ */ jsx5("code", { children: entry.kind }),
|
|
2210
|
+
" \u2014 kept as-is, nothing dropped"
|
|
2211
|
+
] }),
|
|
2212
|
+
entry.text ? /* @__PURE__ */ jsx5(Markdown, { value: entry.text, copyText: adapter?.copyText }) : null,
|
|
2213
|
+
entry.raw ? /* @__PURE__ */ jsxs4("details", { className: "scui-tool-technical", children: [
|
|
2214
|
+
/* @__PURE__ */ jsx5("summary", { children: "Technical details" }),
|
|
2215
|
+
/* @__PURE__ */ jsx5("div", { children: /* @__PURE__ */ jsxs4("section", { children: [
|
|
2216
|
+
/* @__PURE__ */ jsx5("strong", { children: "Raw entry" }),
|
|
2217
|
+
/* @__PURE__ */ jsxs4("pre", { children: [
|
|
2218
|
+
entry.raw,
|
|
2219
|
+
entry.truncated ? "\n[truncated]" : ""
|
|
2220
|
+
] })
|
|
2221
|
+
] }) })
|
|
2222
|
+
] }) : null
|
|
2223
|
+
] });
|
|
2224
|
+
}
|
|
2084
2225
|
if (entry.role === "request") return /* @__PURE__ */ jsx5(RequestCard, { entry, adapter, canRespond: state.canRespond });
|
|
2085
2226
|
if (entry.role === "reasoning") {
|
|
2086
2227
|
return /* @__PURE__ */ jsxs4("details", { className: "scui-reasoning", open: entry.streaming, children: [
|
|
@@ -2210,7 +2351,7 @@ function SessionDetails({ semantics }) {
|
|
|
2210
2351
|
] })
|
|
2211
2352
|
] });
|
|
2212
2353
|
}
|
|
2213
|
-
var conversationMemory =
|
|
2354
|
+
var conversationMemory = uiMemory();
|
|
2214
2355
|
function ConversationAnnouncements({ state }) {
|
|
2215
2356
|
const previousBusy = useRef4(state.busy);
|
|
2216
2357
|
const [announcement, setAnnouncement] = useState3("");
|
|
@@ -2390,7 +2531,7 @@ function HarnessLogo({ id, activity, size = 28, onMissingLogo }) {
|
|
|
2390
2531
|
// src/sessions.jsx
|
|
2391
2532
|
import { useEffect as useEffect6, useLayoutEffect as useLayoutEffect3, useRef as useRef5, useState as useState4 } from "preact/hooks";
|
|
2392
2533
|
import { jsx as jsx7, jsxs as jsxs6 } from "preact/jsx-runtime";
|
|
2393
|
-
var sessionListMemory =
|
|
2534
|
+
var sessionListMemory = uiMemory();
|
|
2394
2535
|
function sessionPathParts(value) {
|
|
2395
2536
|
const complete = String(value ?? "").replaceAll("\\", "/");
|
|
2396
2537
|
const boundary = complete.lastIndexOf("/");
|
|
@@ -2412,11 +2553,55 @@ function SessionRow({ row, state, onOpen, onOpenSubagents, now = Date.now() }) {
|
|
|
2412
2553
|
const unreadCount = attention?.unreadCount ?? 0;
|
|
2413
2554
|
const unreadLabel = unreadCount > 99 ? "99+" : String(unreadCount);
|
|
2414
2555
|
const age = relativeAge(row.previewUpdatedAt ?? row.updatedAt, now) || row.age;
|
|
2415
|
-
|
|
2556
|
+
const nounBadges = [];
|
|
2557
|
+
if (row.trigger && row.trigger.kind !== "manual" && row.trigger.kind !== "human") {
|
|
2558
|
+
const runs = row.recurring?.runs;
|
|
2559
|
+
const detail = row.trigger.label || row.trigger.surface || "";
|
|
2560
|
+
nounBadges.push(
|
|
2561
|
+
/* @__PURE__ */ jsxs6("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: [
|
|
2562
|
+
row.trigger.kind,
|
|
2563
|
+
detail ? ` \xB7 ${detail}` : "",
|
|
2564
|
+
runs && runs > 1 ? ` \xB7 \xD7${runs}` : ""
|
|
2565
|
+
] }, "trigger")
|
|
2566
|
+
);
|
|
2567
|
+
}
|
|
2568
|
+
if (row.crossSurface) {
|
|
2569
|
+
nounBadges.push(
|
|
2570
|
+
/* @__PURE__ */ jsxs6("small", { className: "scui-session-noun", "data-noun": "cross-surface", "data-state": row.crossSurface.state, title: row.crossSurface.error ?? void 0, children: [
|
|
2571
|
+
"\u2192",
|
|
2572
|
+
row.crossSurface.platform ?? "elsewhere",
|
|
2573
|
+
" \xB7 ",
|
|
2574
|
+
row.crossSurface.state
|
|
2575
|
+
] }, "cross-surface")
|
|
2576
|
+
);
|
|
2577
|
+
}
|
|
2578
|
+
if (row.participant && row.participant.kind !== "local-user") {
|
|
2579
|
+
nounBadges.push(
|
|
2580
|
+
/* @__PURE__ */ jsx7("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")
|
|
2581
|
+
);
|
|
2582
|
+
}
|
|
2583
|
+
if (row.mirror) {
|
|
2584
|
+
nounBadges.push(
|
|
2585
|
+
/* @__PURE__ */ jsxs6("small", { className: "scui-session-noun", "data-noun": "mirror", "data-truncated": row.mirror.truncated, children: [
|
|
2586
|
+
"mirror of ",
|
|
2587
|
+
harnessDisplayName(row.mirror.canonicalHarness)
|
|
2588
|
+
] }, "mirror")
|
|
2589
|
+
);
|
|
2590
|
+
}
|
|
2591
|
+
if (row.workspaceRef?.kind === "channel") {
|
|
2592
|
+
nounBadges.push(
|
|
2593
|
+
/* @__PURE__ */ jsxs6("small", { className: "scui-session-noun", "data-noun": "workspace", "data-kind": "channel", children: [
|
|
2594
|
+
"#",
|
|
2595
|
+
row.workspaceRef.value ?? "channel"
|
|
2596
|
+
] }, "workspace")
|
|
2597
|
+
);
|
|
2598
|
+
}
|
|
2599
|
+
return /* @__PURE__ */ jsxs6("article", { className: "scui-session", "data-active": row.active, "data-activity": activity, "data-writable": row.writable, "data-workspace-kind": row.workspaceRef?.kind, children: [
|
|
2416
2600
|
/* @__PURE__ */ jsxs6("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: [
|
|
2417
2601
|
/* @__PURE__ */ jsx7(HarnessLogo, { id: row.harness, activity, size: 34 }),
|
|
2418
2602
|
/* @__PURE__ */ jsxs6("span", { className: "scui-session-copy", children: [
|
|
2419
2603
|
/* @__PURE__ */ jsx7("span", { className: "scui-session-title", children: /* @__PURE__ */ jsx7("strong", { children: title }) }),
|
|
2604
|
+
nounBadges.length ? /* @__PURE__ */ jsx7("span", { className: "scui-session-nouns", children: nounBadges }) : null,
|
|
2420
2605
|
path.complete ? /* @__PURE__ */ jsxs6("small", { className: "scui-session-path", title: row.cwd, children: [
|
|
2421
2606
|
/* @__PURE__ */ jsx7("span", { className: "scui-session-path-leading", children: path.leading }),
|
|
2422
2607
|
path.separator ? /* @__PURE__ */ jsx7("span", { className: "scui-session-path-separator", children: path.separator }) : null,
|
|
@@ -2831,10 +3016,54 @@ function HarnessPicker({ harnesses, state, value, disabled = false, adapter, onC
|
|
|
2831
3016
|
|
|
2832
3017
|
// src/messenger.jsx
|
|
2833
3018
|
import { Fragment as Fragment5, jsx as jsx10, jsxs as jsxs9 } from "preact/jsx-runtime";
|
|
2834
|
-
var pendingMessageMemory =
|
|
2835
|
-
var messengerViewMemory =
|
|
2836
|
-
var newChatMemory =
|
|
3019
|
+
var pendingMessageMemory = uiMemory();
|
|
3020
|
+
var messengerViewMemory = uiMemory();
|
|
3021
|
+
var newChatMemory = uiMemory();
|
|
2837
3022
|
var TRACKED_ACTIONS = /* @__PURE__ */ new Set(["resume", "join", "detach", "branch", "reduce", "terminal", "export", "steer", "interrupt", "respond", "configureHarness", "refresh", "loadEarlier", "loadSessions"]);
|
|
3023
|
+
function UniversalNouns({ state, adapter }) {
|
|
3024
|
+
const banners = [];
|
|
3025
|
+
if (state.participant && state.participant.kind !== "local-user") {
|
|
3026
|
+
banners.push(
|
|
3027
|
+
/* @__PURE__ */ jsx10("div", { className: "scui-notice scui-noun-banner", "data-noun": "participant", "data-kind": state.participant.kind, children: state.participant.kind === "foreign" ? /* @__PURE__ */ jsxs9(Fragment5, { children: [
|
|
3028
|
+
"This is ",
|
|
3029
|
+
/* @__PURE__ */ jsx10("strong", { children: state.participant.label ?? "someone else's" }),
|
|
3030
|
+
" conversation",
|
|
3031
|
+
state.participant.origin ? /* @__PURE__ */ jsxs9(Fragment5, { children: [
|
|
3032
|
+
" via ",
|
|
3033
|
+
state.participant.origin
|
|
3034
|
+
] }) : null,
|
|
3035
|
+
"."
|
|
3036
|
+
] }) : /* @__PURE__ */ jsx10(Fragment5, { children: "Unknown participant \u2014 this conversation was not started here." }) }, "participant")
|
|
3037
|
+
);
|
|
3038
|
+
}
|
|
3039
|
+
if (state.mirror) {
|
|
3040
|
+
banners.push(
|
|
3041
|
+
/* @__PURE__ */ jsxs9("div", { className: "scui-notice scui-noun-banner", "data-noun": "mirror", "data-truncated": state.mirror.truncated, children: [
|
|
3042
|
+
state.mirror.bounded ? "Bounded mirror" : "Mirror",
|
|
3043
|
+
" of a ",
|
|
3044
|
+
/* @__PURE__ */ jsx10("strong", { children: harnessDisplayName(state.mirror.canonicalHarness) }),
|
|
3045
|
+
" session \u2014 the canonical record lives in its own store",
|
|
3046
|
+
state.mirror.truncated ? " (truncated here)" : "",
|
|
3047
|
+
".",
|
|
3048
|
+
state.mirror.canonicalKey ? /* @__PURE__ */ jsx10("button", { type: "button", onClick: () => adapter.onIntent({ action: "open_session", key: state.mirror.canonicalKey }), children: "Open canonical session" }) : null
|
|
3049
|
+
] }, "mirror")
|
|
3050
|
+
);
|
|
3051
|
+
}
|
|
3052
|
+
if (state.holder && state.holder.surface !== "supercode") {
|
|
3053
|
+
banners.push(
|
|
3054
|
+
/* @__PURE__ */ jsxs9("div", { className: "scui-notice scui-noun-banner", "data-noun": "holder", "data-surface": state.holder.surface ?? "unheld", children: [
|
|
3055
|
+
state.holder.surface ? /* @__PURE__ */ jsxs9(Fragment5, { children: [
|
|
3056
|
+
"Held by ",
|
|
3057
|
+
/* @__PURE__ */ jsx10("strong", { children: state.holder.surface }),
|
|
3058
|
+
" right now."
|
|
3059
|
+
] }) : /* @__PURE__ */ jsx10(Fragment5, { children: "No surface holds this session right now." }),
|
|
3060
|
+
state.holder.canTakeOver ? /* @__PURE__ */ jsx10("button", { type: "button", onClick: () => dispatchConfirmedIntent(adapter, { action: "take_over" }), children: "Take over" }) : null
|
|
3061
|
+
] }, "holder")
|
|
3062
|
+
);
|
|
3063
|
+
}
|
|
3064
|
+
if (!banners.length) return null;
|
|
3065
|
+
return /* @__PURE__ */ jsx10("div", { className: "scui-noun-banners", children: banners });
|
|
3066
|
+
}
|
|
2838
3067
|
function Receipt({ state, adapter }) {
|
|
2839
3068
|
const receipt = state.reductionReceipt;
|
|
2840
3069
|
if (receipt) return /* @__PURE__ */ jsx10("div", { className: "scui-receipt", children: /* @__PURE__ */ jsxs9("span", { children: [
|
|
@@ -3098,6 +3327,7 @@ function Chat({ state, adapter, onBack, onNew, onClose, components, slots, label
|
|
|
3098
3327
|
state.recoverable ? /* @__PURE__ */ jsx10("button", { type: "button", disabled: Boolean(action), onClick: () => trackedAdapter.onIntent({ action: "refresh" }), children: "Retry" }) : null
|
|
3099
3328
|
] }) : null,
|
|
3100
3329
|
/* @__PURE__ */ jsx10(Receipt, { state, adapter }),
|
|
3330
|
+
/* @__PURE__ */ jsx10(UniversalNouns, { state: actionState, adapter: trackedAdapter }),
|
|
3101
3331
|
/* @__PURE__ */ jsx10(Conversation, { state: actionState, adapter: trackedAdapter, components, slots, memoryKey, pending: pendingMessage, unreadAfterMessages }, memoryKey),
|
|
3102
3332
|
/* @__PURE__ */ jsx10(ContinuationBar, { state: actionState, adapter: trackedAdapter, labels }),
|
|
3103
3333
|
/* @__PURE__ */ jsx10(Advisory, { state: actionState, adapter: trackedAdapter, value: state.interopSettings?.advisories[0] ?? null, onReview: (recommendedChange) => setSettings({ recommendedChange }) }),
|
package/package.json
CHANGED
package/react/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"
|
|
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),
|