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
174 lines
5.8 KiB
JavaScript
174 lines
5.8 KiB
JavaScript
/**
|
|
* Agent source hot-reload watcher.
|
|
*
|
|
* Watches the backend `<pkg>/src` dirs (the TypeScript the dev API child loads via
|
|
* the `eliza-source` condition) and fires a debounced callback when real code
|
|
* changes. Watching each `<pkg>/src` dir directly — never `dist/`, `node_modules/`,
|
|
* or build output — means concurrent package builds rewriting `dist/` generate
|
|
* no events here. That decoupling is the whole point: the old `node --watch`
|
|
* followed imports into `dist/` and reloaded mid-build, so it had to be
|
|
* disabled; this never does.
|
|
*
|
|
* dev-ui.mjs wires `onChange` to the API supervisor's `restart()`.
|
|
*/
|
|
|
|
import { existsSync, readdirSync, watch } from "node:fs";
|
|
import path from "node:path";
|
|
|
|
// Pure-frontend packages are served + HMR'd by Vite and are NOT loaded by the
|
|
// API child, so editing them must not bounce the agent.
|
|
export const HOT_RELOAD_FRONTEND_PACKAGES = new Set([
|
|
"ui",
|
|
"app",
|
|
"os-homepage",
|
|
"homepage",
|
|
"docs",
|
|
"docs-elizacloud-redirect",
|
|
"tui",
|
|
"robot",
|
|
"os",
|
|
]);
|
|
|
|
// Only hand-written agent source: .ts/.tsx/.mts/.cts + .json. Compiled `.js`
|
|
// is deliberately NOT matched — this monorepo emits compiled `.js`/`.d.ts`
|
|
// shadows next to `.ts` source, and reacting to those would bounce the agent on
|
|
// every build. `.d.ts` (declaration emit) is excluded explicitly since it ends
|
|
// in `.ts`.
|
|
export const HOT_RELOAD_CODE_FILE = /\.(?:tsx?|mts|cts|json)$/;
|
|
export const HOT_RELOAD_DECLARATION = /\.d\.[cm]?ts$/;
|
|
export const HOT_RELOAD_TEST_FILE = /\.(?:test|spec)\.[cm]?[jt]sx?$/;
|
|
export const HOT_RELOAD_IGNORED_SEGMENT =
|
|
/(?:^|[/\\])(?:dist|node_modules|\.turbo|\.git|coverage|__tests__|\.vite|build|generated)(?:[/\\]|$)/;
|
|
export const HOT_RELOAD_DEBOUNCE_MS = 350;
|
|
|
|
/**
|
|
* Collect the `<group>/<pkg>/src` dirs whose changes should reload the agent.
|
|
* Skips pure-frontend packages (Vite owns those) and any package without a
|
|
* `src/` dir.
|
|
*
|
|
* @param {string} root Repo root that holds `packages/` and `plugins/`.
|
|
* @returns {string[]} Absolute `<pkg>/src` dirs to watch.
|
|
*/
|
|
export function collectAgentSourceDirs(root) {
|
|
const dirs = [];
|
|
for (const group of ["packages", "plugins"]) {
|
|
const groupDir = path.join(root, group);
|
|
let entries;
|
|
try {
|
|
entries = readdirSync(groupDir, { withFileTypes: true });
|
|
} catch {
|
|
continue;
|
|
}
|
|
for (const entry of entries) {
|
|
if (!entry.isDirectory()) continue;
|
|
if (
|
|
group === "packages" &&
|
|
HOT_RELOAD_FRONTEND_PACKAGES.has(entry.name)
|
|
) {
|
|
continue;
|
|
}
|
|
const srcDir = path.join(groupDir, entry.name, "src");
|
|
if (existsSync(srcDir)) dirs.push(srcDir);
|
|
}
|
|
}
|
|
return dirs;
|
|
}
|
|
|
|
/**
|
|
* Whether a watch event for `absPath` should trigger a reload. A null/empty
|
|
* path (some platforms omit the filename) is treated as reloadable so we never
|
|
* miss a real change. Build output, deps, generated, and test/coverage dirs are
|
|
* ignored, as are declaration (`.d.ts`) and test/spec files; only hand-written
|
|
* source (ts/tsx/mts/cts/json) qualifies.
|
|
*
|
|
* @param {string | null | undefined} absPath
|
|
* @returns {boolean}
|
|
*/
|
|
export function isReloadableChangePath(absPath) {
|
|
if (!absPath) return true;
|
|
if (HOT_RELOAD_IGNORED_SEGMENT.test(absPath)) return false;
|
|
if (HOT_RELOAD_DECLARATION.test(absPath)) return false;
|
|
if (HOT_RELOAD_TEST_FILE.test(absPath)) return false;
|
|
return HOT_RELOAD_CODE_FILE.test(absPath);
|
|
}
|
|
|
|
/**
|
|
* Start watching the agent source dirs. Returns a handle with the number of
|
|
* dirs watched and a `close()`.
|
|
*
|
|
* @param {Object} params
|
|
* @param {string} params.root Repo root.
|
|
* @param {(relPath: string, changedCount: number) => void} params.onChange
|
|
* Debounced; receives one sample path (relative to `root`, or "source" when
|
|
* the filename is unknown) and the number of DISTINCT files that changed in
|
|
* the window — so the caller can ignore bulk rewrites (a git reset / checkout
|
|
* / build touches many files at once; a human edit touches one or a few).
|
|
* @param {(dir: string, err: Error) => void} [params.onError] Per-dir watch
|
|
* setup failure (e.g. a platform without recursive watch).
|
|
* @param {number} [params.debounceMs]
|
|
* @returns {{ count: number, close: () => void }}
|
|
*/
|
|
export function startAgentSourceWatcher({
|
|
root,
|
|
onChange,
|
|
onError,
|
|
debounceMs = HOT_RELOAD_DEBOUNCE_MS,
|
|
}) {
|
|
const dirs = collectAgentSourceDirs(root);
|
|
/** @type {import("node:fs").FSWatcher[]} */
|
|
const watchers = [];
|
|
/** @type {ReturnType<typeof setTimeout> | null} */
|
|
let debounce = null;
|
|
/** Distinct changed paths accumulated in the current debounce window. */
|
|
const pendingFiles = new Set();
|
|
let pendingSample = null;
|
|
|
|
const fire = (absPath) => {
|
|
if (absPath && !isReloadableChangePath(absPath)) return;
|
|
if (absPath) {
|
|
pendingFiles.add(absPath);
|
|
pendingSample = absPath;
|
|
}
|
|
if (debounce) clearTimeout(debounce);
|
|
debounce = setTimeout(() => {
|
|
const count = pendingFiles.size;
|
|
const sample = pendingSample;
|
|
pendingFiles.clear();
|
|
pendingSample = null;
|
|
debounce = null;
|
|
onChange(sample ? path.relative(root, sample) : "source", count);
|
|
}, debounceMs);
|
|
debounce.unref?.();
|
|
};
|
|
|
|
for (const dir of dirs) {
|
|
try {
|
|
const fsWatcher = watch(dir, { recursive: true }, (_event, filename) => {
|
|
fire(filename ? path.join(dir, filename.toString()) : null);
|
|
});
|
|
// A dir vanishing mid-build (clean step) must not crash the dev process.
|
|
fsWatcher.on("error", () => {});
|
|
watchers.push(fsWatcher);
|
|
} catch (err) {
|
|
onError?.(dir, err);
|
|
}
|
|
}
|
|
|
|
return {
|
|
count: dirs.length,
|
|
close() {
|
|
if (debounce) {
|
|
clearTimeout(debounce);
|
|
debounce = null;
|
|
}
|
|
for (const fsWatcher of watchers) {
|
|
try {
|
|
fsWatcher.close();
|
|
} catch {
|
|
// already closed
|
|
}
|
|
}
|
|
},
|
|
};
|
|
}
|