diff --git a/CHANGELOG.md b/CHANGELOG.md index 18a4c7a0c24..ee2d18b865c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -87,6 +87,7 @@ Docs: https://docs.openclaw.ai ### Fixes - Agents/generated media: treat attachment-style message tool actions as completed chat sends, preventing duplicate fallback media posts when generated files were already uploaded. +- Control UI/sessions: show each session's agent runtime in the Sessions table and allow filtering by runtime labels, matching the Agents panel runtime wording. Thanks @vincentkoc. - Discord/streaming: show live reasoning text in progress drafts instead of a bare `Reasoning` status line. - Doctor/status: warn when `OPENCLAW_GATEWAY_TOKEN` would shadow a different active `gateway.auth.token` source for local CLI commands, while avoiding false positives when config points at the same env token. Fixes #74271. Thanks @yelog. - Gateway/HTTP: avoid loading managed outgoing-image media handlers for unrelated requests, so disabled OpenAI-compatible routes return 404 without waiting on lazy media sidecars. Thanks @vincentkoc. diff --git a/ui/src/ui/views/sessions.test.ts b/ui/src/ui/views/sessions.test.ts index 57d9eb5a2dd..d655b9e0592 100644 --- a/ui/src/ui/views/sessions.test.ts +++ b/ui/src/ui/views/sessions.test.ts @@ -367,6 +367,41 @@ describe("sessions view", () => { expect(badge?.textContent?.trim()).toBe("cron"); }); + it("renders and filters the session runtime", async () => { + const container = document.createElement("div"); + render( + renderSessions({ + ...buildProps( + buildMultiResult([ + { + key: "agent:main:claude", + kind: "direct", + updatedAt: 20, + agentRuntime: { id: "claude-cli", fallback: "none", source: "agent" }, + }, + { + key: "agent:main:pi", + kind: "direct", + updatedAt: 10, + agentRuntime: { id: "pi", source: "implicit" }, + }, + ]), + ), + searchQuery: "fallback none", + }), + container, + ); + await Promise.resolve(); + + expect( + Array.from(container.querySelectorAll("thead th")).map((cell) => cell.textContent?.trim()), + ).toContain("Runtime"); + expect(container.querySelector(".session-runtime-cell")?.textContent?.trim()).toBe( + "claude-cli (fallback none)", + ); + expect(container.textContent).not.toContain("agent:main:pi"); + }); + it("keeps raw keys for inherited identity object properties", async () => { const container = document.createElement("div"); render( diff --git a/ui/src/ui/views/sessions.ts b/ui/src/ui/views/sessions.ts index 44a643359ac..e9a13b7fa58 100644 --- a/ui/src/ui/views/sessions.ts +++ b/ui/src/ui/views/sessions.ts @@ -13,6 +13,7 @@ import type { SessionCompactionCheckpoint, SessionsListResult, } from "../types.ts"; +import { resolveAgentRuntimeLabel } from "./agents-utils.ts"; export type SessionsProps = { loading: boolean; @@ -178,7 +179,14 @@ function filterRows( const label = normalizeLowercaseStringOrEmpty(row.label); const kind = normalizeLowercaseStringOrEmpty(row.kind); const displayName = normalizeLowercaseStringOrEmpty(row.displayName); - if (key.includes(q) || label.includes(q) || kind.includes(q) || displayName.includes(q)) { + const runtime = normalizeLowercaseStringOrEmpty(resolveAgentRuntimeLabel(row.agentRuntime)); + if ( + key.includes(q) || + label.includes(q) || + kind.includes(q) || + displayName.includes(q) || + runtime.includes(q) + ) { return true; } const keyParts = parseSessionKeyParts(row.key); @@ -543,6 +551,7 @@ export function renderSessions(props: SessionsProps) { ${sortHeader("key", t("sessionsView.key"), "data-table-key-col")} ${t("sessionsView.label")} ${sortHeader("kind", t("sessionsView.kind"))} + ${t("agents.context.runtime")} ${sortHeader("updated", t("sessionsView.updated"))} ${sortHeader("tokens", t("sessionsView.tokens"))} ${t("sessionsView.compaction")} @@ -556,7 +565,7 @@ export function renderSessions(props: SessionsProps) { ${paginated.length === 0 ? html` - + ${emptyBecauseFiltered ? html`
@@ -748,6 +757,9 @@ function renderRows(row: GatewaySessionRow, props: SessionsProps) { ${row.kind} + + ${resolveAgentRuntimeLabel(row.agentRuntime)} + ${updated} ${formatSessionTokens(row)} @@ -858,7 +870,7 @@ function renderRows(row: GatewaySessionRow, props: SessionsProps) { ...(isExpanded && hasCheckpoints ? [ html` - +