@volter-ai-dev/supercode-ui 0.1.1 → 0.1.3
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/README.md +15 -5
- package/components.d.ts +17 -0
- package/components.mjs +422 -346
- package/composer.d.ts +2 -0
- package/composer.mjs +24 -20
- package/controller.d.ts +24 -0
- package/controller.mjs +197 -69
- package/conversation.d.ts +20 -0
- package/conversation.mjs +80 -65
- package/core.d.ts +41 -0
- package/core.mjs +72 -4
- package/embed.d.ts +2 -0
- package/embed.mjs +421 -345
- package/logo.d.ts +2 -0
- package/logo.mjs +12 -70
- package/messenger.d.ts +10 -0
- package/messenger.mjs +419 -343
- package/package.json +23 -12
- package/sessions.d.ts +9 -0
- package/sessions.mjs +77 -53
- package/styles.css +6 -4
package/composer.d.ts
ADDED
package/composer.mjs
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
// src/
|
|
2
|
-
import
|
|
3
|
-
import { useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "preact/hooks";
|
|
1
|
+
// src/composer.jsx
|
|
2
|
+
import { useEffect, useRef, useState } from "preact/hooks";
|
|
4
3
|
|
|
5
4
|
// core.mjs
|
|
6
5
|
var HARNESS_NAMES = Object.freeze({
|
|
7
6
|
"claude-code": "Claude Code",
|
|
8
7
|
codex: "Codex",
|
|
8
|
+
gemini: "Gemini CLI",
|
|
9
|
+
goose: "Goose",
|
|
9
10
|
opencode: "OpenCode",
|
|
10
11
|
pi: "Pi",
|
|
11
12
|
grok: "Grok"
|
|
@@ -70,15 +71,16 @@ function isSendKey(event) {
|
|
|
70
71
|
return event.key === "Enter" && !event.shiftKey && !event.isComposing;
|
|
71
72
|
}
|
|
72
73
|
|
|
73
|
-
// src/
|
|
74
|
+
// src/memory.js
|
|
75
|
+
var MEMORY_LIMIT = 100;
|
|
76
|
+
function boundedSet(map, key, value) {
|
|
77
|
+
map.delete(key);
|
|
78
|
+
map.set(key, value);
|
|
79
|
+
while (map.size > MEMORY_LIMIT) map.delete(map.keys().next().value);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// src/composer.jsx
|
|
74
83
|
import { jsx, jsxs } from "preact/jsx-runtime";
|
|
75
|
-
var markdown = new MarkdownIt({ html: false, linkify: true, breaks: false });
|
|
76
|
-
var defaultLinkOpen = markdown.renderer.rules.link_open;
|
|
77
|
-
markdown.renderer.rules.link_open = (tokens, index, options, env, self) => {
|
|
78
|
-
tokens[index]?.attrSet("target", "_blank");
|
|
79
|
-
tokens[index]?.attrSet("rel", "noreferrer noopener");
|
|
80
|
-
return defaultLinkOpen ? defaultLinkOpen(tokens, index, options, env, self) : self.renderToken(tokens, index, options);
|
|
81
|
-
};
|
|
82
84
|
var composerMemory = /* @__PURE__ */ new Map();
|
|
83
85
|
function ContinuationBar({ state, adapter, labels = DEFAULT_LABELS }) {
|
|
84
86
|
if (state.mode !== "mirror" || state.canSend) return null;
|
|
@@ -105,11 +107,17 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
|
|
|
105
107
|
const [draft, setDraft] = useState(remembered.draft);
|
|
106
108
|
const [queue, setQueue] = useState(remembered.queue);
|
|
107
109
|
const textarea = useRef(null);
|
|
110
|
+
const remember = (nextDraft, nextQueue) => boundedSet(composerMemory, memoryKey, { draft: nextDraft, queue: nextQueue });
|
|
111
|
+
const updateQueue = (update) => setQueue((items) => {
|
|
112
|
+
const next = update(items);
|
|
113
|
+
remember(draft, next);
|
|
114
|
+
return next;
|
|
115
|
+
});
|
|
108
116
|
useEffect(() => {
|
|
109
117
|
if (!state.busy && state.canSend && queue.length) {
|
|
110
118
|
const [next, ...rest] = queue;
|
|
111
119
|
setQueue(rest);
|
|
112
|
-
|
|
120
|
+
remember(draft, rest);
|
|
113
121
|
onPending?.(next);
|
|
114
122
|
adapter.onIntent({ action: "send", text: next });
|
|
115
123
|
}
|
|
@@ -121,17 +129,13 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
|
|
|
121
129
|
const send = () => {
|
|
122
130
|
const text = draft.trim();
|
|
123
131
|
if (!text) return;
|
|
124
|
-
if (state.busy)
|
|
125
|
-
const next = [...items, text];
|
|
126
|
-
composerMemory.set(memoryKey, { draft: "", queue: next });
|
|
127
|
-
return next;
|
|
128
|
-
});
|
|
132
|
+
if (state.busy) updateQueue((items) => [...items, text]);
|
|
129
133
|
else if (state.canSend) {
|
|
130
134
|
onPending?.(text);
|
|
131
135
|
adapter.onIntent({ action: "send", text });
|
|
132
136
|
} else return;
|
|
133
137
|
setDraft("");
|
|
134
|
-
|
|
138
|
+
remember("", state.busy ? [...queue, text] : queue);
|
|
135
139
|
};
|
|
136
140
|
return /* @__PURE__ */ jsxs("div", { class: "scui-compose", children: [
|
|
137
141
|
queue.length ? /* @__PURE__ */ jsxs("div", { class: "scui-queue", children: [
|
|
@@ -141,14 +145,14 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
|
|
|
141
145
|
] }),
|
|
142
146
|
queue.map((item, index) => /* @__PURE__ */ jsxs("span", { children: [
|
|
143
147
|
item,
|
|
144
|
-
/* @__PURE__ */ jsx("button", { "aria-label": `Remove queued message ${index + 1}`, onClick: () =>
|
|
148
|
+
/* @__PURE__ */ jsx("button", { type: "button", "aria-label": `Remove queued message ${index + 1}`, onClick: () => updateQueue((items) => items.filter((_, itemIndex) => itemIndex !== index)), children: "\xD7" })
|
|
145
149
|
] }, `${index}:${item}`))
|
|
146
150
|
] }) : null,
|
|
147
151
|
/* @__PURE__ */ jsxs("div", { class: "scui-envelope", children: [
|
|
148
152
|
/* @__PURE__ */ jsx("textarea", { ref: textarea, rows: 1, "aria-label": `Message ${harnessDisplayName(state.harness) || "agent"}`, placeholder: state.startup !== "ready" ? "Connecting\u2026" : state.busy ? "Queue a follow-up\u2026" : labels.askAgent, value: draft, disabled: state.mode !== "control" && !state.canSend, onInput: (event) => {
|
|
149
153
|
const value = event.currentTarget.value;
|
|
150
154
|
setDraft(value);
|
|
151
|
-
|
|
155
|
+
remember(value, queue);
|
|
152
156
|
}, onKeyDown: (event) => {
|
|
153
157
|
if (isSendKey(event)) {
|
|
154
158
|
event.preventDefault();
|
package/controller.d.ts
CHANGED
|
@@ -7,7 +7,10 @@ import type {
|
|
|
7
7
|
SupercodeController,
|
|
8
8
|
} from '@volter-ai-dev/supercode-client';
|
|
9
9
|
import type {
|
|
10
|
+
AttachedSessionModel,
|
|
10
11
|
SessionAttention,
|
|
12
|
+
SessionRowModel,
|
|
13
|
+
StartupPhase,
|
|
11
14
|
SupercodeUiIntent,
|
|
12
15
|
SupercodeUiState,
|
|
13
16
|
UiAdapter,
|
|
@@ -15,12 +18,33 @@ import type {
|
|
|
15
18
|
|
|
16
19
|
export interface ClientProjectionOptions {
|
|
17
20
|
now?: number;
|
|
21
|
+
/** Maximum rendered conversation rows after hidden context is removed. Default 120. */
|
|
22
|
+
maxEntries?: number;
|
|
23
|
+
/** Maximum native entries inspected to fill the rendered tail. Default four times `maxEntries`. */
|
|
24
|
+
maxScanEntries?: number;
|
|
25
|
+
/** Maximum characters retained independently in each rendered entry field. Default 16,000. */
|
|
26
|
+
maxEntryChars?: number;
|
|
27
|
+
/** Maximum controller-inventory rows projected when the host does not supply `sessions`. Default 100. */
|
|
28
|
+
maxSessions?: number;
|
|
29
|
+
/** Age in milliseconds for treating an inventory row as recently live. Default five minutes. */
|
|
30
|
+
liveWindowMs?: number;
|
|
31
|
+
/** Bounded native fidelity details; total counts remain truthful when arrays are shortened. */
|
|
32
|
+
maxResidueItems?: number;
|
|
33
|
+
maxResidueChars?: number;
|
|
34
|
+
maxSubagents?: number;
|
|
35
|
+
/** Host bootstrap phase before the controller itself is ready. */
|
|
36
|
+
startup?: StartupPhase;
|
|
18
37
|
attention?: SessionAttention[];
|
|
19
38
|
savedDraft?: string;
|
|
20
39
|
history?: Partial<SupercodeUiState['history']>;
|
|
21
40
|
exportBackTarget?: SessionFormat | null;
|
|
22
41
|
exportReceipt?: SupercodeUiState['exportReceipt'];
|
|
23
42
|
reductionReceipt?: SupercodeUiState['reductionReceipt'];
|
|
43
|
+
/** Trusted-host inventory and lifecycle overlays (for example a machine-wide session catalog). */
|
|
44
|
+
sessions?: SessionRowModel[];
|
|
45
|
+
attached?: AttachedSessionModel | null;
|
|
46
|
+
owned?: AttachedSessionModel | null;
|
|
47
|
+
attachError?: SupercodeUiState['attachError'];
|
|
24
48
|
}
|
|
25
49
|
|
|
26
50
|
export function projectClientSnapshot(
|
package/controller.mjs
CHANGED
|
@@ -3,11 +3,22 @@ import { normalizeUiState } from './core.mjs';
|
|
|
3
3
|
const HARNESS_LABELS = {
|
|
4
4
|
'claude-code': 'Claude Code',
|
|
5
5
|
codex: 'Codex',
|
|
6
|
+
gemini: 'Gemini CLI',
|
|
7
|
+
goose: 'Goose',
|
|
6
8
|
opencode: 'OpenCode',
|
|
7
9
|
pi: 'Pi',
|
|
8
10
|
grok: 'Grok',
|
|
9
11
|
};
|
|
10
12
|
|
|
13
|
+
const DEFAULT_MAX_ENTRIES = 120;
|
|
14
|
+
const DEFAULT_MAX_ENTRY_CHARS = 16_000;
|
|
15
|
+
const DEFAULT_MAX_SESSIONS = 100;
|
|
16
|
+
const DEFAULT_LIVE_WINDOW_MS = 300_000;
|
|
17
|
+
const MAX_PILL_LABEL_CHARS = 72;
|
|
18
|
+
const DEFAULT_MAX_RESIDUE_ITEMS = 20;
|
|
19
|
+
const DEFAULT_MAX_RESIDUE_CHARS = 300;
|
|
20
|
+
const DEFAULT_MAX_SUBAGENTS = 50;
|
|
21
|
+
|
|
11
22
|
function label(id, supplied) {
|
|
12
23
|
return supplied || HARNESS_LABELS[id] || id;
|
|
13
24
|
}
|
|
@@ -36,56 +47,145 @@ function payloadText(payload) {
|
|
|
36
47
|
}
|
|
37
48
|
}
|
|
38
49
|
|
|
39
|
-
function
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
50
|
+
function cloneJsonValue(value) {
|
|
51
|
+
if (Array.isArray(value)) return value.map(cloneJsonValue);
|
|
52
|
+
if (value !== null && typeof value === 'object') {
|
|
53
|
+
return Object.fromEntries(Object.entries(value).map(([key, nested]) => [key, cloneJsonValue(nested)]));
|
|
54
|
+
}
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function positiveInteger(value, fallback) {
|
|
59
|
+
return Number.isInteger(value) && value > 0 ? value : fallback;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function truncate(value, max) {
|
|
63
|
+
const text = typeof value === 'string' ? value : '';
|
|
64
|
+
if (text.length <= max) return { text, truncated: false };
|
|
65
|
+
let end = max;
|
|
66
|
+
const last = text.charCodeAt(end - 1);
|
|
67
|
+
const next = text.charCodeAt(end);
|
|
68
|
+
if (last >= 0xd800 && last <= 0xdbff && next >= 0xdc00 && next <= 0xdfff) end -= 1;
|
|
69
|
+
return { text: `${text.slice(0, end)}…`, truncated: true };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function timestampFromMetadata(metadata) {
|
|
73
|
+
if (!metadata || typeof metadata !== 'object' || Array.isArray(metadata)) return null;
|
|
74
|
+
for (const key of ['timestamp', 'ts', 'time', 'created_at', 'createdAt', 'date']) {
|
|
75
|
+
const raw = metadata[key];
|
|
76
|
+
if (typeof raw !== 'string' || raw === '') continue;
|
|
77
|
+
if (/^\d+$/.test(raw)) {
|
|
78
|
+
const value = Number(raw);
|
|
79
|
+
if (Number.isFinite(value) && value > 0) return value < 1e11 ? value * 1000 : value;
|
|
80
|
+
continue;
|
|
51
81
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
82
|
+
const value = Date.parse(raw);
|
|
83
|
+
if (Number.isFinite(value)) return value;
|
|
84
|
+
}
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function projectContext(context) {
|
|
89
|
+
if (!Array.isArray(context)) return undefined;
|
|
90
|
+
const projected = context.flatMap((item) => item && typeof item === 'object'
|
|
91
|
+
&& typeof item.label === 'string' && typeof item.detail === 'string'
|
|
92
|
+
? [{
|
|
93
|
+
...(typeof item.id === 'string' ? { id: item.id } : {}),
|
|
94
|
+
...(typeof item.kind === 'string' ? { kind: item.kind } : {}),
|
|
95
|
+
label: item.label,
|
|
96
|
+
detail: item.detail,
|
|
97
|
+
}]
|
|
98
|
+
: []);
|
|
99
|
+
return projected.length ? projected : undefined;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function requestSummary(entry) {
|
|
103
|
+
const options = Array.isArray(entry.options)
|
|
104
|
+
? entry.options.map((option) => option?.name).filter(Boolean).join(' / ')
|
|
105
|
+
: '';
|
|
106
|
+
const resolution = entry.resolution?.name ? ` → ${entry.resolution.name}` : '';
|
|
107
|
+
return options ? `${entry.requestKind}: ${options}${resolution}` : `${entry.requestKind ?? 'request'}${resolution}`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function projectConversationEntry(entry, maxEntryChars) {
|
|
111
|
+
if (!entry || typeof entry !== 'object' || typeof entry.id !== 'string') return null;
|
|
112
|
+
if (entry.kind === 'message') {
|
|
113
|
+
if (entry.visibility === 'context') return null;
|
|
114
|
+
const body = truncate(entry.text, maxEntryChars);
|
|
115
|
+
const context = projectContext(entry.context);
|
|
116
|
+
return {
|
|
117
|
+
id: entry.id,
|
|
118
|
+
role: entry.role,
|
|
119
|
+
text: body.text,
|
|
120
|
+
ts: timestampFromMetadata(entry.metadata),
|
|
121
|
+
truncated: body.truncated,
|
|
122
|
+
...(context ? { context } : {}),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
if (entry.kind === 'tool') {
|
|
126
|
+
const argumentsText = truncate((entry.arguments ?? '').trim(), maxEntryChars);
|
|
127
|
+
const result = truncate((entry.resultText ?? '').trim(), maxEntryChars);
|
|
128
|
+
const primary = entry.status === 'pending' ? argumentsText : result;
|
|
129
|
+
return {
|
|
130
|
+
id: entry.id,
|
|
131
|
+
role: 'tool',
|
|
132
|
+
text: primary.text,
|
|
133
|
+
ts: timestampFromMetadata(entry.metadata),
|
|
134
|
+
truncated: argumentsText.truncated || result.truncated,
|
|
135
|
+
label: entry.name ?? 'tool',
|
|
136
|
+
arguments: argumentsText.text,
|
|
137
|
+
resultText: result.text,
|
|
138
|
+
status: entry.status,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
if (entry.kind === 'reasoning') {
|
|
142
|
+
const body = truncate(entry.text, maxEntryChars);
|
|
143
|
+
return { id: entry.id, role: 'reasoning', text: body.text, ts: null, truncated: body.truncated, streaming: entry.streaming === true };
|
|
144
|
+
}
|
|
145
|
+
if (entry.kind === 'request') {
|
|
146
|
+
const summary = truncate(requestSummary(entry), maxEntryChars);
|
|
147
|
+
const payload = truncate(payloadText(entry.payload), maxEntryChars);
|
|
148
|
+
return {
|
|
149
|
+
id: entry.id,
|
|
150
|
+
role: 'request',
|
|
151
|
+
text: summary.text,
|
|
152
|
+
ts: null,
|
|
153
|
+
truncated: summary.truncated || payload.truncated,
|
|
154
|
+
request: {
|
|
155
|
+
requestId: cloneJsonValue(entry.requestId),
|
|
156
|
+
requestKind: entry.requestKind,
|
|
157
|
+
payloadText: payload.text,
|
|
158
|
+
options: Array.isArray(entry.options) ? entry.options.map((option) => ({ ...option })) : [],
|
|
159
|
+
cancellable: entry.cancellable === true,
|
|
62
160
|
status: entry.status,
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
161
|
+
resolution: entry.resolution ? { ...entry.resolution } : null,
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
if (entry.kind === 'notice') {
|
|
166
|
+
const body = truncate(entry.text || entry.code, maxEntryChars);
|
|
167
|
+
return { id: entry.id, role: 'notice', text: body.text, ts: null, truncated: body.truncated, code: entry.code };
|
|
168
|
+
}
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function projectConversation(conversation, options) {
|
|
173
|
+
if (!Array.isArray(conversation)) return [];
|
|
174
|
+
const maxEntries = positiveInteger(options.maxEntries, DEFAULT_MAX_ENTRIES);
|
|
175
|
+
const maxEntryChars = positiveInteger(options.maxEntryChars, DEFAULT_MAX_ENTRY_CHARS);
|
|
176
|
+
const maxScanEntries = positiveInteger(options.maxScanEntries, maxEntries * 4);
|
|
177
|
+
const rows = [];
|
|
178
|
+
// Start at the tail and stop as soon as the bounded render window is full. Hidden harness
|
|
179
|
+
// context never consumes a slot. The independent scan cap prevents a corrupt/all-context
|
|
180
|
+
// transcript from turning this display projection back into an unbounded whole-history walk.
|
|
181
|
+
for (let index = conversation.length - 1, scanned = 0;
|
|
182
|
+
index >= 0 && rows.length < maxEntries && scanned < maxScanEntries;
|
|
183
|
+
index -= 1, scanned += 1) {
|
|
184
|
+
const row = projectConversationEntry(conversation[index], maxEntryChars);
|
|
185
|
+
if (row) rows.push(row);
|
|
186
|
+
}
|
|
187
|
+
rows.reverse();
|
|
188
|
+
return rows;
|
|
89
189
|
}
|
|
90
190
|
|
|
91
191
|
function attached(snapshot) {
|
|
@@ -106,16 +206,20 @@ function attached(snapshot) {
|
|
|
106
206
|
};
|
|
107
207
|
}
|
|
108
208
|
|
|
109
|
-
function semantics(session) {
|
|
209
|
+
function semantics(session, options) {
|
|
110
210
|
if (!session) return { fidelity: null, residue: [], residueCount: 0, parseErrors: 0, rawRecords: 0, subagents: [] };
|
|
211
|
+
const residue = Array.isArray(session.residue) ? session.residue : [];
|
|
212
|
+
const maxResidueItems = positiveInteger(options.maxResidueItems, DEFAULT_MAX_RESIDUE_ITEMS);
|
|
213
|
+
const maxResidueChars = positiveInteger(options.maxResidueChars, DEFAULT_MAX_RESIDUE_CHARS);
|
|
214
|
+
const maxSubagents = positiveInteger(options.maxSubagents, DEFAULT_MAX_SUBAGENTS);
|
|
111
215
|
return {
|
|
112
216
|
fidelity: session.fidelity ?? null,
|
|
113
|
-
residue:
|
|
114
|
-
residueCount:
|
|
217
|
+
residue: residue.slice(0, maxResidueItems).map((item) => truncate(String(item), maxResidueChars).text),
|
|
218
|
+
residueCount: residue.length,
|
|
115
219
|
parseErrors: Number.isFinite(session.parse_error_lines) ? session.parse_error_lines : 0,
|
|
116
220
|
rawRecords: Number.isFinite(session.raw_record_count) ? session.raw_record_count : 0,
|
|
117
|
-
subagents: Array.isArray(session.subagents) ? session.subagents.map((child, index) => ({
|
|
118
|
-
id: child.session_id ?? `${child.source}:${index}`,
|
|
221
|
+
subagents: Array.isArray(session.subagents) ? session.subagents.slice(0, maxSubagents).map((child, index) => ({
|
|
222
|
+
id: child.session_id ?? child.agent_id ?? `${child.source}:${index}`,
|
|
119
223
|
source: child.source,
|
|
120
224
|
model: child.model ?? null,
|
|
121
225
|
messages: Array.isArray(child.messages) ? child.messages.length : 0,
|
|
@@ -124,12 +228,33 @@ function semantics(session) {
|
|
|
124
228
|
};
|
|
125
229
|
}
|
|
126
230
|
|
|
231
|
+
function projectPill(snapshot) {
|
|
232
|
+
const text = (value) => truncate(value, MAX_PILL_LABEL_CHARS).text;
|
|
233
|
+
const harness = snapshot.activeHarness ?? 'agent';
|
|
234
|
+
if (snapshot.availability === 'loading') return { tone: 'off', label: text('connecting…') };
|
|
235
|
+
if (snapshot.availability === 'unavailable' || snapshot.availability === 'error') {
|
|
236
|
+
return { tone: 'dead', label: text(snapshot.error?.message ?? 'supercode unavailable') };
|
|
237
|
+
}
|
|
238
|
+
if (snapshot.error) return { tone: 'warn', label: text(snapshot.error.message) };
|
|
239
|
+
if (snapshot.turn?.state === 'running') return { tone: 'live', label: text(`${label(harness)} working…`) };
|
|
240
|
+
if (snapshot.turn?.state === 'interrupting') return { tone: 'warn', label: text('interrupting…') };
|
|
241
|
+
if (snapshot.turn?.state === 'reconciling') return { tone: 'live', label: text('syncing…') };
|
|
242
|
+
if (snapshot.connection?.mode === 'none') return { tone: 'off', label: text('no session') };
|
|
243
|
+
if (snapshot.connection?.mode === 'mirror') {
|
|
244
|
+
return snapshot.connection.messaging === 'live_peer'
|
|
245
|
+
? { tone: 'live', label: text(`${label(harness)} live`) }
|
|
246
|
+
: { tone: 'warn', label: text(`${label(harness)} (read-only)`) };
|
|
247
|
+
}
|
|
248
|
+
return { tone: 'live', label: text(`${label(harness)} ready`) };
|
|
249
|
+
}
|
|
250
|
+
|
|
127
251
|
export function projectClientSnapshot(snapshot, options = {}) {
|
|
128
252
|
const now = Number.isFinite(options.now) ? options.now : Date.now();
|
|
129
|
-
const busy =
|
|
253
|
+
const busy = ['running', 'interrupting', 'reconciling'].includes(snapshot.turn?.state);
|
|
130
254
|
const actions = snapshot.availableActions ?? {};
|
|
131
|
-
const
|
|
132
|
-
const
|
|
255
|
+
const maxSessions = positiveInteger(options.maxSessions, DEFAULT_MAX_SESSIONS);
|
|
256
|
+
const liveWindowMs = positiveInteger(options.liveWindowMs, DEFAULT_LIVE_WINDOW_MS);
|
|
257
|
+
const snapshotSessions = (snapshot.sessions ?? []).slice(0, maxSessions).map((session) => ({
|
|
133
258
|
key: session.key,
|
|
134
259
|
harness: session.harness,
|
|
135
260
|
name: workspaceName(session.cwd),
|
|
@@ -139,20 +264,23 @@ export function projectClientSnapshot(snapshot, options = {}) {
|
|
|
139
264
|
updatedAt: session.updatedAt ?? null,
|
|
140
265
|
messages: session.messageCount ?? null,
|
|
141
266
|
active: session.key === snapshot.activeSessionKey,
|
|
142
|
-
live: typeof session.updatedAt === 'number' && now - session.updatedAt <=
|
|
267
|
+
live: typeof session.updatedAt === 'number' && now - session.updatedAt <= liveWindowMs,
|
|
143
268
|
runtimeStatus: session.liveStatus ?? null,
|
|
144
269
|
}));
|
|
270
|
+
const sessions = options.sessions ?? snapshotSessions;
|
|
271
|
+
const active = options.attached === undefined ? attached(snapshot) : options.attached;
|
|
272
|
+
const owned = options.owned === undefined
|
|
273
|
+
? snapshot.connection?.ownsRuntime ? active : null
|
|
274
|
+
: options.owned;
|
|
275
|
+
const maxEntries = positiveInteger(options.maxEntries, DEFAULT_MAX_ENTRIES);
|
|
276
|
+
const transcript = projectConversation(snapshot.conversation ?? [], options);
|
|
145
277
|
const startup = snapshot.availability === 'loading'
|
|
146
278
|
? snapshot.operation === 'start' ? 'starting' : snapshot.operation === 'refresh' ? 'discovering' : 'connecting'
|
|
147
279
|
: 'ready';
|
|
148
280
|
return normalizeUiState({
|
|
149
|
-
pill: snapshot
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
: snapshot.availability === 'ready' ? { tone: 'live', label: `${label(snapshot.activeHarness) || 'Supercode'} ready` }
|
|
153
|
-
: { tone: 'off', label: 'connecting…' },
|
|
154
|
-
startup,
|
|
155
|
-
transcript: projectConversation(snapshot.conversation ?? []),
|
|
281
|
+
pill: projectPill(snapshot),
|
|
282
|
+
startup: options.startup ?? startup,
|
|
283
|
+
transcript,
|
|
156
284
|
busy,
|
|
157
285
|
operation: snapshot.operation,
|
|
158
286
|
needsInput: Array.isArray(snapshot.requests) && snapshot.requests.some((request) => request.status === 'pending'),
|
|
@@ -177,7 +305,7 @@ export function projectClientSnapshot(snapshot, options = {}) {
|
|
|
177
305
|
residueCount: snapshot.taskPlan?.residue?.length ?? 0,
|
|
178
306
|
observedAt: snapshot.taskPlan?.observedAt ?? null,
|
|
179
307
|
},
|
|
180
|
-
semantics: semantics(snapshot.activeSession),
|
|
308
|
+
semantics: semantics(snapshot.activeSession, options),
|
|
181
309
|
terminalHandoff: snapshot.terminalLaunch ? {
|
|
182
310
|
program: snapshot.terminalLaunch.program,
|
|
183
311
|
arguments: snapshot.terminalLaunch.arguments,
|
|
@@ -198,15 +326,15 @@ export function projectClientSnapshot(snapshot, options = {}) {
|
|
|
198
326
|
history: {
|
|
199
327
|
sessionLimit: options.history?.sessionLimit ?? sessions.length,
|
|
200
328
|
hasMoreSessions: options.history?.hasMoreSessions ?? false,
|
|
201
|
-
transcriptLimit: options.history?.transcriptLimit ??
|
|
202
|
-
hasEarlier: options.history?.hasEarlier ??
|
|
329
|
+
transcriptLimit: options.history?.transcriptLimit ?? maxEntries,
|
|
330
|
+
hasEarlier: options.history?.hasEarlier ?? (snapshot.conversation?.length ?? 0) > maxEntries,
|
|
203
331
|
},
|
|
204
332
|
savedDraft: options.savedDraft ?? '',
|
|
205
333
|
attention: options.attention ?? [],
|
|
206
334
|
sessions,
|
|
207
335
|
attached: active,
|
|
208
|
-
owned
|
|
209
|
-
attachError: null,
|
|
336
|
+
owned,
|
|
337
|
+
attachError: options.attachError ?? null,
|
|
210
338
|
});
|
|
211
339
|
}
|
|
212
340
|
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type {
|
|
2
|
+
ActivityGroupProps,
|
|
3
|
+
MessengerComponents,
|
|
4
|
+
MessengerSlots,
|
|
5
|
+
SessionSemanticsModel,
|
|
6
|
+
SupercodeUiState,
|
|
7
|
+
TaskPlanModel,
|
|
8
|
+
TranscriptEntryModel,
|
|
9
|
+
TranscriptEntryProps,
|
|
10
|
+
UiAdapter,
|
|
11
|
+
} from './index.js';
|
|
12
|
+
export {
|
|
13
|
+
ActivityGroup,
|
|
14
|
+
Conversation,
|
|
15
|
+
LoadingStatus,
|
|
16
|
+
RequestCard,
|
|
17
|
+
SessionDetails,
|
|
18
|
+
TaskPlan,
|
|
19
|
+
TranscriptEntry,
|
|
20
|
+
} from './index.js';
|