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.
- 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-oO4_XKsL.js → command-registry-CO9_pUZG.js} +1 -1
- package/dist/{command-registry-DoRR_ykS.js → command-registry-DddwHMX2.js} +4 -4
- package/dist/{completion-cli-BSVP90BO.js → completion-cli-BIIBXfZj.js} +2 -2
- package/dist/completion-cli-oRV3KMRL.js +2 -0
- package/dist/{doctor-completion-CLfiq4-c.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-DNaH7pBF.js → gateway-cli-BO_W-26g.js} +1 -1
- package/dist/index.js +1 -1
- package/dist/{onboard-VNwsDuUB.js → onboard-Nd5ClWnL.js} +1 -1
- package/dist/{program-q6W4mv2i.js → program-C1tAki7e.js} +1 -1
- package/dist/{prompt-select-styled-2R-9jWL7.js → prompt-select-styled-CxGQYnYg.js} +1 -1
- package/dist/{register.maintenance-DMZegmso.js → register.maintenance-DSOh3Hhg.js} +1 -1
- package/dist/{register.onboard-CxR6bI1n.js → register.onboard-Dq9iFtBA.js} +1 -1
- package/dist/{register.setup-BT2-kAsK.js → register.setup-Cpq-B5e_.js} +1 -1
- package/dist/{register.subclis-B-9NwtNV.js → register.subclis-B6o2P3FE.js} +4 -4
- package/dist/{register.subclis-BQu4VqOI.js → register.subclis-yPh1RFXQ.js} +1 -1
- package/dist/{run-main-BWhbuxQt.js → run-main-Bwe4zE9G.js} +4 -4
- package/dist/{setup-B35nGXkQ.js → setup-srafWXcp.js} +2 -2
- package/dist/{setup.finalize-BueEFawv.js → setup.finalize-VNsHuIdy.js} +3 -3
- package/dist/{update-cli-BbcBxt3_.js → update-cli-EMFxDpgT.js} +3 -3
- package/dist/{voice-cli-CXiYCrQf.js → voice-cli-JpNFw9Go.js} +197 -40
- package/package.json +1 -1
- package/scripts/agora-stt-bridge.mjs +78 -16
- package/dist/completion-cli-B6W-7yKI.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 = "en-US
|
|
17
|
+
const DEFAULT_STT_TIMEOUT_MS = 16e3;
|
|
18
|
+
const DEFAULT_STT_LANGUAGE = "en-US";
|
|
19
19
|
const DEFAULT_HUME_VOICE_ID = "9e068547-5ba4-4c8e-8e03-69282a008f04";
|
|
20
20
|
const DEFAULT_HUME_SPEED = 1.2;
|
|
21
|
-
const DEFAULT_TTS_TIMEOUT_MS =
|
|
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),
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
1058
|
+
const rendered = applyTemplate(command, {
|
|
978
1059
|
lang: resolved.stt.language,
|
|
979
1060
|
timeout_ms: String(timeoutMs)
|
|
980
|
-
})
|
|
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
|
-
|
|
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(
|
|
1026
|
-
const
|
|
1134
|
+
await sleep(350);
|
|
1135
|
+
const messages = normalizeHistoryMessages(await params.client.loadHistory({
|
|
1027
1136
|
sessionKey: params.sessionKey,
|
|
1028
1137
|
limit: 100
|
|
1029
|
-
}))
|
|
1030
|
-
|
|
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
|
-
|
|
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
|
|
1570
|
+
const beforeMessages = normalizeHistoryMessages(await params.gatewayClient.loadHistory({
|
|
1454
1571
|
sessionKey: params.resolved.gateway.sessionKey,
|
|
1455
1572
|
limit: 100
|
|
1456
|
-
}))
|
|
1457
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1646
|
+
voiceInfo("No follow-up heard; returning to wake word.");
|
|
1526
1647
|
return;
|
|
1527
1648
|
}
|
|
1528
1649
|
if (isVoiceSleepIntent(nextTranscript)) {
|
|
1529
|
-
|
|
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
|
-
|
|
1595
|
-
|
|
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
|
-
|
|
1749
|
+
voiceInfo("Transcript empty; waiting for next wake trigger.");
|
|
1598
1750
|
return;
|
|
1599
1751
|
}
|
|
1600
1752
|
if (isVoiceSleepIntent(transcript)) {
|
|
1601
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
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
|
@@ -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
|
|
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
|
-
|
|
961
|
-
|
|
962
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1051
|
+
clearTimers();
|
|
990
1052
|
if (!done) {
|
|
991
1053
|
await fail("interrupted");
|
|
992
1054
|
}
|