fix: single-line agent-server logs in dev:automation mode (#1178)

Switch the agent-server to LOG_JSON=true so the Python SDK emits one
JSON object per log record instead of Rich-formatted output.  Rich was
wrapping long lines across multiple entries and prepending its own
timestamp on top of the HH:MM:SS [agent-server] prefix that logService
already provides.

Add parseAgentServerLogLine() which turns each JSON record into a clean
single-line string:

  INFO     127.0.0.1:51658 - "GET /api/..." 200  h11_impl.py:481

Level-based ANSI colouring is also applied (dim for DEBUG, yellow for
WARNING, red for ERROR/CRITICAL, blue for INFO) so errors still stand
out.  Non-JSON lines (e.g. startup text) fall back to the original
yellow stderr / service-colour stdout behaviour unchanged.

Co-authored-by: openhands <openhands@all-hands.dev>
This commit is contained in:
Tim O'Farrell
2026-06-05 07:43:33 -06:00
committed by GitHub
co-authored by openhands
parent ca57e4ffff
commit 80e01e099d
+43 -2
View File
@@ -119,6 +119,38 @@ function logError(message) {
console.error(`${c.red}✗${c.reset} ${message}`);
}
/**
* Parse one JSON log line produced by the SDK's JsonFormatter and return a
* single-line human-readable string + an appropriate ANSI color.
*
* Returns null for non-JSON lines so callers can fall back to the raw text.
*
* @param {string} rawLine
* @returns {{ text: string; color: string } | null}
*/
function parseAgentServerLogLine(rawLine) {
try {
const obj = JSON.parse(rawLine);
if (!obj.levelname || obj.message === undefined) return null;
const level = obj.levelname.padEnd(8);
const location =
obj.filename && obj.lineno ? ` ${obj.filename}:${obj.lineno}` : "";
const text = `${level} ${obj.message}${location}`;
const lvl = obj.levelname;
const color =
lvl === "DEBUG"
? c.dim
: lvl === "WARNING"
? c.yellow
: lvl === "ERROR" || lvl === "CRITICAL"
? c.red
: c.blue;
return { text, color };
} catch {
return null;
}
}
// ═══════════════════════════════════════════════════════════════════════════
// Configuration
// ═══════════════════════════════════════════════════════════════════════════
@@ -521,6 +553,7 @@ function spawnService(name, command, args, options = {}) {
);
const color = options.color || c.reset;
const parseLogLine = options.parseLogLine;
proc.stdout.on("data", (data) => {
data
@@ -528,7 +561,8 @@ function spawnService(name, command, args, options = {}) {
.split("\n")
.filter(Boolean)
.forEach((line) => {
logService(name, line.trim(), color);
const parsed = parseLogLine ? parseLogLine(line.trim()) : null;
logService(name, parsed ? parsed.text : line.trim(), parsed ? parsed.color : color);
});
});
@@ -538,7 +572,8 @@ function spawnService(name, command, args, options = {}) {
.split("\n")
.filter(Boolean)
.forEach((line) => {
logService(name, line.trim(), c.yellow);
const parsed = parseLogLine ? parseLogLine(line.trim()) : null;
logService(name, parsed ? parsed.text : line.trim(), parsed ? parsed.color : c.yellow);
});
});
@@ -699,6 +734,11 @@ function startAgentServer(config) {
// Ensure the agent-server uses the resolved key from config. This is
// LOCAL_BACKEND_API_KEY when set, or the auto-generated persisted key.
OH_SESSION_API_KEYS_0: config.sessionApiKey,
// Emit structured JSON log lines instead of Rich-formatted output.
// Rich wraps long messages across multiple lines and prepends its own
// timestamp; LOG_JSON=true produces one JSON object per record which
// parseAgentServerLogLine re-formats into a clean single-line entry.
LOG_JSON: "true",
};
spawnService(
@@ -715,6 +755,7 @@ function startAgentServer(config) {
cwd: safeConfig.workspacesPath,
env: agentServerEnv,
color: c.blue,
parseLogLine: parseAgentServerLogLine,
},
);
}