426e9eeabd
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Waiting to run
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Waiting to run
Build Agent Image / build-and-push (push) Waiting to run
Chat shell gestures / Chat shell gesture + parity e2e (push) Waiting to run
Cloud Gateway Discord / Test (push) Waiting to run
Cloud Gateway Webhook / Test (push) Waiting to run
Cloud Tests / lint-and-types (push) Waiting to run
Cloud Tests / unit-tests (push) Waiting to run
Cloud Tests / integration-tests (push) Waiting to run
Cloud Tests / e2e-tests (push) Blocked by required conditions
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
Deploy Apps Worker (Product 2) / Determine environment (push) Waiting to run
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Blocked by required conditions
Deploy Eliza Provisioning Worker / Determine environment (push) Waiting to run
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Blocked by required conditions
Dev Smoke / Classify changed paths (push) Waiting to run
Dev Smoke / bun run dev onboarding chat (push) Blocked by required conditions
Dev Smoke / Vite HMR dependency-level smoke (push) Blocked by required conditions
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Waiting to run
Publish @elizaos/example-code / check_npm (push) Waiting to run
Publish @elizaos/example-code / publish_npm (push) Blocked by required conditions
Publish @elizaos/plugin-elizacloud / verify_version (push) Waiting to run
Publish @elizaos/plugin-elizacloud / publish_npm (push) Blocked by required conditions
Sandbox Live Smoke / Sandbox live smoke (push) Waiting to run
Snap Build & Test / Build Snap (amd64) (push) Waiting to run
Snap Build & Test / Build Snap (arm64) (push) Waiting to run
supply-chain / sbom (push) Waiting to run
supply-chain / vulnerability-scan (push) Waiting to run
Build, Push & Deploy to Phala Cloud / build-and-push (push) Waiting to run
Test Packaging / Validate Packaging Configs (push) Waiting to run
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Waiting to run
Test Packaging / Pack & Test JS Tarballs (push) Waiting to run
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Waiting to run
UI Fixture E2E / ui-fixture-e2e (push) Waiting to run
UI Fixture E2E / fixture-e2e (push) Waiting to run
UI Story Gate / story-gate (push) Waiting to run
vault-ci / test (macos-latest) (push) Waiting to run
vault-ci / test (ubuntu-latest) (push) Waiting to run
vault-ci / test (windows-latest) (push) Waiting to run
vault-ci / app-core wiring tests (push) Waiting to run
verify-patches / verify patches/CHECKSUMS.sha256 (push) Waiting to run
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Waiting to run
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Waiting to run
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Waiting to run
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Waiting to run
Voice Benchmark Smoke / voice bench smoke summary (push) Blocked by required conditions
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) Waiting to run
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) Waiting to run
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) Waiting to run
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) Waiting to run
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) Waiting to run
Test Packaging / Build & Test PyPI Package (push) Waiting to run
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
338 lines
9.6 KiB
JavaScript
338 lines
9.6 KiB
JavaScript
#!/usr/bin/env node
|
|
/** Supports app-core build, packaging, or development orchestration for run node mjs. */
|
|
import { spawn } from "node:child_process";
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import process from "node:process";
|
|
import restartExitCodeDefinition from "../../shared/src/restart-exit-code.json" with {
|
|
type: "json",
|
|
};
|
|
import { registerRestartAndShouldAbort } from "./lib/restart-guard.mjs";
|
|
import { syncElizaEnvAliases } from "./lib/sync-eliza-env-aliases.mjs";
|
|
import {
|
|
chooseElizaRuntime,
|
|
resolveRuntimeExecPath,
|
|
} from "./run-node-runtime.mjs";
|
|
|
|
const args = process.argv.slice(2);
|
|
const cwd = process.cwd();
|
|
|
|
// Load worktree-specific env overrides (ports, state dir) before building the child env.
|
|
// Generated by: bash scripts/worktree-env.sh <slot>
|
|
const _worktreeEnvPath = path.join(cwd, ".env.worktree");
|
|
if (fs.existsSync(_worktreeEnvPath)) {
|
|
try {
|
|
const content = fs.readFileSync(_worktreeEnvPath, "utf-8");
|
|
for (const line of content.split("\n")) {
|
|
const trimmed = line.trim();
|
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
const eq = trimmed.indexOf("=");
|
|
if (eq < 1) continue;
|
|
const key = trimmed.slice(0, eq).trim();
|
|
const val = trimmed.slice(eq + 1).trim();
|
|
// override: false — don't clobber explicitly set env vars
|
|
if (!process.env[key]) {
|
|
process.env[key] = val;
|
|
}
|
|
}
|
|
} catch {
|
|
// Ignore read errors — worktree env is optional
|
|
}
|
|
}
|
|
|
|
syncElizaEnvAliases();
|
|
|
|
const env = { ...process.env };
|
|
if (!env.ELIZA_NAMESPACE) {
|
|
env.ELIZA_NAMESPACE = "eliza";
|
|
}
|
|
// WHY: The child runs dist/eliza.js, which dynamic-imports @elizaos/plugin-*. Node does not
|
|
// use cwd to resolve package names for import("pkg"); we must set NODE_PATH to repo root
|
|
// node_modules so those imports succeed. See docs/plugin-resolution-and-node-path.md.
|
|
const rootModules = path.join(cwd, "node_modules");
|
|
env.NODE_PATH = env.NODE_PATH
|
|
? `${rootModules}${path.delimiter}${env.NODE_PATH}`
|
|
: rootModules;
|
|
const compiler = "tsdown";
|
|
|
|
const distRoot = path.join(cwd, "dist");
|
|
const distEntry = path.join(distRoot, "/entry.js");
|
|
const buildStampPath = path.join(distRoot, ".buildstamp");
|
|
const srcRoot = path.join(cwd, "src");
|
|
const configFiles = [
|
|
path.join(cwd, "tsconfig.json"),
|
|
path.join(cwd, "package.json"),
|
|
];
|
|
|
|
const statMtime = (filePath) => {
|
|
try {
|
|
return fs.statSync(filePath).mtimeMs;
|
|
} catch {
|
|
return null;
|
|
}
|
|
};
|
|
|
|
const isExcludedSource = (filePath) => {
|
|
const relativePath = path.relative(srcRoot, filePath);
|
|
if (relativePath.startsWith("..")) {
|
|
return false;
|
|
}
|
|
return (
|
|
relativePath.endsWith(".test.ts") ||
|
|
relativePath.endsWith(".test.tsx") ||
|
|
relativePath.endsWith(`test-utils.ts`)
|
|
);
|
|
};
|
|
|
|
const findLatestMtime = (dirPath, shouldSkip) => {
|
|
let latest = null;
|
|
const queue = [dirPath];
|
|
while (queue.length > 0) {
|
|
const current = queue.pop();
|
|
if (!current) {
|
|
continue;
|
|
}
|
|
let entries = [];
|
|
try {
|
|
entries = fs.readdirSync(current, { withFileTypes: true });
|
|
} catch {
|
|
continue;
|
|
}
|
|
for (const entry of entries) {
|
|
const fullPath = path.join(current, entry.name);
|
|
if (entry.isDirectory()) {
|
|
queue.push(fullPath);
|
|
continue;
|
|
}
|
|
if (!entry.isFile()) {
|
|
continue;
|
|
}
|
|
if (shouldSkip?.(fullPath)) {
|
|
continue;
|
|
}
|
|
const mtime = statMtime(fullPath);
|
|
if (mtime == null) {
|
|
continue;
|
|
}
|
|
if (latest == null || mtime > latest) {
|
|
latest = mtime;
|
|
}
|
|
}
|
|
}
|
|
return latest;
|
|
};
|
|
|
|
const shouldBuild = () => {
|
|
if (env.ELIZA_FORCE_BUILD === "1") {
|
|
return true;
|
|
}
|
|
const stampMtime = statMtime(buildStampPath);
|
|
if (stampMtime == null) {
|
|
return true;
|
|
}
|
|
if (statMtime(distEntry) == null) {
|
|
return true;
|
|
}
|
|
|
|
for (const filePath of configFiles) {
|
|
const mtime = statMtime(filePath);
|
|
if (mtime != null && mtime > stampMtime) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
const srcMtime = findLatestMtime(srcRoot, isExcludedSource);
|
|
if (srcMtime != null && srcMtime > stampMtime) {
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
const logRunner = (message) => {
|
|
if (env.ELIZA_RUNNER_LOG === "0") {
|
|
return;
|
|
}
|
|
process.stderr.write(`[eliza] ${message}\n`);
|
|
};
|
|
|
|
/** Exit code used by the restart action to signal "restart requested". */
|
|
const RESTART_EXIT_CODE = restartExitCodeDefinition.restartExitCode;
|
|
|
|
/** Guard against infinite restart loops. */
|
|
const MAX_RESTARTS_IN_WINDOW = 5;
|
|
const RESTART_WINDOW_MS = 60_000;
|
|
const restartTimestamps = [];
|
|
|
|
const commandExists = (command) => {
|
|
const pathEnv = process.env.PATH ?? "";
|
|
if (!pathEnv) return false;
|
|
const isWindows = process.platform === "win32";
|
|
const extensions = isWindows
|
|
? (process.env.PATHEXT || ".EXE;.CMD;.BAT;.COM").split(";").filter(Boolean)
|
|
: [""];
|
|
for (const dir of pathEnv.split(path.delimiter).filter(Boolean)) {
|
|
for (const ext of extensions) {
|
|
const candidate = path.join(
|
|
dir,
|
|
isWindows && !command.toLowerCase().endsWith(ext.toLowerCase())
|
|
? `${command}${ext}`
|
|
: command,
|
|
);
|
|
if (fs.existsSync(candidate)) return true;
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
|
|
const runNode = () => {
|
|
const hasBun = commandExists("bun");
|
|
const hasNode = commandExists("node");
|
|
const { runtime, warning } = chooseElizaRuntime({
|
|
requestedRuntime: process.env.ELIZA_RUNTIME,
|
|
platform: process.platform,
|
|
bunVersion: process.versions?.bun,
|
|
hasBun,
|
|
hasNode,
|
|
});
|
|
if (warning) {
|
|
logRunner(`${warning} Set ELIZA_RUNTIME=bun to force Bun runtime.`);
|
|
}
|
|
if (runtime === "bun" && !hasBun) {
|
|
logRunner("Bun runtime was selected, but bun was not found in PATH.");
|
|
process.exit(1);
|
|
}
|
|
if (runtime === "node" && !hasNode) {
|
|
logRunner("Node.js runtime was selected, but node was not found in PATH.");
|
|
process.exit(1);
|
|
}
|
|
const execPath = resolveRuntimeExecPath({
|
|
runtime,
|
|
currentExecPath: process.execPath,
|
|
platform: process.platform,
|
|
explicitNodePath: process.env.ELIZA_NODE_PATH,
|
|
});
|
|
// Forks rename `eliza.mjs` to their own entry filename.
|
|
// Letting them override the spawned filename via `ELIZA_ENTRY_FILE` avoids
|
|
// every fork having to ship a shim at the old name. Read from `env` (the
|
|
// local snapshot built at module load) for consistency with what the child
|
|
// process actually receives — `.env.worktree` overrides applied above are
|
|
// visible here too.
|
|
const entryFile = env.ELIZA_ENTRY_FILE?.trim() || "eliza.mjs";
|
|
const nodeProcess = spawn(execPath, [entryFile, ...args], {
|
|
cwd,
|
|
env,
|
|
stdio: "inherit",
|
|
});
|
|
|
|
nodeProcess.on("error", (err) => {
|
|
logRunner(`Failed to spawn ${execPath}: ${err.message}`);
|
|
process.exit(1);
|
|
});
|
|
|
|
nodeProcess.on("exit", (exitCode, exitSignal) => {
|
|
if (exitSignal) {
|
|
process.exit(1);
|
|
}
|
|
|
|
// Restart loop: when the agent requests a restart it exits with code 75.
|
|
// Re-run the full runner (including the build-staleness check) so any
|
|
// source changes are compiled before the new process starts.
|
|
if (exitCode === RESTART_EXIT_CODE) {
|
|
// Guard against rapid restart loops (window/trim/threshold in restart-guard.mjs).
|
|
if (
|
|
registerRestartAndShouldAbort(
|
|
restartTimestamps,
|
|
Date.now(),
|
|
MAX_RESTARTS_IN_WINDOW,
|
|
RESTART_WINDOW_MS,
|
|
)
|
|
) {
|
|
logRunner(
|
|
`Restart loop detected: ${restartTimestamps.length} restarts in ${RESTART_WINDOW_MS / 1000}s — aborting.`,
|
|
);
|
|
process.exit(1);
|
|
}
|
|
|
|
logRunner("Restart requested — relaunching...");
|
|
|
|
// Re-check whether a rebuild is needed (source files may have changed).
|
|
if (shouldBuild()) {
|
|
logRunner("Building TypeScript (dist is stale).");
|
|
const bunxArgs = [compiler];
|
|
const buildCmd = process.platform === "win32" ? "cmd.exe" : "bunx";
|
|
const buildArgs =
|
|
process.platform === "win32"
|
|
? ["/d", "/s", "/c", "bunx", ...bunxArgs]
|
|
: bunxArgs;
|
|
const build = spawn(buildCmd, buildArgs, {
|
|
cwd,
|
|
env,
|
|
stdio: "inherit",
|
|
});
|
|
build.on("error", (err) => {
|
|
logRunner(`Failed to spawn ${buildCmd}: ${err.message}`);
|
|
process.exit(1);
|
|
});
|
|
build.on("exit", (code, signal) => {
|
|
if (signal || (code !== 0 && code !== null)) {
|
|
logRunner("Rebuild failed, restarting anyway.");
|
|
} else {
|
|
writeBuildStamp();
|
|
}
|
|
runNode();
|
|
});
|
|
} else {
|
|
runNode();
|
|
}
|
|
return;
|
|
}
|
|
|
|
process.exit(exitCode ?? 1);
|
|
});
|
|
};
|
|
|
|
const writeBuildStamp = () => {
|
|
try {
|
|
fs.mkdirSync(distRoot, { recursive: true });
|
|
fs.writeFileSync(buildStampPath, `${Date.now()}\n`);
|
|
} catch (error) {
|
|
// Best-effort stamp; still allow the runner to start.
|
|
logRunner(
|
|
`Failed to write build stamp: ${error?.message ?? "unknown error"}`,
|
|
);
|
|
}
|
|
};
|
|
|
|
if (!shouldBuild()) {
|
|
runNode();
|
|
} else {
|
|
logRunner("Building TypeScript (dist is stale).");
|
|
const bunxArgs = [compiler];
|
|
const buildCmd = process.platform === "win32" ? "cmd.exe" : "bunx";
|
|
const buildArgs =
|
|
process.platform === "win32"
|
|
? ["/d", "/s", "/c", "bunx", ...bunxArgs]
|
|
: bunxArgs;
|
|
const build = spawn(buildCmd, buildArgs, {
|
|
cwd,
|
|
env,
|
|
stdio: "inherit",
|
|
});
|
|
|
|
build.on("error", (err) => {
|
|
logRunner(`Failed to spawn ${buildCmd}: ${err.message}`);
|
|
process.exit(1);
|
|
});
|
|
|
|
build.on("exit", (code, signal) => {
|
|
if (signal) {
|
|
process.exit(1);
|
|
}
|
|
if (code !== 0 && code !== null) {
|
|
process.exit(code);
|
|
}
|
|
writeBuildStamp();
|
|
runNode();
|
|
});
|
|
}
|