mirror of
https://github.com/penpot/penpot.git
synced 2026-10-28 21:40:34 +00:00
🐛 Log errors readably in MCP execute_code console output (#11930)
* 🐛 Log errors readably in MCP execute_code console output The console captured during execute_code rendered every object argument with JSON.stringify. An Error has no enumerable fields, so logging a caught error printed "{}" and the agent lost the message. A circular object or a BigInt made JSON.stringify throw, so a console.log call aborted the executed code with an unrelated serialization error. Format console arguments in a small pure module: errors by their string form (name and message), other objects as indented JSON, falling back to the string form when JSON cannot serialize the value. AI-assisted-by: claude-opus-5.5 Signed-off-by: breken-ai <312387581+breken-ai@users.noreply.github.com> * ♻️ Keep console formatting within ExecuteCodeTaskConsole --------- Signed-off-by: breken-ai <312387581+breken-ai@users.noreply.github.com> Co-authored-by: breken-ai <312387581+breken-ai@users.noreply.github.com>
This commit is contained in:
parent
6f33d8d9d8
commit
fa5e5b6176
23
mcp/packages/plugin/src/ExecuteCodeTaskConsole.test.ts
Normal file
23
mcp/packages/plugin/src/ExecuteCodeTaskConsole.test.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
import { ExecuteCodeTaskConsole } from "./task-handlers/ExecuteCodeTaskHandler.ts";
|
||||
|
||||
test("formats plain objects and primitives", () => {
|
||||
const console = new ExecuteCodeTaskConsole();
|
||||
console.log({ a: 1 }, null, "text", 42, undefined);
|
||||
assert.equal(console.getLog(), '[LOG] {\n "a": 1\n} null text 42 undefined\n');
|
||||
});
|
||||
|
||||
test("keeps the name and message of a logged error", () => {
|
||||
const console = new ExecuteCodeTaskConsole();
|
||||
console.error("failed", new TypeError("shape is undefined"));
|
||||
assert.equal(console.getLog(), "[ERROR] failed TypeError: shape is undefined\n");
|
||||
});
|
||||
|
||||
test("does not throw for values JSON cannot serialize", () => {
|
||||
const console = new ExecuteCodeTaskConsole();
|
||||
const circular: { self?: unknown } = {};
|
||||
circular.self = circular;
|
||||
console.log(circular, { big: 1n });
|
||||
assert.equal(console.getLog(), "[LOG] [object Object] [object Object]\n");
|
||||
});
|
||||
@ -8,7 +8,7 @@ import { PenpotUtils } from "../PenpotUtils.ts";
|
||||
* Provides the same interface as the native console object but appends
|
||||
* all output to an internal log string that can be retrieved.
|
||||
*/
|
||||
class ExecuteCodeTaskConsole {
|
||||
export class ExecuteCodeTaskConsole {
|
||||
/**
|
||||
* Accumulated log output from all console method calls.
|
||||
*/
|
||||
@ -30,15 +30,32 @@ class ExecuteCodeTaskConsole {
|
||||
return this.logOutput;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats one argument of a captured console call.
|
||||
* Errors have no enumerable fields, and values such as circular objects
|
||||
* cannot be serialized to JSON, so fall back to their string form.
|
||||
*/
|
||||
private formatLogArgument(arg: unknown): string {
|
||||
if (arg instanceof Error) {
|
||||
return String(arg);
|
||||
}
|
||||
if (typeof arg === "object") {
|
||||
try {
|
||||
return JSON.stringify(arg, null, 2);
|
||||
} catch {
|
||||
return String(arg);
|
||||
}
|
||||
}
|
||||
return String(arg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a formatted message to the log output.
|
||||
* @param level - Log level prefix (e.g., "LOG", "WARN", "ERROR")
|
||||
* @param args - Arguments to log, will be stringified and joined
|
||||
*/
|
||||
private appendToLog(level: string, ...args: any[]): void {
|
||||
const message = args
|
||||
.map((arg) => (typeof arg === "object" ? JSON.stringify(arg, null, 2) : String(arg)))
|
||||
.join(" ");
|
||||
const message = args.map((arg) => this.formatLogArgument(arg)).join(" ");
|
||||
this.logOutput += `[${level}] ${message}\n`;
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user