@volter-ai-dev/supercode-ui 0.1.65 → 0.1.66
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 +5 -0
- package/host.d.ts +29 -0
- package/host.mjs +128 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -243,6 +243,11 @@ heartbeat churn, treats compaction as a new baseline, and returns the delay for
|
|
|
243
243
|
settlement timer. This keeps unread and finished semantics identical in an editor, extension,
|
|
244
244
|
desktop app, or mobile companion without moving file persistence into the UI package.
|
|
245
245
|
|
|
246
|
+
`createNativeMessengerState` wraps that ledger with bounded per-conversation drafts and per-harness
|
|
247
|
+
Terminal/Headless preferences. It emits one serializable snapshot, so native hosts do not need to
|
|
248
|
+
duplicate state parsing or coordinate several independent maps. The host still owns where that
|
|
249
|
+
snapshot is stored, its debounce and encryption policy, and any multi-device synchronization.
|
|
250
|
+
|
|
246
251
|
Native continuation exposes one action and a quiet execution-transport selector. A host with a real
|
|
247
252
|
terminal provider can add `terminal` to `continuationModes` and handle `onResumeTerminal`; Terminal
|
|
248
253
|
is then the initial choice and Headless remains available from the selector. The UI never infers
|
package/host.d.ts
CHANGED
|
@@ -109,6 +109,35 @@ export function createNativeSessionAttentionTracker(options?: {
|
|
|
109
109
|
onChange?(state: NativeSessionAttentionTrackerState): void;
|
|
110
110
|
}): NativeSessionAttentionTracker;
|
|
111
111
|
|
|
112
|
+
export interface NativeMessengerStateSnapshot extends NativeSessionAttentionTrackerState {
|
|
113
|
+
drafts: Record<string, string>;
|
|
114
|
+
preferredLaunchModes: Record<string, 'headless' | 'terminal'>;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function normalizeNativeMessengerState(value: unknown): NativeMessengerStateSnapshot;
|
|
118
|
+
|
|
119
|
+
export class NativeMessengerState {
|
|
120
|
+
constructor(options?: {
|
|
121
|
+
state?: unknown;
|
|
122
|
+
onChange?(state: NativeMessengerStateSnapshot): void;
|
|
123
|
+
});
|
|
124
|
+
acknowledge(key: string): boolean;
|
|
125
|
+
observeAttention(options: NativeSessionAttentionObservation): {
|
|
126
|
+
attention: import('./index.js').SessionAttention[];
|
|
127
|
+
settleAfterMs: number | null;
|
|
128
|
+
};
|
|
129
|
+
draft(key: string): string;
|
|
130
|
+
setDraft(key: string, draft: string): boolean;
|
|
131
|
+
preferredLaunchMode(harness: string): 'headless' | 'terminal' | null;
|
|
132
|
+
setPreferredLaunchMode(harness: string, mode: 'headless' | 'terminal'): boolean;
|
|
133
|
+
snapshot(): NativeMessengerStateSnapshot;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function createNativeMessengerState(options?: {
|
|
137
|
+
state?: unknown;
|
|
138
|
+
onChange?(state: NativeMessengerStateSnapshot): void;
|
|
139
|
+
}): NativeMessengerState;
|
|
140
|
+
|
|
112
141
|
export interface RemoteUiBindingOptions
|
|
113
142
|
extends Pick<
|
|
114
143
|
UiAdapter,
|
package/host.mjs
CHANGED
|
@@ -3,11 +3,18 @@ import { dispatchControllerIntent, projectClientSnapshot } from './controller.mj
|
|
|
3
3
|
import { conversationPreviewText } from '@volter-ai-dev/supercode-client';
|
|
4
4
|
|
|
5
5
|
const FRAME_SCHEMA = 'supercode.ui-host-state.v1';
|
|
6
|
+
const NATIVE_STATE_LIMIT = 500;
|
|
6
7
|
|
|
7
8
|
function objectRecord(value) {
|
|
8
9
|
return value && typeof value === 'object' && !Array.isArray(value) ? value : null;
|
|
9
10
|
}
|
|
10
11
|
|
|
12
|
+
function setRecentBounded(map, key, value) {
|
|
13
|
+
map.delete(key);
|
|
14
|
+
map.set(key, value);
|
|
15
|
+
while (map.size > NATIVE_STATE_LIMIT) map.delete(map.keys().next().value);
|
|
16
|
+
}
|
|
17
|
+
|
|
11
18
|
function defaultInstanceId() {
|
|
12
19
|
if (typeof globalThis.crypto?.randomUUID === 'function') return globalThis.crypto.randomUUID();
|
|
13
20
|
return `host-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
@@ -325,10 +332,12 @@ export function createSessionAttentionTracker(options) {
|
|
|
325
332
|
function nativeAttentionState(value) {
|
|
326
333
|
const parsed = objectRecord(value);
|
|
327
334
|
const attention = Array.isArray(parsed?.attention)
|
|
328
|
-
? parsed.attention.slice(0,
|
|
335
|
+
? parsed.attention.slice(0, NATIVE_STATE_LIMIT).flatMap((candidate) => {
|
|
329
336
|
const item = objectRecord(candidate);
|
|
330
337
|
if (
|
|
331
338
|
typeof item?.key !== 'string' ||
|
|
339
|
+
!item.key ||
|
|
340
|
+
item.key.length > 200 ||
|
|
332
341
|
!['unseen', 'finished', 'failed'].includes(item.kind)
|
|
333
342
|
) return [];
|
|
334
343
|
return [{
|
|
@@ -349,7 +358,7 @@ function nativeAttentionState(value) {
|
|
|
349
358
|
const cursors = objectRecord(parsed?.observedCursors);
|
|
350
359
|
const observedCursors = cursors
|
|
351
360
|
? Object.fromEntries(
|
|
352
|
-
Object.entries(cursors).slice(0,
|
|
361
|
+
Object.entries(cursors).slice(0, NATIVE_STATE_LIMIT).filter(
|
|
353
362
|
([key, cursor]) => key.length <= 200 && typeof cursor === 'string' && cursor.length <= 200,
|
|
354
363
|
),
|
|
355
364
|
)
|
|
@@ -434,7 +443,7 @@ export class NativeSessionAttentionTracker {
|
|
|
434
443
|
runtimeStatus: row.runtimeStatus,
|
|
435
444
|
});
|
|
436
445
|
if (current.cursor !== null) {
|
|
437
|
-
this.#observedCursors
|
|
446
|
+
setRecentBounded(this.#observedCursors, row.key, current.cursor);
|
|
438
447
|
changed = true;
|
|
439
448
|
}
|
|
440
449
|
continue;
|
|
@@ -468,7 +477,7 @@ export class NativeSessionAttentionTracker {
|
|
|
468
477
|
}
|
|
469
478
|
|
|
470
479
|
if (prior.cursor !== null && this.#observedCursors.get(row.key) !== prior.cursor) {
|
|
471
|
-
this.#observedCursors
|
|
480
|
+
setRecentBounded(this.#observedCursors, row.key, prior.cursor);
|
|
472
481
|
changed = true;
|
|
473
482
|
}
|
|
474
483
|
this.#observedUpdates.set(row.key, {
|
|
@@ -549,7 +558,7 @@ export class NativeSessionAttentionTracker {
|
|
|
549
558
|
...(boundedPreview ? { preview: boundedPreview } : {}),
|
|
550
559
|
};
|
|
551
560
|
if (JSON.stringify(prior) === JSON.stringify(next)) return false;
|
|
552
|
-
this.#attention
|
|
561
|
+
setRecentBounded(this.#attention, key, next);
|
|
553
562
|
return true;
|
|
554
563
|
}
|
|
555
564
|
|
|
@@ -562,6 +571,120 @@ export function createNativeSessionAttentionTracker(options) {
|
|
|
562
571
|
return new NativeSessionAttentionTracker(options);
|
|
563
572
|
}
|
|
564
573
|
|
|
574
|
+
export function normalizeNativeMessengerState(value) {
|
|
575
|
+
const parsed = objectRecord(value);
|
|
576
|
+
const attention = nativeAttentionState(parsed);
|
|
577
|
+
const draftRecord = objectRecord(parsed?.drafts);
|
|
578
|
+
const drafts = draftRecord
|
|
579
|
+
? Object.fromEntries(
|
|
580
|
+
Object.entries(draftRecord).slice(0, NATIVE_STATE_LIMIT).flatMap(([key, draft]) =>
|
|
581
|
+
key && key.length <= 200 && typeof draft === 'string' && draft
|
|
582
|
+
? [[key, draft.slice(0, 50_000)]]
|
|
583
|
+
: [],
|
|
584
|
+
),
|
|
585
|
+
)
|
|
586
|
+
: {};
|
|
587
|
+
const modeRecord = objectRecord(parsed?.preferredLaunchModes);
|
|
588
|
+
const preferredLaunchModes = modeRecord
|
|
589
|
+
? Object.fromEntries(
|
|
590
|
+
Object.entries(modeRecord).slice(0, NATIVE_STATE_LIMIT).filter(
|
|
591
|
+
([key, mode]) =>
|
|
592
|
+
key && key.length <= 200 && (mode === 'headless' || mode === 'terminal'),
|
|
593
|
+
),
|
|
594
|
+
)
|
|
595
|
+
: {};
|
|
596
|
+
return {
|
|
597
|
+
version: 1,
|
|
598
|
+
attention: attention.attention,
|
|
599
|
+
observedCursors: attention.observedCursors,
|
|
600
|
+
drafts,
|
|
601
|
+
preferredLaunchModes,
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/** One serializable, bounded state owner for native-session messenger chrome. Storage location,
|
|
606
|
+
* encryption, debounce, and multi-device synchronization remain host policy. */
|
|
607
|
+
export class NativeMessengerState {
|
|
608
|
+
#attention;
|
|
609
|
+
#drafts;
|
|
610
|
+
#onChange;
|
|
611
|
+
#preferredLaunchModes;
|
|
612
|
+
|
|
613
|
+
constructor(options = {}) {
|
|
614
|
+
const state = normalizeNativeMessengerState(options.state);
|
|
615
|
+
this.#onChange = options.onChange;
|
|
616
|
+
this.#drafts = new Map(Object.entries(state.drafts));
|
|
617
|
+
this.#preferredLaunchModes = new Map(Object.entries(state.preferredLaunchModes));
|
|
618
|
+
this.#attention = new NativeSessionAttentionTracker({
|
|
619
|
+
state,
|
|
620
|
+
onChange: () => this.#emit(),
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
observeAttention(options) {
|
|
625
|
+
return this.#attention.observe(options);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
acknowledge(key) {
|
|
629
|
+
return this.#attention.acknowledge(key);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
draft(key) {
|
|
633
|
+
return this.#drafts.get(key) ?? '';
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
setDraft(key, draft) {
|
|
637
|
+
if (typeof key !== 'string' || !key || key.length > 200 || typeof draft !== 'string') {
|
|
638
|
+
throw new TypeError('NativeMessengerState draft needs a bounded key and string value.');
|
|
639
|
+
}
|
|
640
|
+
const next = draft.slice(0, 50_000);
|
|
641
|
+
const previous = this.#drafts.get(key) ?? '';
|
|
642
|
+
if (previous === next) return false;
|
|
643
|
+
if (next) setRecentBounded(this.#drafts, key, next);
|
|
644
|
+
else this.#drafts.delete(key);
|
|
645
|
+
this.#emit();
|
|
646
|
+
return true;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
preferredLaunchMode(harness) {
|
|
650
|
+
return this.#preferredLaunchModes.get(harness) ?? null;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
setPreferredLaunchMode(harness, mode) {
|
|
654
|
+
if (
|
|
655
|
+
typeof harness !== 'string' ||
|
|
656
|
+
!harness ||
|
|
657
|
+
harness.length > 200 ||
|
|
658
|
+
(mode !== 'headless' && mode !== 'terminal')
|
|
659
|
+
) {
|
|
660
|
+
throw new TypeError('NativeMessengerState launch preference must be headless or terminal.');
|
|
661
|
+
}
|
|
662
|
+
if (this.#preferredLaunchModes.get(harness) === mode) return false;
|
|
663
|
+
setRecentBounded(this.#preferredLaunchModes, harness, mode);
|
|
664
|
+
this.#emit();
|
|
665
|
+
return true;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
snapshot() {
|
|
669
|
+
const attention = this.#attention.snapshot();
|
|
670
|
+
return {
|
|
671
|
+
version: 1,
|
|
672
|
+
attention: attention.attention,
|
|
673
|
+
observedCursors: attention.observedCursors,
|
|
674
|
+
drafts: Object.fromEntries(this.#drafts),
|
|
675
|
+
preferredLaunchModes: Object.fromEntries(this.#preferredLaunchModes),
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
#emit() {
|
|
680
|
+
this.#onChange?.(this.snapshot());
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
export function createNativeMessengerState(options) {
|
|
685
|
+
return new NativeMessengerState(options);
|
|
686
|
+
}
|
|
687
|
+
|
|
565
688
|
/** Browser binding for any transport that can post one intent and return an
|
|
566
689
|
* optional authoritative frame. Local host actions may intercept an intent. */
|
|
567
690
|
export function createRemoteUiBinding(options) {
|