vora-ai 0.1.37 → 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-oO4_XKsL.js → command-registry-CO9_pUZG.js} +1 -1
  6. package/dist/{command-registry-DoRR_ykS.js → command-registry-DddwHMX2.js} +4 -4
  7. package/dist/{completion-cli-BSVP90BO.js → completion-cli-BIIBXfZj.js} +2 -2
  8. package/dist/completion-cli-oRV3KMRL.js +2 -0
  9. package/dist/{doctor-completion-CLfiq4-c.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-DNaH7pBF.js → gateway-cli-BO_W-26g.js} +1 -1
  13. package/dist/index.js +1 -1
  14. package/dist/{onboard-VNwsDuUB.js → onboard-Nd5ClWnL.js} +1 -1
  15. package/dist/{program-q6W4mv2i.js → program-C1tAki7e.js} +1 -1
  16. package/dist/{prompt-select-styled-2R-9jWL7.js → prompt-select-styled-CxGQYnYg.js} +1 -1
  17. package/dist/{register.maintenance-DMZegmso.js → register.maintenance-DSOh3Hhg.js} +1 -1
  18. package/dist/{register.onboard-CxR6bI1n.js → register.onboard-Dq9iFtBA.js} +1 -1
  19. package/dist/{register.setup-BT2-kAsK.js → register.setup-Cpq-B5e_.js} +1 -1
  20. package/dist/{register.subclis-B-9NwtNV.js → register.subclis-B6o2P3FE.js} +4 -4
  21. package/dist/{register.subclis-BQu4VqOI.js → register.subclis-yPh1RFXQ.js} +1 -1
  22. package/dist/{run-main-BWhbuxQt.js → run-main-Bwe4zE9G.js} +4 -4
  23. package/dist/{setup-B35nGXkQ.js → setup-srafWXcp.js} +2 -2
  24. package/dist/{setup.finalize-BueEFawv.js → setup.finalize-VNsHuIdy.js} +3 -3
  25. package/dist/{update-cli-BbcBxt3_.js → update-cli-EMFxDpgT.js} +3 -3
  26. package/dist/{voice-cli-CXiYCrQf.js → voice-cli-JpNFw9Go.js} +197 -40
  27. package/package.json +1 -1
  28. package/scripts/agora-stt-bridge.mjs +78 -16
  29. package/dist/completion-cli-B6W-7yKI.js +0 -2
package/dist/.buildstamp CHANGED
@@ -1 +1 @@
1
- {"builtAt":1776477728789,"head":"7cb18f8b1e45487f1627768901dac2dc10dda292"}
1
+ {"builtAt":1776495873671,"head":"9878c07c9b86f7c090fc2516587e78587cdd02bf"}
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.1.37",
3
- "commit": "7cb18f8b1e45487f1627768901dac2dc10dda292",
4
- "builtAt": "2026-04-18T02:02:09.438Z"
2
+ "version": "0.1.39",
3
+ "commit": "9878c07c9b86f7c090fc2516587e78587cdd02bf",
4
+ "builtAt": "2026-04-18T07:04:34.372Z"
5
5
  }
@@ -1 +1 @@
1
- 61df678f55f820c36800453e93a0b24dc4caa6d8ac7e6ec54d71e4885e06c512
1
+ cff3802555c03fed933598a392060fa774cd83785e5588aab671d80694fd31e2
@@ -10,5 +10,5 @@
10
10
  "signal",
11
11
  "imessage"
12
12
  ],
