项目文件夹

文件
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

400 行
14 KiB
JavaScript
可执行文件

/**
* Pure derivation of the parallel-certification compose topology from the
* test-runner's own plan (#14549). The single source of lane truth is
* `packages/scripts/run-all-tests.mjs --plan=json --all`: each distinct
* `scriptName` in the plan's `summary.byScript` becomes one lane (optionally
* split into TEST_SHARD slices the runner itself understands), and the plan's
* `cloudStep` becomes the dedicated cloud lane — there is deliberately no
* second lane registry to drift from the runner.
*
* Everything here is side-effect-free (no fs, no spawn, no clock) so the shard
* math, cpuset allocation, and YAML rendering are unit-testable and the
* committed compose.yml is byte-reproducible: generate-compose-lanes.mjs feeds
* in the live plan and writes the rendered text; the drift test regenerates
* and compares. Rendering is a hand-rolled emitter over a fixed key order —
* a generic YAML library would not guarantee byte stability across versions.
*/
/** Generator parameters baked into the committed compose.yml. Fixed defaults
* (not `os.cpus()`) so regeneration is machine-independent. */
export const DEFAULT_PARAMS = Object.freeze({
cores: 16,
unitShards: 4,
e2eShards: 2,
gpuParallel: 4,
});
/** Matches serve.mjs CONTEXT_SIZE — one context budget for native and compose. */
export const GPU_CONTEXT_SIZE = 8192;
const CPU_IMAGE =
"${ELIZA_CERT_IMAGE:-ghcr.io/elizaos/certification-gpu:latest}";
const GPU_IMAGE =
"${ELIZA_CERT_GPU_IMAGE:-ghcr.io/elizaos/certification-gpu:latest}";
const REPO_MOUNT = "${ELIZA_REPO_ROOT:-../..}:/repo:ro";
const QUEUE_MOUNT = "${ELIZA_CERT_QUEUE_HOST:-./queue}:/queue";
const MODELS_MOUNT =
"${ELIZA_GPU_MODELS_DIR:-~/.cache/eliza/gpu-vision}:/models:ro";
/** Positive-integer guard for generator parameters; a zero/NaN shard count
* must fail here, not surface as a broken TEST_SHARD spec inside a container. */
export function assertPositiveInteger(value, label) {
if (!Number.isInteger(value) || value < 1) {
throw new Error(
`[compose-lanes] ${label} must be a positive integer, got: ${value}`,
);
}
return value;
}
/** Lane base name for a plan scriptName: `test` → `unit`, `test:e2e` → `e2e`. */
export function laneBaseName(scriptName) {
if (scriptName === "test") return "unit";
return scriptName
.replace(/^test:/, "")
.toLowerCase()
.replace(/[^a-z0-9]+/g, "-");
}
/**
* Derive the lane list from a `--plan=json --all` document. Script lanes run
* `run-all-tests.mjs` filtered to one scriptName (sharded via the runner's own
* TEST_SHARD for the big groups); the plan's cloudStep command becomes the
* `cloud` lane so `--no-cloud` on the script lanes loses no coverage.
*/
export function deriveLanes(plan, params = DEFAULT_PARAMS) {
const byScript = plan?.summary?.byScript;
if (
!byScript ||
typeof byScript !== "object" ||
Object.keys(byScript).length === 0
) {
throw new Error(
"[compose-lanes] plan has no summary.byScript — expected run-all-tests.mjs --plan=json --all output",
);
}
assertPositiveInteger(params.unitShards, "unitShards");
assertPositiveInteger(params.e2eShards, "e2eShards");
const lanes = [];
for (const scriptName of Object.keys(byScript).sort((a, b) =>
a.localeCompare(b),
)) {
const base = laneBaseName(scriptName);
const total =
scriptName === "test"
? params.unitShards
: scriptName === "test:e2e"
? params.e2eShards
: 1;
for (let index = 1; index <= total; index++) {
lanes.push({
name: total > 1 ? `${base}-${index}of${total}` : base,
kind: "run-all-tests",
script: scriptName,
shard: total > 1 ? `${index}/${total}` : null,
});
}
}
if (plan.cloudStep && typeof plan.cloudStep.command === "string") {
lanes.push({
name: "cloud",
kind: "command",
command: plan.cloudStep.command,
script: null,
shard: null,
});
}
const seen = new Set();
for (const lane of lanes) {
if (seen.has(lane.name)) {
throw new Error(
`[compose-lanes] duplicate lane name derived from plan: ${lane.name}`,
);
}
seen.add(lane.name);
}
return lanes;
}
/**
* Partition `totalCores` into contiguous cpuset ranges, one per lane, in lane
* order. The remainder cores go to the earliest lanes — derivation order puts
* the heavy `unit`/`e2e` shards first, so they absorb the extras. More lanes
* than cores is a sizing error the caller must resolve (fewer shards or more
* cores), never silent oversubscription of a pinned set.
*/
export function allocateCpusets(totalCores, laneCount) {
assertPositiveInteger(totalCores, "cores");
assertPositiveInteger(laneCount, "lane count");
if (laneCount > totalCores) {
throw new Error(
`[compose-lanes] ${laneCount} lanes need at least ${laneCount} cores but only ${totalCores} configured — ` +
"lower --unit-shards/--e2e-shards or raise --cores",
);
}
const base = Math.floor(totalCores / laneCount);
const remainder = totalCores % laneCount;
const allocations = [];
let next = 0;
for (let i = 0; i < laneCount; i++) {
const size = base + (i < remainder ? 1 : 0);
const first = next;
const last = next + size - 1;
next = last + 1;
allocations.push({
cpuset: size === 1 ? `${first}` : `${first}-${last}`,
cpus: size,
});
}
return allocations;
}
/** The shell command a script lane runs inside its container (via lane-entry.sh).
* `--no-cloud` is safe because the plan-derived `cloud` lane carries that step. */
export function laneCommand(lane) {
if (lane.kind === "command") return lane.command;
return "node packages/scripts/run-all-tests.mjs --no-cloud --min-tasks=1";
}
/** Compose interpolation treats `$` specially; env values that must reach the
* container verbatim (anchored regexes like `^test$`) need `$$`. */
export function escapeComposeValue(value) {
return value.replace(/\$/g, "$$$$");
}
/**
* Assemble the full compose document model: cpu-profile lane services with
* cpuset pinning + cpu quotas, the single GPU-owning vision service (both
* resident llama-servers live in that one container — lanes share the service
* over HTTP, never the device), and the queue worker.
*/
export function buildComposeModel(plan, params, modelSets) {
const lanes = deriveLanes(plan, params);
const allocations = allocateCpusets(params.cores, lanes.length);
assertPositiveInteger(params.gpuParallel, "gpuParallel");
const ocr = modelSets?.ocr;
const vlm = modelSets?.vlm;
if (!ocr?.files?.model?.name || !vlm?.files?.model?.name) {
throw new Error(
"[compose-lanes] MODEL_SETS missing ocr/vlm entries (scripts/gpu-vision/lib.mjs)",
);
}
const services = [];
lanes.forEach((lane, i) => {
const environment = {
BUN_INSTALL_CACHE_DIR: "/cache/bun",
ELIZA_CERT_QUEUE_DIR: "/queue",
LANE_COMMAND: laneCommand(lane),
LANE_NAME: lane.name,
};
if (lane.kind === "run-all-tests") {
environment.TEST_LANE = "pr";
environment.TEST_SCRIPT_FILTER = `^${lane.script}$`;
if (lane.shard) environment.TEST_SHARD = lane.shard;
}
services.push({
name: `lane-${lane.name}`,
lane: lane.name,
image: CPU_IMAGE,
profiles: ["cpu"],
command: ["bash", "/repo/docker/certification/lane-entry.sh"],
cpuset: allocations[i].cpuset,
cpus: allocations[i].cpus,
environment,
volumes: [
REPO_MOUNT,
`scratch-lane-${lane.name}:/work`,
"build-cache:/cache",
QUEUE_MOUNT,
],
});
});
services.push({
name: "gpu-vision",
image: GPU_IMAGE,
profiles: ["gpu"],
command: ["bash", "/repo/docker/certification/gpu-entry.sh"],
environment: {
LLAMA_CONTEXT: `${GPU_CONTEXT_SIZE}`,
LLAMA_PARALLEL: `${params.gpuParallel}`,
OCR_MMPROJ: `/models/ocr/${ocr.files.mmproj.name}`,
OCR_MODEL: `/models/ocr/${ocr.files.model.name}`,
OCR_PORT: "8090",
VLM_MMPROJ: `/models/vlm/${vlm.files.mmproj.name}`,
VLM_MODEL: `/models/vlm/${vlm.files.model.name}`,
VLM_PORT: "8091",
},
volumes: [REPO_MOUNT, MODELS_MOUNT],
gpu: true,
healthcheck: {
test: [
"CMD-SHELL",
"curl -fsS http://127.0.0.1:8090/health && curl -fsS http://127.0.0.1:8091/health",
],
interval: "10s",
timeout: "5s",
retries: 60,
start_period: "120s",
},
});
services.push({
name: "gpu-queue-worker",
image: GPU_IMAGE,
profiles: ["gpu"],
command: [
"node",
"/repo/docker/certification/queue-worker.mjs",
"--jobs",
"/queue",
"--service",
"ocr=http://gpu-vision:8090",
"--service",
"vlm=http://gpu-vision:8091",
],
dependsOnHealthy: ["gpu-vision"],
volumes: [REPO_MOUNT, QUEUE_MOUNT],
});
return {
lanes,
services,
params,
scripts: Object.keys(plan.summary.byScript).sort(),
};
}
function yamlValue(value) {
if (typeof value === "number") return `${value}`;
// Quote every string: compose values include `${…}` defaults, `^…$$` regexes,
// and `no` / `0-3` scalars YAML would otherwise coerce; uniform quoting keeps
// the emitter trivially deterministic.
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
}
function renderList(lines, indent, items) {
for (const item of items) lines.push(`${indent}- ${yamlValue(item)}`);
}
/** Render the compose model to the exact committed YAML text. */
export function renderCompose(model) {
const { params, scripts } = model;
const lines = [];
lines.push(
"# Single-machine parallel certification stack (#14549, epic #14541).",
"# GENERATED by docker/certification/generate-compose-lanes.mjs from the test",
"# runner's own plan (`run-all-tests.mjs --plan=json --all`) — the lane",
"# services mirror that plan's script groups; do not edit by hand.",
"# Regenerate: node docker/certification/generate-compose-lanes.mjs",
"# Drift check: node docker/certification/generate-compose-lanes.mjs --check",
`# Params: cores=${params.cores} unit-shards=${params.unitShards} e2e-shards=${params.e2eShards} gpu-parallel=${params.gpuParallel}`,
`# Plan scripts: ${scripts.join(", ")} (+ cloud step)`,
"",
"name: eliza-certification",
"",
"services:",
);
for (const service of model.services) {
lines.push(` ${service.name}:`);
lines.push(` image: ${service.image}`);
if (service.profiles) {
lines.push(" profiles:");
renderList(lines, " ", service.profiles);
}
if (service.dependsOnHealthy) {
lines.push(" depends_on:");
for (const dep of service.dependsOnHealthy) {
lines.push(` ${dep}:`);
lines.push(" condition: service_healthy");
}
}
lines.push(" command:");
renderList(lines, " ", service.command);
if (service.cpuset !== undefined) {
lines.push(` cpuset: ${yamlValue(service.cpuset)}`);
lines.push(` cpus: ${service.cpus}`);
}
if (service.environment && Object.keys(service.environment).length > 0) {
lines.push(" environment:");
for (const key of Object.keys(service.environment).sort((a, b) =>
a.localeCompare(b),
)) {
lines.push(
` ${key}: ${yamlValue(escapeComposeValue(service.environment[key]))}`,
);
}
}
if (service.volumes) {
lines.push(" volumes:");
renderList(lines, " ", service.volumes);
}
if (service.gpu) {
lines.push(
" deploy:",
" resources:",
" reservations:",
" devices:",
' - driver: "nvidia"',
" count: 1",
" capabilities:",
' - "gpu"',
);
}
if (service.healthcheck) {
lines.push(" healthcheck:");
lines.push(" test:");
renderList(lines, " ", service.healthcheck.test);
lines.push(` interval: ${service.healthcheck.interval}`);
lines.push(` timeout: ${service.healthcheck.timeout}`);
lines.push(` retries: ${service.healthcheck.retries}`);
lines.push(` start_period: ${service.healthcheck.start_period}`);
}
lines.push(' restart: "no"');
lines.push("");
}
lines.push("volumes:");
lines.push(" build-cache: {}");
for (const lane of model.lanes) {
lines.push(` scratch-lane-${lane.name}: {}`);
}
lines.push("");
return lines.join("\n");
}
/** Parse generator/orchestrator shared CLI params on top of DEFAULT_PARAMS. */
export function resolveParams(flags) {
const read = (key, fallback, label) => {
const raw = flags[key];
if (raw === undefined) return fallback;
const value = Number(raw);
return assertPositiveInteger(value, label);
};
return {
cores: read("cores", DEFAULT_PARAMS.cores, "--cores"),
unitShards: read("unit-shards", DEFAULT_PARAMS.unitShards, "--unit-shards"),
e2eShards: read("e2e-shards", DEFAULT_PARAMS.e2eShards, "--e2e-shards"),
gpuParallel: read(
"gpu-parallel",
DEFAULT_PARAMS.gpuParallel,
"--gpu-parallel",
),
};
}
/** True when the resolved params match the committed defaults — the only case
* where the orchestrator holds the generated text against compose.yml. */
export function paramsAreDefault(params) {
return (
params.cores === DEFAULT_PARAMS.cores &&
params.unitShards === DEFAULT_PARAMS.unitShards &&
params.e2eShards === DEFAULT_PARAMS.e2eShards &&
params.gpuParallel === DEFAULT_PARAMS.gpuParallel
);
}