


















@@ -21,6 +21,10 @@ const RPC_READY_TIMEOUT_MS = readPositiveInt(
2121process.env.OPENCLAW_BUNDLED_PLUGIN_RUNTIME_RPC_READY_MS,
2222210000,
2323);
24+const COMMAND_TIMEOUT_MS = readPositiveInt(
25+process.env.OPENCLAW_BUNDLED_PLUGIN_RUNTIME_COMMAND_MS,
26+120000,
27+);
24282529function readPositiveInt(raw, fallback) {
2630const parsed = Number.parseInt(String(raw || ""), 10);
@@ -38,9 +42,7 @@ function writeJson(file, value) {
38423943function manifestPath(pluginDir, pluginRoot) {
4044const candidates = [
41- ...(isNonEmptyString(pluginRoot)
42- ? [path.join(pluginRoot, "openclaw.plugin.json")]
43- : []),
45+ ...(isNonEmptyString(pluginRoot) ? [path.join(pluginRoot, "openclaw.plugin.json")] : []),
4446path.join(process.cwd(), "dist", "extensions", pluginDir, "openclaw.plugin.json"),
4547path.join(process.cwd(), "dist-runtime", "extensions", pluginDir, "openclaw.plugin.json"),
4648];
@@ -161,22 +163,47 @@ function formatCapturedOutput(label, buffer) {
161163return `${prefix}${buffer.text}`;
162164}
163165164-function runCommand(command, args, options = {}) {
166+export function runCommand(command, args, options = {}) {
165167return new Promise((resolve, reject) => {
168+const { timeoutMs = COMMAND_TIMEOUT_MS, ...spawnOptions } = options;
166169const child = childProcess.spawn(command, args, {
167170stdio: ["ignore", "pipe", "pipe"],
168- ...options,
171+ ...spawnOptions,
169172});
170173let stdout = { text: "", truncatedChars: 0 };
171174let stderr = { text: "", truncatedChars: 0 };
175+let timedOut = false;
176+let settled = false;
172177child.stdout?.on("data", (chunk) => {
173178stdout = appendBoundedOutput(stdout, chunk);
174179});
175180child.stderr?.on("data", (chunk) => {
176181stderr = appendBoundedOutput(stderr, chunk);
177182});
178-child.on("error", reject);
183+const clearCommandTimer = timeoutMs
184+ ? setTimeout(() => {
185+timedOut = true;
186+child.kill("SIGKILL");
187+}, timeoutMs)
188+ : undefined;
189+child.on("error", (error) => {
190+if (settled) {
191+return;
192+}
193+settled = true;
194+if (clearCommandTimer) {
195+clearTimeout(clearCommandTimer);
196+}
197+reject(error);
198+});
179199child.on("close", (status, signal) => {
200+if (settled) {
201+return;
202+}
203+settled = true;
204+if (clearCommandTimer) {
205+clearTimeout(clearCommandTimer);
206+}
180207if (status === 0) {
181208resolve({
182209stdout: stdout.text,
@@ -193,11 +220,10 @@ function runCommand(command, args, options = {}) {
193220.filter(Boolean)
194221.join("\n")
195222.trim();
196-reject(
197-new Error(
198-`${command} ${args.join(" ")} failed with ${signal || status}${detail ? `\n${detail}` : ""}`,
199-),
200-);
223+const outcome = timedOut
224+ ? `timed out after ${timeoutMs}ms`
225+ : `failed with ${signal || status}`;
226+reject(new Error(`${command} ${args.join(" ")} ${outcome}${detail ? `\n${detail}` : ""}`));
201227});
202228});
203229}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。