13
- "rootHelpText": "\n🌊 VORA 0.1.37 (7cb18f8) — Local processing, global conversational AI.\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-DoRR_ykS.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-B-9NwtNV.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-BT2-kAsK.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-CxR6bI1n.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-DMZegmso.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-B-9NwtNV.js";
7
- import { n as registerCoreCliByName, t as getCoreCliCommandNames } from "./command-registry-DoRR_ykS.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-BSVP90BO.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-BWhbuxQt.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:02:08.668Z"
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-B35nGXkQ.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-BWhbuxQt.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-B35nGXkQ.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-DoRR_ykS.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-CLfiq4-c.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-2R-9jWL7.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-VNwsDuUB.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-VNwsDuUB.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-DNaH7pBF.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-CXiYCrQf.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-BbcBxt3_.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-B6W-7yKI.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-B-9NwtNV.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-q6W4mv2i.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-oO4_XKsL.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-BQu4VqOI.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-BQu4VqOI.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-CXiYCrQf.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-BueEFawv.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-BSVP90BO.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-CLfiq4-c.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-CXiYCrQf.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-BSVP90BO.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-2R-9jWL7.js";
31
- import { r as ensureCompletionCacheExists, t as checkShellCompletionStatus } from "./doctor-completion-CLfiq4-c.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 = "en-US,vi-VN";
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),
@@ -782,7 +849,7 @@ function resolveShellCommand(command) {
782
849
  args: ["-lc", command]
783
850
  };
784
851
  }
