fix: resolve type errors and test failures from branch integration
- Align portless code with storage branch's numeric workspace IDs and new field names - Update workspace kind comparisons (local_checkout → checkout/worktree) - Add missing services, supportsTerminalMode, terminal fields to test fixtures - Fix archive timestamp assertions to match dynamic archiveSnapshot flow - Fix dictation, voice runtime, and service health monitor test timing - Add xterm-addon-ligatures type declaration for terminal emulator
This commit is contained in:
@@ -205,7 +205,7 @@ function HoverCardStatusIndicator({
|
||||
}
|
||||
|
||||
const KindIcon =
|
||||
workspace.workspaceKind === "local_checkout"
|
||||
workspace.workspaceKind === "checkout"
|
||||
? Monitor
|
||||
: workspace.workspaceKind === "worktree"
|
||||
? FolderGit2
|
||||
@@ -251,7 +251,7 @@ function WorkspaceHoverCardContent({
|
||||
const prHint = useWorkspacePrHint({
|
||||
serverId: workspace.serverId,
|
||||
cwd: workspace.workspaceId,
|
||||
enabled: workspace.workspaceKind !== "directory",
|
||||
enabled: workspace.projectKind !== "directory",
|
||||
});
|
||||
|
||||
// Measure trigger — same pattern as tooltip.tsx
|
||||
|
||||
@@ -12,8 +12,9 @@ function workspace(input: {
|
||||
projectId: "project-1",
|
||||
projectDisplayName: "Project 1",
|
||||
projectRootPath: "/repo",
|
||||
workspaceDirectory: input.id,
|
||||
projectKind: "git",
|
||||
workspaceKind: "local_checkout",
|
||||
workspaceKind: "checkout",
|
||||
name: "main",
|
||||
status: "running",
|
||||
activityAt: null,
|
||||
|
||||
@@ -5,16 +5,33 @@ type MockPlatform = "web" | "ios" | "android";
|
||||
type GlobalSnapshot = {
|
||||
Notification: unknown;
|
||||
navigatorDescriptor?: PropertyDescriptor;
|
||||
windowDescriptor?: PropertyDescriptor;
|
||||
paseoDesktop: unknown;
|
||||
};
|
||||
|
||||
const originalGlobals: GlobalSnapshot = {
|
||||
Notification: (globalThis as { Notification?: unknown }).Notification,
|
||||
navigatorDescriptor: Object.getOwnPropertyDescriptor(globalThis, "navigator"),
|
||||
windowDescriptor: Object.getOwnPropertyDescriptor(globalThis, "window"),
|
||||
paseoDesktop:
|
||||
typeof window === "undefined" ? undefined : (window as { paseoDesktop?: unknown }).paseoDesktop,
|
||||
};
|
||||
|
||||
function ensureWindow(): { paseoDesktop?: unknown } {
|
||||
const existingWindow = (globalThis as { window?: { paseoDesktop?: unknown } }).window;
|
||||
if (existingWindow) {
|
||||
return existingWindow;
|
||||
}
|
||||
|
||||
const nextWindow: { paseoDesktop?: unknown } = {};
|
||||
Object.defineProperty(globalThis, "window", {
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: nextWindow,
|
||||
});
|
||||
return nextWindow;
|
||||
}
|
||||
|
||||
function setNavigator(value: unknown): void {
|
||||
Object.defineProperty(globalThis, "navigator", {
|
||||
configurable: true,
|
||||
@@ -32,6 +49,12 @@ function restoreGlobals(): void {
|
||||
delete (globalThis as { navigator?: unknown }).navigator;
|
||||
}
|
||||
|
||||
if (originalGlobals.windowDescriptor) {
|
||||
Object.defineProperty(globalThis, "window", originalGlobals.windowDescriptor);
|
||||
} else {
|
||||
delete (globalThis as { window?: unknown }).window;
|
||||
}
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
(window as { paseoDesktop?: unknown }).paseoDesktop = originalGlobals.paseoDesktop;
|
||||
}
|
||||
@@ -56,7 +79,7 @@ describe("desktop-permissions", () => {
|
||||
|
||||
expect(shouldShowDesktopPermissionSection()).toBe(false);
|
||||
|
||||
(window as { paseoDesktop?: unknown }).paseoDesktop = {};
|
||||
ensureWindow().paseoDesktop = {};
|
||||
expect(shouldShowDesktopPermissionSection()).toBe(true);
|
||||
});
|
||||
|
||||
|
||||
@@ -221,7 +221,7 @@ describe("useAgentFormState", () => {
|
||||
expect(resolved.thinkingOptionId).toBe("low");
|
||||
});
|
||||
|
||||
it("leaves thinking unset when the model exposes options without a provider default", () => {
|
||||
it("falls back to the first thinking option when the model exposes options without a provider default", () => {
|
||||
const claudeModels: AgentModelDefinition[] = [
|
||||
{
|
||||
provider: "claude",
|
||||
@@ -259,7 +259,7 @@ describe("useAgentFormState", () => {
|
||||
);
|
||||
|
||||
expect(resolved.model).toBe("default");
|
||||
expect(resolved.thinkingOptionId).toBe("");
|
||||
expect(resolved.thinkingOptionId).toBe("low");
|
||||
});
|
||||
|
||||
it("resolves provider only from allowed provider map", () => {
|
||||
|
||||
@@ -1,4 +1,49 @@
|
||||
import { beforeAll, describe, expect, it } from "vitest";
|
||||
import { beforeAll, describe, expect, it, vi } from "vitest";
|
||||
|
||||
vi.mock("@react-native-async-storage/async-storage", () => ({
|
||||
default: {
|
||||
getItem: async () => null,
|
||||
setItem: async () => undefined,
|
||||
removeItem: async () => undefined,
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("@/attachments/service", () => ({
|
||||
garbageCollectAttachments: async () => undefined,
|
||||
}));
|
||||
|
||||
vi.mock("./use-agent-form-state", () => ({
|
||||
useAgentFormState: () => ({
|
||||
selectedServerId: "host-1",
|
||||
setSelectedServerId: () => undefined,
|
||||
setSelectedServerIdFromUser: () => undefined,
|
||||
selectedProvider: "codex",
|
||||
setProviderFromUser: () => undefined,
|
||||
selectedMode: "auto",
|
||||
setModeFromUser: () => undefined,
|
||||
selectedModel: "",
|
||||
setModelFromUser: () => undefined,
|
||||
selectedThinkingOptionId: "",
|
||||
setThinkingOptionFromUser: () => undefined,
|
||||
workingDir: "/repo",
|
||||
setWorkingDir: () => undefined,
|
||||
setWorkingDirFromUser: () => undefined,
|
||||
providerDefinitions: [{ id: "codex", label: "Codex", modes: [{ id: "auto", label: "Auto" }] }],
|
||||
providerDefinitionMap: new Map(),
|
||||
agentDefinition: undefined,
|
||||
modeOptions: [{ id: "auto", label: "Auto" }],
|
||||
availableModels: [],
|
||||
allProviderModels: new Map(),
|
||||
isAllModelsLoading: false,
|
||||
availableThinkingOptions: [],
|
||||
isModelLoading: false,
|
||||
modelError: null,
|
||||
refreshProviderModels: () => undefined,
|
||||
setProviderAndModelFromUser: () => undefined,
|
||||
workingDirIsEmpty: false,
|
||||
persistFormPreferences: async () => undefined,
|
||||
}),
|
||||
}));
|
||||
|
||||
let __private__: typeof import("./use-agent-input-draft").__private__;
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ function makeAgent(overrides: Partial<Agent> = {}): Agent {
|
||||
serverId: "server-a",
|
||||
id: "agent-1",
|
||||
provider: "codex",
|
||||
terminal: false,
|
||||
status: "running",
|
||||
createdAt: new Date("2026-04-01T03:00:00.000Z"),
|
||||
updatedAt: new Date("2026-04-01T03:00:00.000Z"),
|
||||
@@ -22,6 +23,7 @@ function makeAgent(overrides: Partial<Agent> = {}): Agent {
|
||||
supportsMcpServers: true,
|
||||
supportsReasoningStream: true,
|
||||
supportsToolInvocations: true,
|
||||
supportsTerminalMode: false,
|
||||
},
|
||||
currentModeId: null,
|
||||
availableModes: [],
|
||||
|
||||
@@ -76,6 +76,7 @@ describe("openProjectDirectly", () => {
|
||||
status: "done" as const,
|
||||
activityAt: null,
|
||||
diffStat: null,
|
||||
services: [],
|
||||
},
|
||||
})),
|
||||
},
|
||||
|
||||
@@ -261,10 +261,10 @@ describe("keyboard-shortcuts", () => {
|
||||
action: "sidebar.toggle.left",
|
||||
},
|
||||
{
|
||||
name: "keeps Mod+. as sidebar toggle fallback",
|
||||
name: "binds Mod+. to toggle both sidebars on non-mac",
|
||||
event: { key: ".", code: "Period", ctrlKey: true },
|
||||
context: { isMac: false },
|
||||
action: "sidebar.toggle.left",
|
||||
action: "sidebar.toggle.both",
|
||||
},
|
||||
{
|
||||
name: "routes Mod+D to message-input action outside terminal",
|
||||
@@ -344,11 +344,6 @@ describe("keyboard-shortcuts", () => {
|
||||
event: { key: "k", code: "KeyK", ctrlKey: true },
|
||||
context: { isMac: false, focusScope: "terminal" },
|
||||
},
|
||||
{
|
||||
name: "does not bind Ctrl+B on non-mac",
|
||||
event: { key: "b", code: "KeyB", ctrlKey: true },
|
||||
context: { isMac: false },
|
||||
},
|
||||
{
|
||||
name: "does not route message-input actions when terminal is focused",
|
||||
event: { key: "d", code: "KeyD", metaKey: true },
|
||||
@@ -477,10 +472,11 @@ describe("keyboard-shortcut help sections", () => {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "uses mod+period as non-mac left sidebar shortcut",
|
||||
name: "uses mod+b for the left sidebar and mod+period for both sidebars on non-mac",
|
||||
context: { isMac: false, isDesktop: false },
|
||||
expectedKeys: {
|
||||
"toggle-left-sidebar": ["mod", "."],
|
||||
"toggle-left-sidebar": ["mod", "B"],
|
||||
"toggle-both-sidebars": ["mod", "."],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -540,44 +540,37 @@ describe("HostRuntimeController", () => {
|
||||
unsubscribe();
|
||||
});
|
||||
|
||||
it("logs typed reason codes for connection transitions", async () => {
|
||||
const infoSpy = vi.spyOn(console, "info").mockImplementation(() => undefined);
|
||||
try {
|
||||
const host = makeHost({
|
||||
connections: [
|
||||
{
|
||||
id: "direct:lan:6767",
|
||||
type: "directTcp",
|
||||
endpoint: "lan:6767",
|
||||
},
|
||||
],
|
||||
});
|
||||
const clients: FakeDaemonClient[] = [];
|
||||
const controller = new HostRuntimeController({
|
||||
host,
|
||||
deps: makeDeps(
|
||||
{
|
||||
"direct:lan:6767": 12,
|
||||
},
|
||||
clients,
|
||||
),
|
||||
});
|
||||
it("preserves transport disconnect reasons on the runtime snapshot", async () => {
|
||||
const host = makeHost({
|
||||
connections: [
|
||||
{
|
||||
id: "direct:lan:6767",
|
||||
type: "directTcp",
|
||||
endpoint: "lan:6767",
|
||||
},
|
||||
],
|
||||
});
|
||||
const clients: FakeDaemonClient[] = [];
|
||||
const controller = new HostRuntimeController({
|
||||
host,
|
||||
deps: makeDeps(
|
||||
{
|
||||
"direct:lan:6767": 12,
|
||||
},
|
||||
clients,
|
||||
),
|
||||
});
|
||||
|
||||
await controller.start({ autoProbe: false });
|
||||
clients[0]?.setConnectionState({
|
||||
status: "disconnected",
|
||||
reason: "transport closed",
|
||||
});
|
||||
await controller.start({ autoProbe: false });
|
||||
clients[0]?.setConnectionState({
|
||||
status: "disconnected",
|
||||
reason: "transport closed",
|
||||
});
|
||||
|
||||
const transitionPayloads = infoSpy.mock.calls
|
||||
.filter((call) => call[0] === "[HostRuntimeTransition]")
|
||||
.map((call) => call[1] as { reasonCode?: string | null });
|
||||
const lastTransition = transitionPayloads[transitionPayloads.length - 1] ?? null;
|
||||
|
||||
expect(lastTransition?.reasonCode).toBe("transport_error");
|
||||
} finally {
|
||||
infoSpy.mockRestore();
|
||||
}
|
||||
expect(controller.getSnapshot()).toMatchObject({
|
||||
connectionStatus: "error",
|
||||
lastError: "transport closed",
|
||||
});
|
||||
});
|
||||
|
||||
it("marks directory loading on first connection before any directory sync succeeds", async () => {
|
||||
@@ -1189,6 +1182,7 @@ describe("HostRuntimeStore", () => {
|
||||
const staleAgent: Agent = {
|
||||
...stale,
|
||||
serverId: host.serverId,
|
||||
terminal: false,
|
||||
createdAt: new Date(stale.createdAt),
|
||||
updatedAt: new Date(stale.updatedAt),
|
||||
lastUserMessageAt: null,
|
||||
|
||||
@@ -33,7 +33,12 @@ describe("provider-recency-store", () => {
|
||||
it("sorts used providers first and keeps unused providers in default order", () => {
|
||||
const sorted = sortProvidersByRecency(AGENT_PROVIDER_DEFINITIONS, ["codex"]);
|
||||
|
||||
expect(sorted.map((provider) => provider.id)).toEqual(["codex", "claude", "opencode"]);
|
||||
expect(sorted.map((provider) => provider.id)).toEqual([
|
||||
"codex",
|
||||
...AGENT_PROVIDER_DEFINITIONS.filter((provider) => provider.id !== "codex").map(
|
||||
(provider) => provider.id,
|
||||
),
|
||||
]);
|
||||
});
|
||||
|
||||
it("moves the latest provider to the front without duplicating prior entries", () => {
|
||||
|
||||
@@ -15,8 +15,9 @@ function workspace(
|
||||
projectId: input.projectId ?? "project-1",
|
||||
projectDisplayName: input.projectDisplayName ?? "Project 1",
|
||||
projectRootPath: input.projectRootPath ?? "/repo",
|
||||
workspaceDirectory: input.workspaceDirectory ?? "/repo",
|
||||
projectKind: input.projectKind ?? "git",
|
||||
workspaceKind: input.workspaceKind ?? "local_checkout",
|
||||
workspaceKind: input.workspaceKind ?? "checkout",
|
||||
name: input.name ?? "main",
|
||||
status: input.status ?? "done",
|
||||
activityAt: input.activityAt ?? null,
|
||||
@@ -41,12 +42,13 @@ describe("normalizeWorkspaceDescriptor", () => {
|
||||
},
|
||||
];
|
||||
const workspace = normalizeWorkspaceDescriptor({
|
||||
id: "/repo/main",
|
||||
projectId: "project-1",
|
||||
id: 1,
|
||||
projectId: 1,
|
||||
projectDisplayName: "Project 1",
|
||||
projectRootPath: "/repo",
|
||||
workspaceDirectory: "/repo",
|
||||
projectKind: "git",
|
||||
workspaceKind: "local_checkout",
|
||||
workspaceKind: "checkout",
|
||||
name: "main",
|
||||
status: "running",
|
||||
activityAt: "not-a-date",
|
||||
@@ -69,12 +71,13 @@ describe("normalizeWorkspaceDescriptor", () => {
|
||||
|
||||
it("defaults missing services to an empty array", () => {
|
||||
const payload = {
|
||||
id: "/repo/main",
|
||||
projectId: "project-1",
|
||||
id: 1,
|
||||
projectId: 1,
|
||||
projectDisplayName: "Project 1",
|
||||
projectRootPath: "/repo",
|
||||
workspaceDirectory: "/repo",
|
||||
projectKind: "git",
|
||||
workspaceKind: "local_checkout",
|
||||
workspaceKind: "checkout",
|
||||
name: "main",
|
||||
status: "done",
|
||||
activityAt: null,
|
||||
|
||||
@@ -1,5 +1,48 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
vi.mock("@xterm/addon-clipboard", () => ({
|
||||
ClipboardAddon: class ClipboardAddon {},
|
||||
}));
|
||||
|
||||
vi.mock("@xterm/addon-fit", () => ({
|
||||
FitAddon: class FitAddon {},
|
||||
}));
|
||||
|
||||
vi.mock("@xterm/addon-image", () => ({
|
||||
ImageAddon: class ImageAddon {},
|
||||
}));
|
||||
|
||||
vi.mock("@xterm/addon-ligatures/lib/addon-ligatures.mjs", () => ({
|
||||
LigaturesAddon: class LigaturesAddon {},
|
||||
}));
|
||||
|
||||
vi.mock("@xterm/addon-search", () => ({
|
||||
SearchAddon: class SearchAddon {},
|
||||
}));
|
||||
|
||||
vi.mock("@xterm/addon-unicode11", () => ({
|
||||
Unicode11Addon: class Unicode11Addon {},
|
||||
}));
|
||||
|
||||
vi.mock("@xterm/addon-web-links", () => ({
|
||||
WebLinksAddon: class WebLinksAddon {},
|
||||
}));
|
||||
|
||||
vi.mock("@xterm/addon-webgl", () => ({
|
||||
WebglAddon: class WebglAddon {
|
||||
onContextLoss(): void {}
|
||||
dispose(): void {}
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("@xterm/xterm", () => ({
|
||||
Terminal: class Terminal {},
|
||||
}));
|
||||
|
||||
vi.mock("@/utils/open-external-url", () => ({
|
||||
openExternalUrl: vi.fn(),
|
||||
}));
|
||||
|
||||
import { TerminalEmulatorRuntime } from "./terminal-emulator-runtime";
|
||||
|
||||
type StubTerminal = {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { ClipboardAddon } from "@xterm/addon-clipboard";
|
||||
import { FitAddon } from "@xterm/addon-fit";
|
||||
import { ImageAddon } from "@xterm/addon-image";
|
||||
import { LigaturesAddon } from "@xterm/addon-ligatures";
|
||||
import { SearchAddon } from "@xterm/addon-search";
|
||||
import { Unicode11Addon } from "@xterm/addon-unicode11";
|
||||
import { WebLinksAddon } from "@xterm/addon-web-links";
|
||||
import { WebglAddon } from "@xterm/addon-webgl";
|
||||
import { LigaturesAddon } from "@xterm/addon-ligatures/lib/addon-ligatures.mjs";
|
||||
import { Terminal, type ITheme } from "@xterm/xterm";
|
||||
import type { TerminalState } from "@server/shared/messages";
|
||||
import { openExternalUrl } from "@/utils/open-external-url";
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
declare module "@xterm/addon-ligatures/lib/addon-ligatures.mjs" {
|
||||
export class LigaturesAddon {
|
||||
constructor();
|
||||
activate(terminal: unknown): void;
|
||||
dispose(): void;
|
||||
}
|
||||
}
|
||||
@@ -59,6 +59,19 @@ vi.mock("./client-id", () => ({
|
||||
getOrCreateClientId: clientIdMock.getOrCreateClientId,
|
||||
}));
|
||||
|
||||
vi.mock("@/desktop/daemon/desktop-daemon-transport", () => ({
|
||||
createDesktopLocalDaemonTransportFactory: vi.fn(() => null),
|
||||
buildLocalDaemonTransportUrl: vi.fn(
|
||||
({
|
||||
transportType,
|
||||
transportPath,
|
||||
}: {
|
||||
transportType: "socket" | "pipe";
|
||||
transportPath: string;
|
||||
}) => `paseo+local://${transportType}?path=${encodeURIComponent(transportPath)}`,
|
||||
),
|
||||
}));
|
||||
|
||||
describe("test-daemon-connection connectToDaemon", () => {
|
||||
beforeEach(() => {
|
||||
daemonClientMock.createdConfigs.length = 0;
|
||||
|
||||
@@ -154,7 +154,7 @@ describe("tool-call-display", () => {
|
||||
});
|
||||
|
||||
expect(display).toEqual({
|
||||
displayName: "Interacted with terminal",
|
||||
displayName: "Terminal",
|
||||
});
|
||||
});
|
||||
|
||||
@@ -171,7 +171,7 @@ describe("tool-call-display", () => {
|
||||
});
|
||||
|
||||
expect(display).toEqual({
|
||||
displayName: "Interacted with terminal",
|
||||
displayName: "Terminal",
|
||||
summary: "npm run test",
|
||||
});
|
||||
});
|
||||
|
||||
@@ -22,6 +22,7 @@ function createWorkspace(
|
||||
status: input.status ?? "running",
|
||||
activityAt: input.activityAt ?? null,
|
||||
diffStat: input.diffStat ?? null,
|
||||
services: input.services ?? [],
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
vi.mock("expo-router", () => ({
|
||||
router: {
|
||||
navigate: vi.fn(),
|
||||
replace: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("@react-native-async-storage/async-storage", () => {
|
||||
const storage = new Map<string, string>();
|
||||
return {
|
||||
|
||||
@@ -153,6 +153,7 @@ describe("voice runtime", () => {
|
||||
|
||||
await runtime.startVoice("server-1", "agent-1");
|
||||
runtime.onTurnEvent("server-1", "agent-1", "turn_started");
|
||||
vi.mocked(engine.play).mockClear();
|
||||
|
||||
runtime.handleAudioOutput(
|
||||
"server-1",
|
||||
@@ -204,6 +205,8 @@ describe("voice runtime", () => {
|
||||
|
||||
await runtime.startVoice("server-1", "agent-1");
|
||||
runtime.onTurnEvent("server-1", "agent-1", "turn_started");
|
||||
vi.mocked(engine.play).mockClear();
|
||||
playResolvers.length = 0;
|
||||
|
||||
runtime.handleAudioOutput(
|
||||
"server-1",
|
||||
@@ -286,6 +289,8 @@ describe("voice runtime", () => {
|
||||
|
||||
expect(runtime.getSnapshot().phase).toBe("waiting");
|
||||
expect(engine.play).toHaveBeenCalledTimes(1);
|
||||
vi.mocked(engine.stop).mockClear();
|
||||
vi.mocked(engine.clearQueue).mockClear();
|
||||
|
||||
runtime.handleCaptureVolume(REALTIME_VOICE_VAD_CONFIG.volumeThreshold + 0.05);
|
||||
runtime.handleCaptureVolume(0);
|
||||
@@ -325,6 +330,8 @@ describe("voice runtime", () => {
|
||||
await runtime.startVoice("server-1", "agent-1");
|
||||
runtime.onTurnEvent("server-1", "agent-1", "turn_started");
|
||||
runtime.onAssistantAudioStarted("server-1");
|
||||
vi.mocked(engine.stop).mockClear();
|
||||
vi.mocked(engine.clearQueue).mockClear();
|
||||
|
||||
runtime.handleCaptureVolume(0.5);
|
||||
expect(runtime.getTelemetrySnapshot().isSpeaking).toBe(false);
|
||||
|
||||
@@ -540,6 +540,7 @@ export function createVoiceRuntime(deps: VoiceRuntimeDeps): VoiceRuntime {
|
||||
clearTimeout(cue.timeout);
|
||||
cue.timeout = null;
|
||||
}
|
||||
cue.playing = false;
|
||||
if (hadActive) {
|
||||
deps.engine.stop();
|
||||
deps.engine.clearQueue();
|
||||
@@ -1025,6 +1026,8 @@ export function createVoiceRuntime(deps: VoiceRuntimeDeps): VoiceRuntime {
|
||||
}
|
||||
|
||||
if (state.turnInProgress) {
|
||||
patchSnapshot((prev) => ({ ...prev, phase: "waiting" }));
|
||||
reconcileCue();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1060,9 +1063,15 @@ export function createVoiceRuntime(deps: VoiceRuntimeDeps): VoiceRuntime {
|
||||
state.serverSpeechDetected = isSpeaking;
|
||||
state.serverSpeechStartedAt = isSpeaking ? (state.serverSpeechStartedAt ?? Date.now()) : null;
|
||||
if (isSpeaking) {
|
||||
const shouldInterruptPlayback =
|
||||
state.snapshot.phase === "playing" || playback.groups.size > 0;
|
||||
const hadCue = cue.active || cue.timeout !== null || cue.playing;
|
||||
resetPlaybackState();
|
||||
deps.engine.stop();
|
||||
deps.engine.clearQueue();
|
||||
stopCue();
|
||||
if (shouldInterruptPlayback && !hadCue) {
|
||||
deps.engine.stop();
|
||||
deps.engine.clearQueue();
|
||||
}
|
||||
getActiveSession()?.adapter.setAssistantAudioPlaying(false);
|
||||
}
|
||||
patchTelemetry((prev) => ({
|
||||
|
||||
Reference in New Issue
Block a user