@kolisachint/hoocode-agent 0.4.103 → 0.4.104

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.
@@ -9,7 +9,7 @@ import * as path from "node:path";
9
9
  import { getProviders, } from "@kolisachint/hoocode-ai";
10
10
  import { CombinedAutocompleteProvider, Container, fuzzyFilter, getCapabilities, hyperlink, Loader, Markdown, matchesKey, ProcessTerminal, Spacer, setKeybindings, Text, TruncatedText, TUI, } from "@kolisachint/hoocode-tui";
11
11
  import { spawn, spawnSync } from "child_process";
12
- import { APP_NAME, APP_TITLE, getAgentDir, getAuthPath, getDocsPath, resolveVoicetoolsBin, VERSION, } from "../../config.js";
12
+ import { APP_NAME, APP_TITLE, getAgentDir, getAuthPath, getDocsPath, VERSION } from "../../config.js";
13
13
  import { parseSkillBlock } from "../../core/agent-session.js";
14
14
  import { FooterDataProvider } from "../../core/footer-data-provider.js";
15
15
  import { KeybindingsManager } from "../../core/keybindings.js";
@@ -116,6 +116,8 @@ export class InteractiveMode {
116
116
  editor;
117
117
  editorComponentFactory;
118
118
  voiceSession;
119
+ /** True while the voicetools binary is being resolved/downloaded before a session starts. */
120
+ voiceStarting = false;
119
121
  autocompleteProvider;
120
122
  autocompleteProviderWrappers = [];
121
123
  fdPath;
@@ -2765,9 +2767,10 @@ export class InteractiveMode {
2765
2767
  * through the status line. Pressing the shortcut again stops early.
2766
2768
  */
2767
2769
  toggleVoiceTranscribe() {
2768
- if (this.voiceSession?.running) {
2769
- this.voiceSession.stop();
2770
+ if (this.voiceSession?.running || this.voiceStarting) {
2771
+ this.voiceSession?.stop();
2770
2772
  this.voiceSession = undefined;
2773
+ this.voiceStarting = false;
2771
2774
  this.showStatus("Voice input cancelled");
2772
2775
  return;
2773
2776
  }
@@ -2776,23 +2779,56 @@ export class InteractiveMode {
2776
2779
  transcribing: "Transcribing...",
2777
2780
  done: "Voice input done",
2778
2781
  };
2779
- this.voiceSession = startVoiceTranscribe(resolveVoicetoolsBin(), {
2780
- onStatus: (status) => {
2781
- this.showStatus(statusLabels[status] ?? status);
2782
- if (status === "done") {
2782
+ // Resolve the binary the same way as the other managed tools (webtools etc.):
2783
+ // an explicit VOICETOOLS_BIN wins, otherwise resolve from bin/PATH and download
2784
+ // from the published release on demand. This runs async, so guard re-entry with
2785
+ // `voiceStarting` and let a second press before it resolves cancel the start.
2786
+ this.voiceStarting = true;
2787
+ this.showStatus("Preparing voice input...");
2788
+ void this.resolveVoiceBin()
2789
+ .then((bin) => {
2790
+ // A second key press while resolving cleared the flag: honour the cancel.
2791
+ if (!this.voiceStarting)
2792
+ return;
2793
+ this.voiceStarting = false;
2794
+ if (!bin) {
2795
+ this.showError("Voice input failed: voicetools binary unavailable and could not be downloaded. " +
2796
+ "Install it, set VOICETOOLS_BIN, or ensure a published release exists for this platform.");
2797
+ return;
2798
+ }
2799
+ this.voiceSession = startVoiceTranscribe(bin, {
2800
+ onStatus: (status) => {
2801
+ this.showStatus(statusLabels[status] ?? status);
2802
+ if (status === "done") {
2803
+ this.voiceSession = undefined;
2804
+ }
2805
+ },
2806
+ onSegment: (text) => {
2807
+ // Inject via bracketed paste so the editor treats it as pasted text.
2808
+ this.editor.handleInput(`\x1b[200~${text} \x1b[201~`);
2809
+ },
2810
+ onError: (message) => {
2783
2811
  this.voiceSession = undefined;
2784
- }
2785
- },
2786
- onSegment: (text) => {
2787
- // Inject via bracketed paste so the editor treats it as pasted text.
2788
- this.editor.handleInput(`\x1b[200~${text} \x1b[201~`);
2789
- },
2790
- onError: (message) => {
2791
- this.voiceSession = undefined;
2792
- this.showError(`Voice input failed: ${message}`);
2793
- },
2812
+ this.showError(`Voice input failed: ${message}`);
2813
+ },
2814
+ });
2815
+ })
2816
+ .catch((err) => {
2817
+ this.voiceStarting = false;
2818
+ this.showError(`Voice input failed: ${err instanceof Error ? err.message : String(err)}`);
2794
2819
  });
2795
2820
  }
2821
+ /**
2822
+ * Resolve the `voicetools` binary path. Prefers an explicit VOICETOOLS_BIN
2823
+ * override, otherwise resolves via the managed tools manager (bin dir / PATH /
2824
+ * download from the published release). Returns undefined when unavailable.
2825
+ */
2826
+ async resolveVoiceBin() {
2827
+ const override = process.env.VOICETOOLS_BIN?.trim();
2828
+ if (override)
2829
+ return override;
2830
+ return ensureTool("voicetools", true);
2831
+ }
2796
2832
  showStatus(message) {
2797
2833
  const children = this.chatContainer.children;
2798
2834
  const last = children.length > 0 ? children[children.length - 1] : undefined;
@@ -4471,6 +4507,7 @@ export class InteractiveMode {
4471
4507
  this.voiceSession.stop();
4472
4508
  this.voiceSession = undefined;
4473
4509
  }
4510
+ this.voiceStarting = false;
4474
4511
  if (this.settingsManager.getShowTerminalProgress()) {
4475
4512
  this.ui.terminal.setProgress(false);
4476
4513
  }