mirror of
https://github.com/moltbot/moltbot.git
synced 2026-05-13 23:56:07 +00:00
feat: show persistent chat context usage
This commit is contained in:
committed by
Val Alexander
parent
1ef85c7d4c
commit
04b8ad2e09
@@ -127,6 +127,7 @@ Docs: https://docs.openclaw.ai
|
||||
- QA/Mantis: accept Blacksmith Testbox `tbx_...` lease ids from desktop smoke warmup, so provider overrides do not fail before inspect/run. Thanks @vincentkoc.
|
||||
- Plugins/SDK: add bounded `before_agent_finalize` retry instructions so workflow plugins can request one more model pass. Thanks @100yenadmin.
|
||||
- Plugin SDK: add plugin-owned `SessionEntry` slot projection and scoped trusted-policy session extension reads. (#75609; replaces part of #73384/#74483) Thanks @100yenadmin.
|
||||
- Control UI/WebChat: show a persistent compact context usage indicator from fresh session token data before the high-pressure warning state, while keeping the existing compaction prompt threshold. Fixes #46398; refs #45048, #50071, and #73744. Thanks @walterwkchoy, @AxelrodAI, @Brissux, @vincentkoc, and @BunsDev.
|
||||
- Docs: clarify that IRC uses raw TCP/TLS sockets outside operator-managed forward proxy routing, so direct IRC egress should be explicitly approved before enabling IRC. Thanks @jesse-merhi.
|
||||
- Dependencies: refresh runtime and provider packages including Pi 0.73.0, ACPX adapters, OpenAI, Anthropic, Slack, and TypeScript native preview, while keeping the Bedrock runtime installer override pinned below the Windows ARM Node 24 npm resolver failure.
|
||||
- Contributor PRs: require external pull requests to include after-fix real behavior proof from a real OpenClaw setup, with terminal screenshots, console output, redacted runtime logs, linked artifacts, and copied live output treated as valid evidence while unit tests, mocks, lint, typechecks, snapshots, and CI remain supplemental only.
|
||||
|
||||
@@ -166,7 +166,7 @@ Imported themes are stored only in the current browser profile. They are not wri
|
||||
- The chat header model and thinking pickers patch the active session immediately through `sessions.patch`; they are persistent session overrides, not one-turn-only send options.
|
||||
- Typing `/new` in the Control UI creates and switches to the same fresh dashboard session as New Chat. Typing `/reset` keeps the Gateway's explicit in-place reset for the current session.
|
||||
- The chat model picker requests the Gateway's configured model view. If `agents.defaults.models` is present, that allowlist drives the picker. Otherwise the picker shows explicit `models.providers.*.models` entries plus providers with usable auth. The full catalog stays available through the debug `models.list` RPC with `view: "all"`.
|
||||
- When fresh Gateway session usage reports show high context pressure, the chat composer area shows a context notice and, at recommended compaction levels, a compact button that runs the normal session compaction path. Stale token snapshots are hidden until the Gateway reports fresh usage again.
|
||||
- When fresh Gateway session usage reports include current context tokens, the chat composer area shows a compact context usage indicator. It switches to warning styling at high context pressure and, at recommended compaction levels, shows a compact button that runs the normal session compaction path. Stale token snapshots are hidden until the Gateway reports fresh usage again.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="Talk mode (browser realtime)">
|
||||
|
||||
@@ -146,7 +146,7 @@
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Context usage warning pill */
|
||||
/* Context usage pill */
|
||||
.context-notice {
|
||||
align-self: center;
|
||||
display: inline-flex;
|
||||
@@ -168,6 +168,10 @@
|
||||
animation: fade-in 0.2s var(--ease-out);
|
||||
}
|
||||
|
||||
.context-notice--usage {
|
||||
border-color: color-mix(in srgb, var(--border) 70%, transparent);
|
||||
}
|
||||
|
||||
.context-notice__icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
@@ -175,6 +179,24 @@
|
||||
stroke: currentColor;
|
||||
}
|
||||
|
||||
.context-notice__meter {
|
||||
position: relative;
|
||||
width: 46px;
|
||||
height: 6px;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
border-radius: var(--radius-full);
|
||||
background: color-mix(in srgb, currentColor 16%, transparent);
|
||||
}
|
||||
|
||||
.context-notice__meter-fill {
|
||||
position: absolute;
|
||||
inset: 0 auto 0 0;
|
||||
max-width: 100%;
|
||||
border-radius: inherit;
|
||||
background: currentColor;
|
||||
}
|
||||
|
||||
.context-notice__detail {
|
||||
color: color-mix(in srgb, currentColor 72%, var(--muted));
|
||||
font-variant-numeric: tabular-nums;
|
||||
|
||||
@@ -59,21 +59,30 @@ export function getContextNoticeViewModel(
|
||||
detail: string;
|
||||
color: string;
|
||||
bg: string;
|
||||
warning: boolean;
|
||||
compactRecommended: boolean;
|
||||
} | null {
|
||||
if (session?.totalTokensFresh === false) {
|
||||
return null;
|
||||
}
|
||||
const used = session?.totalTokens ?? 0;
|
||||
const used = session?.totalTokens;
|
||||
const limit = session?.contextTokens ?? defaultContextTokens ?? 0;
|
||||
if (!used || !limit) {
|
||||
if (typeof used !== "number" || !Number.isFinite(used) || used < 0 || !limit) {
|
||||
return null;
|
||||
}
|
||||
const ratio = used / limit;
|
||||
if (ratio < CONTEXT_NOTICE_RATIO) {
|
||||
return null;
|
||||
}
|
||||
const pct = Math.min(Math.round(ratio * 100), 100);
|
||||
const warning = ratio >= CONTEXT_NOTICE_RATIO;
|
||||
if (!warning) {
|
||||
return {
|
||||
pct,
|
||||
detail: `${formatTokensCompact(used)} / ${formatTokensCompact(limit)}`,
|
||||
color: "var(--muted)",
|
||||
bg: "color-mix(in srgb, var(--muted) 8%, transparent)",
|
||||
warning,
|
||||
compactRecommended: false,
|
||||
};
|
||||
}
|
||||
// Read theme semantic tokens so color tracks the active theme (Dash, dark, light ...).
|
||||
const { warnRgb, dangerRgb } = getThemeNoticeColors();
|
||||
const [wr, wg, wb] = warnRgb;
|
||||
@@ -90,6 +99,7 @@ export function getContextNoticeViewModel(
|
||||
detail: `${formatTokensCompact(used)} / ${formatTokensCompact(limit)}`,
|
||||
color,
|
||||
bg,
|
||||
warning,
|
||||
compactRecommended: ratio >= CONTEXT_COMPACT_RATIO,
|
||||
};
|
||||
}
|
||||
@@ -107,25 +117,34 @@ export function renderContextNotice(
|
||||
const compactDisabled = options.compactDisabled === true || options.compactBusy === true;
|
||||
return html`
|
||||
<div
|
||||
class="context-notice"
|
||||
class="context-notice ${model.warning ? "context-notice--warning" : "context-notice--usage"}"
|
||||
role="status"
|
||||
style="--ctx-color:${model.color};--ctx-bg:${model.bg}"
|
||||
title=${`Session context usage: ${model.detail} (${model.pct}%)`}
|
||||
>
|
||||
<svg
|
||||
class="context-notice__icon"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z" />
|
||||
<line x1="12" y1="9" x2="12" y2="13" />
|
||||
<line x1="12" y1="17" x2="12.01" y2="17" />
|
||||
</svg>
|
||||
${model.warning
|
||||
? html`
|
||||
<svg
|
||||
class="context-notice__icon"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z" />
|
||||
<line x1="12" y1="9" x2="12" y2="13" />
|
||||
<line x1="12" y1="17" x2="12.01" y2="17" />
|
||||
</svg>
|
||||
`
|
||||
: html`
|
||||
<span class="context-notice__meter" aria-hidden="true">
|
||||
<span class="context-notice__meter-fill" style="width:${model.pct}%"></span>
|
||||
</span>
|
||||
`}
|
||||
<span>${model.pct}% context used</span>
|
||||
<span class="context-notice__detail">${model.detail}</span>
|
||||
${canRenderCompact
|
||||
|
||||
@@ -234,7 +234,7 @@ describe("context notice", () => {
|
||||
resetContextNoticeThemeCacheForTest();
|
||||
});
|
||||
|
||||
it("renders only for fresh high current usage", () => {
|
||||
it("renders persistent fresh context usage and keeps high-usage warning behavior", () => {
|
||||
const container = document.createElement("div");
|
||||
vi.spyOn(window, "getComputedStyle").mockReturnValue({
|
||||
getPropertyValue: (name: string) =>
|
||||
@@ -242,19 +242,28 @@ describe("context notice", () => {
|
||||
} as CSSStyleDeclaration);
|
||||
resetContextNoticeThemeCacheForTest();
|
||||
|
||||
expect(
|
||||
getContextNoticeViewModel(
|
||||
{
|
||||
key: "main",
|
||||
kind: "direct",
|
||||
updatedAt: null,
|
||||
inputTokens: 757_300,
|
||||
totalTokens: 46_000,
|
||||
contextTokens: 200_000,
|
||||
},
|
||||
200_000,
|
||||
),
|
||||
).toBeNull();
|
||||
const lowUsageSession: GatewaySessionRow = {
|
||||
key: "main",
|
||||
kind: "direct",
|
||||
updatedAt: null,
|
||||
inputTokens: 757_300,
|
||||
totalTokens: 46_000,
|
||||
contextTokens: 200_000,
|
||||
};
|
||||
const lowUsage = getContextNoticeViewModel(lowUsageSession, 200_000);
|
||||
expect(lowUsage).toMatchObject({
|
||||
pct: 23,
|
||||
detail: "46k / 200k",
|
||||
warning: false,
|
||||
compactRecommended: false,
|
||||
});
|
||||
render(renderContextNotice(lowUsageSession, 200_000), container);
|
||||
expect(container.textContent).toContain("23% context used");
|
||||
expect(container.textContent).toContain("46k / 200k");
|
||||
expect(container.querySelector(".context-notice--usage")).not.toBeNull();
|
||||
expect(container.querySelector(".context-notice__meter")).not.toBeNull();
|
||||
expect(container.querySelector(".context-notice__icon")).toBeNull();
|
||||
expect(container.textContent).not.toContain("757.3k / 200k");
|
||||
|
||||
const session: GatewaySessionRow = {
|
||||
key: "main",
|
||||
@@ -272,6 +281,8 @@ describe("context notice", () => {
|
||||
expect(container.textContent).not.toContain("757.3k / 200k");
|
||||
const notice = container.querySelector<HTMLElement>(".context-notice");
|
||||
expect(notice).not.toBeNull();
|
||||
expect(notice?.classList.contains("context-notice--warning")).toBe(true);
|
||||
expect(notice?.getAttribute("title")).toBe("Session context usage: 190k / 200k (95%)");
|
||||
expect(notice?.style.getPropertyValue("--ctx-color")).toContain("rgb(");
|
||||
expect(notice?.style.getPropertyValue("--ctx-color")).toContain("4, 5, 6");
|
||||
expect(notice?.style.getPropertyValue("--ctx-color")).not.toContain("NaN");
|
||||
|
||||
Reference in New Issue
Block a user