@bojackduy/opencode-voice 0.10.0 → 0.10.1

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.
Files changed (3) hide show
  1. package/lib/stt.js +8 -0
  2. package/lib/tts.js +29 -5
  3. package/package.json +1 -1
package/lib/stt.js CHANGED
@@ -2021,6 +2021,14 @@ export function registerSTT(api, kv, complete, prompts, opts, logger, deps = {})
2021
2021
  }
2022
2022
 
2023
2023
  api.lifecycle?.onDispose?.(() => {
2024
+ // Unload mid-recording must not orphan the sox child: cancel kills it,
2025
+ // bumps the pipeline generation (late transcriptions are dropped), and
2026
+ // clears the sticky toast. processing is not owned by cancel, reset it
2027
+ // here so a later registration starts from idle.
2028
+ try {
2029
+ cancelRecording(logger);
2030
+ } catch {}
2031
+ processing = false;
2024
2032
  if (streamController) {
2025
2033
  const c = streamController;
2026
2034
  streamController = null;
package/lib/tts.js CHANGED
@@ -638,11 +638,23 @@ export function registerTTS(api, kv, complete, prompts, opts, logger, deps = {})
638
638
  let conversationActive = false;
639
639
  let liveNotesActive = false;
640
640
 
641
- api.event.on("session.status", (event) => {
641
+ // Auto-mode session subscriptions. The unsubscribe handles are kept so
642
+ // plugin unload (resumed session / reload) removes them: without this a
643
+ // second registration doubled auto-TTS speech and stale handlers fired
644
+ // cross-session. api.event.on may return nothing (older host), so guard.
645
+ const eventUnsubs = [];
646
+ function subscribe(name, fn) {
647
+ try {
648
+ const unsub = api.event.on(name, fn);
649
+ if (typeof unsub === "function") eventUnsubs.push(unsub);
650
+ } catch {}
651
+ }
652
+
653
+ subscribe("session.status", (event) => {
642
654
  if (event.properties?.status?.type === "busy") wasBusy = true;
643
655
  });
644
656
 
645
- api.event.on("session.idle", async (event) => {
657
+ subscribe("session.idle", async (event) => {
646
658
  if (conversationActive || liveNotesActive) {
647
659
  wasBusy = false;
648
660
  return;
@@ -674,7 +686,7 @@ export function registerTTS(api, kv, complete, prompts, opts, logger, deps = {})
674
686
  await speakWithSessionPrefix(sessionID, llmResult.text, "Ready for your input.");
675
687
  });
676
688
 
677
- api.event.on("permission.asked", async (event) => {
689
+ subscribe("permission.asked", async (event) => {
678
690
  if (conversationActive || liveNotesActive) return;
679
691
  if (kv.get("tts.mode", "off") !== "on") return;
680
692
  await speakWithSessionPrefix(
@@ -683,7 +695,7 @@ export function registerTTS(api, kv, complete, prompts, opts, logger, deps = {})
683
695
  );
684
696
  });
685
697
 
686
- api.event.on("question.asked", async (event) => {
698
+ subscribe("question.asked", async (event) => {
687
699
  if (conversationActive || liveNotesActive) return;
688
700
  if (kv.get("tts.mode", "off") !== "on") return;
689
701
  await speakWithSessionPrefix(
@@ -822,8 +834,20 @@ export function registerTTS(api, kv, complete, prompts, opts, logger, deps = {})
822
834
 
823
835
  // The Chatterbox sidecar holds a multi-hundred-MB torch model; leaving it
824
836
  // running past the session would strand that memory. Same lifecycle hook the
825
- // STT and live-notes servers use.
837
+ // STT and live-notes servers use. Also drops the session subscriptions (no
838
+ // duplicate auto-speech after re-registration), kills owned piper/play
839
+ // audio, and invalidates in-flight normalization so a late completion can
840
+ // never speak after unload.
826
841
  api.lifecycle?.onDispose?.(() => {
842
+ for (const unsub of eventUnsubs) {
843
+ try {
844
+ unsub();
845
+ } catch {}
846
+ }
847
+ eventUnsubs.length = 0;
848
+ try {
849
+ stopSpeech();
850
+ } catch {}
827
851
  chatterboxClient?.stop();
828
852
  chatterboxClient = null;
829
853
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bojackduy/opencode-voice",
3
- "version": "0.10.0",
3
+ "version": "0.10.1",
4
4
  "description": "Speech-to-text and text-to-speech for OpenCode. Record voice prompts with whisper-cpp, hear responses via Piper TTS, with LLM normalization through any OpenAI-compatible endpoint.",
5
5
  "keywords": [
6
6
  "opencode",