Refresh context usage during generation
Update the context-window indicator when each new Agent 0 generation starts while deduplicating streamed updates. Keep the completion refresh for final provider usage and cover the event-driven behavior in the plugin contract and regression test.
Alessandro committed
Aug 27, 2026 at 14:28 UTC
6a6cecff8527b164668c7a6ab2f76b6b1ed7cfa1
3 files changed
+28
-1
plugins/_context_window/AGENTS.md
+2
@@ -41,6 +41,8 @@
41
restores the accepted response after the accounting tail is drained.
42
- Responses API turns keep their native result and callback behavior unchanged.
43
- Older chats without a stored breakdown show the explanatory empty state.
44
+- The indicator refreshes once per new Agent 0 generation and again when the
45
+ run completes; streamed updates to the same generation do not refetch it.
46
- `_model_config` supplies the effective model limit and the
47
`model-context-strip-end` WebUI slot; it does not own this feature's state.
48
- The `contextWindowUsage` Interface setting defaults to visible on mobile and
plugins/_context_window/extensions/webui/apply_snapshot_before/refresh-context-window.js
+19
-1
@@ -3,6 +3,17 @@ import { store as contextWindowStore } from "/plugins/_context_window/webui/cont
3
const OVERRIDE_REVISION_KEY = "_model_config_override_revision";
4
let lastContextId = "";
5
let lastRevision = null;
6
+let lastGenerationKey = "";
7
+
8
+function latestGenerationKey(logs) {
9
+ if (!Array.isArray(logs)) return "";
10
+ for (let index = logs.length - 1; index >= 0; index--) {
11
+ const item = logs[index];
12
+ if (item?.type !== "agent" || Number(item.agentno || 0) !== 0) continue;
13
+ return `${item.no ?? ""}:${item.id ?? ""}`;
14
+ }
15
+ return "";
16
+}
17
18
export default async function refreshContextWindow(ctx) {
19
const snapshot = ctx?.snapshot;
@@ -10,15 +21,22 @@ export default async function refreshContextWindow(ctx) {
21
if (!contextId) {
22
lastContextId = "";
23
lastRevision = null;
24
+ lastGenerationKey = "";
25
return;
26
}
27
28
const contexts = Array.isArray(snapshot?.contexts) ? snapshot.contexts : [];
29
const active = contexts.find(item => item?.id === contextId) || null;
30
const revision = active?.[OVERRIDE_REVISION_KEY] || null;
19
- if (contextId === lastContextId && revision === lastRevision) return;
31
+ const generationKey = latestGenerationKey(snapshot?.logs);
32
+ if (
33
+ contextId === lastContextId
34
+ && revision === lastRevision
35
+ && (!generationKey || generationKey === lastGenerationKey)
36
+ ) return;
37
38
lastContextId = contextId;
39
lastRevision = revision;
40
+ if (generationKey) lastGenerationKey = generationKey;
41
await contextWindowStore.refresh(contextId);
42
}
plugins/_context_window/tests/test_context_window.py
+7
@@ -145,6 +145,10 @@ def test_webui_and_accounting_are_plugin_owned():
145
helper = (ROOT / "plugins/_context_window/helpers/usage.py").read_text(
146
encoding="utf-8"
147
)
148
+ refresh_hook = (
149
+ ROOT
150
+ / "plugins/_context_window/extensions/webui/apply_snapshot_before/refresh-context-window.js"
151
+ ).read_text(encoding="utf-8")
152
153
assert 'id="model-context-strip-end"' in model_switcher
154
assert "contextWindowUsage" not in model_switcher
@@ -186,6 +190,9 @@ def test_webui_and_accounting_are_plugin_owned():
190
assert "Breakdown available after the next message." in component
191
assert "startswith(" not in helper
192
assert "rpartition(" not in helper
193
+ assert 'item?.type !== "agent"' in refresh_hook
194
+ assert "Number(item.agentno || 0) !== 0" in refresh_hook
195
+ assert "generationKey === lastGenerationKey" in refresh_hook
196
197
198
def test_source_prompt_extensions_are_registered():