mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 07:20:43 +00:00
fix: Add runner label to /status (#70595)
* Add runner label to status output * Add changelog entry for status runner label * Fix status runner detection and sanitization
This commit is contained in:
@@ -38,6 +38,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Codex harness/plugins: add a bundled-plugin Codex app-server extension seam for async `tool_result` middleware, fire `after_tool_call` for Codex tool runs, and route mirrored Codex transcript writes through `before_message_write` so tool integrations stop diverging from Pi. Thanks @vincentkoc.
|
||||
- Codex harness/hooks: fire `llm_input`, `llm_output`, and `agent_end` for native Codex app-server turns so lifecycle hooks stop drifting from Pi. Thanks @vincentkoc.
|
||||
- QA/Telegram: record per-scenario reply RTT in the live Telegram QA report and summary, starting with the canary response. (#70550) Thanks @obviyus.
|
||||
- Status: add an explicit `Runner:` field to `/status` so sessions now report whether they are running on embedded Pi, a CLI-backed provider, or an ACP harness agent/backend such as `codex (acp/acpx)` or `gemini (acp/acpx)`. (#70595)
|
||||
|
||||
### Fixes
|
||||
|
||||
|
||||
@@ -95,12 +95,115 @@ describe("buildStatusMessage", () => {
|
||||
expect(normalized).toContain("Session: agent:main:main");
|
||||
expect(normalized).toContain("updated 10m ago");
|
||||
expect(normalized).toContain("Runtime: direct");
|
||||
expect(normalized).toContain("Runner: pi (embedded)");
|
||||
expect(normalized).toContain("Think: medium");
|
||||
expect(normalized).not.toContain("verbose");
|
||||
expect(normalized).toContain("elevated");
|
||||
expect(normalized).toContain("Queue: collect");
|
||||
});
|
||||
|
||||
it("shows the CLI runner for CLI-backed providers", () => {
|
||||
const text = buildStatusMessage({
|
||||
config: {
|
||||
agents: {
|
||||
defaults: {
|
||||
cliBackends: {
|
||||
"claude-cli": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as unknown as OpenClawConfig,
|
||||
agent: {
|
||||
model: "claude-cli/opus",
|
||||
},
|
||||
sessionEntry: {
|
||||
sessionId: "cli",
|
||||
updatedAt: 0,
|
||||
modelProvider: "claude-cli",
|
||||
model: "opus",
|
||||
},
|
||||
sessionKey: "agent:main:main",
|
||||
queue: { mode: "collect", depth: 0 },
|
||||
});
|
||||
|
||||
expect(normalizeTestText(text)).toContain("Runner: claude-cli (cli)");
|
||||
});
|
||||
|
||||
it("falls back to the configured CLI provider when session provider fields are empty", () => {
|
||||
const text = buildStatusMessage({
|
||||
config: {
|
||||
agents: {
|
||||
defaults: {
|
||||
cliBackends: {
|
||||
"claude-cli": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as unknown as OpenClawConfig,
|
||||
agent: {
|
||||
model: "claude-cli/opus",
|
||||
},
|
||||
sessionEntry: {
|
||||
sessionId: "cli-default",
|
||||
updatedAt: 0,
|
||||
},
|
||||
sessionKey: "agent:main:main",
|
||||
queue: { mode: "collect", depth: 0 },
|
||||
});
|
||||
|
||||
expect(normalizeTestText(text)).toContain("Runner: claude-cli (cli)");
|
||||
});
|
||||
|
||||
it("shows the ACP harness agent and backend when ACP owns the session", () => {
|
||||
const text = buildStatusMessage({
|
||||
agent: {
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
},
|
||||
sessionEntry: {
|
||||
sessionId: "acp",
|
||||
updatedAt: 0,
|
||||
acp: {
|
||||
backend: "acpx",
|
||||
agent: "gemini",
|
||||
runtimeSessionName: "status-test",
|
||||
mode: "persistent",
|
||||
state: "idle",
|
||||
lastActivityAt: 0,
|
||||
},
|
||||
},
|
||||
sessionKey: "agent:main:main",
|
||||
queue: { mode: "collect", depth: 0 },
|
||||
});
|
||||
|
||||
expect(normalizeTestText(text)).toContain("Runner: gemini (acp/acpx)");
|
||||
});
|
||||
|
||||
it("sanitizes runner labels sourced from session metadata", () => {
|
||||
const text = buildStatusMessage({
|
||||
agent: {
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
},
|
||||
sessionEntry: {
|
||||
sessionId: "acp-sanitized",
|
||||
updatedAt: 0,
|
||||
acp: {
|
||||
backend: "acpx\nrewritten",
|
||||
agent: "gemini\u001b[2K",
|
||||
runtimeSessionName: "status-test",
|
||||
mode: "persistent",
|
||||
state: "idle",
|
||||
lastActivityAt: 0,
|
||||
},
|
||||
},
|
||||
sessionKey: "agent:main:main",
|
||||
queue: { mode: "collect", depth: 0 },
|
||||
});
|
||||
|
||||
const normalized = normalizeTestText(text);
|
||||
expect(normalized).toContain("Runner: gemini (acp/acpx\\nrewritten)");
|
||||
expect(normalized).not.toContain("\u001b");
|
||||
});
|
||||
|
||||
it("falls back to sessionEntry levels when resolved levels are not passed", () => {
|
||||
const text = buildStatusMessage({
|
||||
agent: {
|
||||
|
||||
@@ -4,6 +4,7 @@ import { DEFAULT_CONTEXT_TOKENS, DEFAULT_MODEL, DEFAULT_PROVIDER } from "../agen
|
||||
import { resolveModelAuthMode } from "../agents/model-auth.js";
|
||||
import {
|
||||
buildModelAliasIndex,
|
||||
isCliProvider,
|
||||
resolveConfiguredModelRef,
|
||||
resolveModelRefFromString,
|
||||
} from "../agents/model-selection.js";
|
||||
@@ -45,6 +46,7 @@ import {
|
||||
normalizeOptionalLowercaseString,
|
||||
normalizeOptionalString,
|
||||
} from "../shared/string-coerce.js";
|
||||
import { sanitizeTerminalText } from "../terminal/safe-text.js";
|
||||
import { resolveStatusTtsSnapshot } from "../tts/status-config.js";
|
||||
import {
|
||||
estimateUsageCost,
|
||||
@@ -193,6 +195,29 @@ function resolveRuntimeLabel(
|
||||
return `${runtime}/${sandboxMode}`;
|
||||
}
|
||||
|
||||
function resolveRunnerLabel(
|
||||
args: Pick<StatusArgs, "config" | "sessionEntry"> & { fallbackProvider?: string },
|
||||
): string {
|
||||
const acpAgentRaw = normalizeOptionalString(args.sessionEntry?.acp?.agent);
|
||||
const acpAgent = acpAgentRaw ? sanitizeTerminalText(acpAgentRaw) : undefined;
|
||||
if (acpAgent) {
|
||||
const backendRaw = normalizeOptionalString(args.sessionEntry?.acp?.backend);
|
||||
const backend = backendRaw ? sanitizeTerminalText(backendRaw) : undefined;
|
||||
return backend ? `${acpAgent} (acp/${backend})` : `${acpAgent} (acp)`;
|
||||
}
|
||||
|
||||
const providerRaw =
|
||||
normalizeOptionalString(args.sessionEntry?.modelProvider) ??
|
||||
normalizeOptionalString(args.sessionEntry?.providerOverride) ??
|
||||
normalizeOptionalString(args.fallbackProvider);
|
||||
const provider = providerRaw ? sanitizeTerminalText(providerRaw) : undefined;
|
||||
if (provider && isCliProvider(provider, args.config)) {
|
||||
return `${provider} (cli)`;
|
||||
}
|
||||
|
||||
return "pi (embedded)";
|
||||
}
|
||||
|
||||
const formatTokens = (total: number | null | undefined, contextTokens: number | null) => {
|
||||
const ctx = contextTokens ?? null;
|
||||
if (total == null) {
|
||||
@@ -658,6 +683,11 @@ export function buildStatusMessage(args: StatusArgs): string {
|
||||
"on";
|
||||
|
||||
const runtime = { label: resolveRuntimeLabel(args) };
|
||||
const runnerLabel = resolveRunnerLabel({
|
||||
config: args.config,
|
||||
sessionEntry: args.sessionEntry,
|
||||
fallbackProvider: activeProvider,
|
||||
});
|
||||
|
||||
const updatedAt = entry?.updatedAt;
|
||||
const sessionLine = [
|
||||
@@ -711,6 +741,7 @@ export function buildStatusMessage(args: StatusArgs): string {
|
||||
});
|
||||
const optionParts = [
|
||||
`Runtime: ${runtime.label}`,
|
||||
`Runner: ${runnerLabel}`,
|
||||
`Think: ${thinkLevel}`,
|
||||
formatFastModeLabel(fastMode),
|
||||
textVerbosity ? `Text: ${textVerbosity}` : null,
|
||||
|
||||
Reference in New Issue
Block a user