





















@@ -4,8 +4,13 @@ import os from "node:os";
44import path from "node:path";
55import process from "node:process";
66import { setTimeout as delay } from "node:timers/promises";
7+import { fileURLToPath } from "node:url";
7889const TOKEN = "bundled-plugin-runtime-smoke-token";
10+const OUTPUT_CAPTURE_CHARS = readPositiveInt(
11+process.env.OPENCLAW_BUNDLED_PLUGIN_RUNTIME_OUTPUT_CHARS,
12+1024 * 1024,
13+);
914const WATCHDOG_MS = readPositiveInt(process.env.OPENCLAW_BUNDLED_PLUGIN_RUNTIME_WATCHDOG_MS, 1000);
1015const READY_TIMEOUT_MS = readPositiveInt(
1116process.env.OPENCLAW_BUNDLED_PLUGIN_RUNTIME_READY_MS,
@@ -136,27 +141,58 @@ function isNonEmptyString(value) {
136141return typeof value === "string" && value.trim().length > 0;
137142}
138143144+export function appendBoundedOutput(buffer, chunk, maxChars = OUTPUT_CAPTURE_CHARS) {
145+const nextText = buffer.text + String(chunk);
146+if (nextText.length <= maxChars) {
147+return { text: nextText, truncatedChars: buffer.truncatedChars };
148+}
149+const truncatedChars = buffer.truncatedChars + nextText.length - maxChars;
150+return { text: nextText.slice(-maxChars), truncatedChars };
151+}
152+153+function formatCapturedOutput(label, buffer) {
154+if (!buffer.text) {
155+return "";
156+}
157+const prefix =
158+buffer.truncatedChars > 0
159+ ? `[${label} truncated ${buffer.truncatedChars} chars; showing tail]\n`
160+ : "";
161+return `${prefix}${buffer.text}`;
162+}
163+139164function runCommand(command, args, options = {}) {
140165return new Promise((resolve, reject) => {
141166const child = childProcess.spawn(command, args, {
142167stdio: ["ignore", "pipe", "pipe"],
143168 ...options,
144169});
145-let stdout = "";
146-let stderr = "";
170+let stdout = { text: "", truncatedChars: 0 };
171+let stderr = { text: "", truncatedChars: 0 };
147172child.stdout?.on("data", (chunk) => {
148-stdout += String(chunk);
173+stdout = appendBoundedOutput(stdout, chunk);
149174});
150175child.stderr?.on("data", (chunk) => {
151-stderr += String(chunk);
176+stderr = appendBoundedOutput(stderr, chunk);
152177});
153178child.on("error", reject);
154179child.on("close", (status, signal) => {
155180if (status === 0) {
156-resolve({ stdout, stderr });
181+resolve({
182+stdout: stdout.text,
183+stderr: stderr.text,
184+stdoutTruncatedChars: stdout.truncatedChars,
185+stderrTruncatedChars: stderr.truncatedChars,
186+});
157187return;
158188}
159-const detail = [stdout, stderr].filter(Boolean).join("\n").trim();
189+const detail = [
190+formatCapturedOutput("stdout", stdout),
191+formatCapturedOutput("stderr", stderr),
192+]
193+.filter(Boolean)
194+.join("\n")
195+.trim();
160196reject(
161197new Error(
162198`${command} ${args.join(" ")} failed with ${signal || status}${detail ? `\n${detail}` : ""}`,
@@ -726,7 +762,7 @@ async function smokeOpenAiTts(pluginIndex) {
726762}
727763}
728764729-function createIsolatedStateEnv(label) {
765+export function createIsolatedStateEnv(label) {
730766const root = fs.mkdtempSync(path.join(os.tmpdir(), `openclaw-${label}-`));
731767const home = path.join(root, "home");
732768const stateDir = path.join(home, ".openclaw");
@@ -735,7 +771,8 @@ function createIsolatedStateEnv(label) {
735771return {
736772 ...process.env,
737773HOME: home,
738-OPENCLAW_HOME: stateDir,
774+USERPROFILE: home,
775+OPENCLAW_HOME: home,
739776OPENCLAW_STATE_DIR: stateDir,
740777OPENCLAW_CONFIG_PATH: configPath,
741778};
@@ -752,16 +789,22 @@ function tailText(text) {
752789return text.split(/\r?\n/u).slice(-120).join("\n");
753790}
754791755-const [command, pluginId, pluginDir, requiresConfigRaw, pluginIndexRaw, pluginRoot, provider] =
756-process.argv.slice(2);
757-const pluginIndex = Number.parseInt(pluginIndexRaw || "0", 10);
792+export async function main(argv = process.argv.slice(2)) {
793+const [command, pluginId, pluginDir, requiresConfigRaw, pluginIndexRaw, pluginRoot, provider] =
794+argv;
795+const pluginIndex = Number.parseInt(pluginIndexRaw || "0", 10);
796+797+if (command === "plugin") {
798+await smokePlugin(pluginId, pluginDir, requiresConfigRaw === "1", pluginIndex, pluginRoot);
799+} else if (command === "tts-global-disable") {
800+await smokeTtsGlobalDisable(pluginId, pluginDir, provider, pluginIndex, pluginRoot);
801+} else if (command === "tts-openai-live") {
802+await smokeOpenAiTts(pluginIndex);
803+} else {
804+throw new Error(`Unknown runtime smoke command: ${command || "(missing)"}`);
805+}
806+}
758807759-if (command === "plugin") {
760-await smokePlugin(pluginId, pluginDir, requiresConfigRaw === "1", pluginIndex, pluginRoot);
761-} else if (command === "tts-global-disable") {
762-await smokeTtsGlobalDisable(pluginId, pluginDir, provider, pluginIndex, pluginRoot);
763-} else if (command === "tts-openai-live") {
764-await smokeOpenAiTts(pluginIndex);
765-} else {
766-throw new Error(`Unknown runtime smoke command: ${command || "(missing)"}`);
808+if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
809+await main();
767810}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。