From 2b5cc727f0302ce7089ad6b320b66543eaca2858 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Sun, 21 Jun 2026 20:45:26 +0800 Subject: [PATCH] refactor(server): move VoiceSession into session/voice/ to match CheckoutSession (#1645) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #1640 extracted the voice subsystem to server/voice/ (a peer of session.ts); #1644 extracted the checkout read side to session/checkout/. The two carves landed in inconsistent homes, so there was no single place to find "what has session.ts been carved into." Relocate server/voice/ -> server/session/voice/ so all session subsystems share one home alongside session/checkout/. Pure relocation: git-tracked renames with import-depth bumped one level (../ -> ../../); the only external touch point is the import path in session.ts. Behavior preserved verbatim — the 17 voice unit tests pass unchanged before and after. Also record the actual shipped layout and the next carve (the checkout mutation handlers still inline in session.ts -> session/checkout/checkout-session.ts) in the decomposition plan, and fix its now-stale voice path. --- docs/refactors/session-decomposition-plan.md | 11 ++++++- packages/server/src/server/session.ts | 2 +- .../{ => session}/voice/voice-session.test.ts | 8 ++--- .../{ => session}/voice/voice-session.ts | 30 +++++++++---------- .../voice/voice-turn-controller.test.ts | 4 +-- .../voice/voice-turn-controller.ts | 8 ++--- 6 files changed, 36 insertions(+), 27 deletions(-) rename packages/server/src/server/{ => session}/voice/voice-session.test.ts (96%) rename packages/server/src/server/{ => session}/voice/voice-session.ts (97%) rename packages/server/src/server/{ => session}/voice/voice-turn-controller.test.ts (99%) rename packages/server/src/server/{ => session}/voice/voice-turn-controller.ts (98%) diff --git a/docs/refactors/session-decomposition-plan.md b/docs/refactors/session-decomposition-plan.md index 531730ec8..ff8bbfab4 100644 --- a/docs/refactors/session-decomposition-plan.md +++ b/docs/refactors/session-decomposition-plan.md @@ -13,6 +13,15 @@ Each domain becomes a controller class in its own file with the **exact** contra Session shrinks to a connection/dispatch shell: it keeps `handleMessage`, the `??` chain (1739-1751), `emit`/`emitBinary`, `sessionLogger`, connection identity, inflight metrics, lifecycle intents, and the **ordered** `cleanup()`. Each `dispatchXMessage` collapses to `return this.xController.dispatch(msg)`. +## Progress (shipped — diverged from the original filenames) + +The first carves shipped as **deep modules with a narrow Host seam**, not the `dispatch(msg)`-owned-set controllers sketched below: `session.ts` keeps each `dispatchXMessage` switch and delegates per case to the subsystem. Home convention that emerged: session subsystems live at **`session//`**, with `session.ts` as the orchestrator shell. + +- **#1640 — VoiceSession** (`session/voice/voice-session.ts`, seam `VoiceSessionHost`): the STT/TTS/dictation/turn-detection subsystem. _(Originally landed at `server/voice/`; relocated under `session/` so all session subsystems share one home.)_ +- **#1644 — CheckoutSession, read side** (`session/checkout/checkout-session.ts`, seam `CheckoutSessionHost`, port `CheckoutDiffSubscriber`): status, branch validate/suggest, diff subscribe/unsubscribe, manual refresh. The workspace-git observer already delegates `emitStatusUpdate`/`scheduleDiffRefresh` to it. + +**Next carve — CheckoutSession mutation side (Slice 4 below).** The 17 checkout _write_ handlers still inline in `session.ts` (`dispatchCheckoutMessage`, ~2010) — branch switch/rename, commit, merge, merge-from-base, pull, push, PR create/merge, github set-auto-merge/get-check-details, PR status, PR timeline, github search, stash save/pop/list — move into the existing `session/checkout/checkout-session.ts` behind `CheckoutSessionHost`. The Slice-3 observer entanglement the table fears is already resolved: #1644 moved the status/diff read side into CheckoutSession, so the workspace observer delegates today and this no longer blocks on the WorkspaceController split. + ### Why this is safe at the dispatch seam (verified) `dispatchInboundMessage` builds `a() ?? b() ?? ... ?? dispatchMiscMessage()` and short-circuits on the first non-`undefined` **Promise object** (not its resolved value). Message-type spaces are **disjoint** (no duplicate `case` labels across switches), so at most one dispatcher matches any message — collapsing to delegation cannot change which handler runs. `dispatchTerminalMessage` (2150-2153) already proves this. Two quirks preserved verbatim: schedule/\* is reached via the chat dispatcher's OWN `default` arm (2183), not the top-level `??`; and `start_workspace_script_request` (a workspace type) is special-cased before terminal delegation (2150). @@ -117,7 +126,7 @@ In-place, separately reviewable. Split the `audio_output` TTS-debug branch out o ## Slice 7 — VoiceSessionController (XL) -**Move:** voice handlers + ~25 voice fields + the TTS-debug hook (Slice 6) + `voiceModeAgentId`/`isVoiceMode` + the `shouldAutoAllowVoicePermission` predicate (Slice 3) → `packages/server/src/server/voice/voice-session-controller.ts`. Carve voice types out of `dispatchVoiceAndControlMessage`, leaving infra (restart/shutdown/heartbeat/ping/abort) on the shell. +**Move:** voice handlers + ~25 voice fields + the TTS-debug hook (Slice 6) + `voiceModeAgentId`/`isVoiceMode` + the `shouldAutoAllowVoicePermission` predicate (Slice 3) → the existing `packages/server/src/server/session/voice/voice-session.ts` (see Progress above). Carve voice types out of `dispatchVoiceAndControlMessage`, leaving infra (restart/shutdown/heartbeat/ping/abort) on the shell. **SessionContext surface:** pure `emit`, `emitBinary`, `hasBinaryChannel`, `sessionLogger`/`sessionId`/`paseoHome`, `getSpeechReadiness`, agent-control port `{ loadAgent, reloadWithSystemPrompt, interruptIfRunning, isRunning, sendSpokenText, buildAgentPrompt }`, `getSignal`/`abortCurrent` (Slice 6). diff --git a/packages/server/src/server/session.ts b/packages/server/src/server/session.ts index 86baefd63..cfdf942d2 100644 --- a/packages/server/src/server/session.ts +++ b/packages/server/src/server/session.ts @@ -167,7 +167,7 @@ import { } from "./workspace-registry.js"; import { wrapSpokenInput } from "./voice-config.js"; import { isVoicePermissionAllowed } from "./voice-permission-policy.js"; -import { VoiceSession } from "./voice/voice-session.js"; +import { VoiceSession } from "./session/voice/voice-session.js"; import { CheckoutSession } from "./session/checkout/checkout-session.js"; import { listDirectoryEntries, diff --git a/packages/server/src/server/voice/voice-session.test.ts b/packages/server/src/server/session/voice/voice-session.test.ts similarity index 96% rename from packages/server/src/server/voice/voice-session.test.ts rename to packages/server/src/server/session/voice/voice-session.test.ts index 917ee62c2..896546867 100644 --- a/packages/server/src/server/voice/voice-session.test.ts +++ b/packages/server/src/server/session/voice/voice-session.test.ts @@ -3,18 +3,18 @@ import pino from "pino"; import { describe, expect, test, vi } from "vitest"; import { VoiceSession, type VoiceSessionHost } from "./voice-session.js"; -import type { ManagedAgent } from "../agent/agent-manager.js"; -import type { SessionOutboundMessage } from "../messages.js"; +import type { ManagedAgent } from "../../agent/agent-manager.js"; +import type { SessionOutboundMessage } from "../../messages.js"; import type { SpeechToTextProvider, StreamingTranscriptionCommittedEvent, StreamingTranscriptionEvent, StreamingTranscriptionSession, -} from "../speech/speech-provider.js"; +} from "../../speech/speech-provider.js"; import type { TurnDetectionProvider, TurnDetectionSession, -} from "../speech/turn-detection-provider.js"; +} from "../../speech/turn-detection-provider.js"; const VOICE_AGENT_ID = "11111111-1111-4111-8111-111111111111"; diff --git a/packages/server/src/server/voice/voice-session.ts b/packages/server/src/server/session/voice/voice-session.ts similarity index 97% rename from packages/server/src/server/voice/voice-session.ts rename to packages/server/src/server/session/voice/voice-session.ts index dcfe77baf..2ac9998ed 100644 --- a/packages/server/src/server/voice/voice-session.ts +++ b/packages/server/src/server/session/voice/voice-session.ts @@ -2,25 +2,25 @@ import { v4 as uuidv4 } from "uuid"; import { z } from "zod"; import type pino from "pino"; import { getErrorMessage } from "@getpaseo/protocol/error-utils"; -import type { SessionInboundMessage, SessionOutboundMessage } from "../messages.js"; -import { TTSManager } from "../agent/tts-manager.js"; -import { STTManager } from "../agent/stt-manager.js"; -import type { SpeechToTextProvider, TextToSpeechProvider } from "../speech/speech-provider.js"; -import type { TurnDetectionProvider } from "../speech/turn-detection-provider.js"; -import { maybePersistTtsDebugAudio } from "../agent/tts-debug.js"; -import { isPaseoDictationDebugEnabled } from "../agent/recordings-debug.js"; +import type { SessionInboundMessage, SessionOutboundMessage } from "../../messages.js"; +import { TTSManager } from "../../agent/tts-manager.js"; +import { STTManager } from "../../agent/stt-manager.js"; +import type { SpeechToTextProvider, TextToSpeechProvider } from "../../speech/speech-provider.js"; +import type { TurnDetectionProvider } from "../../speech/turn-detection-provider.js"; +import { maybePersistTtsDebugAudio } from "../../agent/tts-debug.js"; +import { isPaseoDictationDebugEnabled } from "../../agent/recordings-debug.js"; import { DictationStreamManager, type DictationStreamOutboundMessage, -} from "../dictation/dictation-stream-manager.js"; +} from "../../dictation/dictation-stream-manager.js"; import { createVoiceTurnController, type VoiceTurnController } from "./voice-turn-controller.js"; -import { buildVoiceModeSystemPrompt, stripVoiceModeSystemPrompt } from "../voice-config.js"; -import type { VoiceCallerContext, VoiceSpeakHandler } from "../voice-types.js"; -import type { ManagedAgent } from "../agent/agent-manager.js"; -import type { AgentSessionConfig } from "../agent/agent-sdk-types.js"; -import type { LocalSpeechModelId } from "../speech/providers/local/models.js"; -import { toResolver, type Resolvable } from "../speech/provider-resolver.js"; -import type { SpeechReadinessSnapshot, SpeechReadinessState } from "../speech/speech-runtime.js"; +import { buildVoiceModeSystemPrompt, stripVoiceModeSystemPrompt } from "../../voice-config.js"; +import type { VoiceCallerContext, VoiceSpeakHandler } from "../../voice-types.js"; +import type { ManagedAgent } from "../../agent/agent-manager.js"; +import type { AgentSessionConfig } from "../../agent/agent-sdk-types.js"; +import type { LocalSpeechModelId } from "../../speech/providers/local/models.js"; +import { toResolver, type Resolvable } from "../../speech/provider-resolver.js"; +import type { SpeechReadinessSnapshot, SpeechReadinessState } from "../../speech/speech-runtime.js"; const PCM_SAMPLE_RATE = 16000; const PCM_CHANNELS = 1; diff --git a/packages/server/src/server/voice/voice-turn-controller.test.ts b/packages/server/src/server/session/voice/voice-turn-controller.test.ts similarity index 99% rename from packages/server/src/server/voice/voice-turn-controller.test.ts rename to packages/server/src/server/session/voice/voice-turn-controller.test.ts index 6f2a304c5..4f2a7e96c 100644 --- a/packages/server/src/server/voice/voice-turn-controller.test.ts +++ b/packages/server/src/server/session/voice/voice-turn-controller.test.ts @@ -7,11 +7,11 @@ import type { StreamingTranscriptionCommittedEvent, StreamingTranscriptionEvent, StreamingTranscriptionSession, -} from "../speech/speech-provider.js"; +} from "../../speech/speech-provider.js"; import type { TurnDetectionProvider, TurnDetectionSession, -} from "../speech/turn-detection-provider.js"; +} from "../../speech/turn-detection-provider.js"; import { createVoiceTurnController } from "./voice-turn-controller.js"; class FakeTurnDetectionSession extends EventEmitter implements TurnDetectionSession { diff --git a/packages/server/src/server/voice/voice-turn-controller.ts b/packages/server/src/server/session/voice/voice-turn-controller.ts similarity index 98% rename from packages/server/src/server/voice/voice-turn-controller.ts rename to packages/server/src/server/session/voice/voice-turn-controller.ts index eb7e1d3dd..20e2b4ba1 100644 --- a/packages/server/src/server/voice/voice-turn-controller.ts +++ b/packages/server/src/server/session/voice/voice-turn-controller.ts @@ -2,14 +2,14 @@ import { Buffer } from "node:buffer"; import type { Logger } from "pino"; import { v4 as uuidv4 } from "uuid"; -import { Pcm16MonoResampler } from "../agent/pcm16-resampler.js"; -import { parsePcmRateFromFormat } from "../speech/audio.js"; +import { Pcm16MonoResampler } from "../../agent/pcm16-resampler.js"; +import { parsePcmRateFromFormat } from "../../speech/audio.js"; import type { SpeechToTextProvider, StreamingTranscriptionEvent, StreamingTranscriptionSession, -} from "../speech/speech-provider.js"; -import type { TurnDetectionProvider } from "../speech/turn-detection-provider.js"; +} from "../../speech/speech-provider.js"; +import type { TurnDetectionProvider } from "../../speech/turn-detection-provider.js"; const VOICE_FINAL_TRANSCRIPT_TIMEOUT_MS = 10_000;