@frockbot/plugin-voice 0.3.21 → 0.3.23
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/package.json +7 -7
- package/src/client/index.test.ts +29 -1
- package/src/client/index.ts +27 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frockbot/plugin-voice",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.23",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -26,12 +26,12 @@
|
|
|
26
26
|
"typecheck": "vue-tsc --noEmit -p tsconfig.json"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@frockbot/client-core": "0.3.
|
|
30
|
-
"@frockbot/client-ui": "0.3.
|
|
31
|
-
"@frockbot/kernel-contracts": "0.3.
|
|
32
|
-
"@frockbot/plugin-flock": "0.3.
|
|
33
|
-
"@frockbot/plugin-shell": "0.3.
|
|
34
|
-
"@frockbot/protocol": "0.3.
|
|
29
|
+
"@frockbot/client-core": "0.3.23",
|
|
30
|
+
"@frockbot/client-ui": "0.3.23",
|
|
31
|
+
"@frockbot/kernel-contracts": "0.3.23",
|
|
32
|
+
"@frockbot/plugin-flock": "0.3.23",
|
|
33
|
+
"@frockbot/plugin-shell": "0.3.23",
|
|
34
|
+
"@frockbot/protocol": "0.3.23",
|
|
35
35
|
"cordis": "4.0.0-rc.8",
|
|
36
36
|
"vue": "3.5.41"
|
|
37
37
|
},
|
package/src/client/index.test.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
type ClientSurfaceRegistry,
|
|
6
6
|
} from "@frockbot/client-core";
|
|
7
7
|
import { computed, ref } from "vue";
|
|
8
|
+
import { frockBotWebDataKey } from "@frockbot/plugin-shell/shared";
|
|
8
9
|
import { voiceClientPlugin } from "./index.js";
|
|
9
10
|
import { voiceClientStateKey, type VoiceClientStateV1 } from "./state.js";
|
|
10
11
|
|
|
@@ -26,9 +27,22 @@ function surfaceRegistry(): ClientSurfaceRegistry {
|
|
|
26
27
|
|
|
27
28
|
function mount(
|
|
28
29
|
hostedRequest: NonNullable<ClientPluginContext["transport"]["hostedRequest"]>,
|
|
29
|
-
): {
|
|
30
|
+
): {
|
|
31
|
+
calls: string[];
|
|
32
|
+
state: { value: VoiceClientStateV1 };
|
|
33
|
+
reloadHolds: () => number;
|
|
34
|
+
} {
|
|
30
35
|
const calls: string[] = [];
|
|
31
36
|
const surfaces = surfaceRegistry();
|
|
37
|
+
const web = ref({
|
|
38
|
+
reloadHolds: 0,
|
|
39
|
+
holdReload: () => {
|
|
40
|
+
web.value.reloadHolds += 1;
|
|
41
|
+
return () => {
|
|
42
|
+
web.value.reloadHolds -= 1;
|
|
43
|
+
};
|
|
44
|
+
},
|
|
45
|
+
});
|
|
32
46
|
let state: unknown;
|
|
33
47
|
const context: ClientPluginContext = {
|
|
34
48
|
transport: {
|
|
@@ -40,6 +54,7 @@ function mount(
|
|
|
40
54
|
},
|
|
41
55
|
inject: (key) => {
|
|
42
56
|
if (key === clientSurfaceRegistryKey) return surfaces as never;
|
|
57
|
+
if (key === frockBotWebDataKey) return web as never;
|
|
43
58
|
throw new Error("unexpected client provider");
|
|
44
59
|
},
|
|
45
60
|
provide: (key, value) => {
|
|
@@ -52,10 +67,23 @@ function mount(
|
|
|
52
67
|
return {
|
|
53
68
|
calls,
|
|
54
69
|
state: state as { value: VoiceClientStateV1 },
|
|
70
|
+
reloadHolds: () => web.value.reloadHolds,
|
|
55
71
|
};
|
|
56
72
|
}
|
|
57
73
|
|
|
58
74
|
describe("Voice client contribution", () => {
|
|
75
|
+
test("holds the shell's reload while a session is live", () => {
|
|
76
|
+
const mounted = mount(() => Promise.reject(new Error("unauthenticated")));
|
|
77
|
+
|
|
78
|
+
expect(mounted.reloadHolds()).toBe(0);
|
|
79
|
+
mounted.state.value.status = "listening";
|
|
80
|
+
expect(mounted.reloadHolds()).toBe(1);
|
|
81
|
+
mounted.state.value.status = "speaking";
|
|
82
|
+
expect(mounted.reloadHolds()).toBe(1);
|
|
83
|
+
mounted.state.value.status = "offline";
|
|
84
|
+
expect(mounted.reloadHolds()).toBe(0);
|
|
85
|
+
});
|
|
86
|
+
|
|
59
87
|
test("does not read User voice state before authenticated chrome mounts", () => {
|
|
60
88
|
const mounted = mount(() => Promise.reject(new Error("unauthenticated")));
|
|
61
89
|
|
package/src/client/index.ts
CHANGED
|
@@ -11,8 +11,9 @@ import {
|
|
|
11
11
|
type VoiceMicrophoneV1,
|
|
12
12
|
} from "@frockbot/plugin-shell/client/voice-microphone";
|
|
13
13
|
import { showClientNotificationV1 } from "@frockbot/plugin-shell/client/notify";
|
|
14
|
+
import { frockBotWebDataKey } from "@frockbot/plugin-shell/shared";
|
|
14
15
|
import { VOICE_ASSISTANT_INPUT_SAMPLE_RATE_V1 } from "@frockbot/protocol";
|
|
15
|
-
import { ref } from "vue";
|
|
16
|
+
import { ref, watch } from "vue";
|
|
16
17
|
import {
|
|
17
18
|
decodeVoiceAssistantViewV1,
|
|
18
19
|
VOICE_MAX_TOOL_CALLS_V1,
|
|
@@ -299,6 +300,28 @@ export const voiceClientPlugin: ClientPlugin = (ctx) => {
|
|
|
299
300
|
if (state.value.status === "offline") void refresh();
|
|
300
301
|
}, VOICE_PENDING_POLL_INTERVAL_MS_V1);
|
|
301
302
|
|
|
303
|
+
/*
|
|
304
|
+
* A live session is work a reload would throw away, and the Voice surface
|
|
305
|
+
* is a panel rather than an overlay, so the shell cannot see it any other
|
|
306
|
+
* way. Holding is how a Package says "not right now" without the shell
|
|
307
|
+
* knowing anything about Voice.
|
|
308
|
+
*/
|
|
309
|
+
const web = ctx.inject(frockBotWebDataKey);
|
|
310
|
+
let releaseHold: (() => void) | undefined;
|
|
311
|
+
const stopHold = watch(
|
|
312
|
+
() => state.value.status !== "offline",
|
|
313
|
+
(live) => {
|
|
314
|
+
if (live && !releaseHold) releaseHold = web.value.holdReload();
|
|
315
|
+
else if (!live && releaseHold) {
|
|
316
|
+
releaseHold();
|
|
317
|
+
releaseHold = undefined;
|
|
318
|
+
}
|
|
319
|
+
},
|
|
320
|
+
// Synchronous: the hold has to be up before anything can act on the
|
|
321
|
+
// status change, or a reload could land in the gap.
|
|
322
|
+
{ immediate: true, flush: "sync" },
|
|
323
|
+
);
|
|
324
|
+
|
|
302
325
|
return [
|
|
303
326
|
ctx.provide(voiceClientStateKey, state),
|
|
304
327
|
surfaces.register({
|
|
@@ -313,6 +336,9 @@ export const voiceClientPlugin: ClientPlugin = (ctx) => {
|
|
|
313
336
|
component: VoiceToggle,
|
|
314
337
|
}),
|
|
315
338
|
() => {
|
|
339
|
+
stopHold();
|
|
340
|
+
releaseHold?.();
|
|
341
|
+
releaseHold = undefined;
|
|
316
342
|
clearInterval(pendingPoll);
|
|
317
343
|
attempt += 1;
|
|
318
344
|
session?.close();
|