vora-ai 0.1.38 → 0.1.39

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 (29) hide show
  1. package/dist/.buildstamp +1 -1
  2. package/dist/build-info.json +3 -3
  3. package/dist/canvas-host/a2ui/.bundle.hash +1 -1
  4. package/dist/cli-startup-metadata.json +1 -1
  5. package/dist/{command-registry-CNW-_45W.js → command-registry-CO9_pUZG.js} +1 -1
  6. package/dist/{command-registry-BRC9rQRa.js → command-registry-DddwHMX2.js} +4 -4
  7. package/dist/{completion-cli-wmAo3zAc.js → completion-cli-BIIBXfZj.js} +2 -2
  8. package/dist/completion-cli-oRV3KMRL.js +2 -0
  9. package/dist/{doctor-completion-D9ct64dl.js → doctor-completion-C6bLYDy-.js} +1 -1
  10. package/dist/entry.js +1 -1
  11. package/dist/extensions/telegram/.vora-runtime-deps-stamp.json +1 -1
  12. package/dist/{gateway-cli-DDZpfER2.js → gateway-cli-BO_W-26g.js} +1 -1
  13. package/dist/index.js +1 -1
  14. package/dist/{onboard-BgmpmwJd.js → onboard-Nd5ClWnL.js} +1 -1
  15. package/dist/{program-CSIyPhvT.js → program-C1tAki7e.js} +1 -1
  16. package/dist/{prompt-select-styled-BXm08-uh.js → prompt-select-styled-CxGQYnYg.js} +1 -1
  17. package/dist/{register.maintenance-Bpuqh4Q-.js → register.maintenance-DSOh3Hhg.js} +1 -1
  18. package/dist/{register.onboard-D-PZwo7h.js → register.onboard-Dq9iFtBA.js} +1 -1
  19. package/dist/{register.setup-BJyCA8h7.js → register.setup-Cpq-B5e_.js} +1 -1
  20. package/dist/{register.subclis-BMN-F1BZ.js → register.subclis-B6o2P3FE.js} +4 -4
  21. package/dist/{register.subclis-Cdl19lw2.js → register.subclis-yPh1RFXQ.js} +1 -1
  22. package/dist/{run-main-CHgQSnRn.js → run-main-Bwe4zE9G.js} +4 -4
  23. package/dist/{setup-Cu2MyUlf.js → setup-srafWXcp.js} +2 -2
  24. package/dist/{setup.finalize-CRAM1yrm.js → setup.finalize-VNsHuIdy.js} +3 -3
  25. package/dist/{update-cli-DhdDYxXa.js → update-cli-EMFxDpgT.js} +3 -3
  26. package/dist/{voice-cli-BRD6l0Sl.js → voice-cli-JpNFw9Go.js} +172 -40
  27. package/package.json +1 -1
  28. package/scripts/agora-stt-bridge.mjs +27 -8
  29. package/dist/completion-cli-CZqBPvMj.js +0 -2
package/dist/.buildstamp CHANGED
@@ -1 +1 @@
1
- {"builtAt":1776480007494,"head":"93abfe1b07bd746d7181c78415ddc656e7bb8a4f"}
1
+ {"builtAt":1776495873671,"head":"9878c07c9b86f7c090fc2516587e78587cdd02bf"}
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.1.38",
3
- "commit": "93abfe1b07bd746d7181c78415ddc656e7bb8a4f",
4
- "builtAt": "2026-04-18T02:40:08.213Z"
2
+ "version": "0.1.39",
3
+ "commit": "9878c07c9b86f7c090fc2516587e78587cdd02bf",
4
+ "builtAt": "2026-04-18T07:04:34.372Z"
5
5
  }
@@ -1 +1 @@
1
- 01b677ec20c9e797dda796c71459c28ae4b6a18ed0c7a835cedc7b231dee672f
1
+ cff3802555c03fed933598a392060fa774cd83785e5588aab671d80694fd31e2
@@ -10,5 +10,5 @@
10
10
  "signal",
11
11
  "imessage"
12
12
  ],
13
- "rootHelpText": "\n🌊 VORA 0.1.38 (93abfe1) — I speak fluent Audio, mild sarcasm, and aggressive Voice Activity Detection.\n\nUsage: vora [options] [command]\n\nOptions:\n --container <name> Run the CLI inside a running Podman/Docker container\n named <name> (default: env VORA_CONTAINER)\n --dev Dev profile: isolate state under ~/.vora-dev, default\n gateway port 19001, and shift derived ports\n (browser/canvas)\n -h, --help Display help for command\n --log-level <level> Global log level override for file + console\n (silent|fatal|error|warn|info|debug|trace)\n --no-color Disable ANSI colors\n --profile <name> Use a named profile (isolates\n VORA_STATE_DIR/VORA_CONFIG_PATH under ~/.vora-<name>)\n -V, --version output the version number\n\nCommands:\n Hint: commands suffixed with * have subcommands. Run <command> --help for details.\n acp * Agent Control Protocol tools\n agent Run one agent turn via the Gateway\n agents * Manage isolated agents (workspaces, auth, routing)\n approvals * Manage exec approvals (gateway or node host)\n backup * Create and verify local backup archives for Vora state\n channels * Manage connected chat channels (Telegram, Discord, etc.)\n clawbot * Legacy clawbot command aliases\n completion Generate shell completion script\n config * Non-interactive config helpers\n (get/set/unset/file/validate). Default: starts guided\n setup.\n configure Interactive configuration for credentials, channels,\n gateway, and agent defaults\n cron * Manage cron jobs via the Gateway scheduler\n daemon * Gateway service (legacy alias)\n dashboard Open the Control UI with your current token\n devices * Device pairing + token management\n directory * Lookup contact and group IDs (self, peers, groups) for\n supported chat channels\n dns * DNS helpers for wide-area discovery (Tailscale + CoreDNS)\n docs Search the live Vora docs\n doctor Health checks + quick fixes for the gateway and channels\n gateway * Run, inspect, and query the WebSocket Gateway\n health Fetch health from the running gateway\n help Display help for command\n hooks * Manage internal agent hooks\n logs Tail gateway file logs via RPC\n message * Send, read, and manage messages\n models * Discover, scan, and configure models\n node * Run and manage the headless node host service\n nodes * Manage gateway-owned node pairing and node commands\n onboard Interactive onboarding for gateway, workspace, and skills\n pairing * Secure DM pairing (approve inbound requests)\n plugins * Manage Vora plugins and extensions\n qr Generate iOS pairing QR/setup code\n reset Reset local config/state (keeps the CLI installed)\n sandbox * Manage sandbox containers for agent isolation\n secrets * Secrets runtime reload controls\n security * Security tools and local config audits\n sessions * List stored conversation sessions\n setup Initialize local config and agent workspace\n skills * List and inspect available skills\n status Show channel health and recent session recipients\n system * System events, heartbeat, and presence\n tasks * Inspect durable background task state\n tui Open a terminal UI connected to the Gateway\n uninstall Uninstall the gateway service + local data (CLI remains)\n update * Update Vora and inspect update channel status\n voice * Wake-word terminal voice loop (OpenWakeWord trigger + STT\n bridge + Gateway chat + optional Hume TTS)\n webhooks * Webhook helpers and integrations\n\nExamples:\n vora models --help\n Show detailed help for the models command.\n vora onboard\n Run interactive onboarding for the gateway, workspace, and skills.\n vora configure --section model --section gateway\n Re-open only the model and gateway configuration sections.\n vora gateway --port 27106\n Run the WebSocket Gateway locally.\n vora --dev gateway\n Run a dev Gateway (isolated state/config) on ws://127.0.0.1:19001.\n vora gateway --force\n Kill anything bound to the default gateway port, then start it.\n vora models status --plain\n Show the configured provider and default-model state.\n vora gateway ...\n Gateway control via WebSocket.\n vora message send --channel telegram --target @mychat --message \"Hi\"\n Send through a configured channel and print the result in the terminal.\n\nDocs: https://docs.vora.ai/cli\n\n"
13
+ "rootHelpText": "\n🌊 VORA 0.1.39 (9878c07) — Powered by Agora. Listening on your command.\n\nUsage: vora [options] [command]\n\nOptions:\n --container <name> Run the CLI inside a running Podman/Docker container\n named <name> (default: env VORA_CONTAINER)\n --dev Dev profile: isolate state under ~/.vora-dev, default\n gateway port 19001, and shift derived ports\n (browser/canvas)\n -h, --help Display help for command\n --log-level <level> Global log level override for file + console\n (silent|fatal|error|warn|info|debug|trace)\n --no-color Disable ANSI colors\n --profile <name> Use a named profile (isolates\n VORA_STATE_DIR/VORA_CONFIG_PATH under ~/.vora-<name>)\n -V, --version output the version number\n\nCommands:\n Hint: commands suffixed with * have subcommands. Run <command> --help for details.\n acp * Agent Control Protocol tools\n agent Run one agent turn via the Gateway\n agents * Manage isolated agents (workspaces, auth, routing)\n approvals * Manage exec approvals (gateway or node host)\n backup * Create and verify local backup archives for Vora state\n channels * Manage connected chat channels (Telegram, Discord, etc.)\n clawbot * Legacy clawbot command aliases\n completion Generate shell completion script\n config * Non-interactive config helpers\n (get/set/unset/file/validate). Default: starts guided\n setup.\n configure Interactive configuration for credentials, channels,\n gateway, and agent defaults\n cron * Manage cron jobs via the Gateway scheduler\n daemon * Gateway service (legacy alias)\n dashboard Open the Control UI with your current token\n devices * Device pairing + token management\n directory * Lookup contact and group IDs (self, peers, groups) for\n supported chat channels\n dns * DNS helpers for wide-area discovery (Tailscale + CoreDNS)\n docs Search the live Vora docs\n doctor Health checks + quick fixes for the gateway and channels\n gateway * Run, inspect, and query the WebSocket Gateway\n health Fetch health from the running gateway\n help Display help for command\n hooks * Manage internal agent hooks\n logs Tail gateway file logs via RPC\n message * Send, read, and manage messages\n models * Discover, scan, and configure models\n node * Run and manage the headless node host service\n nodes * Manage gateway-owned node pairing and node commands\n onboard Interactive onboarding for gateway, workspace, and skills\n pairing * Secure DM pairing (approve inbound requests)\n plugins * Manage Vora plugins and extensions\n qr Generate iOS pairing QR/setup code\n reset Reset local config/state (keeps the CLI installed)\n sandbox * Manage sandbox containers for agent isolation\n secrets * Secrets runtime reload controls\n security * Security tools and local config audits\n sessions * List stored conversation sessions\n setup Initialize local config and agent workspace\n skills * List and inspect available skills\n status Show channel health and recent session recipients\n system * System events, heartbeat, and presence\n tasks * Inspect durable background task state\n tui Open a terminal UI connected to the Gateway\n uninstall Uninstall the gateway service + local data (CLI remains)\n update * Update Vora and inspect update channel status\n voice * Wake-word terminal voice loop (OpenWakeWord trigger + STT\n bridge + Gateway chat + optional Hume TTS)\n webhooks * Webhook helpers and integrations\n\nExamples:\n vora models --help\n Show detailed help for the models command.\n vora onboard\n Run interactive onboarding for the gateway, workspace, and skills.\n vora configure --section model --section gateway\n Re-open only the model and gateway configuration sections.\n vora gateway --port 27106\n Run the WebSocket Gateway locally.\n vora --dev gateway\n Run a dev Gateway (isolated state/config) on ws://127.0.0.1:19001.\n vora gateway --force\n Kill anything bound to the default gateway port, then start it.\n vora models status --plain\n Show the configured provider and default-model state.\n vora gateway ...\n Gateway control via WebSocket.\n vora message send --channel telegram --target @mychat --message \"Hi\"\n Send through a configured channel and print the result in the terminal.\n\nDocs: https://docs.vora.ai/cli\n\n"
14
14
  }
