Skip to content

Commit 0b1238b

Browse files
committed
fix(agent): extract resolveAgentRunLifecycleEndLogLevel, use subsystem logger, add test coverage
1 parent 56aea04 commit 0b1238b

2 files changed

Lines changed: 82 additions & 7 deletions

File tree

src/agents/agent-command.ingress-diagnostics.test.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,3 +299,52 @@ describe("emitIngressModelUsageDiagnostic", () => {
299299
expect(event.context).toEqual({ limit: 128000 });
300300
});
301301
});
302+
303+
// ---------------------------------------------------------------------------
304+
// resolveAgentRunLifecycleEndLogLevel
305+
// ---------------------------------------------------------------------------
306+
describe("resolveAgentRunLifecycleEndLogLevel", () => {
307+
/**
308+
* Constructs a minimal AgentRunTerminalOutcome for log-level testing.
309+
* All fields beyond `reason` and `status` are optional at the type level;
310+
* omitting them keeps each case focused on the reason.
311+
*/
312+
function outcome(reason: string, status: "ok" | "error" | "timeout" = "error") {
313+
return testing.resolveAgentRunLifecycleEndLogLevel({
314+
reason,
315+
status,
316+
} as Parameters<typeof testing.resolveAgentRunLifecycleEndLogLevel>[0]);
317+
}
318+
319+
it("returns info for completed", () => {
320+
expect(outcome("completed", "ok")).toBe("info");
321+
});
322+
323+
it("returns warn for cancelled", () => {
324+
expect(outcome("cancelled")).toBe("warn");
325+
});
326+
327+
it("returns warn for timed_out", () => {
328+
expect(outcome("timed_out", "timeout")).toBe("warn");
329+
});
330+
331+
it("returns warn for hard_timeout", () => {
332+
expect(outcome("hard_timeout", "timeout")).toBe("warn");
333+
});
334+
335+
it("returns error for failed", () => {
336+
expect(outcome("failed")).toBe("error");
337+
});
338+
339+
it("returns error for aborted", () => {
340+
expect(outcome("aborted")).toBe("error");
341+
});
342+
343+
it("returns error for blocked", () => {
344+
expect(outcome("blocked")).toBe("error");
345+
});
346+
347+
it("returns error for abandoned", () => {
348+
expect(outcome("abandoned")).toBe("error");
349+
});
350+
});

src/agents/agent-command.ts

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,10 @@ import {
7373
resolveMessageChannel,
7474
} from "../utils/message-channel.js";
7575
import { estimateUsageCost, resolveModelCostConfig } from "../utils/usage-format.js";
76-
import { buildAgentRunTerminalOutcome } from "./agent-run-terminal-outcome.js";
76+
import {
77+
buildAgentRunTerminalOutcome,
78+
type AgentRunTerminalOutcome,
79+
} from "./agent-run-terminal-outcome.js";
7780
import { resolveAgentRuntimeConfig } from "./agent-runtime-config.js";
7881
import {
7982
clearAutoFallbackPrimaryProbeSelection,
@@ -154,6 +157,27 @@ import { ensureAgentWorkspace } from "./workspace.js";
154157

155158
const log = createSubsystemLogger("agents/agent-command");
156159

160+
/**
161+
* Maps a normalized terminal outcome to a lifecycle-end log level.
162+
*
163+
* Completed runs are informational; timeouts and cancellations are warnings;
164+
* hard failures (including aborted, blocked, and abandoned liveness states)
165+
* remain at error level so they surface in monitoring.
166+
*/
167+
function resolveAgentRunLifecycleEndLogLevel(
168+
outcome: AgentRunTerminalOutcome,
169+
): "info" | "warn" | "error" {
170+
if (outcome.reason === "completed") return "info";
171+
if (
172+
outcome.reason === "cancelled" ||
173+
outcome.reason === "timed_out" ||
174+
outcome.reason === "hard_timeout"
175+
) {
176+
return "warn";
177+
}
178+
return "error";
179+
}
180+
157181
function hasExactConfiguredProviderModel(params: {
158182
cfg: OpenClawConfig;
159183
provider: string;
@@ -1888,12 +1912,13 @@ async function agentCommandInternal(
18881912
? { providerStarted: runResult.meta.providerStarted }
18891913
: {}),
18901914
});
1891-
const logMsg = `[agent] run ${runId} ended with stopReason=${stopReason}`;
1892-
if (outcome.reason === "completed" || outcome.reason === "cancelled") {
1893-
console.info(logMsg);
1894-
} else {
1895-
console.warn(logMsg);
1896-
}
1915+
const level = resolveAgentRunLifecycleEndLogLevel(outcome);
1916+
log[level](`run ${runId} ended with stopReason=${stopReason}`, {
1917+
runId,
1918+
stopReason,
1919+
reason: outcome.reason,
1920+
status: outcome.status,
1921+
});
18971922
}
18981923
emitAgentEvent({
18991924
runId,
@@ -2756,6 +2781,7 @@ export const testing = {
27562781
resolveExplicitAgentCommandSessionKey,
27572782
ingressDiagnosticChannel,
27582783
emitIngressModelUsageDiagnostic,
2784+
resolveAgentRunLifecycleEndLogLevel,
27592785
};
27602786

27612787
/** @deprecated Use `testing`. */

0 commit comments

Comments
 (0)