Files
openclaw/scripts/watch-node.mjs
T
Tabula Myriad a9ae1a6778 feat: Triad development iteration complete
Matrix Protocol:
- docker-compose.matrix.yml: Dendrite homeserver + PostgreSQL + Nginx TLS
- src/channels/plugins/matrix-channel.ts: OpenClaw plugin implementation
- docs/matrix-triad-setup.md: Setup guide with auth scheme (@tm1-4:triad.local)

MCP Server Integration:
- docs/mcp-triad-integration.md: SearXNG, Playwright, GitHub MCP configs
- docs/mcp-curiosity-mapping.md: Gap-to-capability mapping

Node Sync Architecture:
- src/services/node-sync-service.ts: WebSocket peer sync + presence detection
- src/services/node-sync-service.test.ts: Unit tests
- docs/node-sync-architecture.md: Architecture docs

Triad Resilience:
- scripts/triad-corruption-check.mjs: SQLite + log + config + git integrity
- docs/triad-resilience.md: Recovery procedures
- .secure/deployment-logs/README.md: Schema v2
- skills/triad-heartbeat/SKILL.md: Corruption check integration

NPM Publish Workflow:
- scripts/npm-publish.mjs: version, changelog, validate, publish, rollback
- .github/workflows/npm-publish.yml: GitHub Actions with provenance
- docs/npm-publish-guide.md: Complete documentation

All deliverables tested in Docker before production.
2026-03-24 00:44:50 -04:00

152 lines
4.1 KiB
JavaScript
Executable File

#!/usr/bin/env node
import { spawn } from "node:child_process";
import path from "node:path";
import process from "node:process";
import { pathToFileURL } from "node:url";
import chokidar from "chokidar";
import { isRestartRelevantRunNodePath, runNodeWatchedPaths } from "./run-node.mjs";
const WATCH_NODE_RUNNER = "scripts/run-node.mjs";
const WATCH_RESTART_SIGNAL = "SIGTERM";
const buildRunnerArgs = (args) => [WATCH_NODE_RUNNER, ...args];
const normalizePath = (filePath) =>
String(filePath ?? "")
.replaceAll("\\", "/")
.replace(/^\.\/+/, "");
const resolveRepoPath = (filePath, cwd) => {
const rawPath = String(filePath ?? "");
if (path.isAbsolute(rawPath)) {
return normalizePath(path.relative(cwd, rawPath));
}
return normalizePath(rawPath);
};
const isIgnoredWatchPath = (filePath, cwd) =>
!isRestartRelevantRunNodePath(resolveRepoPath(filePath, cwd));
export async function runWatchMain(params = {}) {
const deps = {
spawn: params.spawn ?? spawn,
process: params.process ?? process,
cwd: params.cwd ?? process.cwd(),
args: params.args ?? process.argv.slice(2),
env: params.env ? { ...params.env } : { ...process.env },
now: params.now ?? Date.now,
createWatcher:
params.createWatcher ?? ((watchPaths, options) => chokidar.watch(watchPaths, options)),
watchPaths: params.watchPaths ?? runNodeWatchedPaths,
};
const childEnv = { ...deps.env };
const watchSession = `${deps.now()}-${deps.process.pid}`;
childEnv.OPENCLAW_WATCH_MODE = "1";
childEnv.OPENCLAW_WATCH_SESSION = watchSession;
if (deps.args.length > 0) {
childEnv.OPENCLAW_WATCH_COMMAND = deps.args.join(" ");
}
return await new Promise((resolve) => {
let settled = false;
let shuttingDown = false;
let restartRequested = false;
let watchProcess = null;
let onSigInt;
let onSigTerm;
const watcher = deps.createWatcher(deps.watchPaths, {
ignoreInitial: true,
ignored: (watchPath) => isIgnoredWatchPath(watchPath, deps.cwd),
});
const settle = (code) => {
if (settled) {
return;
}
settled = true;
if (onSigInt) {
deps.process.off("SIGINT", onSigInt);
}
if (onSigTerm) {
deps.process.off("SIGTERM", onSigTerm);
}
watcher.close?.().catch?.(() => {});
resolve(code);
};
const startRunner = () => {
watchProcess = deps.spawn(deps.process.execPath, buildRunnerArgs(deps.args), {
cwd: deps.cwd,
env: childEnv,
stdio: "inherit",
});
watchProcess.on("exit", () => {
watchProcess = null;
if (shuttingDown) {
return;
}
if (restartRequested) {
restartRequested = false;
startRunner();
}
});
};
const requestRestart = (changedPath) => {
if (shuttingDown || isIgnoredWatchPath(changedPath, deps.cwd)) {
return;
}
if (!watchProcess) {
startRunner();
return;
}
restartRequested = true;
if (typeof watchProcess.kill === "function") {
watchProcess.kill(WATCH_RESTART_SIGNAL);
}
};
watcher.on("add", requestRestart);
watcher.on("change", requestRestart);
watcher.on("unlink", requestRestart);
watcher.on("error", () => {
shuttingDown = true;
if (watchProcess && typeof watchProcess.kill === "function") {
watchProcess.kill(WATCH_RESTART_SIGNAL);
}
settle(1);
});
startRunner();
onSigInt = () => {
shuttingDown = true;
if (watchProcess && typeof watchProcess.kill === "function") {
watchProcess.kill(WATCH_RESTART_SIGNAL);
}
settle(130);
};
onSigTerm = () => {
shuttingDown = true;
if (watchProcess && typeof watchProcess.kill === "function") {
watchProcess.kill(WATCH_RESTART_SIGNAL);
}
settle(143);
};
deps.process.on("SIGINT", onSigInt);
deps.process.on("SIGTERM", onSigTerm);
});
}
if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
void runWatchMain()
.then((code) => process.exit(code))
.catch((err) => {
console.error(err);
process.exit(1);
});
}