@volter-ai-dev/supercode-ui 0.1.57 → 0.1.59
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 +36 -2
- package/components.mjs +12 -11
- package/controller.mjs +10 -4
- package/embed.mjs +12 -11
- package/host.d.ts +103 -0
- package/host.mjs +384 -0
- package/messenger.mjs +12 -11
- package/package.json +7 -1
- package/react/components.mjs +12 -11
- package/react/messenger.mjs +12 -11
- package/react/sessions.mjs +12 -11
- package/sessions.mjs +12 -11
- package/styles.css +6 -6
package/README.md
CHANGED
|
@@ -185,11 +185,45 @@ mounted.update(binding.getState());
|
|
|
185
185
|
```
|
|
186
186
|
|
|
187
187
|
The binding maps standard controller operations, including verified reduce-and-continue, and
|
|
188
|
-
projects its durable reduction receipt directly. Host-owned pagination, durable
|
|
189
|
-
drafts, and artifact materialization remain explicit callbacks.
|
|
188
|
+
projects its durable reduction receipt directly. Host-owned session-list pagination, durable
|
|
189
|
+
attention, drafts, and artifact materialization remain explicit callbacks. Transcript pagination
|
|
190
|
+
dispatches to the controller by default. A
|
|
190
191
|
browser should receive projected state from a trusted host rather than instantiate a local
|
|
191
192
|
controller or gain filesystem authority.
|
|
192
193
|
|
|
194
|
+
### Bind across a process boundary
|
|
195
|
+
|
|
196
|
+
Browser-hosted products use the transport-neutral host binding instead of recreating snapshot
|
|
197
|
+
ordering and intent dispatch around the same controller:
|
|
198
|
+
|
|
199
|
+
```js
|
|
200
|
+
import { createRemoteControllerHost, createRemoteUiBinding } from '@volter-ai-dev/supercode-ui/host';
|
|
201
|
+
|
|
202
|
+
// Trusted process
|
|
203
|
+
const host = createRemoteControllerHost(controller);
|
|
204
|
+
events.send(host.getFrame());
|
|
205
|
+
host.subscribe((frame) => events.send(frame));
|
|
206
|
+
http.onIntent((intent) => host.dispatch(intent));
|
|
207
|
+
|
|
208
|
+
// Browser
|
|
209
|
+
const binding = createRemoteUiBinding({
|
|
210
|
+
initialFrame,
|
|
211
|
+
dispatch: (intent) => http.postIntent(intent),
|
|
212
|
+
attention: {
|
|
213
|
+
state: loadAttention(),
|
|
214
|
+
onChange: saveAttention,
|
|
215
|
+
},
|
|
216
|
+
});
|
|
217
|
+
events.onFrame((frame) => binding.receive(frame));
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Each serializable frame carries a host-process identity, workspace generation, monotonic sequence,
|
|
221
|
+
controller revision, canonical UI state, and stable opaque reconnect identities. The browser store
|
|
222
|
+
rejects duplicate and stale frames, including delayed action responses from a retired host process.
|
|
223
|
+
The optional attention tracker baselines initial inventory without inventing unread dots, persists
|
|
224
|
+
by stable opaque identity, and marks only newer conversation evidence or a proven runtime completion.
|
|
225
|
+
HTTP, SSE, WebSocket, authentication, and product shell behavior remain host-owned transports.
|
|
226
|
+
|
|
193
227
|
Native continuation exposes one action and a quiet execution-transport selector. A host with a real
|
|
194
228
|
terminal provider can add `terminal` to `continuationModes` and handle `onResumeTerminal`; Terminal
|
|
195
229
|
is then the initial choice and Headless remains available from the selector. The UI never infers
|
package/components.mjs
CHANGED
|
@@ -2345,31 +2345,32 @@ function SessionRow({ row, state, onOpen, onOpenSubagents, now = Date.now() }) {
|
|
|
2345
2345
|
/* @__PURE__ */ jsxs7("button", { className: "scui-session-main", "data-session-key": row.key, type: "button", "aria-label": `${title} \xB7 ${harnessDisplayName(row.harness)}${row.writable ? "" : " \xB7 Read-only"}${working ? " \xB7 Working" : ""}${path.complete ? ` \xB7 ${path.complete}` : ""}${preview ? ` \xB7 ${preview}` : ""}${unreadCount ? ` \xB7 ${unreadCount} unread` : ""}${age ? ` \xB7 ${age}` : ""}`, "aria-current": row.active ? "true" : void 0, onClick: () => onOpen(row), children: [
|
|
2346
2346
|
/* @__PURE__ */ jsx8(HarnessLogo, { id: row.harness, activity, size: 34 }),
|
|
2347
2347
|
/* @__PURE__ */ jsxs7("span", { className: "scui-session-copy", children: [
|
|
2348
|
-
/* @__PURE__ */
|
|
2349
|
-
/* @__PURE__ */ jsx8("strong", { children: title }),
|
|
2350
|
-
/* @__PURE__ */ jsx8("span", { className: "scui-session-meta", children: age ? /* @__PURE__ */ jsx8("time", { children: age }) : null })
|
|
2351
|
-
] }),
|
|
2348
|
+
/* @__PURE__ */ jsx8("span", { className: "scui-session-title", children: /* @__PURE__ */ jsx8("strong", { children: title }) }),
|
|
2352
2349
|
path.complete ? /* @__PURE__ */ jsxs7("small", { className: "scui-session-path", title: row.cwd, children: [
|
|
2353
2350
|
/* @__PURE__ */ jsx8("span", { className: "scui-session-path-leading", children: path.leading }),
|
|
2354
2351
|
path.separator ? /* @__PURE__ */ jsx8("span", { className: "scui-session-path-separator", children: path.separator }) : null,
|
|
2355
2352
|
path.trailing ? /* @__PURE__ */ jsx8("span", { className: "scui-session-path-trailing", children: path.trailing }) : null
|
|
2356
2353
|
] }) : null,
|
|
2357
|
-
working || preview
|
|
2354
|
+
working || preview ? /* @__PURE__ */ jsxs7("span", { className: "scui-session-preview", children: [
|
|
2358
2355
|
working ? /* @__PURE__ */ jsxs7("span", { className: "scui-session-working", "aria-hidden": "true", children: [
|
|
2359
2356
|
/* @__PURE__ */ jsx8("i", {}),
|
|
2360
2357
|
/* @__PURE__ */ jsx8("i", {}),
|
|
2361
2358
|
/* @__PURE__ */ jsx8("i", {})
|
|
2362
2359
|
] }) : null,
|
|
2363
|
-
preview || working ? /* @__PURE__ */ jsx8("small", { children: preview || "Working\u2026" }) : null
|
|
2364
|
-
unreadCount ? /* @__PURE__ */ jsx8("b", { "aria-label": `${unreadCount} unread messages`, children: unreadLabel }) : null
|
|
2360
|
+
preview || working ? /* @__PURE__ */ jsx8("small", { children: preview || "Working\u2026" }) : null
|
|
2365
2361
|
] }) : null,
|
|
2366
2362
|
state.attachError?.key === row.key ? /* @__PURE__ */ jsx8("em", { children: state.attachError.message }) : null
|
|
2367
2363
|
] })
|
|
2368
2364
|
] }),
|
|
2369
|
-
row.subagentCount ? /* @__PURE__ */ jsxs7("
|
|
2370
|
-
/* @__PURE__ */
|
|
2371
|
-
|
|
2372
|
-
|
|
2365
|
+
row.subagentCount || age || unreadCount ? /* @__PURE__ */ jsxs7("span", { className: "scui-session-rail", children: [
|
|
2366
|
+
/* @__PURE__ */ jsxs7("span", { className: "scui-session-rail-meta", children: [
|
|
2367
|
+
row.subagentCount ? /* @__PURE__ */ jsxs7("button", { className: "scui-session-agents", type: "button", title: `${row.subagentCount} agent sessions`, "aria-label": `Open ${row.subagentCount} agent sessions in ${title}`, onClick: () => onOpenSubagents?.(row), children: [
|
|
2368
|
+
/* @__PURE__ */ jsx8(UiIcon, { name: "agents", size: 14 }),
|
|
2369
|
+
/* @__PURE__ */ jsx8("span", { children: row.subagentCount })
|
|
2370
|
+
] }) : null,
|
|
2371
|
+
age ? /* @__PURE__ */ jsx8("time", { children: age }) : null
|
|
2372
|
+
] }),
|
|
2373
|
+
unreadCount ? /* @__PURE__ */ jsx8("b", { "aria-label": `${unreadCount} unread messages`, children: unreadLabel }) : null
|
|
2373
2374
|
] }) : null
|
|
2374
2375
|
] });
|
|
2375
2376
|
}
|
package/controller.mjs
CHANGED
|
@@ -433,7 +433,7 @@ function attached(snapshot) {
|
|
|
433
433
|
harness: session.harness,
|
|
434
434
|
name: workspaceName(session.cwd),
|
|
435
435
|
cwd: session.cwd ?? '',
|
|
436
|
-
title: session.title ?? 'Untitled chat',
|
|
436
|
+
title: session.displayTitle ?? session.title ?? 'Untitled chat',
|
|
437
437
|
};
|
|
438
438
|
}
|
|
439
439
|
|
|
@@ -509,8 +509,10 @@ function projectClientSnapshotInternal(snapshot, options, imageRegistry) {
|
|
|
509
509
|
harness: session.harness,
|
|
510
510
|
name: workspaceName(session.cwd),
|
|
511
511
|
cwd: session.cwd ?? '',
|
|
512
|
-
title: session.title ?? 'Untitled chat',
|
|
513
|
-
|
|
512
|
+
title: session.displayTitle ?? session.title ?? 'Untitled chat',
|
|
513
|
+
preview: session.preview ?? '',
|
|
514
|
+
age: relativeAge(session.previewUpdatedAt ?? session.updatedAt, now),
|
|
515
|
+
previewUpdatedAt: session.previewUpdatedAt ?? null,
|
|
514
516
|
updatedAt: session.updatedAt ?? null,
|
|
515
517
|
messages: session.messageCount ?? null,
|
|
516
518
|
active: session.key === snapshot.activeSessionKey,
|
|
@@ -677,7 +679,11 @@ export async function dispatchControllerIntent(controller, intent, options = {})
|
|
|
677
679
|
if (intent.action === 'draft') return options.onDraft?.(intent.text);
|
|
678
680
|
if (intent.action === 'ack') return options.onAcknowledge?.(intent.key);
|
|
679
681
|
if (intent.action === 'loadSessions') return options.onLoadSessions?.();
|
|
680
|
-
if (intent.action === 'loadEarlier')
|
|
682
|
+
if (intent.action === 'loadEarlier') {
|
|
683
|
+
return options.onLoadEarlier
|
|
684
|
+
? options.onLoadEarlier()
|
|
685
|
+
: controller.dispatch({ type: 'loadEarlier' });
|
|
686
|
+
}
|
|
681
687
|
return options.onUnsupported?.(intent);
|
|
682
688
|
}
|
|
683
689
|
|
package/embed.mjs
CHANGED
|
@@ -2262,31 +2262,32 @@ function SessionRow({ row, state, onOpen, onOpenSubagents, now = Date.now() }) {
|
|
|
2262
2262
|
/* @__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: [
|
|
2263
2263
|
/* @__PURE__ */ jsx7(HarnessLogo, { id: row.harness, activity, size: 34 }),
|
|
2264
2264
|
/* @__PURE__ */ jsxs6("span", { className: "scui-session-copy", children: [
|
|
2265
|
-
/* @__PURE__ */
|
|
2266
|
-
/* @__PURE__ */ jsx7("strong", { children: title }),
|
|
2267
|
-
/* @__PURE__ */ jsx7("span", { className: "scui-session-meta", children: age ? /* @__PURE__ */ jsx7("time", { children: age }) : null })
|
|
2268
|
-
] }),
|
|
2265
|
+
/* @__PURE__ */ jsx7("span", { className: "scui-session-title", children: /* @__PURE__ */ jsx7("strong", { children: title }) }),
|
|
2269
2266
|
path.complete ? /* @__PURE__ */ jsxs6("small", { className: "scui-session-path", title: row.cwd, children: [
|
|
2270
2267
|
/* @__PURE__ */ jsx7("span", { className: "scui-session-path-leading", children: path.leading }),
|
|
2271
2268
|
path.separator ? /* @__PURE__ */ jsx7("span", { className: "scui-session-path-separator", children: path.separator }) : null,
|
|
2272
2269
|
path.trailing ? /* @__PURE__ */ jsx7("span", { className: "scui-session-path-trailing", children: path.trailing }) : null
|
|
2273
2270
|
] }) : null,
|
|
2274
|
-
working || preview
|
|
2271
|
+
working || preview ? /* @__PURE__ */ jsxs6("span", { className: "scui-session-preview", children: [
|
|
2275
2272
|
working ? /* @__PURE__ */ jsxs6("span", { className: "scui-session-working", "aria-hidden": "true", children: [
|
|
2276
2273
|
/* @__PURE__ */ jsx7("i", {}),
|
|
2277
2274
|
/* @__PURE__ */ jsx7("i", {}),
|
|
2278
2275
|
/* @__PURE__ */ jsx7("i", {})
|
|
2279
2276
|
] }) : null,
|
|
2280
|
-
preview || working ? /* @__PURE__ */ jsx7("small", { children: preview || "Working\u2026" }) : null
|
|
2281
|
-
unreadCount ? /* @__PURE__ */ jsx7("b", { "aria-label": `${unreadCount} unread messages`, children: unreadLabel }) : null
|
|
2277
|
+
preview || working ? /* @__PURE__ */ jsx7("small", { children: preview || "Working\u2026" }) : null
|
|
2282
2278
|
] }) : null,
|
|
2283
2279
|
state.attachError?.key === row.key ? /* @__PURE__ */ jsx7("em", { children: state.attachError.message }) : null
|
|
2284
2280
|
] })
|
|
2285
2281
|
] }),
|
|
2286
|
-
row.subagentCount ? /* @__PURE__ */ jsxs6("
|
|
2287
|
-
/* @__PURE__ */
|
|
2288
|
-
|
|
2289
|
-
|
|
2282
|
+
row.subagentCount || age || unreadCount ? /* @__PURE__ */ jsxs6("span", { className: "scui-session-rail", children: [
|
|
2283
|
+
/* @__PURE__ */ jsxs6("span", { className: "scui-session-rail-meta", children: [
|
|
2284
|
+
row.subagentCount ? /* @__PURE__ */ jsxs6("button", { className: "scui-session-agents", type: "button", title: `${row.subagentCount} agent sessions`, "aria-label": `Open ${row.subagentCount} agent sessions in ${title}`, onClick: () => onOpenSubagents?.(row), children: [
|
|
2285
|
+
/* @__PURE__ */ jsx7(UiIcon, { name: "agents", size: 14 }),
|
|
2286
|
+
/* @__PURE__ */ jsx7("span", { children: row.subagentCount })
|
|
2287
|
+
] }) : null,
|
|
2288
|
+
age ? /* @__PURE__ */ jsx7("time", { children: age }) : null
|
|
2289
|
+
] }),
|
|
2290
|
+
unreadCount ? /* @__PURE__ */ jsx7("b", { "aria-label": `${unreadCount} unread messages`, children: unreadLabel }) : null
|
|
2290
2291
|
] }) : null
|
|
2291
2292
|
] });
|
|
2292
2293
|
}
|
package/host.d.ts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type { SupercodeClientSnapshot, SupercodeController } from '@volter-ai-dev/supercode-client';
|
|
2
|
+
import type { ClientProjectionOptions, ControllerBindingOptions } from './controller.js';
|
|
3
|
+
import type { SupercodeUiIntent, SupercodeUiState, UiAdapter } from './index.js';
|
|
4
|
+
|
|
5
|
+
export interface RemoteUiFrame {
|
|
6
|
+
schema: 'supercode.ui-host-state.v1';
|
|
7
|
+
hostInstanceId: string;
|
|
8
|
+
generation: number;
|
|
9
|
+
sequence: number;
|
|
10
|
+
controllerRevision: number;
|
|
11
|
+
workspace: string;
|
|
12
|
+
state: SupercodeUiState;
|
|
13
|
+
sessionIdentities: ReadonlyArray<{ key: string; identity: string }>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface RemoteControllerHostOptions extends Omit<ControllerBindingOptions, 'projection'> {
|
|
17
|
+
hostInstanceId?: string;
|
|
18
|
+
projection?(snapshot: SupercodeClientSnapshot): ClientProjectionOptions;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class RemoteControllerHost {
|
|
22
|
+
constructor(controller: SupercodeController, options?: RemoteControllerHostOptions);
|
|
23
|
+
close(): void;
|
|
24
|
+
dispatch(intent: unknown): Promise<RemoteUiFrame>;
|
|
25
|
+
getFrame(): RemoteUiFrame;
|
|
26
|
+
refreshProjection(): RemoteUiFrame;
|
|
27
|
+
restore(identity: string, connection: 'observe' | 'attach'): Promise<RemoteUiFrame>;
|
|
28
|
+
subscribe(listener: (frame: RemoteUiFrame) => void): () => void;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function createRemoteControllerHost(
|
|
32
|
+
controller: SupercodeController,
|
|
33
|
+
options?: RemoteControllerHostOptions,
|
|
34
|
+
): RemoteControllerHost;
|
|
35
|
+
|
|
36
|
+
export function assertRemoteUiFrame(value: unknown): RemoteUiFrame;
|
|
37
|
+
|
|
38
|
+
export class RemoteUiStore {
|
|
39
|
+
constructor(initialFrame?: RemoteUiFrame | null);
|
|
40
|
+
getFrame(): RemoteUiFrame | null;
|
|
41
|
+
getState(): SupercodeUiState | null;
|
|
42
|
+
receive(frame: unknown): boolean;
|
|
43
|
+
subscribe(listener: (state: SupercodeUiState, frame: RemoteUiFrame) => void): () => void;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function createRemoteUiStore(initialFrame?: RemoteUiFrame | null): RemoteUiStore;
|
|
47
|
+
|
|
48
|
+
export interface SessionAttentionTrackerState {
|
|
49
|
+
version: 1;
|
|
50
|
+
sessions: Record<string, {
|
|
51
|
+
observedAt: number;
|
|
52
|
+
messages: number | null;
|
|
53
|
+
activity: import('./index.js').SessionActivity;
|
|
54
|
+
attention: import('./index.js').SessionAttention['kind'] | null;
|
|
55
|
+
}>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface SessionAttentionTrackerOptions {
|
|
59
|
+
state?: unknown;
|
|
60
|
+
onChange?(state: SessionAttentionTrackerState): void;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export class SessionAttentionTracker {
|
|
64
|
+
constructor(options?: SessionAttentionTrackerOptions);
|
|
65
|
+
acknowledge(frame: RemoteUiFrame, key: string): boolean;
|
|
66
|
+
attention(frame: RemoteUiFrame): import('./index.js').SessionAttention[];
|
|
67
|
+
observe(frame: RemoteUiFrame): import('./index.js').SessionAttention[];
|
|
68
|
+
snapshot(): SessionAttentionTrackerState;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function createSessionAttentionTracker(
|
|
72
|
+
options?: SessionAttentionTrackerOptions,
|
|
73
|
+
): SessionAttentionTracker;
|
|
74
|
+
|
|
75
|
+
export interface RemoteUiBindingOptions
|
|
76
|
+
extends Pick<
|
|
77
|
+
UiAdapter,
|
|
78
|
+
| 'confirmIntent'
|
|
79
|
+
| 'pickContext'
|
|
80
|
+
| 'resolveImage'
|
|
81
|
+
| 'openKeyboardShortcuts'
|
|
82
|
+
| 'onClose'
|
|
83
|
+
| 'onOpen'
|
|
84
|
+
| 'copyText'
|
|
85
|
+
| 'now'
|
|
86
|
+
> {
|
|
87
|
+
initialFrame?: RemoteUiFrame | null;
|
|
88
|
+
attention?: SessionAttentionTrackerOptions;
|
|
89
|
+
dispatch(intent: SupercodeUiIntent): Promise<RemoteUiFrame | null | void>;
|
|
90
|
+
handleIntent?(intent: SupercodeUiIntent): boolean | Promise<boolean>;
|
|
91
|
+
onAcknowledge?(key: string): void | Promise<void>;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface RemoteUiBinding {
|
|
95
|
+
adapter: UiAdapter;
|
|
96
|
+
close(): void;
|
|
97
|
+
getFrame(): RemoteUiFrame | null;
|
|
98
|
+
getState(): SupercodeUiState | null;
|
|
99
|
+
receive(frame: unknown): boolean;
|
|
100
|
+
subscribe(listener: (state: SupercodeUiState, frame: RemoteUiFrame) => void): () => void;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function createRemoteUiBinding(options: RemoteUiBindingOptions): RemoteUiBinding;
|
package/host.mjs
ADDED
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
import { parseSupercodeUiIntent, sessionActivity } from './core.mjs';
|
|
2
|
+
import { dispatchControllerIntent, projectClientSnapshot } from './controller.mjs';
|
|
3
|
+
|
|
4
|
+
const FRAME_SCHEMA = 'supercode.ui-host-state.v1';
|
|
5
|
+
|
|
6
|
+
function objectRecord(value) {
|
|
7
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : null;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function defaultInstanceId() {
|
|
11
|
+
if (typeof globalThis.crypto?.randomUUID === 'function') return globalThis.crypto.randomUUID();
|
|
12
|
+
return `host-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Runtime guard for transport payloads received from a trusted Supercode host. */
|
|
16
|
+
export function assertRemoteUiFrame(value) {
|
|
17
|
+
const frame = objectRecord(value);
|
|
18
|
+
const state = objectRecord(frame?.state);
|
|
19
|
+
if (
|
|
20
|
+
frame?.schema !== FRAME_SCHEMA ||
|
|
21
|
+
typeof frame.hostInstanceId !== 'string' ||
|
|
22
|
+
!frame.hostInstanceId ||
|
|
23
|
+
!Number.isSafeInteger(frame.generation) ||
|
|
24
|
+
frame.generation < 1 ||
|
|
25
|
+
!Number.isSafeInteger(frame.sequence) ||
|
|
26
|
+
frame.sequence < 1 ||
|
|
27
|
+
!Number.isSafeInteger(frame.controllerRevision) ||
|
|
28
|
+
frame.controllerRevision < 0 ||
|
|
29
|
+
typeof frame.workspace !== 'string' ||
|
|
30
|
+
!state ||
|
|
31
|
+
!Array.isArray(state.sessions) ||
|
|
32
|
+
!Array.isArray(state.transcript) ||
|
|
33
|
+
!Array.isArray(frame.sessionIdentities) ||
|
|
34
|
+
!frame.sessionIdentities.every(
|
|
35
|
+
(item) =>
|
|
36
|
+
objectRecord(item) &&
|
|
37
|
+
typeof item.key === 'string' &&
|
|
38
|
+
typeof item.identity === 'string',
|
|
39
|
+
)
|
|
40
|
+
) {
|
|
41
|
+
throw new TypeError('Malformed Supercode remote UI frame.');
|
|
42
|
+
}
|
|
43
|
+
return value;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Trusted-host binding that projects one ordered, serializable UI frame and
|
|
47
|
+
* dispatches the same typed intents as an in-process messenger. */
|
|
48
|
+
export class RemoteControllerHost {
|
|
49
|
+
#bindingOptions;
|
|
50
|
+
#controller;
|
|
51
|
+
#frame;
|
|
52
|
+
#generation = 0;
|
|
53
|
+
#hostInstanceId;
|
|
54
|
+
#lastControllerRevision = -1;
|
|
55
|
+
#listeners = new Set();
|
|
56
|
+
#sequence = 0;
|
|
57
|
+
#unsubscribe;
|
|
58
|
+
#workspace = null;
|
|
59
|
+
|
|
60
|
+
constructor(controller, options = {}) {
|
|
61
|
+
if (
|
|
62
|
+
!controller ||
|
|
63
|
+
typeof controller.getSnapshot !== 'function' ||
|
|
64
|
+
typeof controller.subscribe !== 'function' ||
|
|
65
|
+
typeof controller.dispatch !== 'function'
|
|
66
|
+
) {
|
|
67
|
+
throw new TypeError('RemoteControllerHost requires a SupercodeController-compatible object.');
|
|
68
|
+
}
|
|
69
|
+
this.#controller = controller;
|
|
70
|
+
this.#hostInstanceId = options.hostInstanceId ?? defaultInstanceId();
|
|
71
|
+
if (typeof this.#hostInstanceId !== 'string' || !this.#hostInstanceId) {
|
|
72
|
+
throw new TypeError('RemoteControllerHost hostInstanceId must be a non-empty string.');
|
|
73
|
+
}
|
|
74
|
+
this.#bindingOptions = options;
|
|
75
|
+
this.#capture(true);
|
|
76
|
+
this.#unsubscribe = controller.subscribe(() => this.#capture(false));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
close() {
|
|
80
|
+
this.#unsubscribe?.();
|
|
81
|
+
this.#unsubscribe = null;
|
|
82
|
+
this.#listeners.clear();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
getFrame() {
|
|
86
|
+
return this.#frame;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
subscribe(listener) {
|
|
90
|
+
this.#listeners.add(listener);
|
|
91
|
+
listener(this.#frame);
|
|
92
|
+
return () => this.#listeners.delete(listener);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Reproject host-owned overlays such as machine-wide inventory or durable attention. */
|
|
96
|
+
refreshProjection() {
|
|
97
|
+
this.#capture(true);
|
|
98
|
+
return this.#frame;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async dispatch(value) {
|
|
102
|
+
const intent = parseSupercodeUiIntent(value);
|
|
103
|
+
if (!intent) throw new TypeError('Invalid Supercode UI intent.');
|
|
104
|
+
const sequence = this.#sequence;
|
|
105
|
+
await dispatchControllerIntent(this.#controller, intent, this.#bindingOptions);
|
|
106
|
+
if (this.#sequence === sequence) this.#capture(true);
|
|
107
|
+
return this.#frame;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async restore(identity, connection) {
|
|
111
|
+
if (typeof identity !== 'string' || !identity) {
|
|
112
|
+
throw new TypeError('RemoteControllerHost restore identity must be a non-empty string.');
|
|
113
|
+
}
|
|
114
|
+
if (connection !== 'observe' && connection !== 'attach') {
|
|
115
|
+
throw new TypeError('RemoteControllerHost restore connection must be observe or attach.');
|
|
116
|
+
}
|
|
117
|
+
const sequence = this.#sequence;
|
|
118
|
+
await this.#controller.dispatch({ type: 'restore', identity, connection });
|
|
119
|
+
if (this.#sequence === sequence) this.#capture(true);
|
|
120
|
+
return this.#frame;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
#capture(force) {
|
|
124
|
+
const snapshot = this.#controller.getSnapshot();
|
|
125
|
+
if (snapshot.workspace !== this.#workspace) {
|
|
126
|
+
this.#workspace = snapshot.workspace;
|
|
127
|
+
this.#generation += 1;
|
|
128
|
+
this.#lastControllerRevision = -1;
|
|
129
|
+
force = true;
|
|
130
|
+
}
|
|
131
|
+
if (!force && snapshot.revision === this.#lastControllerRevision) return;
|
|
132
|
+
this.#lastControllerRevision = snapshot.revision;
|
|
133
|
+
this.#sequence += 1;
|
|
134
|
+
const projection = this.#bindingOptions.projection?.(snapshot) ?? {};
|
|
135
|
+
this.#frame = Object.freeze({
|
|
136
|
+
schema: FRAME_SCHEMA,
|
|
137
|
+
hostInstanceId: this.#hostInstanceId,
|
|
138
|
+
generation: this.#generation,
|
|
139
|
+
sequence: this.#sequence,
|
|
140
|
+
controllerRevision: snapshot.revision,
|
|
141
|
+
workspace: snapshot.workspace,
|
|
142
|
+
state: projectClientSnapshot(snapshot, projection),
|
|
143
|
+
sessionIdentities: Object.freeze(
|
|
144
|
+
snapshot.sessions.map((session) =>
|
|
145
|
+
Object.freeze({ key: session.key, identity: session.identity }),
|
|
146
|
+
),
|
|
147
|
+
),
|
|
148
|
+
});
|
|
149
|
+
for (const listener of this.#listeners) listener(this.#frame);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function createRemoteControllerHost(controller, options) {
|
|
154
|
+
return new RemoteControllerHost(controller, options);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Monotonic browser-side store. Once a newer host process is accepted, frames
|
|
158
|
+
* from every retired process are rejected permanently. */
|
|
159
|
+
export class RemoteUiStore {
|
|
160
|
+
#frame = null;
|
|
161
|
+
#listeners = new Set();
|
|
162
|
+
#retiredHostInstances = new Set();
|
|
163
|
+
|
|
164
|
+
constructor(initialFrame) {
|
|
165
|
+
if (initialFrame !== undefined && initialFrame !== null) this.receive(initialFrame);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
getFrame() {
|
|
169
|
+
return this.#frame;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
getState() {
|
|
173
|
+
return this.#frame?.state ?? null;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
subscribe(listener) {
|
|
177
|
+
this.#listeners.add(listener);
|
|
178
|
+
if (this.#frame) listener(this.#frame.state, this.#frame);
|
|
179
|
+
return () => this.#listeners.delete(listener);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
receive(value) {
|
|
183
|
+
const next = assertRemoteUiFrame(value);
|
|
184
|
+
const current = this.#frame;
|
|
185
|
+
if (current) {
|
|
186
|
+
if (next.hostInstanceId !== current.hostInstanceId) {
|
|
187
|
+
if (this.#retiredHostInstances.has(next.hostInstanceId)) return false;
|
|
188
|
+
this.#retiredHostInstances.add(current.hostInstanceId);
|
|
189
|
+
} else if (
|
|
190
|
+
next.generation < current.generation ||
|
|
191
|
+
(next.generation === current.generation && next.sequence <= current.sequence)
|
|
192
|
+
) {
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
this.#frame = next;
|
|
197
|
+
for (const listener of this.#listeners) listener(next.state, next);
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function createRemoteUiStore(initialFrame) {
|
|
203
|
+
return new RemoteUiStore(initialFrame);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function attentionState(value) {
|
|
207
|
+
const parsed = objectRecord(value);
|
|
208
|
+
const sessions = objectRecord(parsed?.sessions);
|
|
209
|
+
return {
|
|
210
|
+
version: 1,
|
|
211
|
+
sessions: sessions
|
|
212
|
+
? Object.fromEntries(
|
|
213
|
+
Object.entries(sessions).flatMap(([identity, record]) => {
|
|
214
|
+
const item = objectRecord(record);
|
|
215
|
+
if (!item) return [];
|
|
216
|
+
return [[identity, {
|
|
217
|
+
observedAt: Number.isFinite(item.observedAt) ? item.observedAt : 0,
|
|
218
|
+
messages: Number.isFinite(item.messages) ? item.messages : null,
|
|
219
|
+
activity: typeof item.activity === 'string' ? item.activity : 'idle',
|
|
220
|
+
attention: ['unseen', 'finished', 'failed'].includes(item.attention)
|
|
221
|
+
? item.attention
|
|
222
|
+
: null,
|
|
223
|
+
}]];
|
|
224
|
+
}),
|
|
225
|
+
)
|
|
226
|
+
: {},
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function activeActivity(value) {
|
|
231
|
+
return value === 'running' || value === 'working' || value === 'needs-input';
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** Reference durable attention reducer. It baselines initial inventory without
|
|
235
|
+
* manufacturing unread state, then records only newer conversation or runtime evidence. */
|
|
236
|
+
export class SessionAttentionTracker {
|
|
237
|
+
#onChange;
|
|
238
|
+
#state;
|
|
239
|
+
|
|
240
|
+
constructor(options = {}) {
|
|
241
|
+
this.#state = attentionState(options.state);
|
|
242
|
+
this.#onChange = options.onChange;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
observe(frameValue) {
|
|
246
|
+
const frame = assertRemoteUiFrame(frameValue);
|
|
247
|
+
const identities = new Map(frame.sessionIdentities.map((item) => [item.key, item.identity]));
|
|
248
|
+
let changed = false;
|
|
249
|
+
for (const row of frame.state.sessions) {
|
|
250
|
+
const identity = identities.get(row.key);
|
|
251
|
+
if (!identity) continue;
|
|
252
|
+
const observedAt = row.previewUpdatedAt ?? row.updatedAt ?? 0;
|
|
253
|
+
const messages = Number.isFinite(row.messages) ? row.messages : null;
|
|
254
|
+
const activity = sessionActivity(frame.state, row);
|
|
255
|
+
const prior = this.#state.sessions[identity];
|
|
256
|
+
if (!prior) {
|
|
257
|
+
this.#state.sessions[identity] = {
|
|
258
|
+
observedAt,
|
|
259
|
+
messages,
|
|
260
|
+
activity,
|
|
261
|
+
attention: null,
|
|
262
|
+
};
|
|
263
|
+
changed = true;
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
const newerConversation =
|
|
267
|
+
observedAt > prior.observedAt ||
|
|
268
|
+
(messages !== null && prior.messages !== null && messages > prior.messages);
|
|
269
|
+
let attention = prior.attention;
|
|
270
|
+
if (newerConversation) attention = 'unseen';
|
|
271
|
+
if (activeActivity(prior.activity) && !activeActivity(activity)) {
|
|
272
|
+
attention = frame.state.error ? 'failed' : 'finished';
|
|
273
|
+
}
|
|
274
|
+
const next = {
|
|
275
|
+
observedAt: Math.max(prior.observedAt, observedAt),
|
|
276
|
+
messages:
|
|
277
|
+
messages === null
|
|
278
|
+
? prior.messages
|
|
279
|
+
: prior.messages === null
|
|
280
|
+
? messages
|
|
281
|
+
: Math.max(prior.messages, messages),
|
|
282
|
+
activity,
|
|
283
|
+
attention,
|
|
284
|
+
};
|
|
285
|
+
if (JSON.stringify(next) !== JSON.stringify(prior)) {
|
|
286
|
+
this.#state.sessions[identity] = next;
|
|
287
|
+
changed = true;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
if (changed) this.#emit();
|
|
291
|
+
return this.attention(frame);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
attention(frameValue) {
|
|
295
|
+
const frame = assertRemoteUiFrame(frameValue);
|
|
296
|
+
return frame.sessionIdentities.flatMap(({ key, identity }) => {
|
|
297
|
+
const attention = this.#state.sessions[identity]?.attention;
|
|
298
|
+
return attention ? [{ key, kind: attention }] : [];
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
acknowledge(frameValue, key) {
|
|
303
|
+
const frame = assertRemoteUiFrame(frameValue);
|
|
304
|
+
const identity = frame.sessionIdentities.find((item) => item.key === key)?.identity;
|
|
305
|
+
if (!identity || !this.#state.sessions[identity]?.attention) return false;
|
|
306
|
+
this.#state.sessions[identity].attention = null;
|
|
307
|
+
this.#emit();
|
|
308
|
+
return true;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
snapshot() {
|
|
312
|
+
return structuredClone(this.#state);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
#emit() {
|
|
316
|
+
this.#onChange?.(this.snapshot());
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export function createSessionAttentionTracker(options) {
|
|
321
|
+
return new SessionAttentionTracker(options);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/** Browser binding for any transport that can post one intent and return an
|
|
325
|
+
* optional authoritative frame. Local host actions may intercept an intent. */
|
|
326
|
+
export function createRemoteUiBinding(options) {
|
|
327
|
+
if (!options || typeof options.dispatch !== 'function') {
|
|
328
|
+
throw new TypeError('createRemoteUiBinding requires a dispatch function.');
|
|
329
|
+
}
|
|
330
|
+
const store = new RemoteUiStore(options.initialFrame);
|
|
331
|
+
const attention = options.attention
|
|
332
|
+
? new SessionAttentionTracker(options.attention)
|
|
333
|
+
: null;
|
|
334
|
+
let projectedState = null;
|
|
335
|
+
const listeners = new Set();
|
|
336
|
+
const project = (frame) => {
|
|
337
|
+
projectedState = attention
|
|
338
|
+
? { ...frame.state, attention: attention.observe(frame) }
|
|
339
|
+
: frame.state;
|
|
340
|
+
for (const listener of listeners) listener(projectedState, frame);
|
|
341
|
+
};
|
|
342
|
+
const unsubscribeStore = store.subscribe((_state, frame) => project(frame));
|
|
343
|
+
const adapter = {
|
|
344
|
+
async onIntent(value) {
|
|
345
|
+
const intent = parseSupercodeUiIntent(value);
|
|
346
|
+
if (!intent) throw new TypeError('Invalid Supercode UI intent.');
|
|
347
|
+
if (intent.action === 'ack' && attention) {
|
|
348
|
+
const frame = store.getFrame();
|
|
349
|
+
if (frame && attention.acknowledge(frame, intent.key)) project(frame);
|
|
350
|
+
await options.onAcknowledge?.(intent.key);
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
if (await options.handleIntent?.(intent)) return;
|
|
354
|
+
const frame = await options.dispatch(intent);
|
|
355
|
+
if (frame) store.receive(frame);
|
|
356
|
+
},
|
|
357
|
+
...(options.confirmIntent ? { confirmIntent: options.confirmIntent } : {}),
|
|
358
|
+
...(options.pickContext ? { pickContext: options.pickContext } : {}),
|
|
359
|
+
...(options.resolveImage ? { resolveImage: options.resolveImage } : {}),
|
|
360
|
+
...(options.openKeyboardShortcuts
|
|
361
|
+
? { openKeyboardShortcuts: options.openKeyboardShortcuts }
|
|
362
|
+
: {}),
|
|
363
|
+
...(options.onClose ? { onClose: options.onClose } : {}),
|
|
364
|
+
...(options.onOpen ? { onOpen: options.onOpen } : {}),
|
|
365
|
+
...(options.copyText ? { copyText: options.copyText } : {}),
|
|
366
|
+
...(options.now ? { now: options.now } : {}),
|
|
367
|
+
};
|
|
368
|
+
return {
|
|
369
|
+
adapter,
|
|
370
|
+
getFrame: () => store.getFrame(),
|
|
371
|
+
getState: () => projectedState ?? store.getState(),
|
|
372
|
+
receive: (frame) => store.receive(frame),
|
|
373
|
+
subscribe(listener) {
|
|
374
|
+
listeners.add(listener);
|
|
375
|
+
const frame = store.getFrame();
|
|
376
|
+
if (frame && projectedState) listener(projectedState, frame);
|
|
377
|
+
return () => listeners.delete(listener);
|
|
378
|
+
},
|
|
379
|
+
close() {
|
|
380
|
+
unsubscribeStore();
|
|
381
|
+
listeners.clear();
|
|
382
|
+
},
|
|
383
|
+
};
|
|
384
|
+
}
|
package/messenger.mjs
CHANGED
|
@@ -2259,31 +2259,32 @@ function SessionRow({ row, state, onOpen, onOpenSubagents, now = Date.now() }) {
|
|
|
2259
2259
|
/* @__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: [
|
|
2260
2260
|
/* @__PURE__ */ jsx7(HarnessLogo, { id: row.harness, activity, size: 34 }),
|
|
2261
2261
|
/* @__PURE__ */ jsxs6("span", { className: "scui-session-copy", children: [
|
|
2262
|
-
/* @__PURE__ */
|
|
2263
|
-
/* @__PURE__ */ jsx7("strong", { children: title }),
|
|
2264
|
-
/* @__PURE__ */ jsx7("span", { className: "scui-session-meta", children: age ? /* @__PURE__ */ jsx7("time", { children: age }) : null })
|
|
2265
|
-
] }),
|
|
2262
|
+
/* @__PURE__ */ jsx7("span", { className: "scui-session-title", children: /* @__PURE__ */ jsx7("strong", { children: title }) }),
|
|
2266
2263
|
path.complete ? /* @__PURE__ */ jsxs6("small", { className: "scui-session-path", title: row.cwd, children: [
|
|
2267
2264
|
/* @__PURE__ */ jsx7("span", { className: "scui-session-path-leading", children: path.leading }),
|
|
2268
2265
|
path.separator ? /* @__PURE__ */ jsx7("span", { className: "scui-session-path-separator", children: path.separator }) : null,
|
|
2269
2266
|
path.trailing ? /* @__PURE__ */ jsx7("span", { className: "scui-session-path-trailing", children: path.trailing }) : null
|
|
2270
2267
|
] }) : null,
|
|
2271
|
-
working || preview
|
|
2268
|
+
working || preview ? /* @__PURE__ */ jsxs6("span", { className: "scui-session-preview", children: [
|
|
2272
2269
|
working ? /* @__PURE__ */ jsxs6("span", { className: "scui-session-working", "aria-hidden": "true", children: [
|
|
2273
2270
|
/* @__PURE__ */ jsx7("i", {}),
|
|
2274
2271
|
/* @__PURE__ */ jsx7("i", {}),
|
|
2275
2272
|
/* @__PURE__ */ jsx7("i", {})
|
|
2276
2273
|
] }) : null,
|
|
2277
|
-
preview || working ? /* @__PURE__ */ jsx7("small", { children: preview || "Working\u2026" }) : null
|
|
2278
|
-
unreadCount ? /* @__PURE__ */ jsx7("b", { "aria-label": `${unreadCount} unread messages`, children: unreadLabel }) : null
|
|
2274
|
+
preview || working ? /* @__PURE__ */ jsx7("small", { children: preview || "Working\u2026" }) : null
|
|
2279
2275
|
] }) : null,
|
|
2280
2276
|
state.attachError?.key === row.key ? /* @__PURE__ */ jsx7("em", { children: state.attachError.message }) : null
|
|
2281
2277
|
] })
|
|
2282
2278
|
] }),
|
|
2283
|
-
row.subagentCount ? /* @__PURE__ */ jsxs6("
|
|
2284
|
-
/* @__PURE__ */
|
|
2285
|
-
|
|
2286
|
-
|
|
2279
|
+
row.subagentCount || age || unreadCount ? /* @__PURE__ */ jsxs6("span", { className: "scui-session-rail", children: [
|
|
2280
|
+
/* @__PURE__ */ jsxs6("span", { className: "scui-session-rail-meta", children: [
|
|
2281
|
+
row.subagentCount ? /* @__PURE__ */ jsxs6("button", { className: "scui-session-agents", type: "button", title: `${row.subagentCount} agent sessions`, "aria-label": `Open ${row.subagentCount} agent sessions in ${title}`, onClick: () => onOpenSubagents?.(row), children: [
|
|
2282
|
+
/* @__PURE__ */ jsx7(UiIcon, { name: "agents", size: 14 }),
|
|
2283
|
+
/* @__PURE__ */ jsx7("span", { children: row.subagentCount })
|
|
2284
|
+
] }) : null,
|
|
2285
|
+
age ? /* @__PURE__ */ jsx7("time", { children: age }) : null
|
|
2286
|
+
] }),
|
|
2287
|
+
unreadCount ? /* @__PURE__ */ jsx7("b", { "aria-label": `${unreadCount} unread messages`, children: unreadLabel }) : null
|
|
2287
2288
|
] }) : null
|
|
2288
2289
|
] });
|
|
2289
2290
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter-ai-dev/supercode-ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.59",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Composable default UI kit for Supercode-powered coding-agent experiences",
|
|
6
6
|
"exports": {
|
|
@@ -16,6 +16,10 @@
|
|
|
16
16
|
"types": "./controller.d.ts",
|
|
17
17
|
"import": "./controller.mjs"
|
|
18
18
|
},
|
|
19
|
+
"./host": {
|
|
20
|
+
"types": "./host.d.ts",
|
|
21
|
+
"import": "./host.mjs"
|
|
22
|
+
},
|
|
19
23
|
"./preact": {
|
|
20
24
|
"types": "./components.d.ts",
|
|
21
25
|
"import": "./components.mjs"
|
|
@@ -108,6 +112,8 @@
|
|
|
108
112
|
"core.d.ts",
|
|
109
113
|
"controller.mjs",
|
|
110
114
|
"controller.d.ts",
|
|
115
|
+
"host.mjs",
|
|
116
|
+
"host.d.ts",
|
|
111
117
|
"components.mjs",
|
|
112
118
|
"components.d.ts",
|
|
113
119
|
"activity.mjs",
|
package/react/components.mjs
CHANGED
|
@@ -2345,31 +2345,32 @@ function SessionRow({ row, state, onOpen, onOpenSubagents, now = Date.now() }) {
|
|
|
2345
2345
|
/* @__PURE__ */ jsxs7("button", { className: "scui-session-main", "data-session-key": row.key, type: "button", "aria-label": `${title} \xB7 ${harnessDisplayName(row.harness)}${row.writable ? "" : " \xB7 Read-only"}${working ? " \xB7 Working" : ""}${path.complete ? ` \xB7 ${path.complete}` : ""}${preview ? ` \xB7 ${preview}` : ""}${unreadCount ? ` \xB7 ${unreadCount} unread` : ""}${age ? ` \xB7 ${age}` : ""}`, "aria-current": row.active ? "true" : void 0, onClick: () => onOpen(row), children: [
|
|
2346
2346
|
/* @__PURE__ */ jsx8(HarnessLogo, { id: row.harness, activity, size: 34 }),
|
|
2347
2347
|
/* @__PURE__ */ jsxs7("span", { className: "scui-session-copy", children: [
|
|
2348
|
-
/* @__PURE__ */
|
|
2349
|
-
/* @__PURE__ */ jsx8("strong", { children: title }),
|
|
2350
|
-
/* @__PURE__ */ jsx8("span", { className: "scui-session-meta", children: age ? /* @__PURE__ */ jsx8("time", { children: age }) : null })
|
|
2351
|
-
] }),
|
|
2348
|
+
/* @__PURE__ */ jsx8("span", { className: "scui-session-title", children: /* @__PURE__ */ jsx8("strong", { children: title }) }),
|
|
2352
2349
|
path.complete ? /* @__PURE__ */ jsxs7("small", { className: "scui-session-path", title: row.cwd, children: [
|
|
2353
2350
|
/* @__PURE__ */ jsx8("span", { className: "scui-session-path-leading", children: path.leading }),
|
|
2354
2351
|
path.separator ? /* @__PURE__ */ jsx8("span", { className: "scui-session-path-separator", children: path.separator }) : null,
|
|
2355
2352
|
path.trailing ? /* @__PURE__ */ jsx8("span", { className: "scui-session-path-trailing", children: path.trailing }) : null
|
|
2356
2353
|
] }) : null,
|
|
2357
|
-
working || preview
|
|
2354
|
+
working || preview ? /* @__PURE__ */ jsxs7("span", { className: "scui-session-preview", children: [
|
|
2358
2355
|
working ? /* @__PURE__ */ jsxs7("span", { className: "scui-session-working", "aria-hidden": "true", children: [
|
|
2359
2356
|
/* @__PURE__ */ jsx8("i", {}),
|
|
2360
2357
|
/* @__PURE__ */ jsx8("i", {}),
|
|
2361
2358
|
/* @__PURE__ */ jsx8("i", {})
|
|
2362
2359
|
] }) : null,
|
|
2363
|
-
preview || working ? /* @__PURE__ */ jsx8("small", { children: preview || "Working\u2026" }) : null
|
|
2364
|
-
unreadCount ? /* @__PURE__ */ jsx8("b", { "aria-label": `${unreadCount} unread messages`, children: unreadLabel }) : null
|
|
2360
|
+
preview || working ? /* @__PURE__ */ jsx8("small", { children: preview || "Working\u2026" }) : null
|
|
2365
2361
|
] }) : null,
|
|
2366
2362
|
state.attachError?.key === row.key ? /* @__PURE__ */ jsx8("em", { children: state.attachError.message }) : null
|
|
2367
2363
|
] })
|
|
2368
2364
|
] }),
|
|
2369
|
-
row.subagentCount ? /* @__PURE__ */ jsxs7("
|
|
2370
|
-
/* @__PURE__ */
|
|
2371
|
-
|
|
2372
|
-
|
|
2365
|
+
row.subagentCount || age || unreadCount ? /* @__PURE__ */ jsxs7("span", { className: "scui-session-rail", children: [
|
|
2366
|
+
/* @__PURE__ */ jsxs7("span", { className: "scui-session-rail-meta", children: [
|
|
2367
|
+
row.subagentCount ? /* @__PURE__ */ jsxs7("button", { className: "scui-session-agents", type: "button", title: `${row.subagentCount} agent sessions`, "aria-label": `Open ${row.subagentCount} agent sessions in ${title}`, onClick: () => onOpenSubagents?.(row), children: [
|
|
2368
|
+
/* @__PURE__ */ jsx8(UiIcon, { name: "agents", size: 14 }),
|
|
2369
|
+
/* @__PURE__ */ jsx8("span", { children: row.subagentCount })
|
|
2370
|
+
] }) : null,
|
|
2371
|
+
age ? /* @__PURE__ */ jsx8("time", { children: age }) : null
|
|
2372
|
+
] }),
|
|
2373
|
+
unreadCount ? /* @__PURE__ */ jsx8("b", { "aria-label": `${unreadCount} unread messages`, children: unreadLabel }) : null
|
|
2373
2374
|
] }) : null
|
|
2374
2375
|
] });
|
|
2375
2376
|
}
|
package/react/messenger.mjs
CHANGED
|
@@ -2259,31 +2259,32 @@ function SessionRow({ row, state, onOpen, onOpenSubagents, now = Date.now() }) {
|
|
|
2259
2259
|
/* @__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: [
|
|
2260
2260
|
/* @__PURE__ */ jsx7(HarnessLogo, { id: row.harness, activity, size: 34 }),
|
|
2261
2261
|
/* @__PURE__ */ jsxs6("span", { className: "scui-session-copy", children: [
|
|
2262
|
-
/* @__PURE__ */
|
|
2263
|
-
/* @__PURE__ */ jsx7("strong", { children: title }),
|
|
2264
|
-
/* @__PURE__ */ jsx7("span", { className: "scui-session-meta", children: age ? /* @__PURE__ */ jsx7("time", { children: age }) : null })
|
|
2265
|
-
] }),
|
|
2262
|
+
/* @__PURE__ */ jsx7("span", { className: "scui-session-title", children: /* @__PURE__ */ jsx7("strong", { children: title }) }),
|
|
2266
2263
|
path.complete ? /* @__PURE__ */ jsxs6("small", { className: "scui-session-path", title: row.cwd, children: [
|
|
2267
2264
|
/* @__PURE__ */ jsx7("span", { className: "scui-session-path-leading", children: path.leading }),
|
|
2268
2265
|
path.separator ? /* @__PURE__ */ jsx7("span", { className: "scui-session-path-separator", children: path.separator }) : null,
|
|
2269
2266
|
path.trailing ? /* @__PURE__ */ jsx7("span", { className: "scui-session-path-trailing", children: path.trailing }) : null
|
|
2270
2267
|
] }) : null,
|
|
2271
|
-
working || preview
|
|
2268
|
+
working || preview ? /* @__PURE__ */ jsxs6("span", { className: "scui-session-preview", children: [
|
|
2272
2269
|
working ? /* @__PURE__ */ jsxs6("span", { className: "scui-session-working", "aria-hidden": "true", children: [
|
|
2273
2270
|
/* @__PURE__ */ jsx7("i", {}),
|
|
2274
2271
|
/* @__PURE__ */ jsx7("i", {}),
|
|
2275
2272
|
/* @__PURE__ */ jsx7("i", {})
|
|
2276
2273
|
] }) : null,
|
|
2277
|
-
preview || working ? /* @__PURE__ */ jsx7("small", { children: preview || "Working\u2026" }) : null
|
|
2278
|
-
unreadCount ? /* @__PURE__ */ jsx7("b", { "aria-label": `${unreadCount} unread messages`, children: unreadLabel }) : null
|
|
2274
|
+
preview || working ? /* @__PURE__ */ jsx7("small", { children: preview || "Working\u2026" }) : null
|
|
2279
2275
|
] }) : null,
|
|
2280
2276
|
state.attachError?.key === row.key ? /* @__PURE__ */ jsx7("em", { children: state.attachError.message }) : null
|
|
2281
2277
|
] })
|
|
2282
2278
|
] }),
|
|
2283
|
-
row.subagentCount ? /* @__PURE__ */ jsxs6("
|
|
2284
|
-
/* @__PURE__ */
|
|
2285
|
-
|
|
2286
|
-
|
|
2279
|
+
row.subagentCount || age || unreadCount ? /* @__PURE__ */ jsxs6("span", { className: "scui-session-rail", children: [
|
|
2280
|
+
/* @__PURE__ */ jsxs6("span", { className: "scui-session-rail-meta", children: [
|
|
2281
|
+
row.subagentCount ? /* @__PURE__ */ jsxs6("button", { className: "scui-session-agents", type: "button", title: `${row.subagentCount} agent sessions`, "aria-label": `Open ${row.subagentCount} agent sessions in ${title}`, onClick: () => onOpenSubagents?.(row), children: [
|
|
2282
|
+
/* @__PURE__ */ jsx7(UiIcon, { name: "agents", size: 14 }),
|
|
2283
|
+
/* @__PURE__ */ jsx7("span", { children: row.subagentCount })
|
|
2284
|
+
] }) : null,
|
|
2285
|
+
age ? /* @__PURE__ */ jsx7("time", { children: age }) : null
|
|
2286
|
+
] }),
|
|
2287
|
+
unreadCount ? /* @__PURE__ */ jsx7("b", { "aria-label": `${unreadCount} unread messages`, children: unreadLabel }) : null
|
|
2287
2288
|
] }) : null
|
|
2288
2289
|
] });
|
|
2289
2290
|
}
|
package/react/sessions.mjs
CHANGED
|
@@ -351,31 +351,32 @@ function SessionRow({ row, state, onOpen, onOpenSubagents, now = Date.now() }) {
|
|
|
351
351
|
/* @__PURE__ */ jsxs5("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: [
|
|
352
352
|
/* @__PURE__ */ jsx6(HarnessLogo, { id: row.harness, activity, size: 34 }),
|
|
353
353
|
/* @__PURE__ */ jsxs5("span", { className: "scui-session-copy", children: [
|
|
354
|
-
/* @__PURE__ */
|
|
355
|
-
/* @__PURE__ */ jsx6("strong", { children: title }),
|
|
356
|
-
/* @__PURE__ */ jsx6("span", { className: "scui-session-meta", children: age ? /* @__PURE__ */ jsx6("time", { children: age }) : null })
|
|
357
|
-
] }),
|
|
354
|
+
/* @__PURE__ */ jsx6("span", { className: "scui-session-title", children: /* @__PURE__ */ jsx6("strong", { children: title }) }),
|
|
358
355
|
path.complete ? /* @__PURE__ */ jsxs5("small", { className: "scui-session-path", title: row.cwd, children: [
|
|
359
356
|
/* @__PURE__ */ jsx6("span", { className: "scui-session-path-leading", children: path.leading }),
|
|
360
357
|
path.separator ? /* @__PURE__ */ jsx6("span", { className: "scui-session-path-separator", children: path.separator }) : null,
|
|
361
358
|
path.trailing ? /* @__PURE__ */ jsx6("span", { className: "scui-session-path-trailing", children: path.trailing }) : null
|
|
362
359
|
] }) : null,
|
|
363
|
-
working || preview
|
|
360
|
+
working || preview ? /* @__PURE__ */ jsxs5("span", { className: "scui-session-preview", children: [
|
|
364
361
|
working ? /* @__PURE__ */ jsxs5("span", { className: "scui-session-working", "aria-hidden": "true", children: [
|
|
365
362
|
/* @__PURE__ */ jsx6("i", {}),
|
|
366
363
|
/* @__PURE__ */ jsx6("i", {}),
|
|
367
364
|
/* @__PURE__ */ jsx6("i", {})
|
|
368
365
|
] }) : null,
|
|
369
|
-
preview || working ? /* @__PURE__ */ jsx6("small", { children: preview || "Working\u2026" }) : null
|
|
370
|
-
unreadCount ? /* @__PURE__ */ jsx6("b", { "aria-label": `${unreadCount} unread messages`, children: unreadLabel }) : null
|
|
366
|
+
preview || working ? /* @__PURE__ */ jsx6("small", { children: preview || "Working\u2026" }) : null
|
|
371
367
|
] }) : null,
|
|
372
368
|
state.attachError?.key === row.key ? /* @__PURE__ */ jsx6("em", { children: state.attachError.message }) : null
|
|
373
369
|
] })
|
|
374
370
|
] }),
|
|
375
|
-
row.subagentCount ? /* @__PURE__ */ jsxs5("
|
|
376
|
-
/* @__PURE__ */
|
|
377
|
-
|
|
378
|
-
|
|
371
|
+
row.subagentCount || age || unreadCount ? /* @__PURE__ */ jsxs5("span", { className: "scui-session-rail", children: [
|
|
372
|
+
/* @__PURE__ */ jsxs5("span", { className: "scui-session-rail-meta", children: [
|
|
373
|
+
row.subagentCount ? /* @__PURE__ */ jsxs5("button", { className: "scui-session-agents", type: "button", title: `${row.subagentCount} agent sessions`, "aria-label": `Open ${row.subagentCount} agent sessions in ${title}`, onClick: () => onOpenSubagents?.(row), children: [
|
|
374
|
+
/* @__PURE__ */ jsx6(UiIcon, { name: "agents", size: 14 }),
|
|
375
|
+
/* @__PURE__ */ jsx6("span", { children: row.subagentCount })
|
|
376
|
+
] }) : null,
|
|
377
|
+
age ? /* @__PURE__ */ jsx6("time", { children: age }) : null
|
|
378
|
+
] }),
|
|
379
|
+
unreadCount ? /* @__PURE__ */ jsx6("b", { "aria-label": `${unreadCount} unread messages`, children: unreadLabel }) : null
|
|
379
380
|
] }) : null
|
|
380
381
|
] });
|
|
381
382
|
}
|
package/sessions.mjs
CHANGED
|
@@ -351,31 +351,32 @@ function SessionRow({ row, state, onOpen, onOpenSubagents, now = Date.now() }) {
|
|
|
351
351
|
/* @__PURE__ */ jsxs5("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: [
|
|
352
352
|
/* @__PURE__ */ jsx6(HarnessLogo, { id: row.harness, activity, size: 34 }),
|
|
353
353
|
/* @__PURE__ */ jsxs5("span", { className: "scui-session-copy", children: [
|
|
354
|
-
/* @__PURE__ */
|
|
355
|
-
/* @__PURE__ */ jsx6("strong", { children: title }),
|
|
356
|
-
/* @__PURE__ */ jsx6("span", { className: "scui-session-meta", children: age ? /* @__PURE__ */ jsx6("time", { children: age }) : null })
|
|
357
|
-
] }),
|
|
354
|
+
/* @__PURE__ */ jsx6("span", { className: "scui-session-title", children: /* @__PURE__ */ jsx6("strong", { children: title }) }),
|
|
358
355
|
path.complete ? /* @__PURE__ */ jsxs5("small", { className: "scui-session-path", title: row.cwd, children: [
|
|
359
356
|
/* @__PURE__ */ jsx6("span", { className: "scui-session-path-leading", children: path.leading }),
|
|
360
357
|
path.separator ? /* @__PURE__ */ jsx6("span", { className: "scui-session-path-separator", children: path.separator }) : null,
|
|
361
358
|
path.trailing ? /* @__PURE__ */ jsx6("span", { className: "scui-session-path-trailing", children: path.trailing }) : null
|
|
362
359
|
] }) : null,
|
|
363
|
-
working || preview
|
|
360
|
+
working || preview ? /* @__PURE__ */ jsxs5("span", { className: "scui-session-preview", children: [
|
|
364
361
|
working ? /* @__PURE__ */ jsxs5("span", { className: "scui-session-working", "aria-hidden": "true", children: [
|
|
365
362
|
/* @__PURE__ */ jsx6("i", {}),
|
|
366
363
|
/* @__PURE__ */ jsx6("i", {}),
|
|
367
364
|
/* @__PURE__ */ jsx6("i", {})
|
|
368
365
|
] }) : null,
|
|
369
|
-
preview || working ? /* @__PURE__ */ jsx6("small", { children: preview || "Working\u2026" }) : null
|
|
370
|
-
unreadCount ? /* @__PURE__ */ jsx6("b", { "aria-label": `${unreadCount} unread messages`, children: unreadLabel }) : null
|
|
366
|
+
preview || working ? /* @__PURE__ */ jsx6("small", { children: preview || "Working\u2026" }) : null
|
|
371
367
|
] }) : null,
|
|
372
368
|
state.attachError?.key === row.key ? /* @__PURE__ */ jsx6("em", { children: state.attachError.message }) : null
|
|
373
369
|
] })
|
|
374
370
|
] }),
|
|
375
|
-
row.subagentCount ? /* @__PURE__ */ jsxs5("
|
|
376
|
-
/* @__PURE__ */
|
|
377
|
-
|
|
378
|
-
|
|
371
|
+
row.subagentCount || age || unreadCount ? /* @__PURE__ */ jsxs5("span", { className: "scui-session-rail", children: [
|
|
372
|
+
/* @__PURE__ */ jsxs5("span", { className: "scui-session-rail-meta", children: [
|
|
373
|
+
row.subagentCount ? /* @__PURE__ */ jsxs5("button", { className: "scui-session-agents", type: "button", title: `${row.subagentCount} agent sessions`, "aria-label": `Open ${row.subagentCount} agent sessions in ${title}`, onClick: () => onOpenSubagents?.(row), children: [
|
|
374
|
+
/* @__PURE__ */ jsx6(UiIcon, { name: "agents", size: 14 }),
|
|
375
|
+
/* @__PURE__ */ jsx6("span", { children: row.subagentCount })
|
|
376
|
+
] }) : null,
|
|
377
|
+
age ? /* @__PURE__ */ jsx6("time", { children: age }) : null
|
|
378
|
+
] }),
|
|
379
|
+
unreadCount ? /* @__PURE__ */ jsx6("b", { "aria-label": `${unreadCount} unread messages`, children: unreadLabel }) : null
|
|
379
380
|
] }) : null
|
|
380
381
|
] });
|
|
381
382
|
}
|
package/styles.css
CHANGED
|
@@ -102,18 +102,18 @@
|
|
|
102
102
|
.scui-search { display:flex; flex:1; min-width:0; align-items:center; gap:7px; padding:6px 8px; border:1px solid var(--scui-border); border-radius:8px; background:var(--scui-bg-raised); color:var(--scui-muted) }
|
|
103
103
|
.scui-search input { flex:1; min-width:0; border:0; outline:0; background:transparent; color:var(--scui-fg) }
|
|
104
104
|
.scui-session-rows { flex:1; min-height:0; overflow:auto; padding:0 6px 8px }
|
|
105
|
-
.scui-session { display:
|
|
105
|
+
.scui-session { display:grid; grid-template-columns:minmax(0,1fr) auto; width:100%; align-items:stretch; border-radius:7px; background:transparent }
|
|
106
106
|
.scui-session:hover,.scui-session[data-active="true"] { background:var(--scui-fill) }
|
|
107
|
-
.scui-session-main { display:flex; min-width:0;
|
|
108
|
-
.scui-session-
|
|
107
|
+
.scui-session-main { display:flex; min-width:0; align-items:flex-start; gap:9px; padding:8px; border:0; background:transparent; text-align:left; cursor:pointer }
|
|
108
|
+
.scui-session-rail { display:grid; min-width:26px; align-content:space-between; justify-items:end; gap:4px; padding:8px 8px 8px 0 }.scui-session-rail-meta { display:flex; align-items:center; justify-content:flex-end; gap:4px }.scui-session-rail time { color:var(--scui-muted); font-size:10px; white-space:nowrap }.scui-session-rail > b { display:grid; min-width:16px; height:16px; padding:0 4px; place-items:center; border-radius:8px; background:var(--scui-accent); color:var(--scui-bg); font-size:9px; line-height:16px }
|
|
109
|
+
.scui-session-agents { display:flex; flex:none; align-items:center; gap:2px; padding:2px 4px; border:0; border-radius:6px; background:transparent; color:var(--scui-muted); cursor:pointer; font-size:9px; font-variant-numeric:tabular-nums; white-space:nowrap }.scui-session-agents:hover { background:var(--scui-bg-raised); color:var(--scui-fg) }
|
|
109
110
|
.scui-session-copy { display:grid; flex:1; min-width:0; gap:1px }
|
|
110
111
|
.scui-session-copy > span { display:flex; align-items:baseline; gap:7px; min-width:0 }
|
|
111
112
|
.scui-session-copy strong,.scui-session-copy span > small { overflow:hidden; text-overflow:ellipsis; white-space:nowrap }
|
|
112
|
-
.scui-session-copy strong { flex:1; font-size:12.5px }.scui-session-
|
|
113
|
+
.scui-session-copy strong { flex:1; font-size:12.5px }.scui-session-working { display:flex; flex:none; width:14px; height:8px; align-self:center; align-items:center; justify-content:space-between; color:var(--scui-muted) }.scui-session-working > i { width:3px; height:3px; border-radius:50%; background:currentColor; animation:scui-session-dots 1.2s ease-in-out infinite }.scui-session-working > i:nth-child(2) { animation-delay:.15s }.scui-session-working > i:nth-child(3) { animation-delay:.3s }
|
|
113
114
|
.scui-session-path { display:flex; min-width:0; overflow:hidden; color:var(--scui-muted); font-size:9.5px; white-space:nowrap }
|
|
114
115
|
.scui-session-path-leading { flex:0 1 auto; overflow:hidden; text-overflow:ellipsis }.scui-session-path-separator { flex:none }.scui-session-path-trailing { flex:none; max-width:58%; overflow:hidden; direction:rtl; text-align:left; text-overflow:ellipsis; unicode-bidi:plaintext }
|
|
115
|
-
.scui-session-preview small { flex:1; min-width:0; color:var(--scui-muted); font-size:10.5px }.scui-session-
|
|
116
|
-
.scui-session[data-writable="false"] .scui-session-preview b { background:var(--scui-muted) }
|
|
116
|
+
.scui-session-preview small { flex:1; min-width:0; color:var(--scui-muted); font-size:10.5px }.scui-session[data-writable="false"] .scui-session-rail > b { background:var(--scui-muted) }
|
|
117
117
|
.scui-session-copy em { overflow:hidden; color:var(--scui-danger); font-size:10px; font-style:normal; text-overflow:ellipsis; white-space:nowrap }
|
|
118
118
|
.scui-load { display:block; margin:7px auto; padding:5px 10px; border:1px solid var(--scui-border); border-radius:8px; background:var(--scui-bg-raised); color:var(--scui-muted); cursor:pointer }.scui-load:disabled { opacity:.65; cursor:default }
|
|
119
119
|
|