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.
- package/dist/.buildstamp +1 -1
- package/dist/build-info.json +3 -3
- package/dist/canvas-host/a2ui/.bundle.hash +1 -1
- package/dist/cli-startup-metadata.json +1 -1
- package/dist/{command-registry-CNW-_45W.js → command-registry-CO9_pUZG.js} +1 -1
- package/dist/{command-registry-BRC9rQRa.js → command-registry-DddwHMX2.js} +4 -4
- package/dist/{completion-cli-wmAo3zAc.js → completion-cli-BIIBXfZj.js} +2 -2
- package/dist/completion-cli-oRV3KMRL.js +2 -0
- package/dist/{doctor-completion-D9ct64dl.js → doctor-completion-C6bLYDy-.js} +1 -1
- package/dist/entry.js +1 -1
- package/dist/extensions/telegram/.vora-runtime-deps-stamp.json +1 -1
- package/dist/{gateway-cli-DDZpfER2.js → gateway-cli-BO_W-26g.js} +1 -1
- package/dist/index.js +1 -1
- package/dist/{onboard-BgmpmwJd.js → onboard-Nd5ClWnL.js} +1 -1
- package/dist/{program-CSIyPhvT.js → program-C1tAki7e.js} +1 -1
- package/dist/{prompt-select-styled-BXm08-uh.js → prompt-select-styled-CxGQYnYg.js} +1 -1
- package/dist/{register.maintenance-Bpuqh4Q-.js → register.maintenance-DSOh3Hhg.js} +1 -1
- package/dist/{register.onboard-D-PZwo7h.js → register.onboard-Dq9iFtBA.js} +1 -1
- package/dist/{register.setup-BJyCA8h7.js → register.setup-Cpq-B5e_.js} +1 -1
- package/dist/{register.subclis-BMN-F1BZ.js → register.subclis-B6o2P3FE.js} +4 -4
- package/dist/{register.subclis-Cdl19lw2.js → register.subclis-yPh1RFXQ.js} +1 -1
- package/dist/{run-main-CHgQSnRn.js → run-main-Bwe4zE9G.js} +4 -4
- package/dist/{setup-Cu2MyUlf.js → setup-srafWXcp.js} +2 -2
- package/dist/{setup.finalize-CRAM1yrm.js → setup.finalize-VNsHuIdy.js} +3 -3
- package/dist/{update-cli-DhdDYxXa.js → update-cli-EMFxDpgT.js} +3 -3
- package/dist/{voice-cli-BRD6l0Sl.js → voice-cli-JpNFw9Go.js} +172 -40
- package/package.json +1 -1
- package/scripts/agora-stt-bridge.mjs +27 -8
- package/dist/completion-cli-CZqBPvMj.js +0 -2
package/dist/.buildstamp
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"builtAt":
|
|
1
|
+
{"builtAt":1776495873671,"head":"9878c07c9b86f7c090fc2516587e78587cdd02bf"}
|
package/dist/build-info.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
cff3802555c03fed933598a392060fa774cd83785e5588aab671d80694fd31e2
|
|
@@ -10,5 +10,5 @@
|
|
|
10
10
|
"signal",
|
|
11
11
|
"imessage"
|
|
12
12
|
],
|
|
13
|
-
"rootHelpText": "\n🌊 VORA 0.1.
|
|
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,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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
7
|
-
import { n as registerCoreCliByName, t as getCoreCliCommandNames } from "./command-registry-
|
|
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";
|
|
@@ -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-
|
|
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-
|
|
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
|
});
|
|
@@ -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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
31
|
-
import { r as ensureCompletionCacheExists, t as checkShellCompletionStatus } from "./doctor-completion-
|
|
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 =
|
|
18
|
-
const DEFAULT_STT_LANGUAGE = "
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
1006
|
-
|
|
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(
|
|
1050
|
-
const
|
|
1134
|
+
await sleep(350);
|
|
1135
|
+
const messages = normalizeHistoryMessages(await params.client.loadHistory({
|
|
1051
1136
|
sessionKey: params.sessionKey,
|
|
1052
1137
|
limit: 100
|
|
1053
|
-
}))
|
|
1054
|
-
|
|
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
|
-
|
|
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
|
|
1570
|
+
const beforeMessages = normalizeHistoryMessages(await params.gatewayClient.loadHistory({
|
|
1478
1571
|
sessionKey: params.resolved.gateway.sessionKey,
|
|
1479
1572
|
limit: 100
|
|
1480
|
-
}))
|
|
1481
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1646
|
+
voiceInfo("No follow-up heard; returning to wake word.");
|
|
1551
1647
|
return;
|
|
1552
1648
|
}
|
|
1553
1649
|
if (isVoiceSleepIntent(nextTranscript)) {
|
|
1554
|
-
|
|
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
|
-
|
|
1620
|
-
|
|
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
|
-
|
|
1749
|
+
voiceInfo("Transcript empty; waiting for next wake trigger.");
|
|
1623
1750
|
return;
|
|
1624
1751
|
}
|
|
1625
1752
|
if (isVoiceSleepIntent(transcript)) {
|
|
1626
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
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
|
@@ -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 = "
|
|
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
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
[
|