@volter-ai-dev/supercode-ui 0.1.64 → 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 +11 -0
- package/host.d.ts +66 -0
- package/host.mjs +364 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -237,6 +237,17 @@ The optional attention tracker baselines initial inventory without inventing unr
|
|
|
237
237
|
by stable opaque identity, and marks only newer conversation evidence or a proven runtime completion.
|
|
238
238
|
HTTP, SSE, WebSocket, authentication, and product shell behavior remain host-owned transports.
|
|
239
239
|
|
|
240
|
+
A native-store inbox can use `createNativeSessionAttentionTracker` instead. It consumes projected
|
|
241
|
+
session rows plus their native descriptors, persists opaque message cursors, ignores tool and
|
|
242
|
+
heartbeat churn, treats compaction as a new baseline, and returns the delay for one host-owned
|
|
243
|
+
settlement timer. This keeps unread and finished semantics identical in an editor, extension,
|
|
244
|
+
desktop app, or mobile companion without moving file persistence into the UI package.
|
|
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
|
+
|
|
240
251
|
Native continuation exposes one action and a quiet execution-transport selector. A host with a real
|
|
241
252
|
terminal provider can add `terminal` to `continuationModes` and handle `onResumeTerminal`; Terminal
|
|
242
253
|
is then the initial choice and Headless remains available from the selector. The UI never infers
|
package/host.d.ts
CHANGED
|
@@ -72,6 +72,72 @@ export function createSessionAttentionTracker(
|
|
|
72
72
|
options?: SessionAttentionTrackerOptions,
|
|
73
73
|
): SessionAttentionTracker;
|
|
74
74
|
|
|
75
|
+
export interface NativeSessionAttentionTrackerState {
|
|
76
|
+
version: 1;
|
|
77
|
+
attention: import('./index.js').SessionAttention[];
|
|
78
|
+
observedCursors: Record<string, string>;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface NativeSessionAttentionObservation {
|
|
82
|
+
sessions: readonly import('./index.js').SessionRowModel[];
|
|
83
|
+
descriptors: readonly import('@volter-ai-dev/supercode-harness-sdk').SessionDescriptor[];
|
|
84
|
+
keyForDescriptor(
|
|
85
|
+
descriptor: import('@volter-ai-dev/supercode-harness-sdk').SessionDescriptor,
|
|
86
|
+
): string;
|
|
87
|
+
controller?: SupercodeClientSnapshot | null;
|
|
88
|
+
attachedKey?: string | null;
|
|
89
|
+
panelVisible?: boolean;
|
|
90
|
+
now?: number;
|
|
91
|
+
settleMs?: number;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export class NativeSessionAttentionTracker {
|
|
95
|
+
constructor(options?: {
|
|
96
|
+
state?: unknown;
|
|
97
|
+
onChange?(state: NativeSessionAttentionTrackerState): void;
|
|
98
|
+
});
|
|
99
|
+
acknowledge(key: string): boolean;
|
|
100
|
+
observe(options: NativeSessionAttentionObservation): {
|
|
101
|
+
attention: import('./index.js').SessionAttention[];
|
|
102
|
+
settleAfterMs: number | null;
|
|
103
|
+
};
|
|
104
|
+
snapshot(): NativeSessionAttentionTrackerState;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function createNativeSessionAttentionTracker(options?: {
|
|
108
|
+
state?: unknown;
|
|
109
|
+
onChange?(state: NativeSessionAttentionTrackerState): void;
|
|
110
|
+
}): NativeSessionAttentionTracker;
|
|
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
|
+
|
|
75
141
|
export interface RemoteUiBindingOptions
|
|
76
142
|
extends Pick<
|
|
77
143
|
UiAdapter,
|
package/host.mjs
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
import { parseSupercodeUiIntent, sessionActivity } from './core.mjs';
|
|
2
2
|
import { dispatchControllerIntent, projectClientSnapshot } from './controller.mjs';
|
|
3
|
+
import { conversationPreviewText } from '@volter-ai-dev/supercode-client';
|
|
3
4
|
|
|
4
5
|
const FRAME_SCHEMA = 'supercode.ui-host-state.v1';
|
|
6
|
+
const NATIVE_STATE_LIMIT = 500;
|
|
5
7
|
|
|
6
8
|
function objectRecord(value) {
|
|
7
9
|
return value && typeof value === 'object' && !Array.isArray(value) ? value : null;
|
|
8
10
|
}
|
|
9
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
|
+
|
|
10
18
|
function defaultInstanceId() {
|
|
11
19
|
if (typeof globalThis.crypto?.randomUUID === 'function') return globalThis.crypto.randomUUID();
|
|
12
20
|
return `host-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
@@ -321,6 +329,362 @@ export function createSessionAttentionTracker(options) {
|
|
|
321
329
|
return new SessionAttentionTracker(options);
|
|
322
330
|
}
|
|
323
331
|
|
|
332
|
+
function nativeAttentionState(value) {
|
|
333
|
+
const parsed = objectRecord(value);
|
|
334
|
+
const attention = Array.isArray(parsed?.attention)
|
|
335
|
+
? parsed.attention.slice(0, NATIVE_STATE_LIMIT).flatMap((candidate) => {
|
|
336
|
+
const item = objectRecord(candidate);
|
|
337
|
+
if (
|
|
338
|
+
typeof item?.key !== 'string' ||
|
|
339
|
+
!item.key ||
|
|
340
|
+
item.key.length > 200 ||
|
|
341
|
+
!['unseen', 'finished', 'failed'].includes(item.kind)
|
|
342
|
+
) return [];
|
|
343
|
+
return [{
|
|
344
|
+
key: item.key,
|
|
345
|
+
kind: item.kind,
|
|
346
|
+
unreadCount: Number.isSafeInteger(item.unreadCount)
|
|
347
|
+
? Math.max(1, Math.min(999, item.unreadCount))
|
|
348
|
+
: 1,
|
|
349
|
+
...(Number.isSafeInteger(item.afterMessages) && item.afterMessages >= 0
|
|
350
|
+
? { afterMessages: item.afterMessages }
|
|
351
|
+
: {}),
|
|
352
|
+
...(typeof item.preview === 'string' && item.preview
|
|
353
|
+
? { preview: item.preview.slice(0, 240) }
|
|
354
|
+
: {}),
|
|
355
|
+
}];
|
|
356
|
+
})
|
|
357
|
+
: [];
|
|
358
|
+
const cursors = objectRecord(parsed?.observedCursors);
|
|
359
|
+
const observedCursors = cursors
|
|
360
|
+
? Object.fromEntries(
|
|
361
|
+
Object.entries(cursors).slice(0, NATIVE_STATE_LIMIT).filter(
|
|
362
|
+
([key, cursor]) => key.length <= 200 && typeof cursor === 'string' && cursor.length <= 200,
|
|
363
|
+
),
|
|
364
|
+
)
|
|
365
|
+
: {};
|
|
366
|
+
return { version: 1, attention, observedCursors };
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function conversationObservation(descriptor) {
|
|
370
|
+
const candidates = (descriptor?.latest_message_candidates ?? []).flatMap((candidate) => {
|
|
371
|
+
if (
|
|
372
|
+
typeof candidate?.cursor !== 'string' ||
|
|
373
|
+
!candidate.cursor ||
|
|
374
|
+
(candidate.role !== 'user' && candidate.role !== 'assistant')
|
|
375
|
+
) return [];
|
|
376
|
+
const preview = conversationPreviewText([candidate]);
|
|
377
|
+
return preview
|
|
378
|
+
? [{ cursor: candidate.cursor, role: candidate.role, preview }]
|
|
379
|
+
: [];
|
|
380
|
+
});
|
|
381
|
+
return { cursor: candidates[0]?.cursor ?? null, candidates };
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function assistantDelta(current, priorCursor) {
|
|
385
|
+
if (current.cursor === null || current.cursor === priorCursor) return 0;
|
|
386
|
+
const priorIndex = priorCursor === null
|
|
387
|
+
? -1
|
|
388
|
+
: current.candidates.findIndex((candidate) => candidate.cursor === priorCursor);
|
|
389
|
+
const newlyObserved = priorIndex >= 0
|
|
390
|
+
? current.candidates.slice(0, priorIndex)
|
|
391
|
+
: current.candidates;
|
|
392
|
+
return newlyObserved.filter((candidate) => candidate.role === 'assistant').length;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/** Native-cursor attention reducer for a messenger-style session catalog. Tool records and
|
|
396
|
+
* heartbeat writes never manufacture unread counts; only new human-visible assistant boundaries
|
|
397
|
+
* do. The host owns persistence and the one settlement timer requested by `observe()`. */
|
|
398
|
+
export class NativeSessionAttentionTracker {
|
|
399
|
+
#attention;
|
|
400
|
+
#observedCursors;
|
|
401
|
+
#observedUpdates = new Map();
|
|
402
|
+
#onChange;
|
|
403
|
+
#priorRuntimeActive = false;
|
|
404
|
+
#priorRuntimeKey = null;
|
|
405
|
+
|
|
406
|
+
constructor(options = {}) {
|
|
407
|
+
const state = nativeAttentionState(options.state);
|
|
408
|
+
this.#attention = new Map(state.attention.map((item) => [item.key, item]));
|
|
409
|
+
this.#observedCursors = new Map(Object.entries(state.observedCursors));
|
|
410
|
+
this.#onChange = options.onChange;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
observe(options) {
|
|
414
|
+
const rows = Array.isArray(options?.sessions) ? options.sessions : [];
|
|
415
|
+
const descriptors = Array.isArray(options?.descriptors) ? options.descriptors : [];
|
|
416
|
+
if (typeof options?.keyForDescriptor !== 'function') {
|
|
417
|
+
throw new TypeError('NativeSessionAttentionTracker requires keyForDescriptor.');
|
|
418
|
+
}
|
|
419
|
+
const now = Number.isFinite(options.now) ? options.now : Date.now();
|
|
420
|
+
const settleMs = Number.isFinite(options.settleMs) && options.settleMs >= 0
|
|
421
|
+
? options.settleMs
|
|
422
|
+
: 15_000;
|
|
423
|
+
const descriptorByKey = new Map(
|
|
424
|
+
descriptors.map((descriptor) => [options.keyForDescriptor(descriptor), descriptor]),
|
|
425
|
+
);
|
|
426
|
+
let changed = false;
|
|
427
|
+
let settleAfterMs = null;
|
|
428
|
+
|
|
429
|
+
for (const row of rows) {
|
|
430
|
+
const current = conversationObservation(descriptorByKey.get(row.key));
|
|
431
|
+
let prior = this.#observedUpdates.get(row.key);
|
|
432
|
+
if (prior === undefined) {
|
|
433
|
+
const persistedCursor = this.#observedCursors.get(row.key) ?? null;
|
|
434
|
+
prior = {
|
|
435
|
+
cursor: persistedCursor,
|
|
436
|
+
messages: row.messages,
|
|
437
|
+
runtimeStatus: row.runtimeStatus,
|
|
438
|
+
};
|
|
439
|
+
if (persistedCursor === null) {
|
|
440
|
+
this.#observedUpdates.set(row.key, {
|
|
441
|
+
cursor: current.cursor,
|
|
442
|
+
messages: row.messages,
|
|
443
|
+
runtimeStatus: row.runtimeStatus,
|
|
444
|
+
});
|
|
445
|
+
if (current.cursor !== null) {
|
|
446
|
+
setRecentBounded(this.#observedCursors, row.key, current.cursor);
|
|
447
|
+
changed = true;
|
|
448
|
+
}
|
|
449
|
+
continue;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
const rewritten = row.messages !== null && prior.messages !== null && row.messages < prior.messages;
|
|
454
|
+
const cursorChanged = current.cursor !== null && current.cursor !== prior.cursor;
|
|
455
|
+
const settledAt = row.previewUpdatedAt ?? row.updatedAt;
|
|
456
|
+
const elapsed = settledAt === null ? null : now - settledAt;
|
|
457
|
+
const settled = elapsed !== null && elapsed >= settleMs;
|
|
458
|
+
const completed = prior.runtimeStatus === 'busy' && row.runtimeStatus === 'idle';
|
|
459
|
+
|
|
460
|
+
if (rewritten) {
|
|
461
|
+
prior.cursor = current.cursor;
|
|
462
|
+
} else if (cursorChanged && row.runtimeStatus !== 'busy' && (completed || settled)) {
|
|
463
|
+
const delta = assistantDelta(current, prior.cursor);
|
|
464
|
+
if (delta > 0) {
|
|
465
|
+
changed = this.#mark(
|
|
466
|
+
row.key,
|
|
467
|
+
completed ? 'finished' : 'unseen',
|
|
468
|
+
row.preview,
|
|
469
|
+
delta,
|
|
470
|
+
prior.messages,
|
|
471
|
+
) || changed;
|
|
472
|
+
}
|
|
473
|
+
prior.cursor = current.cursor;
|
|
474
|
+
} else if (cursorChanged && row.runtimeStatus !== 'busy' && elapsed !== null) {
|
|
475
|
+
const remaining = Math.max(0, settleMs - elapsed);
|
|
476
|
+
settleAfterMs = settleAfterMs === null ? remaining : Math.min(settleAfterMs, remaining);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
if (prior.cursor !== null && this.#observedCursors.get(row.key) !== prior.cursor) {
|
|
480
|
+
setRecentBounded(this.#observedCursors, row.key, prior.cursor);
|
|
481
|
+
changed = true;
|
|
482
|
+
}
|
|
483
|
+
this.#observedUpdates.set(row.key, {
|
|
484
|
+
cursor: prior.cursor,
|
|
485
|
+
messages: row.messages,
|
|
486
|
+
runtimeStatus: row.runtimeStatus,
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const controller = options.controller;
|
|
491
|
+
const runtimeActive = controller
|
|
492
|
+
? controller.turn?.state !== 'idle' || controller.requests?.some((request) => request.status === 'pending')
|
|
493
|
+
: false;
|
|
494
|
+
const runtimeKey = options.attachedKey || this.#priorRuntimeKey;
|
|
495
|
+
if (
|
|
496
|
+
this.#priorRuntimeActive &&
|
|
497
|
+
!runtimeActive &&
|
|
498
|
+
this.#priorRuntimeKey &&
|
|
499
|
+
!(options.panelVisible === true && options.attachedKey === this.#priorRuntimeKey)
|
|
500
|
+
) {
|
|
501
|
+
const lastAssistant = [...(controller?.conversation ?? [])].reverse().find(
|
|
502
|
+
(entry) => entry.kind === 'message' && entry.role === 'assistant' && entry.text.trim(),
|
|
503
|
+
);
|
|
504
|
+
const preview = lastAssistant?.kind === 'message'
|
|
505
|
+
? lastAssistant.text.replace(/\s+/g, ' ').trim()
|
|
506
|
+
: undefined;
|
|
507
|
+
changed = this.#mark(
|
|
508
|
+
this.#priorRuntimeKey,
|
|
509
|
+
controller?.error ? 'failed' : 'finished',
|
|
510
|
+
preview,
|
|
511
|
+
) || changed;
|
|
512
|
+
}
|
|
513
|
+
this.#priorRuntimeActive = runtimeActive;
|
|
514
|
+
this.#priorRuntimeKey = runtimeActive ? runtimeKey : null;
|
|
515
|
+
|
|
516
|
+
if (changed) this.#emit();
|
|
517
|
+
const visible = new Set(rows.map((row) => row.key));
|
|
518
|
+
return {
|
|
519
|
+
attention: [...this.#attention.values()].filter((item) => visible.has(item.key)),
|
|
520
|
+
settleAfterMs,
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
acknowledge(key) {
|
|
525
|
+
if (!this.#attention.delete(key)) return false;
|
|
526
|
+
this.#emit();
|
|
527
|
+
return true;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
snapshot() {
|
|
531
|
+
return {
|
|
532
|
+
version: 1,
|
|
533
|
+
attention: structuredClone([...this.#attention.values()]),
|
|
534
|
+
observedCursors: Object.fromEntries(this.#observedCursors),
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
#mark(key, kind, preview, unreadDelta = 1, afterMessages = null) {
|
|
539
|
+
const prior = this.#attention.get(key);
|
|
540
|
+
const boundedPreview = typeof preview === 'string' && preview
|
|
541
|
+
? preview.slice(0, 240)
|
|
542
|
+
: undefined;
|
|
543
|
+
const sameEvent = boundedPreview
|
|
544
|
+
? prior?.preview === boundedPreview
|
|
545
|
+
: prior?.kind === kind && prior.preview === undefined;
|
|
546
|
+
const unreadCount = sameEvent
|
|
547
|
+
? (prior?.unreadCount ?? Math.max(1, unreadDelta))
|
|
548
|
+
: Math.min((prior?.unreadCount ?? 0) + Math.max(1, unreadDelta), 999);
|
|
549
|
+
const next = {
|
|
550
|
+
key,
|
|
551
|
+
kind: prior?.kind === 'failed' && kind !== 'failed' ? 'failed' : kind,
|
|
552
|
+
unreadCount,
|
|
553
|
+
...(prior?.afterMessages !== undefined
|
|
554
|
+
? { afterMessages: prior.afterMessages }
|
|
555
|
+
: Number.isSafeInteger(afterMessages) && afterMessages >= 0
|
|
556
|
+
? { afterMessages }
|
|
557
|
+
: {}),
|
|
558
|
+
...(boundedPreview ? { preview: boundedPreview } : {}),
|
|
559
|
+
};
|
|
560
|
+
if (JSON.stringify(prior) === JSON.stringify(next)) return false;
|
|
561
|
+
setRecentBounded(this.#attention, key, next);
|
|
562
|
+
return true;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
#emit() {
|
|
566
|
+
this.#onChange?.(this.snapshot());
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
export function createNativeSessionAttentionTracker(options) {
|
|
571
|
+
return new NativeSessionAttentionTracker(options);
|
|
572
|
+
}
|
|
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
|
+
|
|
324
688
|
/** Browser binding for any transport that can post one intent and return an
|
|
325
689
|
* optional authoritative frame. Local host actions may intercept an intent. */
|
|
326
690
|
export function createRemoteUiBinding(options) {
|