























@@ -682,10 +682,88 @@ const logRunner = (message, deps) => {
682682return;
683683}
684684const line = `[openclaw] ${message}\n`;
685+deps.runNodeProgress?.clearLine();
685686deps.stderr.write(line);
687+deps.runNodeProgress?.render();
686688deps.outputTee?.write(line);
687689};
688690691+const RUN_NODE_PROGRESS_FRAMES = ["-", "\\", "|", "/"];
692+693+const shouldUseRunNodeProgress = (deps) =>
694+deps.stderr?.isTTY === true &&
695+deps.env.OPENCLAW_RUNNER_PROGRESS !== "0" &&
696+deps.env.CI !== "true" &&
697+!deps.outputTee;
698+699+const createRunNodeProgress = (label, deps) => {
700+if (!shouldUseRunNodeProgress(deps)) {
701+return null;
702+}
703+const startedAt = Date.now();
704+let frameIndex = 0;
705+let active = true;
706+let visible = false;
707+708+const clearLine = () => {
709+if (!visible) {
710+return;
711+}
712+deps.stderr.write("\r\x1b[2K");
713+visible = false;
714+};
715+const render = () => {
716+if (!active) {
717+return;
718+}
719+const elapsedSeconds = Math.max(0, Math.round((Date.now() - startedAt) / 1000));
720+const frame = RUN_NODE_PROGRESS_FRAMES[frameIndex % RUN_NODE_PROGRESS_FRAMES.length];
721+frameIndex += 1;
722+deps.stderr.write(`\r[openclaw] ${frame} ${label} (${elapsedSeconds}s)`);
723+visible = true;
724+};
725+const timer = setInterval(render, 120);
726+timer.unref?.();
727+render();
728+729+return {
730+ clearLine,
731+ render,
732+stop() {
733+if (!active) {
734+return;
735+}
736+active = false;
737+clearInterval(timer);
738+clearLine();
739+},
740+};
741+};
742+743+const withRunNodeProgress = async (deps, label, callback) => {
744+const previousProgress = deps.runNodeProgress;
745+const progress = createRunNodeProgress(label, deps);
746+if (progress) {
747+deps.runNodeProgress = progress;
748+}
749+try {
750+return await callback();
751+} finally {
752+if (progress) {
753+progress.stop();
754+deps.runNodeProgress = previousProgress;
755+}
756+}
757+};
758+759+const writeRunnerStream = (deps, stream, chunk) => {
760+deps.runNodeProgress?.clearLine();
761+stream.write(chunk);
762+deps.runNodeProgress?.render();
763+};
764+765+const shouldPipeSpawnedOutput = (deps) => Boolean(deps.outputTee || deps.runNodeProgress);
766+689767const sanitizeCpuProfileNamePart = (value) => {
690768const normalized = String(value ?? "")
691769.trim()
@@ -810,24 +888,26 @@ const runOpenClaw = async (deps) => {
810888};
811889812890const pipeSpawnedOutput = (childProcess, deps) => {
813-if (!deps.outputTee) {
891+if (!shouldPipeSpawnedOutput(deps)) {
814892return;
815893}
816894const stderrFilter =
817895deps.env[RUN_NODE_FILTER_SYNC_IO_STDERR_ENV] === "1"
818896 ? createSyncIoTraceStderrFilter(deps)
819897 : null;
820898childProcess.stdout?.on("data", (chunk) => {
821-deps.stdout.write(chunk);
822-deps.outputTee.write(chunk);
899+writeRunnerStream(deps, deps.stdout, chunk);
900+deps.outputTee?.write(chunk);
823901});
824902childProcess.stderr?.on("data", (chunk) => {
903+deps.runNodeProgress?.clearLine();
825904if (stderrFilter) {
826905stderrFilter.write(chunk);
827906} else {
828907deps.stderr.write(chunk);
829908}
830-deps.outputTee.write(chunk);
909+deps.runNodeProgress?.render();
910+deps.outputTee?.write(chunk);
831911});
832912childProcess.stderr?.on("end", () => {
833913stderrFilter?.flush();
@@ -1253,36 +1333,45 @@ export async function runNodeMain(params = {}) {
12531333);
12541334logRunner("Building bundled plugin assets.", deps);
12551335const buildCmd = deps.execPath;
1256-const assetBuild = deps.spawn(buildCmd, bundledPluginAssetBuildArgs, {
1257-cwd: deps.cwd,
1258-env: deps.env,
1259-stdio: deps.outputTee ? ["inherit", "pipe", "pipe"] : "inherit",
1260-});
1261-pipeSpawnedOutput(assetBuild, deps);
1262-const assetBuildRes = await waitForSpawnedProcess(assetBuild, deps);
1263-const assetBuildInterruptedExitCode = getInterruptedSpawnExitCode(assetBuildRes);
1264-if (assetBuildInterruptedExitCode !== null) {
1265-return assetBuildInterruptedExitCode;
1266-}
1267-if (assetBuildRes.exitCode !== 0 && assetBuildRes.exitCode !== null) {
1268-return assetBuildRes.exitCode;
1269-}
1336+const compileExitCode = await withRunNodeProgress(
1337+deps,
1338+"Building local CLI artifacts",
1339+async () => {
1340+const assetBuild = deps.spawn(buildCmd, bundledPluginAssetBuildArgs, {
1341+cwd: deps.cwd,
1342+env: deps.env,
1343+stdio: shouldPipeSpawnedOutput(deps) ? ["inherit", "pipe", "pipe"] : "inherit",
1344+});
1345+pipeSpawnedOutput(assetBuild, deps);
1346+const assetBuildRes = await waitForSpawnedProcess(assetBuild, deps);
1347+const assetBuildInterruptedExitCode = getInterruptedSpawnExitCode(assetBuildRes);
1348+if (assetBuildInterruptedExitCode !== null) {
1349+return assetBuildInterruptedExitCode;
1350+}
1351+if (assetBuildRes.exitCode !== 0 && assetBuildRes.exitCode !== null) {
1352+return assetBuildRes.exitCode;
1353+}
127013541271-const buildArgs = compilerArgs;
1272-const build = deps.spawn(buildCmd, buildArgs, {
1273-cwd: deps.cwd,
1274-env: deps.env,
1275-stdio: deps.outputTee ? ["inherit", "pipe", "pipe"] : "inherit",
1276-});
1277-pipeSpawnedOutput(build, deps);
1355+const build = deps.spawn(buildCmd, compilerArgs, {
1356+cwd: deps.cwd,
1357+env: deps.env,
1358+stdio: shouldPipeSpawnedOutput(deps) ? ["inherit", "pipe", "pipe"] : "inherit",
1359+});
1360+pipeSpawnedOutput(build, deps);
127813611279-const buildRes = await waitForSpawnedProcess(build, deps);
1280-const interruptedExitCode = getInterruptedSpawnExitCode(buildRes);
1281-if (interruptedExitCode !== null) {
1282-return interruptedExitCode;
1283-}
1284-if (buildRes.exitCode !== 0 && buildRes.exitCode !== null) {
1285-return buildRes.exitCode;
1362+const buildRes = await waitForSpawnedProcess(build, deps);
1363+const interruptedExitCode = getInterruptedSpawnExitCode(buildRes);
1364+if (interruptedExitCode !== null) {
1365+return interruptedExitCode;
1366+}
1367+if (buildRes.exitCode !== 0 && buildRes.exitCode !== null) {
1368+return buildRes.exitCode;
1369+}
1370+return 0;
1371+},
1372+);
1373+if (compileExitCode !== 0) {
1374+return compileExitCode;
12861375}
12871376if (!(await syncRuntimeArtifacts(deps))) {
12881377return 1;
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。