项目文件夹

文件
wehub-resource-sync 426e9eeabd
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
ci / test (push) Has been cancelled
ci / lint-and-format (push) Has been cancelled
ci / build (push) Has been cancelled
ci / dev-startup (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
Markdown Links / Relative Markdown Links (push) Has been cancelled
Quality (Extended) / Homepage Build (PR smoke) (push) Has been cancelled
Quality (Extended) / Comment-only diff guard (push) Has been cancelled
Quality (Extended) / Format + Type Safety Ratchet (push) Has been cancelled
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Has been cancelled
Quality (Extended) / Develop Gate (lint) (push) Has been cancelled
Chat shell gestures / Chat shell gesture + parity e2e (push) Has been cancelled
Cloud Gateway Discord / Test (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Has been cancelled
Build Agent Image / build-and-push (push) Has been cancelled
Dev Smoke / bun run dev onboarding chat (push) Has been cancelled
Dev Smoke / Vite HMR dependency-level smoke (push) Has been cancelled
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Has been cancelled
Publish @elizaos/example-code / check_npm (push) Has been cancelled
Publish @elizaos/example-code / publish_npm (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / verify_version (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / publish_npm (push) Has been cancelled
Sandbox Live Smoke / Sandbox live smoke (push) Has been cancelled
Snap Build & Test / Build Snap (amd64) (push) Has been cancelled
Snap Build & Test / Build Snap (arm64) (push) Has been cancelled
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Has been cancelled
Cloud Gateway Webhook / Test (push) Has been cancelled
Cloud Tests / lint-and-types (push) Has been cancelled
Cloud Tests / unit-tests (push) Has been cancelled
Cloud Tests / integration-tests (push) Has been cancelled
Cloud Tests / e2e-tests (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Apps Worker (Product 2) / Determine environment (push) Has been cancelled
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Has been cancelled
Deploy Eliza Provisioning Worker / Determine environment (push) Has been cancelled
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Has been cancelled
Dev Smoke / Classify changed paths (push) Has been cancelled
supply-chain / sbom (push) Has been cancelled
supply-chain / vulnerability-scan (push) Has been cancelled
Build, Push & Deploy to Phala Cloud / build-and-push (push) Has been cancelled
Test Packaging / Validate Packaging Configs (push) Has been cancelled
Test Packaging / Build & Test PyPI Package (push) Has been cancelled
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Has been cancelled
Test Packaging / Pack & Test JS Tarballs (push) Has been cancelled
UI Fixture E2E / ui-fixture-e2e (push) Has been cancelled
UI Fixture E2E / fixture-e2e (push) Has been cancelled
UI Story Gate / story-gate (push) Has been cancelled
vault-ci / test (macos-latest) (push) Has been cancelled
vault-ci / test (ubuntu-latest) (push) Has been cancelled
vault-ci / test (windows-latest) (push) Has been cancelled
vault-ci / app-core wiring tests (push) Has been cancelled
verify-patches / verify patches/CHECKSUMS.sha256 (push) Has been cancelled
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Has been cancelled
Voice Benchmark Smoke / voice bench smoke summary (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Has been cancelled
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:43:05 +08:00

225 行
6.7 KiB
TypeScript

/**
* End-to-end test for packages/app-core/scripts/omnivoice-fuse/freeze-voice.mjs.
*
* The CLI builds an ELZ2 v2 voice preset from a corpus directory. We
* exercise the corpus-discovery + selection + dry-run + skip-encode write
* paths against a synthesized minimal corpus (1 short WAV + manifest
* entry); we do NOT run the FFI encode path here (the fused
* libelizainference is not built in CI by default — that's covered by
* the dedicated FFI smoke tests).
*
* Coverage:
* - --dry-run lists clips without writing anything.
* - --skip-encode writes a parseable ELZ2 v2 file under <out>.
* - the written preset round-trips via readVoicePresetFile and the
* refText + instruct + metadata fields are preserved.
* - --voice rejects path-unsafe ids.
* - --max-seconds bounds the selected reference length.
*/
import { execFileSync } from "node:child_process";
import {
existsSync,
mkdirSync,
mkdtempSync,
readFileSync,
rmSync,
writeFileSync,
} from "node:fs";
import { tmpdir } from "node:os";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { readVoicePresetFile } from "../src/services/voice/voice-preset-format";
const HERE = path.dirname(fileURLToPath(import.meta.url));
// .../plugins/plugin-local-inference/__tests__ -> repo root
const REPO_ROOT = path.resolve(HERE, "../../..");
const SCRIPT = path.join(
REPO_ROOT,
"packages",
"app-core",
"scripts",
"voice",
"freeze-voice.mjs",
);
interface RunResult {
stdout: string;
stderr: string;
code: number;
}
function runFreeze(args: string[], opts: { allowFail?: boolean } = {}): RunResult {
try {
const stdout = execFileSync("bun", [SCRIPT, ...args], {
cwd: REPO_ROOT,
encoding: "utf8",
stdio: ["ignore", "pipe", "pipe"],
});
return { stdout, stderr: "", code: 0 };
} catch (err) {
const e = err as { stdout?: Buffer | string; stderr?: Buffer | string; status?: number };
if (!opts.allowFail) {
throw err;
}
return {
stdout: typeof e.stdout === "string" ? e.stdout : e.stdout?.toString() ?? "",
stderr: typeof e.stderr === "string" ? e.stderr : e.stderr?.toString() ?? "",
code: e.status ?? 1,
};
}
}
/** Build a minimal 16-bit PCM mono RIFF/WAVE file at `srHz` for `durSec`. */
function writeSineWav(filePath: string, durSec: number, srHz: number) {
const n = Math.round(durSec * srHz);
const dataLen = n * 2;
const buf = Buffer.alloc(44 + dataLen);
buf.write("RIFF", 0, "ascii");
buf.writeUInt32LE(36 + dataLen, 4);
buf.write("WAVE", 8, "ascii");
buf.write("fmt ", 12, "ascii");
buf.writeUInt32LE(16, 16); // fmt size
buf.writeUInt16LE(1, 20); // PCM
buf.writeUInt16LE(1, 22); // channels
buf.writeUInt32LE(srHz, 24);
buf.writeUInt32LE(srHz * 2, 28); // byte rate
buf.writeUInt16LE(2, 32); // block align
buf.writeUInt16LE(16, 34); // bits/sample
buf.write("data", 36, "ascii");
buf.writeUInt32LE(dataLen, 40);
const f = 440;
for (let i = 0; i < n; i++) {
const s = Math.round(Math.sin((2 * Math.PI * f * i) / srHz) * 0.1 * 32767);
buf.writeInt16LE(s, 44 + i * 2);
}
writeFileSync(filePath, buf);
}
describe("freeze-voice.mjs CLI", () => {
let corpusDir: string;
let outDir: string;
beforeEach(() => {
corpusDir = mkdtempSync(path.join(tmpdir(), "freeze-voice-corpus-"));
outDir = mkdtempSync(path.join(tmpdir(), "freeze-voice-out-"));
mkdirSync(path.join(corpusDir, "audio"), { recursive: true });
// Two clips: a 3-second sample and a 5-second sample, both at 44.1 kHz.
writeSineWav(path.join(corpusDir, "audio", "same_001.wav"), 3.0, 44100);
writeSineWav(path.join(corpusDir, "audio", "same_003.wav"), 5.0, 44100);
const manifest = [
JSON.stringify({
id: "same_001",
audio_path: "audio/same_001.wav",
duration_s: 3.0,
sample_rate: 44100,
channels: 1,
bit_depth: 16,
transcript: "First reference sentence for the freeze test.",
excluded: false,
}),
JSON.stringify({
id: "same_003",
audio_path: "audio/same_003.wav",
duration_s: 5.0,
sample_rate: 44100,
channels: 1,
bit_depth: 16,
transcript: "Second reference sentence containing more words.",
excluded: false,
}),
].join("\n");
writeFileSync(path.join(corpusDir, "manifest.jsonl"), manifest);
});
afterEach(() => {
rmSync(corpusDir, { recursive: true, force: true });
rmSync(outDir, { recursive: true, force: true });
});
it("--dry-run lists selected clips without writing", () => {
const out = path.join(outDir, "voice-preset-test.bin");
const res = runFreeze([
"--voice",
"test",
"--corpus",
corpusDir,
"--out",
out,
"--dry-run",
"--skip-encode",
]);
expect(res.stdout).toContain("selected 2 clip(s)");
expect(res.stdout).toContain("dry-run: skipping load + encode + write");
expect(existsSync(out)).toBe(false);
});
it("--skip-encode writes a parseable ELZ2 v2 file with refText + instruct + metadata", () => {
const out = path.join(outDir, "voice-preset-test.bin");
const instruct = "young adult female, warm";
const res = runFreeze([
"--voice",
"test",
"--corpus",
corpusDir,
"--out",
out,
"--skip-encode",
"--instruct",
instruct,
]);
expect(res.code).toBe(0);
expect(existsSync(out)).toBe(true);
const bytes = new Uint8Array(readFileSync(out));
const parsed = readVoicePresetFile(bytes);
expect(parsed.version).toBe(2);
expect(parsed.instruct).toBe(instruct);
expect(parsed.refText).toContain("First reference sentence");
expect(parsed.refText).toContain("Second reference sentence");
expect(parsed.refAudioTokens.K).toBe(0); // --skip-encode left these empty
expect(parsed.refAudioTokens.refT).toBe(0);
expect(parsed.metadata.voiceId).toBe("test");
expect(parsed.metadata.generator).toBe("freeze-voice.mjs");
expect(Array.isArray(parsed.metadata.referenceClipIds)).toBe(true);
expect((parsed.metadata.referenceClipIds as string[]).length).toBeGreaterThan(0);
});
it("--max-seconds bounds selected reference length", () => {
const out = path.join(outDir, "voice-preset-test.bin");
const res = runFreeze([
"--voice",
"test",
"--corpus",
corpusDir,
"--out",
out,
"--skip-encode",
"--max-seconds",
"3.5",
]);
expect(res.code).toBe(0);
// Only the first 3 s clip fits in a 3.5 s budget.
expect(res.stdout).toContain("selected 1 clip(s)");
});
it("rejects path-unsafe voice ids", () => {
const out = path.join(outDir, "voice-preset-test.bin");
const res = runFreeze(
[
"--voice",
"../escape",
"--corpus",
corpusDir,
"--out",
out,
"--skip-encode",
"--dry-run",
],
{ allowFail: true },
);
expect(res.code).not.toBe(0);
expect(res.stderr + res.stdout).toMatch(/path-safe segment/i);
});
});