mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:20:43 +00:00
fix(sessions): show runtime in sessions table
This commit is contained in:
@@ -7,6 +7,7 @@ Docs: https://docs.openclaw.ai
|
||||
### Changes
|
||||
|
||||
- PR triage: mark external pull requests with `proof: supplied` when Barnacle finds structured real behavior proof, keep stale negative proof labels in sync across CRLF-edited PR bodies, and let ClawSweeper own the stronger `proof: sufficient` judgement.
|
||||
- Sessions CLI: show the selected agent runtime in the `openclaw sessions` table so terminal output matches the runtime visibility already present in JSON/status surfaces. Thanks @vincentkoc.
|
||||
- Google Meet/Voice Call: make Twilio dial-in joins speak through the realtime Gemini voice bridge with paced audio streaming, backpressure-aware buffering, barge-in queue clearing, same-session agent consult routing, duplicate-consult coalescing, and no TwiML fallback during realtime speech, giving Meet participants a much snappier OpenClaw voice agent. (#77064) Thanks @scoootscooob.
|
||||
- Voice Call/realtime: add opt-in OpenClaw agent voice context capsules and consult-cadence guidance so Gemini/OpenAI realtime calls can sound like the configured agent without consulting the full agent on every ordinary turn. Thanks @scoootscooob.
|
||||
- Docker/Gateway: harden the gateway container by dropping `NET_RAW` and `NET_ADMIN` capabilities and enabling `no-new-privileges` in the bundled `docker-compose.yml`. Thanks @VintageAyu.
|
||||
|
||||
@@ -3,7 +3,9 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
makeRuntime,
|
||||
mockSessionsConfig,
|
||||
resetMockSessionsConfig,
|
||||
runSessionsJson,
|
||||
setMockSessionsConfig,
|
||||
writeStore,
|
||||
} from "./sessions.test-helpers.js";
|
||||
|
||||
@@ -21,6 +23,7 @@ describe("sessionsCommand", () => {
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
resetMockSessionsConfig();
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
@@ -51,6 +54,75 @@ describe("sessionsCommand", () => {
|
||||
expect(row).toContain("pi:opus");
|
||||
});
|
||||
|
||||
it("renders the agent runtime in the tabular view", async () => {
|
||||
setMockSessionsConfig(() => ({
|
||||
agents: {
|
||||
defaults: {
|
||||
agentRuntime: { id: "claude-cli" },
|
||||
model: { primary: "anthropic/claude-opus-4-7" },
|
||||
models: { "anthropic/claude-opus-4-7": {} },
|
||||
contextTokens: 200_000,
|
||||
},
|
||||
},
|
||||
}));
|
||||
const store = writeStore(
|
||||
{
|
||||
"agent:main:main": {
|
||||
sessionId: "main-session",
|
||||
updatedAt: Date.now() - 60_000,
|
||||
modelProvider: "claude-cli",
|
||||
model: "claude-opus-4-7",
|
||||
},
|
||||
},
|
||||
"sessions-runtime-table",
|
||||
);
|
||||
|
||||
const { runtime, logs } = makeRuntime();
|
||||
await sessionsCommand({ store }, runtime);
|
||||
|
||||
fs.rmSync(store);
|
||||
|
||||
const tableHeader = logs.find((line) => line.includes("Runtime"));
|
||||
expect(tableHeader).toBeTruthy();
|
||||
|
||||
const row = logs.find((line) => line.includes("agent:main:main")) ?? "";
|
||||
expect(row).toContain("claude-opus-4-7");
|
||||
expect(row).toContain("Claude CLI");
|
||||
});
|
||||
|
||||
it("renders configured CLI runtime when the session stores a canonical provider", async () => {
|
||||
setMockSessionsConfig(() => ({
|
||||
agents: {
|
||||
defaults: {
|
||||
agentRuntime: { id: "claude-cli" },
|
||||
model: { primary: "anthropic/claude-opus-4-7" },
|
||||
models: { "anthropic/claude-opus-4-7": {} },
|
||||
contextTokens: 200_000,
|
||||
},
|
||||
},
|
||||
}));
|
||||
const store = writeStore(
|
||||
{
|
||||
"agent:main:main": {
|
||||
sessionId: "main-session",
|
||||
updatedAt: Date.now() - 60_000,
|
||||
modelProvider: "anthropic",
|
||||
model: "claude-opus-4-7",
|
||||
},
|
||||
},
|
||||
"sessions-runtime-canonical-provider",
|
||||
);
|
||||
|
||||
const { runtime, logs } = makeRuntime();
|
||||
await sessionsCommand({ store }, runtime);
|
||||
|
||||
fs.rmSync(store);
|
||||
|
||||
const row = logs.find((line) => line.includes("agent:main:main")) ?? "";
|
||||
expect(row).toContain("claude-opus-4-7");
|
||||
expect(row).toContain("Claude CLI");
|
||||
});
|
||||
|
||||
it("shows placeholder rows when tokens are missing", async () => {
|
||||
const store = writeStore({
|
||||
"quietchat:group:demo": {
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
import { resolveAgentRuntimeMetadata } from "../agents/agent-runtime-metadata.js";
|
||||
import { DEFAULT_CONTEXT_TOKENS } from "../agents/defaults.js";
|
||||
import { selectAgentHarness } from "../agents/harness/selection.js";
|
||||
import { getRuntimeConfig } from "../config/config.js";
|
||||
import { loadSessionStore, resolveSessionTotalTokens } from "../config/sessions.js";
|
||||
import type { SessionEntry } from "../config/sessions/types.js";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import { info } from "../globals.js";
|
||||
import { parseAgentSessionKey } from "../routing/session-key.js";
|
||||
import { type RuntimeEnv, writeRuntimeJson } from "../runtime.js";
|
||||
import { isCronSessionKey } from "../sessions/session-key-utils.js";
|
||||
import { createLazyImportLoader } from "../shared/lazy-promise.js";
|
||||
import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js";
|
||||
import { resolveAgentRuntimeLabel } from "../status/agent-runtime-label.js";
|
||||
import { isRich, theme } from "../terminal/theme.js";
|
||||
import { resolveSessionStoreTargetsOrExit } from "./session-store-targets.js";
|
||||
import {
|
||||
@@ -30,10 +35,12 @@ type SessionRow = SessionDisplayRow & {
|
||||
agentId: string;
|
||||
kind: "cron" | "direct" | "group" | "global" | "unknown";
|
||||
agentRuntime: ReturnType<typeof resolveAgentRuntimeMetadata>;
|
||||
runtimeLabel: string;
|
||||
};
|
||||
|
||||
const AGENT_PAD = 10;
|
||||
const KIND_PAD = 6;
|
||||
const RUNTIME_PAD = 18;
|
||||
const TOKENS_PAD = 20;
|
||||
const DEFAULT_SESSIONS_LIMIT = 100;
|
||||
const TOP_N_SELECTION_LIMIT = 200;
|
||||
@@ -162,6 +169,64 @@ const formatKindCell = (kind: SessionRow["kind"], rich: boolean) => {
|
||||
return theme.muted(label);
|
||||
};
|
||||
|
||||
function resolveSessionRuntimeLabel(params: {
|
||||
cfg: OpenClawConfig;
|
||||
entry: SessionEntry;
|
||||
agentRuntime: ReturnType<typeof resolveAgentRuntimeMetadata>;
|
||||
modelProvider: string;
|
||||
model: string;
|
||||
agentId: string;
|
||||
sessionKey: string;
|
||||
}): string {
|
||||
const explicitRuntime =
|
||||
normalizeOptionalLowercaseString(params.entry.agentRuntimeOverride) ??
|
||||
normalizeOptionalLowercaseString(params.entry.agentHarnessId) ??
|
||||
(params.agentRuntime.source === "implicit"
|
||||
? undefined
|
||||
: normalizeOptionalLowercaseString(params.agentRuntime.id));
|
||||
if (explicitRuntime && explicitRuntime !== "auto" && explicitRuntime !== "default") {
|
||||
return resolveAgentRuntimeLabel({
|
||||
config: params.cfg,
|
||||
sessionEntry: params.entry,
|
||||
resolvedHarness: explicitRuntime,
|
||||
fallbackProvider: params.modelProvider,
|
||||
});
|
||||
}
|
||||
|
||||
let resolvedHarness: string | undefined;
|
||||
try {
|
||||
const selected = selectAgentHarness({
|
||||
provider: params.modelProvider,
|
||||
modelId: params.model,
|
||||
config: params.cfg,
|
||||
agentId: params.agentId,
|
||||
sessionKey: params.sessionKey,
|
||||
agentHarnessId: params.entry.agentHarnessId,
|
||||
});
|
||||
const id = normalizeOptionalLowercaseString(selected.id);
|
||||
resolvedHarness = id && id !== "pi" ? id : undefined;
|
||||
} catch {
|
||||
resolvedHarness = undefined;
|
||||
}
|
||||
return resolveAgentRuntimeLabel({
|
||||
config: params.cfg,
|
||||
sessionEntry: params.entry,
|
||||
resolvedHarness,
|
||||
fallbackProvider: params.modelProvider,
|
||||
});
|
||||
}
|
||||
|
||||
function formatRuntimeCell(runtimeLabel: string, rich: boolean): string {
|
||||
const label = runtimeLabel.padEnd(RUNTIME_PAD);
|
||||
return rich ? theme.info(label) : label;
|
||||
}
|
||||
|
||||
function toJsonSessionRow(row: SessionRow): Omit<SessionRow, "runtimeLabel"> {
|
||||
const { runtimeLabel, ...jsonRow } = row;
|
||||
void runtimeLabel;
|
||||
return jsonRow;
|
||||
}
|
||||
|
||||
export async function sessionsCommand(
|
||||
opts: {
|
||||
json?: boolean;
|
||||
@@ -225,10 +290,21 @@ export async function sessionsCommand(
|
||||
.map(([key, entry]) => {
|
||||
const row = toSessionDisplayRow(key, entry);
|
||||
const agentId = parseAgentSessionKey(row.key)?.agentId ?? target.agentId;
|
||||
const modelRef = resolveSessionDisplayModelRef(cfg, row);
|
||||
const agentRuntime = resolveAgentRuntimeMetadata(cfg, agentId);
|
||||
return Object.assign({}, row, {
|
||||
agentId,
|
||||
agentRuntime: resolveAgentRuntimeMetadata(cfg, agentId),
|
||||
agentRuntime,
|
||||
kind: classifySessionKey(row.key, store[row.key]),
|
||||
runtimeLabel: resolveSessionRuntimeLabel({
|
||||
cfg,
|
||||
entry,
|
||||
agentRuntime,
|
||||
modelProvider: modelRef.provider,
|
||||
model: modelRef.model,
|
||||
agentId,
|
||||
sessionKey: row.key,
|
||||
}),
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -254,7 +330,8 @@ export async function sessionsCommand(
|
||||
hasMore,
|
||||
activeMinutes: activeMinutes ?? null,
|
||||
sessions: await Promise.all(
|
||||
rows.map(async (r) => {
|
||||
rows.map(async (row) => {
|
||||
const r = toJsonSessionRow(row);
|
||||
const modelRef = resolveSessionDisplayModelRef(cfg, r);
|
||||
return {
|
||||
...r,
|
||||
@@ -306,6 +383,7 @@ export async function sessionsCommand(
|
||||
"Key".padEnd(SESSION_KEY_PAD),
|
||||
"Age".padEnd(SESSION_AGE_PAD),
|
||||
"Model".padEnd(SESSION_MODEL_PAD),
|
||||
"Runtime".padEnd(RUNTIME_PAD),
|
||||
"Tokens (ctx %)".padEnd(TOKENS_PAD),
|
||||
"Flags",
|
||||
].join(" ");
|
||||
@@ -329,6 +407,7 @@ export async function sessionsCommand(
|
||||
formatSessionKeyCell(row.key, rich),
|
||||
formatSessionAgeCell(row.updatedAt, rich),
|
||||
formatSessionModelCell(model, rich),
|
||||
formatRuntimeCell(row.runtimeLabel, rich),
|
||||
formatTokensCell(total, contextTokens ?? null, rich),
|
||||
formatSessionFlagsCell(row, rich),
|
||||
].join(" ");
|
||||
|
||||
57
src/status/agent-runtime-label.ts
Normal file
57
src/status/agent-runtime-label.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { isCliProvider } from "../agents/model-selection.js";
|
||||
import type { SessionEntry } from "../config/sessions/types.js";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import {
|
||||
normalizeOptionalLowercaseString,
|
||||
normalizeOptionalString,
|
||||
} from "../shared/string-coerce.js";
|
||||
import { sanitizeTerminalText } from "../terminal/safe-text.js";
|
||||
|
||||
const AGENT_RUNTIME_LABELS: Readonly<Record<string, string>> = {
|
||||
pi: "OpenClaw Pi Default",
|
||||
codex: "OpenAI Codex",
|
||||
"codex-cli": "OpenAI Codex",
|
||||
"claude-cli": "Claude CLI",
|
||||
"google-gemini-cli": "Gemini CLI",
|
||||
};
|
||||
|
||||
export function resolveAgentRuntimeLabel(args: {
|
||||
config?: OpenClawConfig;
|
||||
sessionEntry?: Pick<
|
||||
SessionEntry,
|
||||
"acp" | "agentRuntimeOverride" | "agentHarnessId" | "modelProvider" | "providerOverride"
|
||||
>;
|
||||
resolvedHarness?: string;
|
||||
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 runtimeRaw =
|
||||
normalizeOptionalString(args.resolvedHarness) ??
|
||||
normalizeOptionalString(args.sessionEntry?.agentRuntimeOverride) ??
|
||||
normalizeOptionalString(args.sessionEntry?.agentHarnessId);
|
||||
const runtime = normalizeOptionalLowercaseString(runtimeRaw);
|
||||
if (runtime && runtime !== "auto" && runtime !== "default") {
|
||||
return AGENT_RUNTIME_LABELS[runtime] ?? sanitizeTerminalText(runtimeRaw ?? runtime);
|
||||
}
|
||||
|
||||
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 (
|
||||
AGENT_RUNTIME_LABELS[normalizeOptionalLowercaseString(providerRaw) ?? ""] ??
|
||||
`${provider} (cli)`
|
||||
);
|
||||
}
|
||||
|
||||
return AGENT_RUNTIME_LABELS.pi;
|
||||
}
|
||||
Reference in New Issue
Block a user