@volter-ai-dev/supercode-ui 0.1.58 → 0.1.60

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 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 attention,
189
- drafts, and artifact materialization remain explicit callbacks. A
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/controller.d.ts CHANGED
@@ -121,6 +121,9 @@ export interface ControllerBindingOptions {
121
121
  onAcknowledge?: (key: string) => void | Promise<void>;
122
122
  onLoadSessions?: () => void | Promise<void>;
123
123
  onLoadEarlier?: () => void | Promise<void>;
124
+ onOpenSubagents?: (key: string) => void | Promise<void>;
125
+ onOpenSubagent?: (parentKey: string, key: string) => void | Promise<void>;
126
+ onCloseSubagents?: () => void | Promise<void>;
124
127
  onStartTerminal?: (intent: Extract<SupercodeUiIntent, { action: 'new' }>) => void | Promise<void>;
125
128
  onResumeTerminal?: (intent: Extract<SupercodeUiIntent, { action: 'resume' }>) => void | Promise<void>;
126
129
  copyText?: UiAdapter['copyText'];
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,14 +509,19 @@ 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
- age: relativeAge(session.updatedAt, now),
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,
517
519
  writable: session.key === snapshot.activeSessionKey && actions.send === true,
518
520
  live: typeof session.updatedAt === 'number' && now - session.updatedAt <= liveWindowMs,
519
521
  runtimeStatus: session.liveStatus ?? null,
522
+ ...(Number.isSafeInteger(session.childSessionCount) && session.childSessionCount > 0
523
+ ? { subagentCount: session.childSessionCount }
524
+ : {}),
520
525
  }));
521
526
  const sessions = options.sessions ?? snapshotSessions;
522
527
  const active = options.attached === undefined ? attached(snapshot) : options.attached;
@@ -677,7 +682,16 @@ export async function dispatchControllerIntent(controller, intent, options = {})
677
682
  if (intent.action === 'draft') return options.onDraft?.(intent.text);
678
683
  if (intent.action === 'ack') return options.onAcknowledge?.(intent.key);
679
684
  if (intent.action === 'loadSessions') return options.onLoadSessions?.();
680
- if (intent.action === 'loadEarlier') return options.onLoadEarlier?.();
685
+ if (intent.action === 'loadEarlier') {
686
+ return options.onLoadEarlier
687
+ ? options.onLoadEarlier()
688
+ : controller.dispatch({ type: 'loadEarlier' });
689
+ }
690
+ if (intent.action === 'openSubagents') return options.onOpenSubagents?.(intent.key);
691
+ if (intent.action === 'openSubagent') {
692
+ return options.onOpenSubagent?.(intent.parentKey, intent.key);
693
+ }
694
+ if (intent.action === 'closeSubagents') return options.onCloseSubagents?.();
681
695
  return options.onUnsupported?.(intent);
682
696
  }
683
697
 
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter-ai-dev/supercode-ui",
3
- "version": "0.1.58",
3
+ "version": "0.1.60",
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",