@kolisachint/hoocode-agent 0.4.102 → 0.4.103

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, VERSION } from "../../config.js";
12
+ import { APP_NAME, APP_TITLE, getAgentDir, getAuthPath, getDocsPath, resolveVoicetoolsBin, 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";
@@ -60,6 +60,7 @@ import { TreeSelectorComponent } from "./components/tree-selector.js";
60
60
  import { UserMessageComponent } from "./components/user-message.js";
61
61
  import { UserMessageSelectorComponent } from "./components/user-message-selector.js";
62
62
  import { getAvailableThemes, getAvailableThemesWithPaths, getEditorTheme, getMarkdownTheme, getThemeByName, initTheme, onThemeChange, setRegisteredThemes, setTheme, setThemeInstance, stopThemeWatcher, Theme, theme, } from "./theme/theme.js";
63
+ import { startVoiceTranscribe } from "./voice-transcribe.js";
63
64
  function isExpandable(obj) {
64
65
  return typeof obj === "object" && obj !== null && "setExpanded" in obj && typeof obj.setExpanded === "function";
65
66
  }
@@ -114,6 +115,7 @@ export class InteractiveMode {
114
115
  defaultEditor;
115
116
  editor;
116
117
  editorComponentFactory;
118
+ voiceSession;
117
119
  autocompleteProvider;
118
120
  autocompleteProviderWrappers = [];
119
121
  fdPath;
@@ -2081,6 +2083,7 @@ export class InteractiveMode {
2081
2083
  });
2082
2084
  this.defaultEditor.onAction("app.team.focus", () => this.enterTeamFocus());
2083
2085
  this.defaultEditor.onAction("app.editor.external", () => this.openExternalEditor());
2086
+ this.defaultEditor.onAction("app.input.voiceTranscribe", () => this.toggleVoiceTranscribe());
2084
2087
  this.defaultEditor.onAction("app.message.followUp", () => this.handleFollowUp());
2085
2088
  this.defaultEditor.onAction("app.message.dequeue", () => this.handleDequeue());
2086
2089
  this.defaultEditor.onAction("app.session.new", () => this.commandExecutor.handleClear());
@@ -2756,6 +2759,40 @@ export class InteractiveMode {
2756
2759
  * If multiple status messages are emitted back-to-back (without anything else being added to the chat),
2757
2760
  * we update the previous status line instead of appending new ones to avoid log spam.
2758
2761
  */
2762
+ /**
2763
+ * Toggle voice-to-text capture. First press spawns `voicetools`, streams
2764
+ * decoded segments into the editor via bracketed paste, and reports state
2765
+ * through the status line. Pressing the shortcut again stops early.
2766
+ */
2767
+ toggleVoiceTranscribe() {
2768
+ if (this.voiceSession?.running) {
2769
+ this.voiceSession.stop();
2770
+ this.voiceSession = undefined;
2771
+ this.showStatus("Voice input cancelled");
2772
+ return;
2773
+ }
2774
+ const statusLabels = {
2775
+ recording: "Recording... speak now (press again to cancel)",
2776
+ transcribing: "Transcribing...",
2777
+ done: "Voice input done",
2778
+ };
2779
+ this.voiceSession = startVoiceTranscribe(resolveVoicetoolsBin(), {
2780
+ onStatus: (status) => {
2781
+ this.showStatus(statusLabels[status] ?? status);
2782
+ if (status === "done") {
2783
+ 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
+ },
2794
+ });
2795
+ }
2759
2796
  showStatus(message) {
2760
2797
  const children = this.chatContainer.children;
2761
2798
  const last = children.length > 0 ? children[children.length - 1] : undefined;
@@ -4430,6 +4467,10 @@ export class InteractiveMode {
4430
4467
  }
4431
4468
  stop() {
4432
4469
  this.unregisterSignalHandlers();
4470
+ if (this.voiceSession?.running) {
4471
+ this.voiceSession.stop();
4472
+ this.voiceSession = undefined;
4473
+ }
4433
4474
  if (this.settingsManager.getShowTerminalProgress()) {
4434
4475
  this.ui.terminal.setProgress(false);
4435
4476
  }