@@ -1,3 +1,3 @@
1
1
  import "./core-command-descriptors-Dmh3xVCU.js";
2
- import { n as registerCoreCliByName } from "./command-registry-BRC9rQRa.js";
2
+ import { n as registerCoreCliByName } from "./command-registry-DddwHMX2.js";
3
3
  export { registerCoreCliByName };
@@ -1,7 +1,7 @@
1
1
  import { O as hasHelpOrVersion, T as getPrimaryCommand } from "./logger-DtBbg3AQ.js";
2
2
  import { n as removeCommandByName, t as registerLazyCommand } from "./register-lazy-command-C4_WvYdL.js";
3
3
  import { t as getCoreCliCommandDescriptors } from "./core-command-descriptors-Dmh3xVCU.js";
4
- import { i as registerSubCliCommands } from "./register.subclis-BMN-F1BZ.js";
4
+ import { i as registerSubCliCommands } from "./register.subclis-B6o2P3FE.js";
5
5
  //#region src/cli/program/command-registry.ts
6
6
  const shouldRegisterCorePrimaryOnly = (argv) => {
7
7
  if (hasHelpOrVersion(argv)) return false;
@@ -15,7 +15,7 @@ const coreEntries = [
15
15
  hasSubcommands: false
16
16
  }],
17
17
  register: async ({ program }) => {
18
- (await import("./register.setup-BJyCA8h7.js")).registerSetupCommand(program);
18
+ (await import("./register.setup-Cpq-B5e_.js")).registerSetupCommand(program);
19
19
  }
20
20
  },
21
21
  {
@@ -25,7 +25,7 @@ const coreEntries = [
25
25
  hasSubcommands: false
26
26
  }],
27
27
  register: async ({ program }) => {
28
- (await import("./register.onboard-D-PZwo7h.js")).registerOnboardCommand(program);
28
+ (await import("./register.onboard-Dq9iFtBA.js")).registerOnboardCommand(program);
29
29
  }
30
30
  },
31
31
  {
@@ -82,7 +82,7 @@ const coreEntries = [
82
82
  }
83
83
  ],
84
84
  register: async ({ program }) => {
85
- (await import("./register.maintenance-Bpuqh4Q-.js")).registerMaintenanceCommands(program);
85
+ (await import("./register.maintenance-DSOh3Hhg.js")).registerMaintenanceCommands(program);
86
86
  }
87
87
  },
