























@@ -134,6 +134,9 @@ const DEFAULT_SKILL_DIR = "~/.codex/skills/custom/telegram-e2e-bot-to-bot";
134134const DEFAULT_CONVEX_ENV_FILE = `${DEFAULT_SKILL_DIR}/convex.local.env`;
135135const DEFAULT_USER_DRIVER = "scripts/e2e/telegram-user-driver.py";
136136const DEFAULT_OUTPUT_ROOT = ".artifacts/qa-e2e/telegram-user-crabbox";
137+export const COMMAND_STDOUT_MAX_CHARS = 1024 * 1024;
138+export const COMMAND_STDERR_TAIL_CHARS = 256 * 1024;
139+export const COMMAND_FAILURE_STDOUT_TAIL_CHARS = 64 * 1024;
137140const REMOTE_ROOT = "/tmp/openclaw-telegram-user-crabbox";
138141const CREDENTIAL_SCRIPT = fileURLToPath(new URL("./telegram-user-credential.ts", import.meta.url));
139142const LOG_READY_TAIL_BYTES = readPositiveInt(
@@ -482,46 +485,115 @@ function shellQuote(value: string) {
482485return `'${value.replaceAll("'", "'\\''")}'`;
483486}
484487488+type AppendCommandStdoutResult = { ok: true; value: string } | { ok: false; message: string };
489+490+function appendCommandText(current: string, chunk: Buffer): string {
491+return current + chunk.toString("utf8");
492+}
493+494+export function appendCommandTextTail(current: string, chunk: Buffer, maxChars: number): string {
495+const next = appendCommandText(current, chunk);
496+return next.length > maxChars ? next.slice(-maxChars) : next;
497+}
498+499+export function appendCommandStdout(
500+current: string,
501+chunk: Buffer,
502+maxChars = COMMAND_STDOUT_MAX_CHARS,
503+): AppendCommandStdoutResult {
504+const next = appendCommandText(current, chunk);
505+if (next.length > maxChars) {
506+return { ok: false, message: `command stdout exceeded ${maxChars} characters` };
507+}
508+return { ok: true, value: next };
509+}
510+511+export function appendCommandStderrTail(
512+current: string,
513+chunk: Buffer,
514+maxChars = COMMAND_STDERR_TAIL_CHARS,
515+): string {
516+return appendCommandTextTail(current, chunk, maxChars);
517+}
518+519+function commandFailureOutput(stdout: string, stderr: string): string {
520+const stdoutTail =
521+stdout.length > COMMAND_FAILURE_STDOUT_TAIL_CHARS
522+ ? `\n[stdout truncated to last ${COMMAND_FAILURE_STDOUT_TAIL_CHARS} characters]\n${stdout.slice(
523+ -COMMAND_FAILURE_STDOUT_TAIL_CHARS,
524+ )}`
525+ : stdout;
526+return `${stdoutTail}${stderr}`;
527+}
528+485529function runCommand(params: {
486530args: string[];
487531command: string;
488532cwd: string;
489533env?: NodeJS.ProcessEnv;
534+outputFile?: string;
490535stdio?: "inherit" | "pipe";
491536stdin?: string;
492537}) {
493538return new Promise<CommandResult>((resolve, reject) => {
539+if (params.outputFile) {
540+fs.writeFileSync(params.outputFile, "");
541+}
494542 const child = spawn(params.command, params.args, {
495543cwd: params.cwd,
496544env: params.env ?? process.env,
497545stdio: ["pipe", "pipe", "pipe"],
498546});
499547 let stdout = "";
500548 let stderr = "";
549+ let stdoutLimitError: string | null = null;
501550 child.stdout.on("data", (chunk: Buffer) => {
502551const text = chunk.toString();
503-stdout += text;
552+if (params.outputFile) {
553+fs.appendFileSync(params.outputFile, text);
554+stdout = appendCommandTextTail(stdout, chunk, COMMAND_FAILURE_STDOUT_TAIL_CHARS);
555+} else if (params.stdio === "inherit") {
556+stdout = appendCommandTextTail(stdout, chunk, COMMAND_FAILURE_STDOUT_TAIL_CHARS);
557+} else {
558+const appended = appendCommandStdout(stdout, chunk);
559+if (!appended.ok) {
560+ stdoutLimitError = appended.message;
561+child.kill("SIGKILL");
562+} else {
563+ stdout = appended.value;
564+}
565+}
504566 if (params.stdio === "inherit") {
505567process.stdout.write(text);
506568}
507569});
508570child.stderr.on("data", (chunk: Buffer) => {
509571const text = chunk.toString();
510-stderr += text;
572+if (params.outputFile) {
573+fs.appendFileSync(params.outputFile, text);
574+}
575+ stderr = appendCommandStderrTail(stderr, chunk);
511576 if (params.stdio === "inherit") {
512577process.stderr.write(text);
513578}
514579});
515580child.on("error", reject);
516581child.on("close", (code, signal) => {
582+if (stdoutLimitError) {
583+reject(new Error(`${params.command} ${params.args.join(" ")} failed: ${stdoutLimitError}`));
584+return;
585+}
517586 if (code === 0) {
518587resolve({ stdout, stderr });
519588return;
520589}
521590 const detail = signal ? `signal ${signal}` : `exit code ${code ?? "unknown"}`;
522591 reject(
523592 new Error(
524-`${params.command} ${params.args.join(" ")} failed with ${detail}\n${stdout}${stderr}`,
593+ `${params.command} ${params.args.join(" ")} failed with ${detail}\n${commandFailureOutput(
594+stdout,
595+stderr,
596+)}`,
525597 ),
526598 );
527599});
@@ -1093,6 +1165,7 @@ async function runRemoteCommand(params: {
10931165args: string[];
10941166command: string;
10951167cwd: string;
1168+outputFile?: string;
10961169stdio?: "inherit" | "pipe";
10971170}) {
10981171let lastError: unknown;
@@ -1130,12 +1203,18 @@ async function scpFromRemote(root: string, inspect: CrabboxInspect, remote: stri
11301203});
11311204}
113212051133-async function sshRun(root: string, inspect: CrabboxInspect, remoteCommand: string) {
1206+async function sshRun(
1207+ root: string,
1208+ inspect: CrabboxInspect,
1209+ remoteCommand: string,
1210+ options: { outputFile?: string } = {},
1211+) {
11341212const ssh = sshArgs(inspect);
11351213return await runRemoteCommand({
11361214command: "ssh",
11371215args: [...ssh.base, ssh.target, remoteCommand],
11381216cwd: root,
1217+outputFile: options.outputFile,
11391218stdio: "inherit",
11401219});
11411220}
@@ -1810,12 +1889,11 @@ async function sendSessionProbe(root: string, opts: Options, outputDir: string)
18101889async function runSessionCommand(root: string, opts: Options, outputDir: string) {
18111890const { session } = readSession(root, opts, outputDir);
18121891 const command = opts.remoteCommand.map(shellQuote).join(" ");
1813-const result = await sshRun(root, session.crabbox.inspect, command);
18141892 const logPath = path.join(
18151893 session.outputDir,
18161894 `remote-command-${new Date().toISOString().replace(/[:.]/gu, "-")}.log`,
18171895 );
1818-fs.writeFileSync(logPath, `${result.stdout}${result.stderr}`);
1896+await sshRun(root, session.crabbox.inspect, command, { outputFile: logPath });
18191897 return { command: opts.remoteCommand, log: path.relative(root, logPath), status: "pass" };
18201898}
18211899@@ -1897,12 +1975,13 @@ async function viewSession(root: string, opts: Options, outputDir: string) {
18971975 throw new Error("view requires --message-id.");
18981976 }
18991977 const link = telegramPrivatePostLink(session.credential.groupId, messageId);
1900-const result = await sshRun(root, session.crabbox.inspect, renderProofViewCommand(link));
19011978 const logPath = path.join(
19021979 session.outputDir,
19031980 `proof-view-${new Date().toISOString().replace(/[:.]/gu, "-")}.log`,
19041981 );
1905-fs.writeFileSync(logPath, `${result.stdout}${result.stderr}`);
1982+ await sshRun(root, session.crabbox.inspect, renderProofViewCommand(link), {
1983+ outputFile: logPath,
1984+ });
19061985 return {
19071986 crop: TELEGRAM_PROOF_CROP,
19081987 geometry: TELEGRAM_PROOF_WINDOW,
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。