@runtypelabs/persona 4.23.2 → 4.25.0
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/dist/animations/glyph-cycle.d.cts +1 -1
- package/dist/animations/glyph-cycle.d.ts +1 -1
- package/dist/animations/{types-CI7OckWI.d.cts → types-Db61aI9J.d.cts} +2 -0
- package/dist/animations/{types-CI7OckWI.d.ts → types-Db61aI9J.d.ts} +2 -0
- package/dist/animations/wipe.d.cts +1 -1
- package/dist/animations/wipe.d.ts +1 -1
- package/dist/animations-extra.d.cts +2 -0
- package/dist/animations-extra.d.ts +2 -0
- package/dist/approval-ui-3NMYNXBD.js +1 -0
- package/dist/approval-ui.cjs +2 -2
- package/dist/approval-ui.d.cts +278 -37
- package/dist/approval-ui.d.ts +278 -37
- package/dist/approval-ui.js +2 -2
- package/dist/{artifacts-ui-F4N6VMKF.js → artifacts-ui-IGB5YKLO.js} +4 -4
- package/dist/artifacts-ui.d.cts +273 -25
- package/dist/artifacts-ui.d.ts +273 -25
- package/dist/{chunk-NIKYKSNZ.js → chunk-7CO4T5KB.js} +1 -1
- package/dist/codegen.cjs +11 -11
- package/dist/codegen.js +13 -13
- package/dist/context-mentions.d.cts +273 -25
- package/dist/context-mentions.d.ts +273 -25
- package/dist/event-stream-view.d.cts +273 -25
- package/dist/event-stream-view.d.ts +273 -25
- package/dist/forms-ui.d.cts +319 -27
- package/dist/forms-ui.d.ts +319 -27
- package/dist/history-view.d.cts +2 -0
- package/dist/history-view.d.ts +2 -0
- package/dist/index.cjs +70 -69
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +344 -39
- package/dist/index.d.ts +344 -39
- package/dist/index.global.js +57 -54
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +70 -69
- package/dist/index.js.map +1 -1
- package/dist/install.global.js +1 -1
- package/dist/install.global.js.map +1 -1
- package/dist/launcher.global.js +2 -2
- package/dist/launcher.global.js.map +1 -1
- package/dist/session-reconnect.d.cts +273 -25
- package/dist/session-reconnect.d.ts +273 -25
- package/dist/smart-dom-reader.d.cts +273 -25
- package/dist/smart-dom-reader.d.ts +273 -25
- package/dist/theme-editor-preview.cjs +74 -73
- package/dist/theme-editor-preview.d.cts +280 -25
- package/dist/theme-editor-preview.d.ts +280 -25
- package/dist/theme-editor-preview.js +51 -50
- package/dist/theme-editor.cjs +10 -10
- package/dist/theme-editor.d.cts +293 -27
- package/dist/theme-editor.d.ts +293 -27
- package/dist/theme-editor.js +10 -10
- package/dist/theme-reference.cjs +1 -1
- package/dist/theme-reference.d.cts +1522 -11
- package/dist/theme-reference.d.ts +1522 -11
- package/dist/theme-reference.js +1 -1
- package/dist/voice-runtime-7DA7MJH4.js +1 -0
- package/dist/voice-runtime.cjs +1 -1
- package/dist/voice-runtime.d.cts +33 -0
- package/dist/voice-runtime.d.ts +33 -0
- package/dist/voice-runtime.js +1 -1
- package/dist/widget.css +1 -1
- package/package.json +3 -2
- package/src/__fixtures__/defaults-parity.v4.json +776 -0
- package/src/approval-ui-entry.ts +0 -1
- package/src/approval-ui-loader.ts +5 -4
- package/src/approval-ui.ts +0 -1
- package/src/client-identity-proof.test.ts +193 -0
- package/src/client-session-warm.test.ts +216 -0
- package/src/client-visitor-history.test.ts +6 -0
- package/src/client.ts +122 -30
- package/src/codegen.test.ts +24 -0
- package/src/components/activity-row.test.ts +100 -0
- package/src/components/activity-row.ts +136 -0
- package/src/components/approval-actions.test.ts +8 -5
- package/src/components/approval-actions.ts +19 -6
- package/src/components/approval-bubble.test.ts +56 -9
- package/src/components/approval-bubble.ts +36 -41
- package/src/components/composer-builder.ts +5 -2
- package/src/components/composer-parts.ts +10 -2
- package/src/components/header-builder.test.ts +32 -0
- package/src/components/header-builder.ts +7 -4
- package/src/components/header-layouts.test.ts +12 -0
- package/src/components/header-layouts.ts +3 -2
- package/src/components/launcher.ts +11 -2
- package/src/components/message-bubble.test.ts +22 -0
- package/src/components/message-bubble.ts +7 -3
- package/src/components/panel.test.ts +52 -1
- package/src/components/panel.ts +47 -8
- package/src/components/pill-composer-builder.test.ts +2 -0
- package/src/components/pill-composer-builder.ts +0 -1
- package/src/components/reasoning-bubble.ts +29 -17
- package/src/components/suggestions.test.ts +1 -0
- package/src/components/suggestions.ts +0 -16
- package/src/components/tool-bubble.ts +131 -114
- package/src/components/tool-details.test.ts +87 -0
- package/src/components/tool-details.ts +102 -0
- package/src/components/welcome.test.ts +8 -0
- package/src/components/welcome.ts +4 -2
- package/src/defaults-parity.test.ts +158 -0
- package/src/defaults.test.ts +69 -0
- package/src/defaults.ts +128 -49
- package/src/generated/runtype-openapi-contract.ts +2 -2
- package/src/index-core.ts +4 -0
- package/src/install.test.ts +24 -0
- package/src/install.ts +3 -1
- package/src/launcher-global.ts +2 -1
- package/src/launcher-variants.test.ts +52 -0
- package/src/runtime/init-update-reset.test.ts +21 -0
- package/src/session.ts +59 -7
- package/src/session.voice.test.ts +72 -1
- package/src/styles/widget-styles.test.ts +19 -0
- package/src/styles/widget.css +295 -23
- package/src/theme-editor/index.ts +2 -0
- package/src/theme-editor/preview-utils.test.ts +8 -0
- package/src/theme-editor/preview-utils.ts +7 -3
- package/src/theme-editor/sections.test.ts +31 -0
- package/src/theme-editor/sections.ts +55 -1
- package/src/theme-editor/state-preferences.test.ts +49 -0
- package/src/theme-editor/state.ts +73 -15
- package/src/theme-reference.test.ts +20 -0
- package/src/theme-reference.ts +11 -5
- package/src/tool-call-display-defaults.test.ts +33 -1
- package/src/types/theme.ts +80 -3
- package/src/types.ts +207 -22
- package/src/ui.activity-row.test.ts +91 -0
- package/src/ui.approval-plugin.test.ts +63 -0
- package/src/ui.composer-compact.test.ts +24 -2
- package/src/ui.composer-placement.test.ts +23 -0
- package/src/ui.detached-panel.test.ts +33 -6
- package/src/ui.docked.test.ts +19 -0
- package/src/ui.header-update-stability.test.ts +25 -0
- package/src/ui.identity-proof.test.ts +58 -0
- package/src/ui.launcher-teaser.test.ts +25 -0
- package/src/ui.message-width.test.ts +17 -2
- package/src/ui.reasoning-display.test.ts +92 -0
- package/src/ui.rebuild-composer.test.ts +20 -3
- package/src/ui.scroll.test.ts +32 -0
- package/src/ui.session-init.test.ts +312 -0
- package/src/ui.status-indicator.test.ts +82 -0
- package/src/ui.suggest-replies.test.ts +11 -0
- package/src/ui.ts +422 -74
- package/src/ui.v5-defaults.test.ts +144 -0
- package/src/ui.voice-prewarm.test.ts +222 -0
- package/src/ui.welcome.test.ts +92 -3
- package/src/utils/__snapshots__/tokens-parity.test.ts.snap +811 -140
- package/src/utils/code-generators.ts +14 -0
- package/src/utils/composer-compact.test.ts +0 -2
- package/src/utils/composer-compact.ts +2 -4
- package/src/utils/composer-placement.ts +2 -3
- package/src/utils/config-merge.test.ts +81 -5
- package/src/utils/config-merge.ts +13 -3
- package/src/utils/defaults-provenance.ts +67 -0
- package/src/utils/dock.ts +7 -1
- package/src/utils/launcher-variant.ts +16 -0
- package/src/utils/panel-config.ts +97 -0
- package/src/utils/status-indicator.ts +39 -0
- package/src/utils/theme.test.ts +182 -0
- package/src/utils/theme.ts +131 -18
- package/src/utils/tokens.test.ts +122 -1
- package/src/utils/tokens.ts +177 -17
- package/src/voice/runtype-voice-provider.ts +238 -27
- package/src/voice/voice.test.ts +272 -2
- package/src/welcome.test.ts +23 -0
- package/src/welcome.ts +16 -9
- package/dist/approval-ui-K7R5Q5OC.js +0 -1
- package/dist/voice-runtime-EFR3R5ES.js +0 -1
package/src/voice/voice.test.ts
CHANGED
|
@@ -217,14 +217,17 @@ describe('Voice Support Check', () => {
|
|
|
217
217
|
const WS_OPEN = 1;
|
|
218
218
|
|
|
219
219
|
class MockWebSocket {
|
|
220
|
+
static CONNECTING = 0;
|
|
220
221
|
static OPEN = WS_OPEN;
|
|
221
222
|
static instances: MockWebSocket[] = [];
|
|
222
223
|
|
|
223
224
|
url: string;
|
|
224
225
|
protocols: string | string[] | undefined;
|
|
226
|
+
/** Negotiated subprotocol, set by the test before `triggerOpen()`. */
|
|
227
|
+
protocol = '';
|
|
225
228
|
binaryType = '';
|
|
226
229
|
readyState = 0;
|
|
227
|
-
sent: ArrayBuffer
|
|
230
|
+
sent: Array<ArrayBuffer | string> = [];
|
|
228
231
|
closeCalls: Array<{ code?: number; reason?: string }> = [];
|
|
229
232
|
onopen: (() => void) | null = null;
|
|
230
233
|
onmessage: ((ev: { data: unknown }) => void) | null = null;
|
|
@@ -237,7 +240,7 @@ class MockWebSocket {
|
|
|
237
240
|
MockWebSocket.instances.push(this);
|
|
238
241
|
}
|
|
239
242
|
|
|
240
|
-
send(data: ArrayBuffer) {
|
|
243
|
+
send(data: ArrayBuffer | string) {
|
|
241
244
|
this.sent.push(data);
|
|
242
245
|
}
|
|
243
246
|
|
|
@@ -535,4 +538,271 @@ describe('RuntypeVoiceProvider (realtime streaming)', () => {
|
|
|
535
538
|
engine.finishedCb?.(); // playback drained
|
|
536
539
|
expect(statuses).toEqual(['listening']);
|
|
537
540
|
});
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
describe('RuntypeVoiceProvider prewarm', () => {
|
|
544
|
+
let getUserMedia: ReturnType<typeof vi.fn>;
|
|
545
|
+
let fetchMock: ReturnType<typeof vi.fn>;
|
|
546
|
+
|
|
547
|
+
beforeEach(() => {
|
|
548
|
+
vi.useFakeTimers();
|
|
549
|
+
MockWebSocket.instances = [];
|
|
550
|
+
getUserMedia = vi.fn(async () => makeStream().stream);
|
|
551
|
+
fetchMock = vi.fn(async () => new Response(null, { status: 204 }));
|
|
552
|
+
vi.stubGlobal('WebSocket', MockWebSocket);
|
|
553
|
+
vi.stubGlobal('navigator', { mediaDevices: { getUserMedia } });
|
|
554
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
555
|
+
(globalThis as any).window.AudioContext = MockAudioContext;
|
|
556
|
+
(globalThis as any).window.webkitAudioContext = MockAudioContext;
|
|
557
|
+
(globalThis as any).window.location = { protocol: 'https:' };
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
afterEach(() => {
|
|
561
|
+
vi.useRealTimers();
|
|
562
|
+
vi.unstubAllGlobals();
|
|
563
|
+
delete (globalThis as any).window.AudioContext;
|
|
564
|
+
delete (globalThis as any).window.webkitAudioContext;
|
|
565
|
+
delete (globalThis as any).window.location;
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
const config = (extra: Partial<NonNullable<VoiceConfig['runtype']>> = {}) => ({
|
|
569
|
+
agentId: 'agent/1',
|
|
570
|
+
clientToken: 'ct_secret',
|
|
571
|
+
host: 'https://api.example.com',
|
|
572
|
+
...extra,
|
|
573
|
+
});
|
|
574
|
+
const attachConfig = (extra: Partial<NonNullable<VoiceConfig['runtype']>> = {}) =>
|
|
575
|
+
config({ prewarmMode: 'attach', ...extra });
|
|
576
|
+
const lastWs = () => MockWebSocket.instances[MockWebSocket.instances.length - 1];
|
|
577
|
+
const openAs = (ws: MockWebSocket, protocol: string) => {
|
|
578
|
+
ws.protocol = protocol;
|
|
579
|
+
ws.triggerOpen();
|
|
580
|
+
};
|
|
581
|
+
|
|
582
|
+
describe("'request' mode (default)", () => {
|
|
583
|
+
it('POSTs the prewarm endpoint with the bearer token and no body', () => {
|
|
584
|
+
new RuntypeVoiceProvider(config()).prewarm();
|
|
585
|
+
|
|
586
|
+
expect(fetchMock).toHaveBeenCalledOnce();
|
|
587
|
+
const [url, init] = fetchMock.mock.calls[0];
|
|
588
|
+
expect(url).toBe('https://api.example.com/v1/client/agents/agent%2F1/voice/prewarm');
|
|
589
|
+
expect(init).toEqual({
|
|
590
|
+
method: 'POST',
|
|
591
|
+
headers: { Authorization: 'Bearer ct_secret' },
|
|
592
|
+
keepalive: true,
|
|
593
|
+
});
|
|
594
|
+
expect(MockWebSocket.instances).toHaveLength(0);
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
it.each([
|
|
598
|
+
['http://localhost:8787', 'http://localhost:8787'],
|
|
599
|
+
['wss://api.example.com', 'https://api.example.com'],
|
|
600
|
+
['api.example.com', 'https://api.example.com'],
|
|
601
|
+
])('derives the http base from %s', (host, base) => {
|
|
602
|
+
new RuntypeVoiceProvider(config({ host })).prewarm();
|
|
603
|
+
expect(fetchMock.mock.calls[0][0]).toBe(`${base}/v1/client/agents/agent%2F1/voice/prewarm`);
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
it('fires at most once per 30s per provider', () => {
|
|
607
|
+
const provider = new RuntypeVoiceProvider(config());
|
|
608
|
+
provider.prewarm();
|
|
609
|
+
provider.prewarm();
|
|
610
|
+
vi.advanceTimersByTime(29_999);
|
|
611
|
+
provider.prewarm();
|
|
612
|
+
expect(fetchMock).toHaveBeenCalledOnce();
|
|
613
|
+
vi.advanceTimersByTime(1);
|
|
614
|
+
provider.prewarm();
|
|
615
|
+
expect(fetchMock).toHaveBeenCalledTimes(2);
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
it('swallows rejected and throwing fetches without emitting errors or status', async () => {
|
|
619
|
+
const errors: Error[] = [];
|
|
620
|
+
const statuses: string[] = [];
|
|
621
|
+
fetchMock.mockRejectedValueOnce(new Error('offline'));
|
|
622
|
+
const provider = new RuntypeVoiceProvider(config());
|
|
623
|
+
provider.onError((e) => errors.push(e));
|
|
624
|
+
provider.onStatusChange((s) => statuses.push(s));
|
|
625
|
+
provider.prewarm();
|
|
626
|
+
await vi.advanceTimersByTimeAsync(30_000);
|
|
627
|
+
fetchMock.mockImplementationOnce(() => {
|
|
628
|
+
throw new Error('sync');
|
|
629
|
+
});
|
|
630
|
+
expect(() => provider.prewarm()).not.toThrow();
|
|
631
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
632
|
+
expect(fetchMock).toHaveBeenCalledTimes(2);
|
|
633
|
+
expect(errors).toEqual([]);
|
|
634
|
+
expect(statuses).toEqual([]);
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
it('does nothing without credentials or during a live call', async () => {
|
|
638
|
+
new RuntypeVoiceProvider(config({ clientToken: undefined })).prewarm();
|
|
639
|
+
expect(fetchMock).not.toHaveBeenCalled();
|
|
640
|
+
|
|
641
|
+
const provider = new RuntypeVoiceProvider(config());
|
|
642
|
+
await provider.startListening();
|
|
643
|
+
provider.prewarm();
|
|
644
|
+
expect(fetchMock).not.toHaveBeenCalled();
|
|
645
|
+
});
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
describe("'attach' mode", () => {
|
|
649
|
+
it('opens the voice socket with the attach capability and subprotocol, then pings', () => {
|
|
650
|
+
new RuntypeVoiceProvider(attachConfig()).prewarm();
|
|
651
|
+
|
|
652
|
+
const ws = lastWs();
|
|
653
|
+
expect(ws.url).toBe('wss://api.example.com/ws/agents/agent%2F1/voice?clientCapabilities=attach');
|
|
654
|
+
expect(ws.protocols).toEqual(['runtype.bearer', 'runtype.attach', 'ct_secret']);
|
|
655
|
+
expect(fetchMock).not.toHaveBeenCalled();
|
|
656
|
+
openAs(ws, 'runtype.attach');
|
|
657
|
+
expect(ws.sent).toEqual(['{"type":"ping"}']);
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
it('sends a clamped attachIdleMs when configured', () => {
|
|
661
|
+
new RuntypeVoiceProvider(attachConfig({ attachIdleMs: 5_000 })).prewarm();
|
|
662
|
+
expect(lastWs().url).toContain('clientCapabilities=attach&attachIdleMs=30000');
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
it('never attaches twice concurrently', () => {
|
|
666
|
+
const provider = new RuntypeVoiceProvider(attachConfig());
|
|
667
|
+
provider.prewarm();
|
|
668
|
+
vi.advanceTimersByTime(60_000);
|
|
669
|
+
provider.prewarm();
|
|
670
|
+
expect(MockWebSocket.instances).toHaveLength(1);
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
it('reuses an attached socket on click: sends start and captures on it', async () => {
|
|
674
|
+
const statuses: string[] = [];
|
|
675
|
+
const provider = new RuntypeVoiceProvider(attachConfig());
|
|
676
|
+
provider.onStatusChange((s) => statuses.push(s));
|
|
677
|
+
provider.prewarm();
|
|
678
|
+
const ws = lastWs();
|
|
679
|
+
openAs(ws, 'runtype.attach');
|
|
680
|
+
ws.triggerMessage('{"type":"attached","idleMs":30000}');
|
|
681
|
+
|
|
682
|
+
await provider.startListening();
|
|
683
|
+
|
|
684
|
+
expect(MockWebSocket.instances).toHaveLength(1);
|
|
685
|
+
expect(ws.sent).toEqual(['{"type":"ping"}', '{"type":"start"}']);
|
|
686
|
+
expect(statuses).toEqual(['listening']);
|
|
687
|
+
pumpCapture(constantBuffer(0.1));
|
|
688
|
+
expect(ws.sent).toHaveLength(3);
|
|
689
|
+
// The call owns the socket now: the attach idle timer no longer closes it.
|
|
690
|
+
vi.advanceTimersByTime(600_000);
|
|
691
|
+
expect(ws.closeCalls).toEqual([]);
|
|
692
|
+
});
|
|
693
|
+
|
|
694
|
+
it('adopts a socket whose server ignored attach without sending start', async () => {
|
|
695
|
+
const transcripts: string[] = [];
|
|
696
|
+
const provider = new RuntypeVoiceProvider(attachConfig());
|
|
697
|
+
provider.onTranscript((_role, text) => transcripts.push(text));
|
|
698
|
+
provider.prewarm();
|
|
699
|
+
const ws = lastWs();
|
|
700
|
+
openAs(ws, 'runtype.bearer');
|
|
701
|
+
expect(ws.sent).toEqual([]);
|
|
702
|
+
|
|
703
|
+
await provider.startListening();
|
|
704
|
+
|
|
705
|
+
expect(MockWebSocket.instances).toHaveLength(1);
|
|
706
|
+
expect(ws.sent).toEqual([]);
|
|
707
|
+
ws.triggerMessage(JSON.stringify({ type: 'transcript_final', role: 'user', text: 'hi' }));
|
|
708
|
+
expect(transcripts).toEqual(['hi']);
|
|
709
|
+
});
|
|
710
|
+
|
|
711
|
+
it('opens a fresh socket when the attached one dropped before the click', async () => {
|
|
712
|
+
const provider = new RuntypeVoiceProvider(attachConfig());
|
|
713
|
+
provider.prewarm();
|
|
714
|
+
const attached = lastWs();
|
|
715
|
+
openAs(attached, 'runtype.attach');
|
|
716
|
+
attached.triggerClose(4408);
|
|
717
|
+
|
|
718
|
+
await provider.startListening();
|
|
719
|
+
|
|
720
|
+
expect(MockWebSocket.instances).toHaveLength(2);
|
|
721
|
+
expect(lastWs().url).toBe('wss://api.example.com/ws/agents/agent%2F1/voice');
|
|
722
|
+
expect(lastWs().protocols).toEqual(['runtype.bearer', 'ct_secret']);
|
|
723
|
+
});
|
|
724
|
+
|
|
725
|
+
it('abandons a handshake stalled for 5s and opens a fresh socket', async () => {
|
|
726
|
+
const provider = new RuntypeVoiceProvider(attachConfig());
|
|
727
|
+
provider.prewarm();
|
|
728
|
+
const stalled = lastWs();
|
|
729
|
+
|
|
730
|
+
const started = provider.startListening();
|
|
731
|
+
await vi.advanceTimersByTimeAsync(4_999);
|
|
732
|
+
expect(MockWebSocket.instances).toHaveLength(1);
|
|
733
|
+
await vi.advanceTimersByTimeAsync(1);
|
|
734
|
+
await started;
|
|
735
|
+
|
|
736
|
+
expect(stalled.closeCalls).toEqual([{ code: 1000, reason: 'prewarm released' }]);
|
|
737
|
+
expect(MockWebSocket.instances).toHaveLength(2);
|
|
738
|
+
expect(lastWs().protocols).toEqual(['runtype.bearer', 'ct_secret']);
|
|
739
|
+
});
|
|
740
|
+
|
|
741
|
+
it('uses an attach that completes while the click waits on it', async () => {
|
|
742
|
+
const provider = new RuntypeVoiceProvider(attachConfig());
|
|
743
|
+
provider.prewarm();
|
|
744
|
+
const ws = lastWs();
|
|
745
|
+
|
|
746
|
+
const started = provider.startListening();
|
|
747
|
+
await vi.advanceTimersByTimeAsync(1_000);
|
|
748
|
+
openAs(ws, 'runtype.attach');
|
|
749
|
+
await started;
|
|
750
|
+
|
|
751
|
+
expect(MockWebSocket.instances).toHaveLength(1);
|
|
752
|
+
expect(ws.sent).toContain('{"type":"start"}');
|
|
753
|
+
});
|
|
754
|
+
|
|
755
|
+
it('closes an unused attached socket at the idle window', () => {
|
|
756
|
+
const errors: Error[] = [];
|
|
757
|
+
const provider = new RuntypeVoiceProvider(attachConfig());
|
|
758
|
+
provider.onError((e) => errors.push(e));
|
|
759
|
+
provider.prewarm();
|
|
760
|
+
const ws = lastWs();
|
|
761
|
+
openAs(ws, 'runtype.attach');
|
|
762
|
+
|
|
763
|
+
vi.advanceTimersByTime(29_999);
|
|
764
|
+
expect(ws.closeCalls).toEqual([]);
|
|
765
|
+
vi.advanceTimersByTime(1);
|
|
766
|
+
expect(ws.closeCalls).toEqual([{ code: 1000, reason: 'prewarm released' }]);
|
|
767
|
+
expect(errors).toEqual([]);
|
|
768
|
+
});
|
|
769
|
+
|
|
770
|
+
it("follows the server's idle window from the attached reply", () => {
|
|
771
|
+
const provider = new RuntypeVoiceProvider(attachConfig({ attachIdleMs: 120_000 }));
|
|
772
|
+
provider.prewarm();
|
|
773
|
+
const ws = lastWs();
|
|
774
|
+
openAs(ws, 'runtype.attach');
|
|
775
|
+
vi.advanceTimersByTime(1_000);
|
|
776
|
+
ws.triggerMessage('{"type":"attached","idleMs":60000}');
|
|
777
|
+
|
|
778
|
+
vi.advanceTimersByTime(58_999);
|
|
779
|
+
expect(ws.closeCalls).toEqual([]);
|
|
780
|
+
vi.advanceTimersByTime(1);
|
|
781
|
+
expect(ws.closeCalls).toHaveLength(1);
|
|
782
|
+
});
|
|
783
|
+
|
|
784
|
+
it('closes the attaching socket when the call is hung up mid-handshake', async () => {
|
|
785
|
+
const provider = new RuntypeVoiceProvider(attachConfig());
|
|
786
|
+
provider.prewarm();
|
|
787
|
+
const ws = lastWs();
|
|
788
|
+
|
|
789
|
+
const started = provider.startListening();
|
|
790
|
+
await vi.advanceTimersByTimeAsync(100);
|
|
791
|
+
await provider.stopListening();
|
|
792
|
+
await started;
|
|
793
|
+
|
|
794
|
+
expect(ws.closeCalls).toEqual([{ code: 1000, reason: 'prewarm released' }]);
|
|
795
|
+
expect(MockWebSocket.instances).toHaveLength(1);
|
|
796
|
+
expect(provider.isBargeInActive()).toBe(false);
|
|
797
|
+
});
|
|
798
|
+
|
|
799
|
+
it('disconnect closes an attached socket', async () => {
|
|
800
|
+
const provider = new RuntypeVoiceProvider(attachConfig());
|
|
801
|
+
provider.prewarm();
|
|
802
|
+
const ws = lastWs();
|
|
803
|
+
openAs(ws, 'runtype.attach');
|
|
804
|
+
await provider.disconnect();
|
|
805
|
+
expect(ws.closeCalls).toEqual([{ code: 1000, reason: 'prewarm released' }]);
|
|
806
|
+
});
|
|
807
|
+
});
|
|
538
808
|
});
|
package/src/welcome.test.ts
CHANGED
|
@@ -274,3 +274,26 @@ describe("resolveConversationState", () => {
|
|
|
274
274
|
expect(resolveConversationState([])).toBe("empty");
|
|
275
275
|
});
|
|
276
276
|
});
|
|
277
|
+
|
|
278
|
+
describe("V5 welcome layout defaults", () => {
|
|
279
|
+
it("centers the empty greeting with concise copy and dismisses every variant", () => {
|
|
280
|
+
for (const variant of ["card", "hero", "none"] as const) {
|
|
281
|
+
const resolved = resolveWelcomeConfig(config({ future: { v5Defaults: true }, welcome: { variant } }));
|
|
282
|
+
expect(resolved).toMatchObject({ layout: "centered", align: "center", title: "What can I help with?", subtitle: "", dismiss: "on-first-message", anchor: "bottom" });
|
|
283
|
+
expect(isWelcomeVisible(resolved, [userMessage])).toBe(false);
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it("keeps explicit fields and legacy copy above V5 defaults", () => {
|
|
288
|
+
const resolved = resolveWelcomeConfig(config({ future: { v5Defaults: true }, copy: { welcomeTitle: "Legacy", welcomeSubtitle: "Scope" }, welcome: { layout: "top", variant: "hero", dismiss: "never", anchor: "bottom", align: "start" } }), true);
|
|
289
|
+
expect(resolved).toMatchObject({ title: "Legacy", subtitle: "Scope", layout: "top", dismiss: "never", anchor: "bottom", align: "start" });
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it("makes centered fullscreen geometry available in either default mode", () => {
|
|
293
|
+
for (const v5Defaults of [false, true]) {
|
|
294
|
+
const resolved = resolveWelcomeConfig(config({ future: { v5Defaults }, welcome: { layout: "centered" } }), true);
|
|
295
|
+
expect(resolved.anchor).toBe("center");
|
|
296
|
+
expect(resolveWelcomeConfig(config({ future: { v5Defaults }, welcome: { layout: "centered", anchor: "bottom" } }), true).anchor).toBe("bottom");
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
});
|
package/src/welcome.ts
CHANGED
|
@@ -55,6 +55,7 @@ export interface ResolvedWelcomeConfig {
|
|
|
55
55
|
* Optional on the interface, always populated by `resolveWelcomeConfig`:
|
|
56
56
|
* a plugin literal built against an older minor must still type-check.
|
|
57
57
|
*/
|
|
58
|
+
layout?: "top" | "centered";
|
|
58
59
|
anchor?: AgentWidgetWelcomeAnchor;
|
|
59
60
|
/** Percentage string; validated, falls back to "44%". */
|
|
60
61
|
anchorComposerTop?: string;
|
|
@@ -80,10 +81,14 @@ const warnOnce = (config: AgentWidgetConfig | undefined, messages: string[]) =>
|
|
|
80
81
|
* `copy` alias, else the default above.
|
|
81
82
|
*/
|
|
82
83
|
export const resolveWelcomeConfig = (
|
|
83
|
-
config?: AgentWidgetConfig
|
|
84
|
+
config?: AgentWidgetConfig,
|
|
85
|
+
fullscreen = config?.launcher?.fullHeight === true &&
|
|
86
|
+
!config.launcher.sidebarMode && config.launcher.mountMode !== "docked"
|
|
84
87
|
): ResolvedWelcomeConfig => {
|
|
85
88
|
const welcome = config?.welcome;
|
|
86
89
|
const copy = config?.copy;
|
|
90
|
+
const v5 = config?.future?.v5Defaults === true;
|
|
91
|
+
const layout = welcome?.layout ?? (v5 ? "centered" : "top");
|
|
87
92
|
|
|
88
93
|
const variant: AgentWidgetWelcomeVariant = isSet(welcome, "variant")
|
|
89
94
|
? welcome!.variant!
|
|
@@ -91,13 +96,14 @@ export const resolveWelcomeConfig = (
|
|
|
91
96
|
? "none"
|
|
92
97
|
: "card";
|
|
93
98
|
|
|
94
|
-
//
|
|
99
|
+
// Legacy V4 heroes force dismissal; opting into layout makes it configurable.
|
|
100
|
+
// A hero remains the greeting, so its separate message is always suppressed.
|
|
95
101
|
const dismiss: AgentWidgetWelcomeDismiss =
|
|
96
|
-
variant === "hero"
|
|
102
|
+
variant === "hero" && !v5 && welcome?.layout === undefined
|
|
97
103
|
? "on-first-message"
|
|
98
104
|
: isSet(welcome, "dismiss")
|
|
99
105
|
? welcome!.dismiss!
|
|
100
|
-
: "never";
|
|
106
|
+
: v5 || variant === "hero" ? "on-first-message" : "never";
|
|
101
107
|
|
|
102
108
|
const conflicts: string[] = [];
|
|
103
109
|
if (isSet(welcome, "title") && isSet(copy, "welcomeTitle")) {
|
|
@@ -120,14 +126,14 @@ export const resolveWelcomeConfig = (
|
|
|
120
126
|
'welcome.message is ignored when welcome.variant is "hero": the hero is the greeting.'
|
|
121
127
|
);
|
|
122
128
|
}
|
|
123
|
-
if (variant === "hero" && isSet(welcome, "dismiss") && welcome!.dismiss !== "on-first-message") {
|
|
129
|
+
if (!v5 && welcome?.layout === undefined && variant === "hero" && isSet(welcome, "dismiss") && welcome!.dismiss !== "on-first-message") {
|
|
124
130
|
conflicts.push(
|
|
125
131
|
'welcome.dismiss is pinned to "on-first-message" when welcome.variant is "hero".'
|
|
126
132
|
);
|
|
127
133
|
}
|
|
128
134
|
|
|
129
135
|
const anchor: AgentWidgetWelcomeAnchor =
|
|
130
|
-
welcome?.anchor === "
|
|
136
|
+
welcome?.anchor ?? (layout === "centered" && fullscreen ? "center" : "bottom");
|
|
131
137
|
const anchorTopValid = parseAnchorFraction(welcome?.anchorComposerTop) !== null;
|
|
132
138
|
if (isSet(welcome, "anchorComposerTop") && !anchorTopValid) {
|
|
133
139
|
conflicts.push(
|
|
@@ -147,16 +153,16 @@ export const resolveWelcomeConfig = (
|
|
|
147
153
|
return {
|
|
148
154
|
title: isSet(welcome, "title")
|
|
149
155
|
? welcome!.title!
|
|
150
|
-
: copy?.welcomeTitle ?? DEFAULT_WELCOME_TITLE,
|
|
156
|
+
: copy?.welcomeTitle ?? (v5 ? "What can I help with?" : DEFAULT_WELCOME_TITLE),
|
|
151
157
|
kicker: welcome?.kicker ?? "",
|
|
152
158
|
subtitle: isSet(welcome, "subtitle")
|
|
153
159
|
? welcome!.subtitle!
|
|
154
|
-
: copy?.welcomeSubtitle ?? DEFAULT_WELCOME_SUBTITLE,
|
|
160
|
+
: copy?.welcomeSubtitle ?? (v5 ? "" : DEFAULT_WELCOME_SUBTITLE),
|
|
155
161
|
icon: welcome?.icon,
|
|
156
162
|
align:
|
|
157
163
|
welcome?.align === "start" || welcome?.align === "center"
|
|
158
164
|
? welcome.align
|
|
159
|
-
: undefined,
|
|
165
|
+
: layout === "centered" ? "center" : undefined,
|
|
160
166
|
iconPlacement:
|
|
161
167
|
welcome?.icon && typeof welcome.icon !== "function" &&
|
|
162
168
|
welcome.icon.placement === "inline"
|
|
@@ -165,6 +171,7 @@ export const resolveWelcomeConfig = (
|
|
|
165
171
|
variant,
|
|
166
172
|
dismiss,
|
|
167
173
|
message: variant === "hero" ? undefined : welcome?.message,
|
|
174
|
+
layout,
|
|
168
175
|
anchor,
|
|
169
176
|
anchorComposerTop: anchorTopValid
|
|
170
177
|
? welcome!.anchorComposerTop!
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{a as E,g as P}from"./chunk-NQLDCJAP.js";import{a as x}from"./chunk-7NQ4ZTHF.js";import{b as q,c as $}from"./chunk-Q735PSZL.js";import{a}from"./chunk-76IELU7F.js";var R=()=>{},z=t=>{R=t.webMcpToolTitle},I=t=>R(t);import{ChevronDown as Y,ChevronUp as ee,ShieldAlert as te,ShieldCheck as K,ShieldX as _}from"lucide";var k=new Map,B=t=>{let o=(t.startsWith(E)?t.slice(E.length):t).replace(/([a-z0-9])([A-Z])/g,"$1 $2").split(/[_\-\s.]+/).filter(Boolean);if(o.length===0)return t;let e=o.join(" ").toLowerCase();return e.charAt(0).toUpperCase()+e.slice(1)},F=t=>t?.approval!==!1?t?.approval:void 0,X=(t,r)=>{let o=F(r)?.detailsDisplay??"collapsed";return k.get(t)??o==="expanded"},j=(t,r,o)=>{let e=F(o);t.setAttribute("aria-expanded",r?"true":"false");let u=t.querySelector("[data-approval-details-label]");u&&(u.textContent=r?e?.hideDetailsLabel??"Hide details":e?.showDetailsLabel??"Show details");let i=t.querySelector("[data-approval-details-chevron]");if(i){i.innerHTML="";let g=x(r?ee:Y,14,"currentColor",2);g&&i.appendChild(g)}},V=(t,r,o)=>{let e=r.querySelector('button[data-bubble-type="approval"]'),u=r.querySelector("[data-approval-details]");if(!e||!u)return;let i=X(t,o);j(e,i,o),u.style.display=i?"":"none"};var Z=(t,r)=>{let o=t.approval,e=r?.approval!==!1?r?.approval:void 0,u=o?.status==="pending",i=a("div",["persona-approval-bubble","persona-w-full","persona-rounded-2xl","persona-border","persona-shadow-sm","persona-overflow-hidden"].join(" "));if(i.id=`bubble-${t.id}`,i.setAttribute("data-message-id",t.id),i.style.backgroundColor=e?.backgroundColor??"var(--persona-approval-bg, #fefce8)",i.style.borderColor=e?.borderColor??"var(--persona-approval-border, #fef08a)",i.style.boxShadow=e?.shadow!==void 0?e.shadow.trim()===""?"none":e.shadow:"var(--persona-approval-shadow, 0 5px 15px rgba(15, 23, 42, 0.08))",!o)return i;let g=a("div","persona-flex persona-items-start persona-gap-3 persona-px-4 persona-py-3"),l=a("div","persona-flex-shrink-0 persona-mt-0.5");l.setAttribute("data-approval-icon","true");let v=o.status==="denied"?_:o.status==="timeout"?te:K,C=o.status==="approved"?"var(--persona-feedback-success, #16a34a)":o.status==="denied"?"var(--persona-feedback-error, #dc2626)":o.status==="timeout"?"var(--persona-feedback-warning, #ca8a04)":e?.titleColor??"currentColor",A=x(v,20,C,2);A&&l.appendChild(A);let m=a("div","persona-flex-1 persona-min-w-0"),f=a("div","persona-flex persona-items-center persona-gap-2"),w=a("span","persona-text-sm persona-font-medium persona-text-persona-primary");if(e?.titleColor&&(w.style.color=e.titleColor),w.textContent=e?.title??"Approval Required",f.appendChild(w),!u){let s=a("span","persona-inline-flex persona-items-center persona-px-2 persona-py-0.5 persona-rounded-full persona-text-xs persona-font-medium");s.setAttribute("data-approval-status",o.status),o.status==="approved"?(s.style.backgroundColor="var(--persona-palette-colors-success-100, #dcfce7)",s.style.color="var(--persona-palette-colors-success-700, #15803d)",s.textContent="Approved"):o.status==="denied"?(s.style.backgroundColor="var(--persona-palette-colors-error-100, #fee2e2)",s.style.color="var(--persona-palette-colors-error-700, #b91c1c)",s.textContent="Denied"):o.status==="timeout"&&(s.style.backgroundColor="var(--persona-palette-colors-warning-100, #fef3c7)",s.style.color="var(--persona-palette-colors-warning-700, #b45309)",s.textContent="Timeout"),f.appendChild(s)}m.appendChild(f);let y=o.toolType==="webmcp"||o.toolName.startsWith(E)?I(o.toolName):void 0,N=e?.formatDescription?.({toolName:o.toolName,toolType:o.toolType,description:o.description,parameters:o.parameters,...y?{displayTitle:y}:{},...o.reason?{reason:o.reason}:{}}),D=!o.toolName,H=N||(D?o.description:`The assistant wants to use \u201C${y??B(o.toolName)}\u201D.`),h=a("p","persona-text-sm persona-mt-0.5 persona-text-persona-muted");if(h.setAttribute("data-approval-summary","true"),e?.descriptionColor&&(h.style.color=e.descriptionColor),h.textContent=H,m.appendChild(h),o.reason){let s=a("p","persona-text-sm persona-mt-1 persona-text-persona-muted");s.setAttribute("data-approval-reason","true"),e?.reasonColor?s.style.color=e.reasonColor:e?.descriptionColor&&(s.style.color=e.descriptionColor);let n=a("span","persona-font-medium");n.textContent=`${e?.reasonLabel??"Agent's stated reason:"} `,s.appendChild(n),s.appendChild(document.createTextNode(o.reason)),m.appendChild(s)}let W=e?.detailsDisplay??"collapsed",M=!!o.description&&!D,S=M||!!o.parameters;if(W!=="hidden"&&S){let s=X(t.id,r),n=a("button","persona-inline-flex persona-items-center persona-gap-1 persona-mt-1 persona-p-0 persona-border-none persona-bg-transparent persona-text-xs persona-font-medium persona-cursor-pointer persona-text-persona-muted");n.type="button",n.setAttribute("data-expand-header","true"),n.setAttribute("data-bubble-type","approval"),e?.descriptionColor&&(n.style.color=e.descriptionColor);let p=a("span");p.setAttribute("data-approval-details-label","true");let b=a("span","persona-inline-flex persona-items-center");b.setAttribute("data-approval-details-chevron","true"),n.append(p,b),j(n,s,r),m.appendChild(n);let c=a("div");if(c.setAttribute("data-approval-details","true"),c.style.display=s?"":"none",M){let d=a("p","persona-text-sm persona-mt-1 persona-text-persona-muted");e?.descriptionColor&&(d.style.color=e.descriptionColor),d.textContent=o.description,c.appendChild(d)}if(o.parameters){let d=a("pre","persona-mt-2 persona-text-xs persona-p-2 persona-rounded persona-overflow-x-auto persona-max-h-32 persona-bg-persona-container persona-text-persona-primary");e?.parameterBackgroundColor&&(d.style.backgroundColor=e.parameterBackgroundColor),e?.parameterTextColor&&(d.style.color=e.parameterTextColor),d.style.fontSize="0.75rem",d.style.lineHeight="1rem",d.textContent=P(o.parameters),c.appendChild(d)}m.appendChild(c)}if(u){let s=a("div","persona-flex persona-gap-2 persona-mt-2");s.setAttribute("data-approval-buttons","true");let n=a("button","persona-inline-flex persona-items-center persona-px-3 persona-py-1.5 persona-rounded-md persona-text-xs persona-font-medium persona-border-none persona-cursor-pointer");n.type="button",n.style.backgroundColor=e?.approveButtonColor??"var(--persona-approval-approve-bg, #22c55e)",n.style.color=e?.approveButtonTextColor??"#ffffff",n.setAttribute("data-approval-action","approve");let p=x(K,14,e?.approveButtonTextColor??"#ffffff",2);p&&(p.style.marginRight="4px",n.appendChild(p));let b=document.createTextNode(e?.approveLabel??"Approve");n.appendChild(b);let c=a("button","persona-inline-flex persona-items-center persona-px-3 persona-py-1.5 persona-rounded-md persona-text-xs persona-font-medium persona-cursor-pointer");c.type="button",c.style.backgroundColor=e?.denyButtonColor??"transparent",c.style.color=e?.denyButtonTextColor??"var(--persona-feedback-error, #dc2626)",c.style.border=`1px solid ${e?.denyButtonTextColor?e.denyButtonTextColor:"var(--persona-palette-colors-error-200, #fca5a5)"}`,c.setAttribute("data-approval-action","deny");let d=x(_,14,e?.denyButtonTextColor??"var(--persona-feedback-error, #dc2626)",2);d&&(d.style.marginRight="4px",c.appendChild(d));let L=document.createTextNode(e?.denyLabel??"Deny");c.appendChild(L),s.append(n,c),m.appendChild(s)}return g.append(l,m),i.appendChild(g),i};import{Ban as oe,ChevronDown as G,ShieldCheck as ne}from"lucide";var re=()=>({keyHandlers:new Map,popovers:new Map,pendingOrder:[],latestPendingApprovalId:null}),J=(t,r)=>{let o=t.keyHandlers.get(r);o&&(document.removeEventListener("keydown",o),t.keyHandlers.delete(r));let e=t.popovers.get(r);e&&(e.destroy(),t.popovers.delete(r))},T=(t,r)=>{J(t,r);let o=t.pendingOrder.indexOf(r);o!==-1&&t.pendingOrder.splice(o,1),t.latestPendingApprovalId===r&&(t.latestPendingApprovalId=t.pendingOrder.length?t.pendingOrder[t.pendingOrder.length-1]:null)},ae=t=>t?.approval!==!1?t?.approval:void 0,se=(t,r)=>{let o=r?.detailsDisplay??"collapsed";return k.get(t)??o==="expanded"},U=t=>{let r=a("span","persona-approval-kbd");return r.textContent=t,r},pe=(t,r)=>{let o=a("span","persona-approval-title");r?.titleColor&&(o.style.color=r.titleColor);let u=t.toolType==="webmcp"||t.toolName.startsWith(E)?I(t.toolName):void 0,i=r?.formatDescription?.({toolName:t.toolName,toolType:t.toolType,description:t.description??"",parameters:t.parameters,...u?{displayTitle:u}:{},...t.reason?{reason:t.reason}:{}});if(i)return o.textContent=i,o;let g=u??B(t.toolName),l=t.toolType&&t.toolType!=="webmcp"?t.toolType:null;o.append("The assistant wants to use ");let v=document.createElement("strong");if(v.textContent=g,o.appendChild(v),l){o.append(" from ");let C=document.createElement("strong");C.textContent=l,o.appendChild(C)}return o},le=t=>{let r=a("div","persona-approval-resolved"),o=x(oe,15,"currentColor",2);o&&r.appendChild(o);let e=a("span","persona-approval-resolved-name");return e.textContent=t.toolName?B(t.toolName):"Tool",r.append(e,document.createTextNode(t.status==="timeout"?" timed out":" denied")),r},ie=(t,r,o,e,u,i,g)=>{let l=a("div","persona-approval-card persona-shadow-sm");l.id=`bubble-${r.id}`,l.setAttribute("data-message-id",r.id),l.setAttribute("data-bubble-type","approval"),e?.backgroundColor&&(l.style.background=e.backgroundColor),e?.borderColor&&(l.style.borderColor=e.borderColor),e?.shadow!==void 0&&(l.style.boxShadow=e.shadow.trim()===""?"none":e.shadow);let v=e?.detailsDisplay??"collapsed",C=!!o.description&&v!=="hidden",A=o.parameters!=null&&v!=="hidden",m=C||A,f=m&&se(r.id,e),w=e?.showDetailsLabel??"Show details",O=e?.hideDetailsLabel??"Hide details",y=a("button","persona-approval-head");y.type="button",m?(y.setAttribute("data-action","toggle-params"),y.setAttribute("aria-expanded",f?"true":"false"),y.setAttribute("aria-label",f?O:w)):y.setAttribute("data-static","true");let N=a("span","persona-approval-logo"),D=x(ne,16,"currentColor",2);D&&N.appendChild(D),y.appendChild(N);let H=pe(o,e);if(m){let n=a("span","persona-approval-toggle");n.setAttribute("aria-hidden","true");let p=x(G,14,"currentColor",2);p&&n.appendChild(p),H.append(" "),H.appendChild(n)}y.appendChild(H),l.appendChild(y);let h=a("div","persona-approval-body");if(m){let n=a("div","persona-approval-details");if(n.setAttribute("data-role","params"),n.hidden=!f,C){let p=a("p","persona-approval-desc");e?.descriptionColor&&(p.style.color=e.descriptionColor),p.textContent=o.description,n.appendChild(p)}if(A){let p=a("pre","persona-approval-params");e?.parameterBackgroundColor&&(p.style.background=e.parameterBackgroundColor),e?.parameterTextColor&&(p.style.color=e.parameterTextColor),p.textContent=P(o.parameters),n.appendChild(p)}h.appendChild(n)}if(o.reason){let n=a("p","persona-approval-reason");e?.reasonColor?n.style.color=e.reasonColor:e?.descriptionColor&&(n.style.color=e.descriptionColor);let p=a("span","persona-approval-reason-label");p.textContent=`${e?.reasonLabel??"Agent's stated reason:"} `,n.append(p,document.createTextNode(o.reason)),h.appendChild(n)}let W=a("div","persona-approval-actions"),M=null,S=n=>{e?.approveButtonColor&&(n.style.background=e.approveButtonColor),e?.approveButtonTextColor&&(n.style.color=e.approveButtonTextColor)},s=a("button","persona-approval-deny");if(s.type="button",s.setAttribute("data-action","deny"),e?.denyButtonColor&&(s.style.background=e.denyButtonColor),e?.denyButtonTextColor&&(s.style.color=e.denyButtonTextColor),s.append(e?.denyLabel??"Deny"),g){let n=a("div","persona-approval-split"),p=a("button","persona-approval-primary");p.type="button",p.setAttribute("data-action","always"),S(p),p.append(e?.approveLabel??"Always allow",U("\u23CE"));let b=a("button","persona-approval-caret");b.type="button",b.setAttribute("data-action","toggle-menu"),b.setAttribute("aria-label","More options"),S(b);let c=x(G,15,"currentColor",2);c&&b.appendChild(c),n.append(p,b),W.append(n,s),s.append(U("Esc"));let d=a("div","persona-approval-menu"),L=a("button","persona-approval-menu-item");L.type="button",L.append("Allow once",U("\u2318\u23CE")),d.appendChild(L),M=q({anchor:n,content:d,placement:"bottom-start",matchAnchorWidth:!0}),t.popovers.set(r.id,M),L.addEventListener("click",()=>{T(t,r.id),u()})}else{let n=a("button","persona-approval-primary persona-approval-primary--solo");n.type="button",n.setAttribute("data-action","allow"),S(n),n.append(e?.approveLabel??"Allow"),W.append(n,s)}return h.appendChild(W),l.appendChild(h),l.addEventListener("click",n=>{let p=n.target instanceof Element?n.target.closest("[data-action]"):null;if(!p)return;let b=p.getAttribute("data-action");if(b==="toggle-params"){let c=l.querySelector('[data-role="params"]');if(c){let d=c.hidden;c.hidden=!d,y.setAttribute("aria-expanded",d?"true":"false"),y.setAttribute("aria-label",d?O:w),k.set(r.id,d)}return}if(b==="toggle-menu"){M?.toggle();return}if(b==="always"){T(t,r.id),u({remember:!0});return}if(b==="allow"){T(t,r.id),u();return}if(b==="deny"){T(t,r.id),i();return}}),l},Q=()=>{let t=re();return{plugin:{id:"persona-built-in-approval",renderApproval:({message:e,approve:u,deny:i,config:g})=>{let l=e?.approval;if(!l)return null;let v=ae(g);if(l.status!=="pending"){if(T(t,e.id),l.status==="approved"){let m=document.createElement("div");return m.style.display="none",m}return le(l)}J(t,e.id);let C=v?.enableAlwaysAllow===!0,A=ie(t,e,l,v,u,i,C);if(C){t.pendingOrder.includes(e.id)||t.pendingOrder.push(e.id),t.latestPendingApprovalId=t.pendingOrder[t.pendingOrder.length-1];let m=f=>{$(f)||e.id===t.latestPendingApprovalId&&(f.key!=="Escape"&&f.key!=="Enter"||(f.preventDefault(),f.stopImmediatePropagation(),T(t,e.id),f.key==="Escape"?i():f.metaKey||f.ctrlKey?u():u({remember:!0})))};t.keyHandlers.set(e.id,m),document.addEventListener("keydown",m)}return A}},teardown:()=>{for(let e of[...t.keyHandlers.keys(),...t.popovers.keys()])T(t,e);t.latestPendingApprovalId=null}}};export{k as approvalDetailsExpansionState,Z as createApprovalBubble,Q as createBuiltInApprovalPlugin,B as humanizeToolName,z as initApprovalUi,V as updateApprovalDetailsUI};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{a as m}from"./chunk-5EIIHQLQ.js";var g=16e3,y=24e3,k=4096,C=4,S=1380533830;function E(r){return r.byteLength>=44&&new DataView(r).getUint32(0,!1)===S?new Uint8Array(r,44):new Uint8Array(r)}function V(r){let t=r.replace(/\/+$/,"");return/^wss?:\/\//i.test(t)?t:/^https?:\/\//i.test(t)?t.replace(/^http/i,"ws"):`${typeof window<"u"&&window.location?.protocol==="https:"?"wss:":"ws:"}//${t}`}var d=class{constructor(t){this.config=t;this.type="runtype";this.ws=null;this.captureContext=null;this.levelCallbacks=[];this.mediaStream=null;this.sourceNode=null;this.processor=null;this.playback=null;this.callLive=!1;this.isSpeaking=!1;this.callGeneration=0;this.intentionalClose=!1;this.resultCallbacks=[];this.errorCallbacks=[];this.statusCallbacks=[];this.transcriptCallbacks=[];this.metricsCallbacks=[]}async connect(){}async startListening(){if(this.callLive)return;let t=this.config?.agentId,e=this.config?.clientToken,i=this.config?.host;if(!t)throw new Error("Runtype voice requires an agentId");if(!e)throw new Error("Runtype voice requires a clientToken");if(!i)throw new Error("Runtype voice requires a host (or widget apiUrl)");let s=++this.callGeneration;this.intentionalClose=!1,this.callLive=!0;try{let o=await navigator.mediaDevices.getUserMedia({audio:{sampleRate:g,channelCount:1,echoCancellation:!0}});if(s!==this.callGeneration){o.getTracks().forEach(a=>a.stop());return}this.mediaStream=o;let p=window.AudioContext||window.webkitAudioContext,u=new p({sampleRate:g});u.state==="suspended"&&await u.resume().catch(()=>{}),this.captureContext=u;let l=this.config?.createPlaybackEngine?await this.config.createPlaybackEngine():new m(y);if(s!==this.callGeneration){l.destroy(),o.getTracks().forEach(a=>a.stop()),u.close().catch(()=>{});return}this.playback=l,l.onFinished(()=>{s===this.callGeneration&&(this.isSpeaking=!1,this.ws&&this.ws.readyState===WebSocket.OPEN&&this.emitStatus("listening"))});let f=`${V(i)}/ws/agents/${encodeURIComponent(t)}/voice`,n=new WebSocket(f,["runtype.bearer",e]);n.binaryType="arraybuffer",this.ws=n,n.onopen=()=>{s===this.callGeneration&&(this.emitStatus("listening"),this.startCapture(u,o,n,s))},n.onmessage=a=>this.handleMessage(a,s),n.onerror=()=>{s===this.callGeneration&&(this.emitError(new Error("Voice connection failed")),this.emitStatus("error"),this.cleanup())},n.onclose=a=>{if(this.intentionalClose){this.intentionalClose=!1;return}if(s===this.callGeneration){if(a.code!==1e3){let c=a.code?` (code ${a.code})`:"";this.emitError(new Error(`Voice connection closed${c}`)),this.emitStatus("error")}else this.emitStatus("idle");this.cleanup()}}}catch(o){throw this.cleanup(),this.emitError(o),this.emitStatus("error"),o}}async stopListening(){this.cleanup(),this.emitStatus("idle")}async disconnect(){this.cleanup(),this.emitStatus("disconnected"),this.resultCallbacks=[],this.errorCallbacks=[],this.statusCallbacks=[],this.transcriptCallbacks=[],this.metricsCallbacks=[]}stopPlayback(){this.playback&&this.playback.flush(),this.isSpeaking=!1,this.ws&&this.ws.readyState===WebSocket.OPEN&&this.emitStatus("listening")}getInterruptionMode(){return"barge-in"}isBargeInActive(){return this.callLive}onLevel(t){this.levelCallbacks.push(t)}async deactivateBargeIn(){this.cleanup(),this.emitStatus("idle")}startCapture(t,e,i,s){let o=t.createMediaStreamSource(e);this.sourceNode=o;let p=t.createScriptProcessor(k,1,1);this.processor=p,p.onaudioprocess=u=>{if(s!==this.callGeneration||i.readyState!==WebSocket.OPEN)return;let l=u.inputBuffer.getChannelData(0),f=new Int16Array(l.length),n=0;for(let a=0;a<l.length;a++){let c=Math.max(-1,Math.min(1,l[a]));n+=c*c,f[a]=c<0?c*32768:c*32767}if(i.send(f.buffer),this.levelCallbacks.length>0){let a=Math.sqrt(n/l.length),c=Math.max(0,Math.min(1,a*C));for(let b of this.levelCallbacks)b(c)}},o.connect(p),p.connect(t.destination)}handleMessage(t,e){if(e!==this.callGeneration)return;if(t.data instanceof ArrayBuffer){this.handleAudioFrame(t.data,e);return}let i;try{i=JSON.parse(t.data)}catch{return}switch(i.type){case"transcript_interim":this.emitStatus("listening"),this.emitTranscript("user",i.text??"",!1);break;case"transcript_final":{let s=i.role==="assistant"?"assistant":"user";this.emitStatus(s==="user"?"processing":"speaking"),this.emitTranscript(s,i.text??"",!0);break}case"audio_end":this.playback?this.playback.markStreamEnd():(this.isSpeaking=!1,this.emitStatus("listening"));break;case"metrics":this.emitMetrics({llmMs:i.llm_ms,ttsMs:i.tts_ms,firstAudioMs:i.first_audio_ms,totalMs:i.total_ms});break;case"error":this.emitError(new Error(i.error||"Voice error")),this.emitStatus("error");break}}handleAudioFrame(t,e){if(e!==this.callGeneration||!this.playback)return;let i=E(t);i.length!==0&&(this.isSpeaking||(this.isSpeaking=!0,this.emitStatus("speaking")),this.playback.enqueue(i))}cleanup(){if(this.callGeneration+=1,this.callLive=!1,this.isSpeaking=!1,this.processor&&(this.processor.onaudioprocess=null,this.processor.disconnect(),this.processor=null),this.sourceNode&&(this.sourceNode.disconnect(),this.sourceNode=null),this.mediaStream&&(this.mediaStream.getTracks().forEach(t=>t.stop()),this.mediaStream=null),this.captureContext&&(this.captureContext.close().catch(()=>{}),this.captureContext=null),this.playback&&(this.playback.destroy(),this.playback=null),this.ws){this.intentionalClose=!0;try{this.ws.close(1e3,"client ended call")}catch{}this.ws=null}}onResult(t){this.resultCallbacks.push(t)}onError(t){this.errorCallbacks.push(t)}onStatusChange(t){this.statusCallbacks.push(t)}onTranscript(t){this.transcriptCallbacks.push(t)}onMetrics(t){this.metricsCallbacks.push(t)}emitStatus(t){this.statusCallbacks.forEach(e=>e(t))}emitError(t){this.errorCallbacks.forEach(e=>e(t))}emitTranscript(t,e,i){this.transcriptCallbacks.forEach(s=>s(t,e,i))}emitMetrics(t){this.metricsCallbacks.forEach(e=>e(t))}};var h=class{constructor(t={}){this.config=t;this.type="browser";this.recognition=null;this.resultCallbacks=[];this.errorCallbacks=[];this.statusCallbacks=[];this.isListening=!1;this.w=typeof window<"u"?window:void 0}async connect(){this.statusCallbacks.forEach(t=>t("connected"))}async startListening(){try{if(this.isListening)throw new Error("Already listening");if(!this.w)throw new Error("Window object not available");let t=this.w.SpeechRecognition||this.w.webkitSpeechRecognition;if(!t)throw new Error("Browser speech recognition not supported");this.recognition=new t,this.recognition.lang=this.config?.language||"en-US",this.recognition.continuous=this.config?.continuous||!1,this.recognition.interimResults=!0,this.recognition.onresult=e=>{let i=Array.from(e.results).map(o=>o[0]).map(o=>o.transcript).join(""),s=e.results[e.results.length-1].isFinal;this.resultCallbacks.forEach(o=>o({text:i,confidence:s?.8:.5,provider:"browser"})),s&&!this.config?.continuous&&this.stopListening()},this.recognition.onerror=e=>{this.errorCallbacks.forEach(i=>i(new Error(e.error))),this.statusCallbacks.forEach(i=>i("error"))},this.recognition.onstart=()=>{this.isListening=!0,this.statusCallbacks.forEach(e=>e("listening"))},this.recognition.onend=()=>{this.isListening=!1,this.statusCallbacks.forEach(e=>e("idle"))},this.recognition.start()}catch(t){throw this.errorCallbacks.forEach(e=>e(t)),this.statusCallbacks.forEach(e=>e("error")),t}}async stopListening(){this.recognition&&(this.recognition.stop(),this.recognition=null),this.isListening=!1,this.statusCallbacks.forEach(t=>t("idle"))}onResult(t){this.resultCallbacks.push(t)}onError(t){this.errorCallbacks.push(t)}onStatusChange(t){this.statusCallbacks.push(t)}async disconnect(){await this.stopListening(),this.statusCallbacks.forEach(t=>t("disconnected"))}static isSupported(){return"SpeechRecognition"in window||"webkitSpeechRecognition"in window}};function v(r){switch(r.type){case"runtype":if(!r.runtype)throw new Error("Runtype voice provider requires configuration");return new d(r.runtype);case"browser":if(!h.isSupported())throw new Error("Browser speech recognition not supported");return new h(r.browser||{});case"custom":{let t=r.custom;if(!t)throw new Error("Custom voice provider requires a `custom` provider instance or factory");let e=typeof t=="function"?t():t;if(!e||typeof e.startListening!="function")throw new Error("Custom voice provider `custom` must be a VoiceProvider (or a factory returning one)");return e}default:throw new Error(`Unknown voice provider type: ${r.type}`)}}function w(r){if(r?.type==="custom"&&r.custom)return v({type:"custom",custom:r.custom});if(r?.type==="runtype"&&r.runtype)return v({type:"runtype",runtype:r.runtype});if(h.isSupported())return v({type:"browser",browser:r?.browser||{language:"en-US"}});throw new Error("No supported voice providers available")}function P(r){try{return w(r),!0}catch{return!1}}export{h as BrowserVoiceProvider,d as RuntypeVoiceProvider,w as createBestAvailableVoiceProvider,v as createVoiceProvider,P as isVoiceSupported};
|