88
88
  {
@@ -3,8 +3,8 @@ import { t as formatDocsLink } from "./links-D-QW4VCX.js";
3
3
  import { r as theme } from "./theme-DEBOahQW.js";
4
4
  import { _ as resolveStateDir } from "./paths-5dqsPi5h.js";
5
5
  import { m as pathExists } from "./utils-D25qzO4S.js";
6
- import { n as loadValidatedConfigForPluginRegistration, r as registerSubCliByName, t as getSubCliEntries } from "./register.subclis-BMN-F1BZ.js";
7
- import { n as registerCoreCliByName, t as getCoreCliCommandNames } from "./command-registry-BRC9rQRa.js";
6
+ import { n as loadValidatedConfigForPluginRegistration, r as registerSubCliByName, t as getSubCliEntries } from "./register.subclis-B6o2P3FE.js";
7
+ import { n as registerCoreCliByName, t as getCoreCliCommandNames } from "./command-registry-DddwHMX2.js";
8
8
  import { t as getProgramContext } from "./program-context-C0Ru__k1.js";
9
9
  import path from "node:path";
10
10
  import os from "node:os";
@@ -0,0 +1,2 @@
1
+ import { a as registerCompletionCli } from "./completion-cli-BIIBXfZj.js";
2
+ export { registerCompletionCli };
@@ -1,7 +1,7 @@
1
1
  import { t as resolveVoraPackageRoot } from "./vora-root-fqgy2cBi.js";
2
2
  import { n as resolveCliName } from "./cli-name-C_IzpPbS.js";
3
3
  import { t as note } from "./note-DfcrAbT0.js";
4
- import { c as usesSlowDynamicCompletion, i as isCompletionInstalled, o as resolveCompletionCachePath, r as installCompletion, s as resolveShellFromEnv, t as completionCacheExists } from "./completion-cli-wmAo3zAc.js";
4
+ import { c as usesSlowDynamicCompletion, i as isCompletionInstalled, o as resolveCompletionCachePath, r as installCompletion, s as resolveShellFromEnv, t as completionCacheExists } from "./completion-cli-BIIBXfZj.js";
5
5
  import path from "node:path";
6
6
  import { spawnSync } from "node:child_process";
7
7
  //#region src/commands/doctor-completion.ts
package/dist/entry.js CHANGED
@@ -200,7 +200,7 @@ function tryHandleRootHelpFastPath(argv, deps = {}) {
200
200
  }
201
201
  function runMainOrRootHelp(argv) {
202
202
  if (tryHandleRootHelpFastPath(argv)) return;
203
- import("./run-main-CHgQSnRn.js").then(({ runCli }) => runCli(argv)).catch((error) => {
203
+ import("./run-main-Bwe4zE9G.js").then(({ runCli }) => runCli(argv)).catch((error) => {
204
204
  console.error("[vora] Failed to start CLI:", error instanceof Error ? error.stack ?? error.message : error);
205
205
  process$1.exitCode = 1;
206
206
  });
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "fingerprint": "f5cc4428157244c03371a02e98bc2b0556e1853613db4dfe31efcfd24cef35f1",
3
- "generatedAt": "2026-04-18T02:40:07.361Z"
3
+ "generatedAt": "2026-04-18T07:04:33.559Z"
4
4
  }
@@ -196,7 +196,7 @@ import { s as normalizeUpdateChannel } from "./update-channels-C8-n6_kE.js";
196
196
  import { n as compareSemverStrings, o as resolveNpmChannelTag, t as checkUpdateStatus } from "./update-check-Cw2Zo7Rn.js";
197
197
  import { i as resolveGatewayStartupPluginIds, r as resolveConfiguredDeferredChannelPluginIds } from "./channel-plugin-ids-BgeBHwp9.js";
198
198
  import { l as startTaskRegistryMaintenance, n as getInspectableTaskRegistrySummary } from "./task-registry.maintenance-CfvslEdg.js";
199
- import { t as runSetupWizard } from "./setup-Cu2MyUlf.js";
199
+ import { t as runSetupWizard } from "./setup-srafWXcp.js";
200
200
  import { _ as buildGogWatchStartArgs, g as buildGogWatchServeArgs, i as ensureTailscaleEndpoint, w as resolveGmailHookRuntimeConfig } from "./gmail-setup-utils-CstpQWwK.js";
201
201
  import { i as loadAgentIdentity, o as pruneAgentConfig, r as findAgentEntryIndex, t as applyAgentConfig } from "./agents.config-BXIFVvWT.js";
202
202
  import { a as resolveApnsAuthConfigFromEnv, c as shouldClearStoredApnsRegistration, d as MediaOffloadError, f as parseMessageWithAttachments, l as resolveApnsRelayConfigFromEnv, n as loadApnsRegistration, o as sendApnsAlert, r as normalizeApnsEnvironment, s as sendApnsBackgroundWake, t as clearApnsRegistrationIfCurrent, u as normalizeRpcAttachmentsToChatAttachments } from "./push-apns-wrSw6X5_.js";
package/dist/index.js CHANGED
@@ -28,7 +28,7 @@ let saveSessionStore;
28
28
  let toWhatsappJid;
29
29
  let waitForever;
30
30
  async function loadLegacyCliDeps() {
31
- const [{ installGaxiosFetchCompat }, { runCli }] = await Promise.all([import("./gaxios-fetch-compat-BZRlVrBH.js"), import("./run-main-CHgQSnRn.js")]);
31
+ const [{ installGaxiosFetchCompat }, { runCli }] = await Promise.all([import("./gaxios-fetch-compat-BZRlVrBH.js"), import("./run-main-Bwe4zE9G.js")]);
32
32
  return {
33
33
  installGaxiosFetchCompat,
34
34
  runCli
@@ -11,7 +11,7 @@ import { c as normalizeGatewayTokenInput, d as randomToken, f as resolveControlU
11
11
  import { n as logConfigUpdated } from "./logging-BQ-k0nK2.js";
12
12
  import { t as WizardCancelledError } from "./prompts-BVJ3zQFl.js";
13
13
  import { t as createClackPrompter } from "./clack-prompter-Be8sX0Lg.js";
14
- import { t as runSetupWizard } from "./setup-Cu2MyUlf.js";
14
+ import { t as runSetupWizard } from "./setup-srafWXcp.js";
15
15
  import { a as resolveManifestProviderOnboardAuthFlags } from "./provider-auth-choices-DcNzlbgs.js";
16
16
  import { i as resolveDeprecatedAuthChoiceReplacement, n as isDeprecatedAuthChoice, r as normalizeLegacyOnboardAuthChoice, t as formatDeprecatedNonInteractiveAuthChoiceError } from "./auth-choice-legacy-DGeZIT-G.js";
17
17
  import { r as applyLocalSetupWorkspaceConfig } from "./onboard-config-DkkHPbTO.js";
@@ -7,7 +7,7 @@ import { n as VERSION } from "./version-A4Ns-pm9.js";
7
7
  import { n as resolveCliName } from "./cli-name-C_IzpPbS.js";
8
8
  import { t as emitCliBanner } from "./banner-C5yJCudk.js";
9
9
  import { n as resolveCliChannelOptions } from "./channel-options-D_8z6c-h.js";
10
- import { i as registerProgramCommands } from "./command-registry-BRC9rQRa.js";
10
+ import { i as registerProgramCommands } from "./command-registry-DddwHMX2.js";
11
11
  import { n as setProgramContext } from "./program-context-C0Ru__k1.js";
12
12
  import { t as isCommandJsonOutputMode } from "./json-mode-KmFHsA3R.js";
13
13
  import "./ports-CQo1U86K.js";
@@ -108,7 +108,7 @@ import { t as ensureSystemdUserLingerInteractive } from "./systemd-linger-B_gzrf
108
108
  import { t as formatHealthCheckFailure } from "./health-format-TyQPx9cW.js";
109
109
  import { a as stripUnknownConfigKeys, i as resolveConfigPathTarget, n as formatConfigPath, r as noteOpencodeProviderOverrides, t as runDoctorConfigPreflight } from "./doctor-config-preflight-B2IpAFkH.js";
110
110
  import { a as isMattermostMutableAllowEntry, i as isMSTeamsMutableAllowEntry, n as isGoogleChatMutableAllowEntry, o as isSlackMutableAllowEntry, r as isIrcMutableAllowEntry, s as isZalouserMutableGroupEntry, t as isDiscordMutableAllowEntry } from "./mutable-allowlist-detectors-zVflHd8P.js";
111
- import { n as doctorShellCompletion } from "./doctor-completion-D9ct64dl.js";
111
+ import { n as doctorShellCompletion } from "./doctor-completion-C6bLYDy-.js";
112
112
  import { t as collectChannelStatusIssues } from "./channels-status-issues-CCJ10zgF.js";
113
113
  import { t as resolveDefaultChannelAccountContext } from "./channel-account-context-ChxhkByq.js";
114
114
  import "./doctor-state-migrations-BlH3irT5.js";
@@ -16,7 +16,7 @@ import { r as resolveGatewayService } from "./service-BnUY5mkU.js";
16
16
  import { n as resolveConfiguredSecretInputWithFallback } from "./resolve-configured-secret-input-string-BGHutVyf.js";
17
17
  import { n as runCommandWithRuntime } from "./cli-utils-4n6KI2ho.js";
18
18
  import { a as removePath, i as listAgentSessionDirs, o as removeStateAndLinkedPaths, r as buildCleanupPlan, s as removeWorkspaceDirs } from "./backup-create-DZood8Ea.js";
19
- import { n as doctorCommand, t as selectStyled } from "./prompt-select-styled-BXm08-uh.js";
19
+ import { n as doctorCommand, t as selectStyled } from "./prompt-select-styled-CxGQYnYg.js";
20
20
  import path from "node:path";
21
21
  import { cancel, confirm, isCancel, multiselect } from "@clack/prompts";
22
22
  //#region src/infra/clipboard.ts
@@ -4,7 +4,7 @@ import { r as theme } from "./theme-DEBOahQW.js";
4
4
  import { n as runCommandWithRuntime } from "./cli-utils-4n6KI2ho.js";
5
5
  import { a as resolveManifestProviderOnboardAuthFlags } from "./provider-auth-choices-DcNzlbgs.js";
6
6
  import { n as formatAuthChoiceChoicesForCli } from "./auth-choice-options-Blz8a0tL.js";
7
- import { n as CORE_ONBOARD_AUTH_FLAGS, t as setupWizardCommand } from "./onboard-BgmpmwJd.js";
7
+ import { n as CORE_ONBOARD_AUTH_FLAGS, t as setupWizardCommand } from "./onboard-Nd5ClWnL.js";
8
8
  //#region src/cli/program/register.onboard.ts
9
9
  function resolveInstallDaemonFlag(command, opts) {
10
10
  if (!command || typeof command !== "object") return;
@@ -11,7 +11,7 @@ import { s as resolveSessionTranscriptsDir } from "./paths-UyCuvwtc.js";
11
11
  import { n as safeParseWithSchema } from "./zod-parse-DHX0rEx9.js";
12
12
  import { n as runCommandWithRuntime } from "./cli-utils-4n6KI2ho.js";
13
13
  import { n as logConfigUpdated, t as formatConfigPath } from "./logging-BQ-k0nK2.js";
14
- import { t as setupWizardCommand } from "./onboard-BgmpmwJd.js";
14
+ import { t as setupWizardCommand } from "./onboard-Nd5ClWnL.js";
15
15
  import fs from "node:fs/promises";
16
16
  import { z } from "zod";
17
17
  import JSON5 from "json5";
@@ -30,7 +30,7 @@ const entries = [
30
30
  description: "Run, inspect, and query the WebSocket Gateway",
31
31
  hasSubcommands: true,
32
32
  register: async (program) => {
33
- (await import("./gateway-cli-DDZpfER2.js")).registerGatewayCli(program);
33
+ (await import("./gateway-cli-BO_W-26g.js")).registerGatewayCli(program);
34
34
  }
35
35
  },
36
36
  {
@@ -118,7 +118,7 @@ const entries = [
118
118
  description: "Wake-word terminal voice loop (OpenWakeWord trigger + STT bridge + Gateway chat + optional Hume TTS)",
119
119
  hasSubcommands: true,
120
120
  register: async (program) => {
121
- (await import("./voice-cli-BRD6l0Sl.js")).registerVoiceCli(program);
121
+ (await import("./voice-cli-JpNFw9Go.js")).registerVoiceCli(program);
122
122
  }
123
123
  },
124
124
  {
@@ -244,7 +244,7 @@ const entries = [
244
244
  description: "Update Vora and inspect update channel status",
245
245
  hasSubcommands: true,
246
246
  register: async (program) => {
247
- (await import("./update-cli-DhdDYxXa.js")).registerUpdateCli(program);
247
+ (await import("./update-cli-EMFxDpgT.js")).registerUpdateCli(program);
248
248
  }
249
249
  },
250
250
  {
@@ -252,7 +252,7 @@ const entries = [
252
252
  description: "Generate shell completion script",
253
253
  hasSubcommands: false,
254
254
  register: async (program) => {
255
- (await import("./completion-cli-CZqBPvMj.js")).registerCompletionCli(program);
255
+ (await import("./completion-cli-oRV3KMRL.js")).registerCompletionCli(program);
256
256
  }
257
257
  }
258
258
  ];
@@ -1,3 +1,3 @@
1
1
  import "./subcli-descriptors-Cc423xKz.js";
2
- import { n as loadValidatedConfigForPluginRegistration, r as registerSubCliByName } from "./register.subclis-BMN-F1BZ.js";
2
+ import { n as loadValidatedConfigForPluginRegistration, r as registerSubCliByName } from "./register.subclis-B6o2P3FE.js";
3
3
  export { loadValidatedConfigForPluginRegistration, registerSubCliByName };
@@ -374,7 +374,7 @@ async function runCli(argv = process$1.argv) {
374
374
  }
375
375
  if (await tryRouteCli(normalizedArgv)) return;
376
376
  enableConsoleCapture();
377
- const { buildProgram } = await import("./program-CSIyPhvT.js");
377
+ const { buildProgram } = await import("./program-C1tAki7e.js");
378
378
  const program = buildProgram();
379
379
  const { installUnhandledRejectionHandler } = await import("./unhandled-rejections-D0HD8aaS.js");
380
380
  installUnhandledRejectionHandler();
@@ -388,10 +388,10 @@ async function runCli(argv = process$1.argv) {
388
388
  const { getProgramContext } = await import("./program-context-Bntrf4rW.js");
389
389
  const ctx = getProgramContext(program);
390
390
  if (ctx) {
391
- const { registerCoreCliByName } = await import("./command-registry-CNW-_45W.js");
391
+ const { registerCoreCliByName } = await import("./command-registry-CO9_pUZG.js");
392
392
  await registerCoreCliByName(program, ctx, primary, parseArgv);
393
393
  }
394
- const { registerSubCliByName } = await import("./register.subclis-Cdl19lw2.js");
394
+ const { registerSubCliByName } = await import("./register.subclis-yPh1RFXQ.js");
395
395
  await registerSubCliByName(program, primary);
396
396
  }
397
397
  if (!shouldSkipPluginCommandRegistration({
@@ -400,7 +400,7 @@ async function runCli(argv = process$1.argv) {
400
400
  hasBuiltinPrimary: primary !== null && program.commands.some((command) => command.name() === primary)
401
401
  })) {
402
402
  const { registerPluginCliCommands } = await import("./cli-QwFXIS1_.js");
403
- const { loadValidatedConfigForPluginRegistration } = await import("./register.subclis-Cdl19lw2.js");
403
+ const { loadValidatedConfigForPluginRegistration } = await import("./register.subclis-yPh1RFXQ.js");
404
404
  const config = await loadValidatedConfigForPluginRegistration();
405
405
  if (config) {
406
406
  await registerPluginCliCommands(program, config, void 0, void 0, {
@@ -106,7 +106,7 @@ async function setupQuickstartVoiceRuntime(params) {
106
106
  "STT/TTS use the VORA backend by default, so local Agora/Hume keys are not required."
107
107
  ].join("\n"), "Voice runtime");
108
108
  try {
109
- const { runVoiceSetup } = await import("./voice-cli-BRD6l0Sl.js");
109
+ const { runVoiceSetup } = await import("./voice-cli-JpNFw9Go.js");
110
110
  await runVoiceSetup({}, params.runtime);
111
111
  await params.prompter.note([
112
112
  "Voice runtime is ready.",
@@ -464,7 +464,7 @@ async function runSetupWizard(opts, runtime = defaultRuntime, prompter) {
464
464
  mode
465
465
  });
466
466
  await writeConfigFile(nextConfig);
467
- const { finalizeSetupWizard } = await import("./setup.finalize-CRAM1yrm.js");
467
+ const { finalizeSetupWizard } = await import("./setup.finalize-VNsHuIdy.js");
468
468
  const { launchedTui } = await finalizeSetupWizard({
469
469
  flow,
470
470
  opts,
@@ -10,12 +10,12 @@ import { f as resolveControlUiLinks, g as waitForGatewayReachable, i as formatCo
10
10
  import { r as resolveGatewayService, t as describeGatewayServiceRestart } from "./service-BnUY5mkU.js";
11
11
  import { i as isSystemdUserServiceAvailable } from "./systemd-9Gclqlhk.js";
12
12
  import { t as listConfiguredWebSearchProviders } from "./runtime-B0GlgEXr.js";
13
- import { r as installCompletion } from "./completion-cli-wmAo3zAc.js";
13
+ import { r as installCompletion } from "./completion-cli-BIIBXfZj.js";
14
14
  import { r as healthCommand } from "./health-DJqooRWK.js";
15
15
  import { t as ensureControlUiAssetsBuilt } from "./control-ui-assets-DVSZ-yd-.js";
16
16
  import { t as resolveSetupSecretInputString } from "./setup.secret-input-DeWdLao-.js";
17
17
  import { t as formatHealthCheckFailure } from "./health-format-TyQPx9cW.js";
18
- import { r as ensureCompletionCacheExists, t as checkShellCompletionStatus } from "./doctor-completion-D9ct64dl.js";
18
+ import { r as ensureCompletionCacheExists, t as checkShellCompletionStatus } from "./doctor-completion-C6bLYDy-.js";
19
19
  import { t as runTui } from "./tui-DCtnC9C0.js";
20
20
  import path from "node:path";
21
21
  import os from "node:os";
@@ -351,7 +351,7 @@ async function finalizeSetupWizard(options) {
351
351
  if (hatchChoice === "voice") {
352
352
  restoreTerminalState("pre-setup voice", { resumeStdinIfPaused: true });
353
353
  try {
354
- const { runVoiceLoop } = await import("./voice-cli-BRD6l0Sl.js");
354
+ const { runVoiceLoop } = await import("./voice-cli-JpNFw9Go.js");
355
355
  await runVoiceLoop({
356
356
  url: links.wsUrl,
357
357
  token: settings.authMode === "token" ? settings.gatewayToken : void 0,
@@ -21,14 +21,14 @@ import { a as terminateStaleGatewayPids, i as renderRestartDiagnostics, s as wai
21
21
  import { r as formatDurationPrecise } from "./format-duration-C7KLrjgc.js";
22
22
  import { o as trimLogTail } from "./restart-sentinel-CjIY0IZ8.js";
23
23
  import { t as formatHelpExamples } from "./help-format-DC7U6A-Y.js";
24
- import { r as installCompletion } from "./completion-cli-wmAo3zAc.js";
24
+ import { r as installCompletion } from "./completion-cli-BIIBXfZj.js";
25
25
  import { n as renderTable, t as getTerminalTableWidth } from "./table-CYPwb2Cg.js";
26
26
  import { c as resolveEffectiveUpdateChannel, i as formatUpdateChannelLabel, l as resolveUpdateChannelDisplay, r as channelToNpmTag, s as normalizeUpdateChannel } from "./update-channels-C8-n6_kE.js";
27
27
  import { i as fetchNpmTagVersion, n as compareSemverStrings, o as resolveNpmChannelTag, r as fetchNpmPackageTargetStatus, t as checkUpdateStatus } from "./update-check-Cw2Zo7Rn.js";
28
28
  import { a as collectInstalledGlobalPackageErrors, c as detectGlobalInstallManagerForRoot, d as resolveGlobalInstallSpec, f as resolveGlobalPackageRoot, h as readPackageVersion, i as cleanupGlobalRenameDirs, l as globalInstallArgs, m as readPackageName, n as runGatewayUpdate, o as createGlobalInstallEnv, p as normalizePackageTagInput, r as canResolveRegistryVersionForPackageTarget, s as detectGlobalInstallManagerByPresence, u as resolveExpectedInstalledVersionFromSpec } from "./server-startup-matrix-migration-DL743qrx.js";
29
29
  import { n as updateNpmInstalledPlugins, t as syncPluginsForUpdateChannel } from "./update-XayfF-ly.js";
30
- import { n as doctorCommand, t as selectStyled } from "./prompt-select-styled-BXm08-uh.js";
31
- import { r as ensureCompletionCacheExists, t as checkShellCompletionStatus } from "./doctor-completion-D9ct64dl.js";
30
+ import { n as doctorCommand, t as selectStyled } from "./prompt-select-styled-CxGQYnYg.js";
31
+ import { r as ensureCompletionCacheExists, t as checkShellCompletionStatus } from "./doctor-completion-C6bLYDy-.js";
32
32
  import { i as resolveUpdateAvailability, n as formatUpdateOneLiner, t as formatUpdateAvailableHint } from "./status.update-209xudNW.js";
33
33
  import path from "node:path";
34
34
  import { spawn, spawnSync } from "node:child_process";
@@ -14,11 +14,11 @@ import readline from "node:readline";
14
14
  const DEFAULT_WAKE_MODEL_FILE = "hey_vora.onnx";
15
15
  const DEFAULT_WAKE_THRESHOLD = .5;
16
16
  const DEFAULT_WAIT_MS = 4e4;
17
- const DEFAULT_STT_TIMEOUT_MS = 25e3;
18
- const DEFAULT_STT_LANGUAGE = "vi-VN,en-US";
17
+ const DEFAULT_STT_TIMEOUT_MS = 16e3;
18
+ const DEFAULT_STT_LANGUAGE = "en-US";
19
19
  const DEFAULT_HUME_VOICE_ID = "9e068547-5ba4-4c8e-8e03-69282a008f04";
20
20
  const DEFAULT_HUME_SPEED = 1.2;
21
- const DEFAULT_TTS_TIMEOUT_MS = 2e4;
21
+ const DEFAULT_TTS_TIMEOUT_MS = 8e3;
22
22
  const DEFAULT_TTS_MAX_CHARS = 900;
23
23
  const DEFAULT_ELEVENLABS_VOICE_ID = "JBFqnCBsd6RMkjVDRZzb";
24
24
  const DEFAULT_ELEVENLABS_MODEL_ID = "eleven_multilingual_v2";
@@ -26,16 +26,82 @@ const DEFAULT_ELEVENLABS_OUTPUT_FORMAT = "mp3_44100_128";
26
26
  const DEFAULT_VOICE_BACKEND_URL = "https://vora-ai-backend-uemj.onrender.com";
27
27
  const DEFAULT_FOLLOW_UP_MS = 45e3;
28
28
  const DEFAULT_FOLLOW_UP_MAX_TURNS = 4;
29
- const DEFAULT_FOLLOW_UP_STT_TIMEOUT_MS = 12e3;
29
+ const DEFAULT_FOLLOW_UP_STT_TIMEOUT_MS = 1e4;
30
30
  const DEFAULT_BACKEND_PROBE_TIMEOUT_MS = 6e4;
31
31
  const DEFAULT_BACKEND_STT_PROBE_TIMEOUT_MS = 6e4;
32
- const DEFAULT_STT_BRIDGE_COMMAND_GRACE_MS = 6e4;
32
+ const DEFAULT_STT_BRIDGE_COMMAND_GRACE_MS = 2e4;
33
+ const DEFAULT_WAKE_ACK_WAIT_MS = 12e3;
33
34
  const WAKE_PYTHON_MODULES = [
34
35
  "openwakeword",
35
36
  "pyaudio",
36
37
  "numpy"
37
38
  ];
38
39
  const WAKE_PYTHON_DEPENDENCY_CHECK_TIMEOUT_MS = 6e4;
40
+ function boolFromEnv(value) {
41
+ if (typeof value !== "string") return false;
42
+ const normalized = value.trim().toLowerCase();
43
+ return [
44
+ "1",
45
+ "true",
46
+ "yes",
47
+ "on"
48
+ ].includes(normalized);
49
+ }
50
+ function isVoiceDebug(opts) {
51
+ return Boolean(opts?.debug) || boolFromEnv(process.env.VORA_VOICE_DEBUG);
52
+ }
53
+ function formatMs(ms) {
54
+ if (ms < 1e3) return `${ms}ms`;
55
+ return `${(ms / 1e3).toFixed(ms % 1e3 === 0 ? 0 : 1)}s`;
56
+ }
57
+ function voiceDebugLog(message, opts) {
58
+ if (isVoiceDebug(opts)) defaultRuntime.log(theme.muted(message));
59
+ }
60
+ function voiceInfo(message) {
61
+ defaultRuntime.log(`${theme.accent("›")} ${message}`);
62
+ }
63
+ function voiceSuccess(message) {
64
+ defaultRuntime.log(`${theme.success("✓")} ${message}`);
65
+ }
66
+ function voiceWarn(message) {
67
+ defaultRuntime.log(`${theme.warn("!")} ${message}`);
68
+ }
69
+ function voiceBox(title, lines) {
70
+ const width = Math.max(title.length + 4, ...lines.map((line) => line.length + 4), 52);
71
+ const top = `┌─ ${title} ${"─".repeat(Math.max(0, width - title.length - 4))}`;
72
+ const bottom = `└${"─".repeat(width - 1)}`;
73
+ defaultRuntime.log(theme.accent(top));
74
+ for (const line of lines) defaultRuntime.log(`${theme.accent("│")} ${line}`);
75
+ defaultRuntime.log(theme.accent(bottom));
76
+ }
77
+ function wrapVoiceText(text, width = 86) {
78
+ const output = [];
79
+ for (const rawLine of text.replace(/\r/g, "").split("\n")) {
80
+ const line = rawLine.trim();
81
+ if (!line) {
82
+ output.push("");
83
+ continue;
84
+ }
85
+ let current = "";
86
+ for (const word of line.split(/\s+/g)) {
87
+ if (!current) {
88
+ current = word;
89
+ continue;
90
+ }
91
+ if (`${current} ${word}`.length > width) {
92
+ output.push(current);
93
+ current = word;
94
+ continue;
95
+ }
96
+ current = `${current} ${word}`;
97
+ }
98
+ if (current) output.push(current);
99
+ }
100
+ return output.length > 0 ? output : [""];
101
+ }
102
+ function voiceMessageBox(label, text) {
103
+ voiceBox(label, wrapVoiceText(text));
104
+ }
39
105
  function resolveBundledAgoraBridgeScriptPath() {
40
106
  const candidates = [path.resolve(import.meta.dirname, "..", "scripts", "agora-stt-bridge.mjs"), path.resolve(import.meta.dirname, "..", "..", "scripts", "agora-stt-bridge.mjs")];
41
107
  return candidates.find((candidate) => existsSync(candidate)) ?? candidates[0];
@@ -248,6 +314,7 @@ function resolveVoiceRuntimeOptions(opts) {
248
314
  backendUrl
249
315
  });
250
316
  return {
317
+ debug: isVoiceDebug(opts),
251
318
  gateway: {
252
319
  url: trimToUndefined(opts.url),
253
320
  token: trimToUndefined(opts.token),
@@ -816,13 +883,18 @@ async function runShellCommand(command, timeoutMs, opts) {
816
883
  });
817
884
  });
818
885
  });
886
+ let timeoutHandle;
819
887
  const timeout = new Promise((_, reject) => {
820
- setTimeout(() => {
888
+ timeoutHandle = setTimeout(() => {
821
889
  child.kill("SIGTERM");
822
890
  reject(/* @__PURE__ */ new Error(`command timeout after ${timeoutMs}ms`));
823
891
  }, timeoutMs);
824
892
  });
825
- return await Promise.race([done, timeout]);
893
+ try {
894
+ return await Promise.race([done, timeout]);
895
+ } finally {
896
+ if (timeoutHandle) clearTimeout(timeoutHandle);
897
+ }
826
898
  }
827
899
  async function runStreamingCommand(bin, args, cwd) {
828
900
  await new Promise((resolve, reject) => {
@@ -988,22 +1060,32 @@ async function transcribeSpeech(resolved, opts) {
988
1060
  timeout_ms: String(timeoutMs)
989
1061
  });
990
1062
  const phase = opts?.phase ?? "voice";
991
- defaultRuntime.log(`[voice] preparing ${phase} STT (${resolved.stt.language}); wait for "listening now" before speaking`);
1063
+ const startedAt = Date.now();
1064
+ voiceInfo(`STT ${phase}: starting Agora bridge (${resolved.stt.language}, timeout ${formatMs(timeoutMs)}). Wait for "Listening now" before speaking.`);
992
1065
  let readyLogged = false;
993
1066
  const result = await runShellCommand(rendered, timeoutMs + DEFAULT_STT_BRIDGE_COMMAND_GRACE_MS, { onStderrLine: (line) => {
994
1067
  const message = line.trim();
995
1068
  if (!message) return;
996
1069
  if (message === "[agora-stt-bridge] ready") {
997
1070
  readyLogged = true;
998
- process.stdout.write("\x07");
999
- defaultRuntime.log(`[voice] listening now (${resolved.stt.language}); speak now`);
1071
+ if (boolFromEnv(process.env.VORA_VOICE_BEEP)) process.stdout.write("\x07");
1072
+ voiceSuccess(`Listening now (${resolved.stt.language}). Speak now.`);
1073
+ return;
1074
+ }
1075
+ const stage = message.match(/^\[agora-stt-bridge\] stage=([a-z0-9_-]+)(?:\s+(.*))?$/i);
1076
+ if (stage) {
1077
+ const label = stage[1]?.replaceAll("_", " ") ?? "bridge";
1078
+ const detail = stage[2]?.trim();
1079
+ voiceInfo(`STT ${label}${detail ? `: ${detail}` : ""}`);
1000
1080
  return;
1001
1081
  }
1002
- if (message.includes("fatal:") || message.includes("managed browser unavailable") || message.includes("browser auto-open disabled")) defaultRuntime.error(message);
1082
+ if (message.includes("fatal:") || message.includes("managed browser unavailable") || message.includes("browser auto-open disabled") || message.includes("timeout waiting")) defaultRuntime.error(message);
1003
1083
  } });
1004
1084
  if (result.code !== 0) throw new Error(`Agora STT bridge failed (exit ${result.code}): ${result.stderr.trim() || "no stderr"}`);
1005
- if (!readyLogged) defaultRuntime.log("[voice] STT bridge completed without an explicit ready signal");
1006
- return extractTranscriptFromCommandOutput(result.stdout);
1085
+ if (!readyLogged) voiceWarn("STT bridge completed without an explicit ready signal");
1086
+ const transcript = extractTranscriptFromCommandOutput(result.stdout);
1087
+ voiceDebugLog(`[voice] STT ${phase} completed in ${formatMs(Date.now() - startedAt)}`);
1088
+ return transcript;
1007
1089
  }
1008
1090
  function normalizeHistoryMessages(payload) {
1009
1091
  if (!payload || typeof payload !== "object") return [];
@@ -1043,15 +1125,19 @@ function latestAssistantText(messages) {
1043
1125
  }
1044
1126
  return null;
1045
1127
  }
1128
+ function countAssistantMessages(messages) {
1129
+ return messages.filter(isAssistantMessage).length;
1130
+ }
1046
1131
  async function waitForAssistantReply(params) {
1047
1132
  const startedAt = Date.now();
1048
1133
  while (Date.now() - startedAt < params.waitMs) {
1049
- await sleep(1e3);
1050
- const current = latestAssistantText(normalizeHistoryMessages(await params.client.loadHistory({
1134
+ await sleep(350);
1135
+ const messages = normalizeHistoryMessages(await params.client.loadHistory({
1051
1136
  sessionKey: params.sessionKey,
1052
1137
  limit: 100
1053
- })));
1054
- if (current && current !== params.beforeText) return current;
1138
+ }));
1139
+ const current = latestAssistantText(messages);
1140
+ if (current && (current !== params.beforeText || countAssistantMessages(messages) > params.beforeAssistantCount)) return current;
1055
1141
  }
1056
1142
  return null;
1057
1143
  }
@@ -1226,6 +1312,7 @@ async function speakReply(resolved, text) {
1226
1312
  if (resolved.tts.provider === "none") return;
1227
1313
  const ttsText = prepareTtsText(text);
1228
1314
  if (!ttsText) return;
1315
+ const startedAt = Date.now();
1229
1316
  if (resolved.tts.provider === "hume") {
1230
1317
  if (!resolved.tts.backendUrl && !resolved.tts.humeApiKey) {
1231
1318
  defaultRuntime.error("[voice] Hume TTS skipped: missing backend/API key");
@@ -1233,6 +1320,7 @@ async function speakReply(resolved, text) {
1233
1320
  }
1234
1321
  let audioPath;
1235
1322
  try {
1323
+ voiceInfo(`TTS: synthesizing with Hume (timeout ${formatMs(resolved.tts.timeoutMs)})`);
1236
1324
  audioPath = await synthesizeHumeAudio({
1237
1325
  text: ttsText,
1238
1326
  apiKey: resolved.tts.humeApiKey,
@@ -1242,12 +1330,14 @@ async function speakReply(resolved, text) {
1242
1330
  timeoutMs: resolved.tts.timeoutMs
1243
1331
  });
1244
1332
  } catch (error) {
1245
- defaultRuntime.error(`[voice] Hume TTS failed: ${String(error)}; using system fallback`);
1333
+ defaultRuntime.error(`[voice] Hume TTS failed after ${formatMs(Date.now() - startedAt)}: ${String(error)}; using system fallback`);
1246
1334
  if (!await speakWithSystemVoice(ttsText)) defaultRuntime.error("[voice] system TTS fallback unavailable");
1247
1335
  return;
1248
1336
  }
1249
1337
  try {
1338
+ voiceInfo(`TTS: audio ready in ${formatMs(Date.now() - startedAt)}; playing`);
1250
1339
  if (!await playAudioFile(audioPath)) defaultRuntime.log(`[voice] Hume audio generated: ${audioPath}`);
1340
+ else voiceSuccess(`TTS: finished in ${formatMs(Date.now() - startedAt)}`);
1251
1341
  } finally {
1252
1342
  await promises.rm(audioPath, { force: true }).catch(() => void 0);
1253
1343
  }
@@ -1259,6 +1349,7 @@ async function speakReply(resolved, text) {
1259
1349
  }
1260
1350
  let audioPath;
1261
1351
  try {
1352
+ voiceInfo(`TTS: synthesizing with ElevenLabs (timeout ${formatMs(resolved.tts.timeoutMs)})`);
1262
1353
  audioPath = await synthesizeElevenLabsAudio({
1263
1354
  text: ttsText,
1264
1355
  apiKey: resolved.tts.elevenLabsApiKey,
@@ -1269,12 +1360,14 @@ async function speakReply(resolved, text) {
1269
1360
  timeoutMs: resolved.tts.timeoutMs
1270
1361
  });
1271
1362
  } catch (error) {
1272
- defaultRuntime.error(`[voice] ElevenLabs TTS failed: ${String(error)}`);
1363
+ defaultRuntime.error(`[voice] ElevenLabs TTS failed after ${formatMs(Date.now() - startedAt)}: ${String(error)}`);
1273
1364
  if (!await speakWithSystemVoice(ttsText)) defaultRuntime.error("[voice] system TTS fallback unavailable");
1274
1365
  return;
1275
1366
  }
1276
1367
  try {
1368
+ voiceInfo(`TTS: audio ready in ${formatMs(Date.now() - startedAt)}; playing`);
1277
1369
  if (!await playAudioFile(audioPath)) defaultRuntime.log(`[voice] audio generated: ${audioPath}`);
1370
+ else voiceSuccess(`TTS: finished in ${formatMs(Date.now() - startedAt)}`);
1278
1371
  } finally {
1279
1372
  await promises.rm(audioPath, { force: true }).catch(() => void 0);
1280
1373
  }
@@ -1468,36 +1561,39 @@ async function prepareVoiceTurn(params) {
1468
1561
  async function runGatewayVoiceTurn(params) {
1469
1562
  const transcript = params.transcript.trim();
1470
1563
  if (!transcript) return { replied: false };
1471
- defaultRuntime.log(`You: ${transcript}`);
1564
+ voiceMessageBox("You", transcript);
1472
1565
  const turn = await prepareVoiceTurn({
1473
1566
  transcript,
1474
1567
  state: params.state,
1475
1568
  followUp: params.followUp
1476
1569
  });
1477
- const beforeText = latestAssistantText(normalizeHistoryMessages(await params.gatewayClient.loadHistory({
1570
+ const beforeMessages = normalizeHistoryMessages(await params.gatewayClient.loadHistory({
1478
1571
  sessionKey: params.resolved.gateway.sessionKey,
1479
1572
  limit: 100
1480
- })));
1481
- const run = await params.gatewayClient.sendChat({
1573
+ }));
1574
+ const beforeText = latestAssistantText(beforeMessages);
1575
+ const beforeAssistantCount = countAssistantMessages(beforeMessages);
1576
+ voiceDebugLog(`[voice] run started: ${(await params.gatewayClient.sendChat({
1482
1577
  sessionKey: params.resolved.gateway.sessionKey,
1483
1578
  message: turn.message,
1484
1579
  thinking: params.resolved.gateway.thinking,
1485
1580
  deliver: params.resolved.gateway.deliver,
1486
1581
  attachments: turn.attachments,
1487
1582
  timeoutMs: params.resolved.gateway.timeoutMs
1488
- });
1489
- defaultRuntime.log(`[voice] run started: ${run.runId}`);
1583
+ })).runId}`, params.resolved);
1584
+ voiceInfo("VORA is thinking...");
1490
1585
  const reply = await waitForAssistantReply({
1491
1586
  client: params.gatewayClient,
1492
1587
  sessionKey: params.resolved.gateway.sessionKey,
1493
1588
  beforeText,
1589
+ beforeAssistantCount,
1494
1590
  waitMs: params.resolved.gateway.waitMs
1495
1591
  });
1496
1592
  if (!reply) {
1497
1593
  defaultRuntime.error(`[voice] no assistant reply received within ${params.resolved.gateway.waitMs}ms`);
1498
1594
  return { replied: false };
1499
1595
  }
1500
- defaultRuntime.log(`Vora: ${reply}`);
1596
+ voiceMessageBox("Vora", reply);
1501
1597
  await speakReply(params.resolved, reply);
1502
1598
  if (params.state) {
1503
1599
  params.state.lastUser = transcript;
@@ -1531,7 +1627,7 @@ async function runVoiceConversation(params) {
1531
1627
  if (params.resolved.once) return;
1532
1628
  if (!params.resolved.conversation.followUpEnabled || followUpTurns >= params.resolved.conversation.followUpMaxTurns) return;
1533
1629
  const followUpListenMs = Math.min(params.resolved.conversation.followUpMs, params.resolved.conversation.followUpSttTimeoutMs);
1534
- defaultRuntime.log(`[voice] preparing follow-up window (${Math.round(followUpListenMs / 1e3)}s after ready); wait for "listening now"`);
1630
+ voiceInfo(`Follow-up window: ${Math.round(followUpListenMs / 1e3)}s after STT is ready. Wait for "Listening now".`);
1535
1631
  let nextTranscript = "";
1536
1632
  try {
1537
1633
  nextTranscript = (await transcribeSpeech(params.resolved, {
@@ -1541,17 +1637,17 @@ async function runVoiceConversation(params) {
1541
1637
  })).trim();
1542
1638
  } catch (error) {
1543
1639
  if (String(error).toLowerCase().includes("timeout")) {
1544
- defaultRuntime.log("[voice] follow-up silence; returning to wake word");
1640
+ voiceInfo("No follow-up heard; returning to wake word.");
1545
1641
  return;
1546
1642
  }
1547
1643
  throw error;
1548
1644
  }
1549
1645
  if (!nextTranscript) {
1550
- defaultRuntime.log("[voice] follow-up silence; returning to wake word");
1646
+ voiceInfo("No follow-up heard; returning to wake word.");
1551
1647
  return;
1552
1648
  }
1553
1649
  if (isVoiceSleepIntent(nextTranscript)) {
1554
- defaultRuntime.log("[voice] sleeping; wake word armed next");
1650
+ voiceInfo("Sleep command heard; wake word will arm again.");
1555
1651
  return;
1556
1652
  }
1557
1653
  followUp = true;
@@ -1559,6 +1655,31 @@ async function runVoiceConversation(params) {
1559
1655
  transcript = nextTranscript;
1560
1656
  }
1561
1657
  }
1658
+ async function runWakeGreeting(params) {
1659
+ const beforeMessages = normalizeHistoryMessages(await params.gatewayClient.loadHistory({
1660
+ sessionKey: params.resolved.gateway.sessionKey,
1661
+ limit: 100
1662
+ }));
1663
+ const beforeText = latestAssistantText(beforeMessages);
1664
+ const beforeAssistantCount = countAssistantMessages(beforeMessages);
1665
+ voiceInfo("Wake detected. Sending \"Hey Vora!\"");
1666
+ voiceDebugLog(`[voice] wake ack run started: ${(await params.gatewayClient.sendChat({
1667
+ sessionKey: params.resolved.gateway.sessionKey,
1668
+ message: "Hey Vora!",
1669
+ thinking: params.resolved.gateway.thinking,
1670
+ deliver: false,
1671
+ timeoutMs: params.resolved.gateway.timeoutMs
1672
+ })).runId}`, params.resolved);
1673
+ const reply = await waitForAssistantReply({
1674
+ client: params.gatewayClient,
1675
+ sessionKey: params.resolved.gateway.sessionKey,
1676
+ beforeText,
1677
+ beforeAssistantCount,
1678
+ waitMs: Math.min(params.resolved.gateway.waitMs, DEFAULT_WAKE_ACK_WAIT_MS)
1679
+ });
1680
+ if (reply) voiceMessageBox("Vora", reply);
1681
+ else voiceWarn("VORA did not answer \"Hey Vora!\" quickly; starting STT anyway.");
1682
+ }
1562
1683
  async function runVoiceLoop(opts) {
1563
1684
  const resolved = resolveVoiceRuntimeOptions(opts);
1564
1685
  if (!resolved.wake.pythonBin) throw new Error("python runtime missing. Install python3 and re-run `vora voice doctor`.");
@@ -1606,6 +1727,7 @@ async function runVoiceLoop(opts) {
1606
1727
  }
1607
1728
  }
1608
1729
  wakeWord.onVolume((event) => {
1730
+ if (!resolved.debug) return;
1609
1731
  if (Date.now() - latestVolumeLogAt < 3e3) return;
1610
1732
  latestVolumeLogAt = Date.now();
1611
1733
  defaultRuntime.log(`[voice] mic level=${event.volume}%`);
@@ -1616,14 +1738,19 @@ async function runVoiceLoop(opts) {
1616
1738
  busy = true;
1617
1739
  wakeWord.stop();
1618
1740
  try {
1619
- defaultRuntime.log(`[voice] wake trigger model=${event.model} score=${event.score.toFixed(2)} latency=${(Math.max(0, Date.now() / 1e3 - event.sourceTimestampSec) * 1e3).toFixed(0)}ms`);
1620
- const transcript = (await transcribeSpeech(resolved)).trim();
1741
+ const latencyMs = (Math.max(0, Date.now() / 1e3 - event.sourceTimestampSec) * 1e3).toFixed(0);
1742
+ voiceBox("Wake", [`Wake word detected. Score ${event.score.toFixed(2)}.`, resolved.debug ? `model=${event.model} latency=${latencyMs}ms` : "VORA is waking up."]);
1743
+ await runWakeGreeting({
1744
+ resolved,
1745
+ gatewayClient
1746
+ });
1747
+ const transcript = (await transcribeSpeech(resolved, { phase: "command" })).trim();
1621
1748
  if (!transcript) {
1622
- defaultRuntime.log("[voice] transcript empty; waiting for next wake trigger");
1749
+ voiceInfo("Transcript empty; waiting for next wake trigger.");
1623
1750
  return;
1624
1751
  }
1625
1752
  if (isVoiceSleepIntent(transcript)) {
1626
- defaultRuntime.log("[voice] sleep command heard; wake word remains armed");
1753
+ voiceInfo("Sleep command heard; wake word remains armed.");
1627
1754
  return;
1628
1755
  }
1629
1756
  await runVoiceConversation({
@@ -1642,7 +1769,7 @@ async function runVoiceLoop(opts) {
1642
1769
  busy = false;
1643
1770
  if (!stopRequested) try {
1644
1771
  await wakeWord.start();
1645
- defaultRuntime.log("[voice] wake word armed for next turn");
1772
+ voiceSuccess("Wake word armed for next turn.");
1646
1773
  } catch (err) {
1647
1774
  defaultRuntime.error(`[voice] failed to restart wake word: ${String(err)}`);
1648
1775
  requestStop();
@@ -1651,10 +1778,15 @@ async function runVoiceLoop(opts) {
1651
1778
  })();
1652
1779
  });
1653
1780
  await wakeWord.start();
1654
- defaultRuntime.log(theme.heading("Voice Loop"));
1655
- defaultRuntime.log(`wake=${resolved.wake.modelPath} threshold=${resolved.wake.threshold} stt=${resolved.stt.provider} tts=${resolved.tts.provider}`);
1656
- if (resolved.conversation.followUpEnabled && !resolved.once) defaultRuntime.log(`follow-up=on max=${resolved.conversation.followUpMaxTurns} listen=${Math.round(Math.min(resolved.conversation.followUpMs, resolved.conversation.followUpSttTimeoutMs) / 1e3)}s`);
1657
- defaultRuntime.log("Listening for wake word. Press Ctrl+C to stop. Run `vora voice doctor` if dependencies fail.");
1781
+ voiceBox("VORA Voice", [
1782
+ `Gateway session: ${resolved.gateway.sessionKey}`,
1783
+ `Wake threshold: ${resolved.wake.threshold}`,
1784
+ `STT: ${resolved.stt.provider} (${resolved.stt.language}; English-only default)`,
1785
+ `TTS: ${resolved.tts.provider} (timeout ${formatMs(resolved.tts.timeoutMs)})`,
1786
+ resolved.conversation.followUpEnabled && !resolved.once ? `Follow-up: on, max ${resolved.conversation.followUpMaxTurns}, listen ${Math.round(Math.min(resolved.conversation.followUpMs, resolved.conversation.followUpSttTimeoutMs) / 1e3)}s` : "Follow-up: off",
1787
+ "Say: Hey Vora. Press Ctrl+C to stop.",
1788
+ resolved.debug ? "Debug logs: on" : "Debug logs: off (set VORA_VOICE_DEBUG=1 to inspect internals)"
1789
+ ]);
1658
1790
  await new Promise((resolve) => {
1659
1791
  resolveStopLoop = resolve;
1660
1792
  process.once("SIGINT", requestStop);
@@ -1668,7 +1800,7 @@ async function runVoiceLoop(opts) {
1668
1800
  }
1669
1801
  }
1670
1802
  function addVoiceOptions(cmd) {
1671
- return cmd.option("--url <url>", "Gateway WebSocket URL").option("--token <token>", "Gateway token").option("--password <password>", "Gateway password").option("--session <key>", "Session key (default: \"main\")").option("--deliver", "Deliver assistant replies to linked channel routes", false).option("--message <text>", "Send an initial message before listening for wake word").option("--thinking <level>", "Thinking level override").option("--timeout-ms <ms>", "chat.send timeout override (ms)").option("--wait-ms <ms>", "Wait budget for assistant reply after send (ms)").option("--once", "Stop after one successful wake->reply turn", false).option("--wake-dir <path>", "Path to wake_word directory").option("--wake-model <path>", "Wake model path or file name").option("--wake-threshold <0..1>", "Wake word threshold").option("--python <bin>", "Python binary for wake word process").option("--stt-provider <manual|agora>", "STT mode (agora uses external bridge command)").option("--stt-lang <lang>", "STT language hint passed to bridge command").option("--stt-timeout-ms <ms>", "STT timeout (ms)").option("--agora-stt-command <cmd>", "External command for Agora STT bridge (supports {lang}, {timeout_ms}); default uses bundled Agora capture bridge").option("--backend-url <url>", `Voice backend URL for provider secrets/tokens (default: ${DEFAULT_VOICE_BACKEND_URL}; use "off" for local env mode)`).option("--tts-provider <none|hume|elevenlabs>", "Voice reply provider").option("--tts-timeout-ms <ms>", "TTS synthesis timeout (ms)").option("--hume-api-key <key>", "Hume API key").option("--hume-voice-id <id>", "Hume voice ID").option("--hume-speed <speed>", "Hume speaking speed (0.5..2, default 1.2)").option("--eleven-labs-api-key <key>", "ElevenLabs API key").option("--eleven-labs-voice-id <id>", "ElevenLabs voice ID").option("--eleven-labs-model-id <id>", "ElevenLabs model ID").option("--eleven-labs-output-format <format>", "ElevenLabs output format").option("--no-follow-up", "Require wake word before every voice turn").option("--follow-up-ms <ms>", "Maximum follow-up listening window after each reply").option("--follow-up-max-turns <n>", "Maximum follow-up turns before re-arming wake word").option("--follow-up-stt-timeout-ms <ms>", "STT timeout for each follow-up turn");
1803
+ return cmd.option("--url <url>", "Gateway WebSocket URL").option("--token <token>", "Gateway token").option("--password <password>", "Gateway password").option("--session <key>", "Session key (default: \"main\")").option("--deliver", "Deliver assistant replies to linked channel routes", false).option("--message <text>", "Send an initial message before listening for wake word").option("--thinking <level>", "Thinking level override").option("--timeout-ms <ms>", "chat.send timeout override (ms)").option("--wait-ms <ms>", "Wait budget for assistant reply after send (ms)").option("--once", "Stop after one successful wake->reply turn", false).option("--wake-dir <path>", "Path to wake_word directory").option("--wake-model <path>", "Wake model path or file name").option("--wake-threshold <0..1>", "Wake word threshold").option("--python <bin>", "Python binary for wake word process").option("--stt-provider <manual|agora>", "STT mode (agora uses external bridge command)").option("--stt-lang <lang>", "STT language hint passed to bridge command").option("--stt-timeout-ms <ms>", "STT timeout (ms)").option("--agora-stt-command <cmd>", "External command for Agora STT bridge (supports {lang}, {timeout_ms}); default uses bundled Agora capture bridge").option("--backend-url <url>", `Voice backend URL for provider secrets/tokens (default: ${DEFAULT_VOICE_BACKEND_URL}; use "off" for local env mode)`).option("--tts-provider <none|hume|elevenlabs>", "Voice reply provider").option("--tts-timeout-ms <ms>", "TTS synthesis timeout (ms)").option("--hume-api-key <key>", "Hume API key").option("--hume-voice-id <id>", "Hume voice ID").option("--hume-speed <speed>", "Hume speaking speed (0.5..2, default 1.2)").option("--eleven-labs-api-key <key>", "ElevenLabs API key").option("--eleven-labs-voice-id <id>", "ElevenLabs voice ID").option("--eleven-labs-model-id <id>", "ElevenLabs model ID").option("--eleven-labs-output-format <format>", "ElevenLabs output format").option("--no-follow-up", "Require wake word before every voice turn").option("--follow-up-ms <ms>", "Maximum follow-up listening window after each reply").option("--follow-up-max-turns <n>", "Maximum follow-up turns before re-arming wake word").option("--follow-up-stt-timeout-ms <ms>", "STT timeout for each follow-up turn").option("--debug", "Show low-level voice runtime diagnostics", false);
1672
1804
  }
1673
1805
  function registerVoiceCli(program) {
1674
1806
  const voice = addVoiceOptions(program.command("voice").description("Wake-word terminal voice loop (OpenWakeWord trigger + STT bridge + Gateway chat + optional Hume TTS)").addHelpText("after", () => `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/voice", "docs.vora.ai/cli/voice")}\n`));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vora-ai",
3
- "version": "0.1.38",
3
+ "version": "0.1.39",
4
4
  "description": "Voice-first AI agent core engine — VORA",
5
5
  "keywords": [],
6
6
  "homepage": "https://github.com/vora-ai/vora-core#readme",
@@ -7,7 +7,7 @@ import http from "node:http";
7
7
  const DEFAULT_TIMEOUT_MS = 45_000;
8
8
  const DEFAULT_API_BASE = "https://api.agora.io";
9
9
  const DEFAULT_BACKEND_URL = "https://vora-ai-backend-uemj.onrender.com";
10
- const DEFAULT_LANGUAGE = "vi-VN,en-US";
10
+ const DEFAULT_LANGUAGE = "en-US";
11
11
  const DEFAULT_RTC_UID = "1002";
12
12
  const DEFAULT_STT_AUDIO_UID = "111";
13
13
  const DEFAULT_STT_TEXT_UID = "222";
@@ -625,6 +625,7 @@ function buildBrowserHtml(browserConfig) {
625
625
  });
626
626
  TextMessage = root.lookupType("agora.audio2text.Text");
627
627
 
628
+ await post("/stage", { stage: "rtc_join", message: "joining Agora RTC" });
628
629
  setStatus("Joining RTC channel...");
629
630
  client = AgoraRTC.createClient({ mode: "live", codec: "vp8" });
630
631
  if (typeof client.setClientRole === "function") {
@@ -640,10 +641,12 @@ function buildBrowserHtml(browserConfig) {
640
641
  const uid = Number.isFinite(uidRaw) ? uidRaw : null;
641
642
  await client.join(CONFIG.appId, CONFIG.channel, rtcToken, uid);
642
643
 
644
+ await post("/stage", { stage: "mic", message: "requesting microphone" });
643
645
  setStatus("Microphone setup...");
644
646
  micTrack = await AgoraRTC.createMicrophoneAudioTrack();
645
647
  await client.publish([micTrack]);
646
648
 
649
+ await post("/stage", { stage: "stt_join", message: "joining Agora STT" });
647
650
  const start = await post("/api/start", { lang: CONFIG.lang });
648
651
  if (!start?.agentId) {
649
652
  throw new Error("Agora STT start returned no agentId");
@@ -832,6 +835,12 @@ async function main() {
832
835
  let startupTimeout;
833
836
  let listenTimeout;
834
837
 
838
+ const emitStage = (stage, message = "") => {
839
+ const safeStage = String(stage || "unknown").replace(/[^a-zA-Z0-9_-]/g, "_");
840
+ const detail = String(message || "").trim();
841
+ process.stderr.write(`[agora-stt-bridge] stage=${safeStage}${detail ? ` ${detail}` : ""}\n`);
842
+ };
843
+
835
844
  const clearTimers = () => {
836
845
  if (startupTimeout) {
837
846
  clearTimeout(startupTimeout);
@@ -904,6 +913,7 @@ async function main() {
904
913
  try {
905
914
  const body = await readJsonBody(req);
906
915
  const lang = typeof body?.lang === "string" && body.lang.trim() ? body.lang.trim() : resolvedConfig.lang;
916
+ emitStage("backend_stt", "requesting STT agent");
907
917
  currentAgentId = await startAgoraStt(resolvedConfig, lang);
908
918
  jsonResponse(res, 200, { ok: true, agentId: currentAgentId });
909
919
  } catch (error) {
@@ -912,6 +922,17 @@ async function main() {
912
922
  return;
913
923
  }
914
924
 
925
+ if (req.method === "POST" && pathname === "/stage") {
926
+ try {
927
+ const body = await readJsonBody(req);
928
+ emitStage(body?.stage, body?.message);
929
+ jsonResponse(res, 200, { ok: true });
930
+ } catch (error) {
931
+ jsonResponse(res, 500, { ok: false, error: String(error) });
932
+ }
933
+ return;
934
+ }
935
+
915
936
  if (req.method === "POST" && pathname === "/ready") {
916
937
  try {
917
938
  const body = await readJsonBody(req);
@@ -1001,18 +1022,16 @@ async function main() {
1001
1022
 
1002
1023
  const localUrl = `http://127.0.0.1:${address.port}/`;
1003
1024
  const source = resolvedConfig.backendUrl ? `backend=${safeBase(resolvedConfig.backendUrl)}` : "direct-agora";
1004
- process.stderr.write(`[agora-stt-bridge] channel=${resolvedConfig.channel} uid=${resolvedConfig.uid} ${source}\n`);
1005
- process.stderr.write(`[agora-stt-bridge] local capture URL:\n${localUrl}\n`);
1006
- process.stderr.write("[agora-stt-bridge] preparing microphone capture...\n");
1025
+ emitStage("session", `channel=${resolvedConfig.channel} uid=${resolvedConfig.uid} ${source}`);
1026
+ emitStage("server", `local capture URL ${localUrl}`);
1027
+ emitStage("browser", "starting hidden capture browser");
1007
1028
  startupTimeout = setTimeout(() => {
1008
1029
  void fail("timeout waiting for microphone/STT readiness");
1009
- }, Math.max(45_000, resolvedConfig.timeoutMs + 15_000));
1030
+ }, Math.max(18_000, resolvedConfig.timeoutMs + 8_000));
1010
1031
  if (config.browserMode === "managed") {
1011
1032
  try {
1012
1033
  browserController = await openManagedBrowser(localUrl, config.showBrowser);
1013
- process.stderr.write(
1014
- `[agora-stt-bridge] managed browser capture started${config.showBrowser ? "" : " (hidden)"}\n`,
1015
- );
1034
+ emitStage("browser", `capture browser started${config.showBrowser ? "" : " hidden"}`);
1016
1035
  } catch (error) {
1017
1036
  await fail(
1018
1037
  [
@@ -1,2 +0,0 @@
1
- import { a as registerCompletionCli } from "./completion-cli-wmAo3zAc.js";
2
- export { registerCompletionCli };