785
- async function runShellCommand(command, timeoutMs) {
852
+ async function runShellCommand(command, timeoutMs, opts) {
786
853
  const shell = resolveShellCommand(command);
787
854
  const child = spawn(shell.bin, shell.args, { stdio: [
788
855
  "ignore",
@@ -791,15 +858,24 @@ async function runShellCommand(command, timeoutMs) {
791
858
  ] });
792
859
  let stdout = "";
793
860
  let stderr = "";
861
+ let stderrLineBuffer = "";
794
862
  child.stdout?.on("data", (chunk) => {
795
863
  stdout += chunk.toString();
796
864
  });
797
865
  child.stderr?.on("data", (chunk) => {
798
- stderr += chunk.toString();
866
+ const text = chunk.toString();
867
+ stderr += text;
868
+ if (opts?.onStderrLine) {
869
+ stderrLineBuffer += text;
870
+ const lines = stderrLineBuffer.split(/\r?\n/g);
871
+ stderrLineBuffer = lines.pop() ?? "";
872
+ for (const line of lines) opts.onStderrLine(line);
873
+ }
799
874
  });
800
875
  const done = new Promise((resolve, reject) => {
801
876
  child.on("error", reject);
802
877
  child.on("close", (code) => {
878
+ if (opts?.onStderrLine && stderrLineBuffer.trim().length > 0) opts.onStderrLine(stderrLineBuffer);
803
879
  resolve({
804
880
  code: typeof code === "number" ? code : 1,
805
881
  stdout,
@@ -807,13 +883,18 @@ async function runShellCommand(command, timeoutMs) {
807
883
  });
808
884
  });
809
885
  });
886
+ let timeoutHandle;
810
887
  const timeout = new Promise((_, reject) => {
811
- setTimeout(() => {
888
+ timeoutHandle = setTimeout(() => {
812
889
  child.kill("SIGTERM");
813
890
  reject(/* @__PURE__ */ new Error(`command timeout after ${timeoutMs}ms`));
814
891
  }, timeoutMs);
815
892
  });
816
- return await Promise.race([done, timeout]);
893
+ try {
894
+ return await Promise.race([done, timeout]);
895
+ } finally {
896
+ if (timeoutHandle) clearTimeout(timeoutHandle);
897
+ }
817
898
  }
818
899
  async function runStreamingCommand(bin, args, cwd) {
819
900
  await new Promise((resolve, reject) => {
@@ -974,12 +1055,37 @@ async function transcribeSpeech(resolved, opts) {
974
1055
  const command = resolved.stt.agoraCommand;
975
1056
  if (!command) throw new Error("Agora STT provider selected but no bridge command is available. Set --agora-stt-command or VORA_AGORA_STT_COMMAND.");
976
1057
  const timeoutMs = opts?.timeoutMs ?? resolved.stt.timeoutMs;
977
- const result = await runShellCommand(applyTemplate(command, {
1058
+ const rendered = applyTemplate(command, {
978
1059
  lang: resolved.stt.language,
979
1060
  timeout_ms: String(timeoutMs)
980
- }), timeoutMs + DEFAULT_STT_BRIDGE_COMMAND_GRACE_MS);
1061
+ });
1062
+ const phase = opts?.phase ?? "voice";
1063
+ const startedAt = Date.now();
1064
+ voiceInfo(`STT ${phase}: starting Agora bridge (${resolved.stt.language}, timeout ${formatMs(timeoutMs)}). Wait for "Listening now" before speaking.`);
1065
+ let readyLogged = false;
1066
+ const result = await runShellCommand(rendered, timeoutMs + DEFAULT_STT_BRIDGE_COMMAND_GRACE_MS, { onStderrLine: (line) => {
1067
+ const message = line.trim();
1068
+ if (!message) return;
1069
+ if (message === "[agora-stt-bridge] ready") {
1070
+ readyLogged = true;
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}` : ""}`);
1080
+ return;
1081
+ }
1082
+ if (message.includes("fatal:") || message.includes("managed browser unavailable") || message.includes("browser auto-open disabled") || message.includes("timeout waiting")) defaultRuntime.error(message);
1083
+ } });
981
1084
  if (result.code !== 0) throw new Error(`Agora STT bridge failed (exit ${result.code}): ${result.stderr.trim() || "no stderr"}`);
982
- 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;
983
1089
  }
984
1090
  function normalizeHistoryMessages(payload) {
985
1091
  if (!payload || typeof payload !== "object") return [];
@@ -1019,15 +1125,19 @@ function latestAssistantText(messages) {
1019
1125
  }
1020
1126
  return null;
1021
1127
  }
1128
+ function countAssistantMessages(messages) {
1129
+ return messages.filter(isAssistantMessage).length;
1130
+ }
1022
1131
  async function waitForAssistantReply(params) {
1023
1132
  const startedAt = Date.now();
1024
1133
  while (Date.now() - startedAt < params.waitMs) {
1025
- await sleep(1e3);
1026
- const current = latestAssistantText(normalizeHistoryMessages(await params.client.loadHistory({
1134
+ await sleep(350);
1135
+ const messages = normalizeHistoryMessages(await params.client.loadHistory({
1027
1136
  sessionKey: params.sessionKey,
1028
1137
  limit: 100
1029
- })));
1030
- 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;
1031
1141
  }
1032
1142
  return null;
1033
1143
  }
@@ -1202,6 +1312,7 @@ async function speakReply(resolved, text) {
1202
1312
  if (resolved.tts.provider === "none") return;
1203
1313
  const ttsText = prepareTtsText(text);
1204
1314
  if (!ttsText) return;
1315
+ const startedAt = Date.now();
1205
1316
  if (resolved.tts.provider === "hume") {
1206
1317
  if (!resolved.tts.backendUrl && !resolved.tts.humeApiKey) {
1207
1318
  defaultRuntime.error("[voice] Hume TTS skipped: missing backend/API key");
@@ -1209,6 +1320,7 @@ async function speakReply(resolved, text) {
1209
1320
  }
1210
1321
  let audioPath;
1211
1322
  try {
1323
+ voiceInfo(`TTS: synthesizing with Hume (timeout ${formatMs(resolved.tts.timeoutMs)})`);
1212
1324
  audioPath = await synthesizeHumeAudio({
1213
1325
  text: ttsText,
1214
1326
  apiKey: resolved.tts.humeApiKey,
@@ -1218,12 +1330,14 @@ async function speakReply(resolved, text) {
1218
1330
  timeoutMs: resolved.tts.timeoutMs
1219
1331
  });
1220
1332
  } catch (error) {
1221
- 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`);
1222
1334
  if (!await speakWithSystemVoice(ttsText)) defaultRuntime.error("[voice] system TTS fallback unavailable");
1223
1335
  return;
1224
1336
  }
1225
1337
  try {
1338
+ voiceInfo(`TTS: audio ready in ${formatMs(Date.now() - startedAt)}; playing`);
1226
1339
  if (!await playAudioFile(audioPath)) defaultRuntime.log(`[voice] Hume audio generated: ${audioPath}`);
1340
+ else voiceSuccess(`TTS: finished in ${formatMs(Date.now() - startedAt)}`);
1227
1341
  } finally {
1228
1342
  await promises.rm(audioPath, { force: true }).catch(() => void 0);
1229
1343
  }
@@ -1235,6 +1349,7 @@ async function speakReply(resolved, text) {
1235
1349
  }
1236
1350
  let audioPath;
1237
1351
  try {
1352
+ voiceInfo(`TTS: synthesizing with ElevenLabs (timeout ${formatMs(resolved.tts.timeoutMs)})`);
1238
1353
  audioPath = await synthesizeElevenLabsAudio({
1239
1354
  text: ttsText,
1240
1355
  apiKey: resolved.tts.elevenLabsApiKey,
@@ -1245,12 +1360,14 @@ async function speakReply(resolved, text) {
1245
1360
  timeoutMs: resolved.tts.timeoutMs
1246
1361
  });
1247
1362
  } catch (error) {
1248
- defaultRuntime.error(`[voice] ElevenLabs TTS failed: ${String(error)}`);
1363
+ defaultRuntime.error(`[voice] ElevenLabs TTS failed after ${formatMs(Date.now() - startedAt)}: ${String(error)}`);
1249
1364
  if (!await speakWithSystemVoice(ttsText)) defaultRuntime.error("[voice] system TTS fallback unavailable");
1250
1365
  return;
1251
1366
  }
1252
1367
  try {
1368
+ voiceInfo(`TTS: audio ready in ${formatMs(Date.now() - startedAt)}; playing`);
1253
1369
  if (!await playAudioFile(audioPath)) defaultRuntime.log(`[voice] audio generated: ${audioPath}`);
1370
+ else voiceSuccess(`TTS: finished in ${formatMs(Date.now() - startedAt)}`);
1254
1371
  } finally {
1255
1372
  await promises.rm(audioPath, { force: true }).catch(() => void 0);
1256
1373
  }
@@ -1444,36 +1561,39 @@ async function prepareVoiceTurn(params) {
1444
1561
  async function runGatewayVoiceTurn(params) {
1445
1562
  const transcript = params.transcript.trim();
1446
1563
  if (!transcript) return { replied: false };
1447
- defaultRuntime.log(`You: ${transcript}`);
1564
+ voiceMessageBox("You", transcript);
1448
1565
  const turn = await prepareVoiceTurn({
1449
1566
  transcript,
1450
1567
  state: params.state,
1451
1568
  followUp: params.followUp
1452
1569
  });
1453
- const beforeText = latestAssistantText(normalizeHistoryMessages(await params.gatewayClient.loadHistory({
1570
+ const beforeMessages = normalizeHistoryMessages(await params.gatewayClient.loadHistory({
1454
1571
  sessionKey: params.resolved.gateway.sessionKey,
1455
1572
  limit: 100
1456
- })));
1457
- 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({
1458
1577
  sessionKey: params.resolved.gateway.sessionKey,
1459
1578
  message: turn.message,
1460
1579
  thinking: params.resolved.gateway.thinking,
1461
1580
  deliver: params.resolved.gateway.deliver,
1462
1581
  attachments: turn.attachments,
1463
1582
  timeoutMs: params.resolved.gateway.timeoutMs
1464
- });
1465
- defaultRuntime.log(`[voice] run started: ${run.runId}`);
1583
+ })).runId}`, params.resolved);
1584
+ voiceInfo("VORA is thinking...");
1466
1585
  const reply = await waitForAssistantReply({
1467
1586
  client: params.gatewayClient,
1468
1587
  sessionKey: params.resolved.gateway.sessionKey,
1469
1588
  beforeText,
1589
+ beforeAssistantCount,
1470
1590
  waitMs: params.resolved.gateway.waitMs
1471
1591
  });
1472
1592
  if (!reply) {
1473
1593
  defaultRuntime.error(`[voice] no assistant reply received within ${params.resolved.gateway.waitMs}ms`);
1474
1594
  return { replied: false };
1475
1595
  }
1476
- defaultRuntime.log(`Vora: ${reply}`);
1596
+ voiceMessageBox("Vora", reply);
1477
1597
  await speakReply(params.resolved, reply);
1478
1598
  if (params.state) {
1479
1599
  params.state.lastUser = transcript;
@@ -1507,26 +1627,27 @@ async function runVoiceConversation(params) {
1507
1627
  if (params.resolved.once) return;
1508
1628
  if (!params.resolved.conversation.followUpEnabled || followUpTurns >= params.resolved.conversation.followUpMaxTurns) return;
1509
1629
  const followUpListenMs = Math.min(params.resolved.conversation.followUpMs, params.resolved.conversation.followUpSttTimeoutMs);
1510
- defaultRuntime.log(`[voice] follow-up window open for ${Math.round(followUpListenMs / 1e3)}s; speak naturally, or stay quiet to sleep`);
1630
+ voiceInfo(`Follow-up window: ${Math.round(followUpListenMs / 1e3)}s after STT is ready. Wait for "Listening now".`);
1511
1631
  let nextTranscript = "";
1512
1632
  try {
1513
1633
  nextTranscript = (await transcribeSpeech(params.resolved, {
1514
1634
  timeoutMs: followUpListenMs,
1515
- prompt: "Follow-up (blank to sleep): "
1635
+ prompt: "Follow-up (blank to sleep): ",
1636
+ phase: "follow-up"
1516
1637
  })).trim();
1517
1638
  } catch (error) {
1518
1639
  if (String(error).toLowerCase().includes("timeout")) {
1519
- defaultRuntime.log("[voice] follow-up silence; returning to wake word");
1640
+ voiceInfo("No follow-up heard; returning to wake word.");
1520
1641
  return;
1521
1642
  }
1522
1643
  throw error;
1523
1644
  }
1524
1645
  if (!nextTranscript) {
1525
- defaultRuntime.log("[voice] follow-up silence; returning to wake word");
1646
+ voiceInfo("No follow-up heard; returning to wake word.");
1526
1647
  return;
1527
1648
  }
1528
1649
  if (isVoiceSleepIntent(nextTranscript)) {
1529
- defaultRuntime.log("[voice] sleeping; wake word armed next");
1650
+ voiceInfo("Sleep command heard; wake word will arm again.");
1530
1651
  return;
1531
1652
  }
1532
1653
  followUp = true;
@@ -1534,6 +1655,31 @@ async function runVoiceConversation(params) {
1534
1655
  transcript = nextTranscript;
1535
1656
  }
1536
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
+ }
1537
1683
  async function runVoiceLoop(opts) {
1538
1684
  const resolved = resolveVoiceRuntimeOptions(opts);
1539
1685
  if (!resolved.wake.pythonBin) throw new Error("python runtime missing. Install python3 and re-run `vora voice doctor`.");
@@ -1581,6 +1727,7 @@ async function runVoiceLoop(opts) {
1581
1727
  }
1582
1728
  }
1583
1729
  wakeWord.onVolume((event) => {
1730
+ if (!resolved.debug) return;
1584
1731
  if (Date.now() - latestVolumeLogAt < 3e3) return;
1585
1732
  latestVolumeLogAt = Date.now();
1586
1733
  defaultRuntime.log(`[voice] mic level=${event.volume}%`);
@@ -1591,14 +1738,19 @@ async function runVoiceLoop(opts) {
1591
1738
  busy = true;
1592
1739
  wakeWord.stop();
1593
1740
  try {
1594
- 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`);
1595
- 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();
1596
1748
  if (!transcript) {
1597
- defaultRuntime.log("[voice] transcript empty; waiting for next wake trigger");
1749
+ voiceInfo("Transcript empty; waiting for next wake trigger.");
1598
1750
  return;
1599
1751
  }
1600
1752
  if (isVoiceSleepIntent(transcript)) {
1601
- defaultRuntime.log("[voice] sleep command heard; wake word remains armed");
1753
+ voiceInfo("Sleep command heard; wake word remains armed.");
1602
1754
  return;
1603
1755
  }
1604
1756
  await runVoiceConversation({
@@ -1617,7 +1769,7 @@ async function runVoiceLoop(opts) {
1617
1769
  busy = false;
1618
1770
  if (!stopRequested) try {
1619
1771
  await wakeWord.start();
1620
- defaultRuntime.log("[voice] wake word armed for next turn");
1772
+ voiceSuccess("Wake word armed for next turn.");
1621
1773
  } catch (err) {
1622
1774
  defaultRuntime.error(`[voice] failed to restart wake word: ${String(err)}`);
1623
1775
  requestStop();
@@ -1626,10 +1778,15 @@ async function runVoiceLoop(opts) {
1626
1778
  })();
1627
1779
  });
1628
1780
  await wakeWord.start();
1629
- defaultRuntime.log(theme.heading("Voice Loop"));
1630
- defaultRuntime.log(`wake=${resolved.wake.modelPath} threshold=${resolved.wake.threshold} stt=${resolved.stt.provider} tts=${resolved.tts.provider}`);
1631
- 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`);
1632
- 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
+ ]);
1633
1790
  await new Promise((resolve) => {
1634
1791
  resolveStopLoop = resolve;
1635
1792
  process.once("SIGINT", requestStop);
@@ -1643,7 +1800,7 @@ async function runVoiceLoop(opts) {
1643
1800
  }
1644
1801
  }
1645
1802
  function addVoiceOptions(cmd) {
1646
- 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);
1647
1804
  }
1648
1805
  function registerVoiceCli(program) {
1649
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.37",
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 = "en-US,vi-VN";
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,25 +641,24 @@ 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");
650
653
  }
651
654
  agentId = String(start.agentId);
655
+ await post("/ready", { agentId });
652
656
  setStatus("Listening... speak your command now.", "ok");
653
657
  } catch (error) {
654
658
  await resolveError(String(error));
655
659
  }
656
660
  }
657
661
 
658
- setTimeout(() => {
659
- void resolveError("timeout waiting for final transcript");
660
- }, Math.max(1000, CONFIG.timeoutMs));
661
-
662
662
  window.addEventListener("beforeunload", () => {
663
663
  void cleanup();
664
664
  });
@@ -831,6 +831,26 @@ async function main() {
831
831
  let done = false;
832
832
  let currentAgentId = "";
833
833
  let browserController = null;
834
+ let ready = false;
835
+ let startupTimeout;
836
+ let listenTimeout;
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
+
844
+ const clearTimers = () => {
845
+ if (startupTimeout) {
846
+ clearTimeout(startupTimeout);
847
+ startupTimeout = undefined;
848
+ }
849
+ if (listenTimeout) {
850
+ clearTimeout(listenTimeout);
851
+ listenTimeout = undefined;
852
+ }
853
+ };
834
854
 
835
855
  const closeBrowser = async () => {
836
856
  if (!browserController) {
@@ -846,6 +866,7 @@ async function main() {
846
866
  return;
847
867
  }
848
868
  done = true;
869
+ clearTimers();
849
870
  await stopAgoraStt(resolvedConfig, currentAgentId).catch(() => undefined);
850
871
  await closeBrowser();
851
872
  if (server) {
@@ -860,6 +881,7 @@ async function main() {
860
881
  return;
861
882
  }
862
883
  done = true;
884
+ clearTimers();
863
885
  await stopAgoraStt(resolvedConfig, currentAgentId).catch(() => undefined);
864
886
  await closeBrowser();
865
887
  if (server) {
@@ -869,6 +891,15 @@ async function main() {
869
891
  process.exit(0);
870
892
  };
871
893
 
894
+ const armListenTimeout = () => {
895
+ if (listenTimeout) {
896
+ return;
897
+ }
898
+ listenTimeout = setTimeout(() => {
899
+ void fail(`timeout after ${resolvedConfig.timeoutMs}ms`);
900
+ }, resolvedConfig.timeoutMs);
901
+ };
902
+
872
903
  server = http.createServer(async (req, res) => {
873
904
  const requestUrl = new URL(req.url || "/", "http://127.0.0.1");
874
905
  const pathname = requestUrl.pathname;
@@ -882,6 +913,7 @@ async function main() {
882
913
  try {
883
914
  const body = await readJsonBody(req);
884
915
  const lang = typeof body?.lang === "string" && body.lang.trim() ? body.lang.trim() : resolvedConfig.lang;
916
+ emitStage("backend_stt", "requesting STT agent");
885
917
  currentAgentId = await startAgoraStt(resolvedConfig, lang);
886
918
  jsonResponse(res, 200, { ok: true, agentId: currentAgentId });
887
919
  } catch (error) {
@@ -890,6 +922,39 @@ async function main() {
890
922
  return;
891
923
  }
892
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
+
936
+ if (req.method === "POST" && pathname === "/ready") {
937
+ try {
938
+ const body = await readJsonBody(req);
939
+ const agentId =
940
+ typeof body?.agentId === "string" && body.agentId.trim().length > 0
941
+ ? body.agentId.trim()
942
+ : currentAgentId;
943
+ if (agentId && agentId !== currentAgentId) {
944
+ currentAgentId = agentId;
945
+ }
946
+ if (!ready) {
947
+ ready = true;
948
+ process.stderr.write("[agora-stt-bridge] ready\n");
949
+ armListenTimeout();
950
+ }
951
+ jsonResponse(res, 200, { ok: true });
952
+ } catch (error) {
953
+ jsonResponse(res, 500, { ok: false, error: String(error) });
954
+ }
955
+ return;
956
+ }
957
+
893
958
  if (req.method === "POST" && pathname === "/api/stop") {
894
959
  try {
895
960
  const body = await readJsonBody(req);
@@ -957,15 +1022,16 @@ async function main() {
957
1022
 
958
1023
  const localUrl = `http://127.0.0.1:${address.port}/`;
959
1024
  const source = resolvedConfig.backendUrl ? `backend=${safeBase(resolvedConfig.backendUrl)}` : "direct-agora";
960
- process.stderr.write(`[agora-stt-bridge] channel=${resolvedConfig.channel} uid=${resolvedConfig.uid} ${source}\n`);
961
- process.stderr.write(`[agora-stt-bridge] local capture URL:\n${localUrl}\n`);
962
- process.stderr.write("[agora-stt-bridge] waiting for one final transcript...\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");
1028
+ startupTimeout = setTimeout(() => {
1029
+ void fail("timeout waiting for microphone/STT readiness");
1030
+ }, Math.max(18_000, resolvedConfig.timeoutMs + 8_000));
963
1031
  if (config.browserMode === "managed") {
964
1032
  try {
965
1033
  browserController = await openManagedBrowser(localUrl, config.showBrowser);
966
- process.stderr.write(
967
- `[agora-stt-bridge] managed browser capture started${config.showBrowser ? "" : " (hidden)"}\n`,
968
- );
1034
+ emitStage("browser", `capture browser started${config.showBrowser ? "" : " hidden"}`);
969
1035
  } catch (error) {
970
1036
  await fail(
971
1037
  [
@@ -981,12 +1047,8 @@ async function main() {
981
1047
  process.stderr.write("[agora-stt-bridge] browser auto-open disabled; open the local capture URL manually.\n");
982
1048
  }
983
1049
 
984
- const timeout = setTimeout(() => {
985
- void fail(`timeout after ${resolvedConfig.timeoutMs}ms`);
986
- }, resolvedConfig.timeoutMs + 15_000);
987
-
988
1050
  const stop = async () => {
989
- clearTimeout(timeout);
1051
+ clearTimers();
990
1052
  if (!done) {
991
1053
  await fail("interrupted");
992
1054
  }
@@ -1,2 +0,0 @@
1
- import { a as registerCompletionCli } from "./completion-cli-BSVP90BO.js";
2
- export { registerCompletionCli };