Skip to content

Commit 05c9492

Browse files
authored
fix: reduce WebUI session latency churn (#76277) thanks @BunsDev
Reduce WebUI/Gateway latency churn by avoiding redundant session reloads, carrying session keys through transcript update events, and deferring explicit media provider discovery. Includes changelog attribution and closes the referenced runtime latency issues.
1 parent 1c4d3e2 commit 05c9492

71 files changed

Lines changed: 768 additions & 1731 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Docs: https://docs.openclaw.ai
1313

1414
### Fixes
1515

16+
- Control UI/Gateway: avoid full session-list reloads for locally applied message-phase session updates, carry known session keys through transcript-file update events, and defer media provider listing when explicit generation model config is present. Refs #76236, #76203, #76188, #76107, and #76166. Thanks @BunsDev.
1617
- Gateway: keep directly requested plugin tools invokable under restrictive tool profiles while preserving explicit deny lists and the HTTP safety deny list, preventing catalog/invoke mismatches that surface as "Tool not available". Thanks @BunsDev.
1718
- Gateway/update: allow beta binaries to refresh gateway services when the config was last written by the matching stable release version, avoiding false newer-config downgrade blocks during beta channel updates.
1819
- Channels: keep Matrix and Mattermost bundled in the core package instead of advertising external npm installs before those channels are cut over. Thanks @vincentkoc.

docs/nodes/audio.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ title: "Audio and voice notes"
1717
5. On success, it replaces `Body` with an `[Audio]` block and sets `{{Transcript}}`.
1818
- **Command parsing**: When transcription succeeds, `CommandBody`/`RawBody` are set to the transcript so slash commands still work.
1919
- **Verbose logging**: In `--verbose`, we log when transcription runs and when it replaces the body.
20-
- **Control UI dictation**: The Chat composer can send a browser-recorded microphone clip to `chat.transcribeAudio`. That Gateway RPC writes the clip to a temporary local file, runs this same audio transcription pipeline, returns draft text to the browser, and deletes the temporary file. It does not create an agent run by itself.
2120

2221
## Auto-detection (default)
2322

docs/providers/arcee.md

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -98,24 +98,24 @@ Arcee AI models can be accessed directly via the Arcee platform or through [Open
9898

9999
OpenClaw currently ships this bundled Arcee catalog:
100100

101-
| Model ref | Name | Input | Context | Cost (in/out per 1M) | Notes |
102-
| ------------------------------ | ---------------------- | ----- | ------- | -------------------- | ------------------------------------------ |
103-
| `arcee/trinity-large-thinking` | Trinity Large Thinking | text | 256K | $0.25 / $0.90 | Default model; reasoning enabled; no tools |
104-
| `arcee/trinity-large-preview` | Trinity Large Preview | text | 128K | $0.25 / $1.00 | General-purpose; 400B params, 13B active |
105-
| `arcee/trinity-mini` | Trinity Mini 26B | text | 128K | $0.045 / $0.15 | Fast and cost-efficient; function calling |
101+
| Model ref | Name | Input | Context | Cost (in/out per 1M) | Notes |
102+
| ------------------------------ | ---------------------- | ----- | ------- | -------------------- | ----------------------------------------- |
103+
| `arcee/trinity-large-thinking` | Trinity Large Thinking | text | 256K | $0.25 / $0.90 | Default model; reasoning enabled |
104+
| `arcee/trinity-large-preview` | Trinity Large Preview | text | 128K | $0.25 / $1.00 | General-purpose; 400B params, 13B active |
105+
| `arcee/trinity-mini` | Trinity Mini 26B | text | 128K | $0.045 / $0.15 | Fast and cost-efficient; function calling |
106106

107107
<Tip>
108-
The onboarding preset sets `arcee/trinity-large-thinking` as the default model. It is reasoning/text-only and does not support tool use or function calling.
108+
The onboarding preset sets `arcee/trinity-large-thinking` as the default model.
109109
</Tip>
110110

111111
## Supported features
112112

113-
| Feature | Supported |
114-
| --------------------------------------------- | ------------------------------------------- |
115-
| Streaming | Yes |
116-
| Tool use / function calling | Model-dependent; not Trinity Large Thinking |
117-
| Structured output (JSON mode and JSON schema) | Yes |
118-
| Extended thinking | Yes (Trinity Large Thinking) |
113+
| Feature | Supported |
114+
| --------------------------------------------- | ---------------------------- |
115+
| Streaming | Yes |
116+
| Tool use / function calling | Yes |
117+
| Structured output (JSON mode and JSON schema) | Yes |
118+
| Extended thinking | Yes (Trinity Large Thinking) |
119119

120120
<AccordionGroup>
121121
<Accordion title="Environment note">

docs/web/control-ui.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,6 @@ Imported themes are stored only in the current browser profile. They are not wri
9696
<AccordionGroup>
9797
<Accordion title="Chat and Talk">
9898
- Chat with the model via Gateway WS (`chat.history`, `chat.send`, `chat.abort`, `chat.inject`).
99-
- Dictate into the Chat composer with server-side STT (`chat.transcribeAudio`). The browser records a short microphone clip and sends it to the Gateway, which runs the configured `tools.media.audio` transcription pipeline and returns draft text without exposing provider credentials to the browser.
10099
- Talk through browser realtime sessions. OpenAI uses direct WebRTC, Google Live uses a constrained one-use browser token over WebSocket, and backend-only realtime voice plugins use the Gateway relay transport. The relay keeps provider credentials on the Gateway while the browser streams microphone PCM through `talk.realtime.relay*` RPCs and sends `openclaw_agent_consult` tool calls back through `chat.send` for the larger configured OpenClaw model.
101100
- Stream tool calls + live tool output cards in Chat (agent events).
102101

@@ -150,7 +149,6 @@ Imported themes are stored only in the current browser profile. They are not wri
150149
<AccordionGroup>
151150
<Accordion title="Send and history semantics">
152151
- `chat.send` is **non-blocking**: it acks immediately with `{ runId, status: "started" }` and the response streams via `chat` events.
153-
- `chat.transcribeAudio` is a one-shot dictation helper for Chat drafts. It accepts browser-recorded base64 audio, keeps uploads below the Gateway WebSocket frame limit, writes a temporary local file, runs media-understanding audio transcription with the active Gateway config, returns `{ text, provider, model }`, and removes the temporary file. It does not create an agent run and is separate from realtime Talk.
154152
- Chat uploads accept images plus non-video files. Images keep the native image path; other files are stored as managed media and shown in history as attachment links.
155153
- Re-sending with the same `idempotencyKey` returns `{ status: "in_flight" }` while running, and `{ status: "ok" }` after completion.
156154
- `chat.history` responses are size-bounded for UI safety. When transcript entries are too large, Gateway may truncate long text fields, omit heavy metadata blocks, and replace oversized messages with a placeholder (`[chat.history omitted: message too large]`).

docs/web/webchat.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ Status: the macOS/iOS SwiftUI chat UI talks directly to the Gateway WebSocket.
2222

2323
## How it works (behavior)
2424

25-
- The UI connects to the Gateway WebSocket and uses `chat.history`, `chat.send`, `chat.inject`, and `chat.transcribeAudio`.
25+
- The UI connects to the Gateway WebSocket and uses `chat.history`, `chat.send`, and `chat.inject`.
2626
- `chat.history` is bounded for stability: Gateway may truncate long text fields, omit heavy metadata, and replace oversized entries with `[chat.history omitted: message too large]`.
2727
- `chat.history` follows the active transcript branch for modern append-only session files, so abandoned rewrite branches and superseded prompt copies are not rendered in WebChat.
2828
- Control UI remembers the backing Gateway `sessionId` returned by `chat.history` and includes it on follow-up `chat.send` calls, so reconnects and page refreshes continue the same stored conversation unless the user starts or resets a session.
@@ -37,7 +37,6 @@ Status: the macOS/iOS SwiftUI chat UI talks directly to the Gateway WebSocket.
3737
and assistant entries whose whole visible text is only the exact silent
3838
token `NO_REPLY` / `no_reply` are omitted.
3939
- Reasoning-flagged reply payloads (`isReasoning: true`) are excluded from WebChat assistant content, transcript replay text, and audio content blocks, so thinking-only payloads do not surface as visible assistant messages or playable audio.
40-
- `chat.transcribeAudio` powers server-side dictation in the Control UI chat composer. The browser records microphone audio, sends it as base64 to the Gateway, and the Gateway runs the configured `tools.media.audio` pipeline. The returned transcript is inserted into the draft; no agent run is started until the user sends it.
4140
- `chat.inject` appends an assistant note directly to the transcript and broadcasts it to the UI (no agent run).
4241
- Aborted runs can keep partial assistant output visible in the UI.
4342
- Gateway persists aborted partial assistant text into transcript history when buffered output exists, and marks those entries with abort metadata.

extensions/arcee/index.test.ts

Lines changed: 0 additions & 176 deletions
Original file line numberDiff line numberDiff line change
@@ -69,14 +69,6 @@ describe("arcee provider plugin", () => {
6969
"arcee/trinity-large-preview",
7070
"arcee/trinity-large-thinking",
7171
]);
72-
expect(
73-
config?.models?.providers?.arcee?.models?.find(
74-
(model) => model.id === "arcee/trinity-large-thinking",
75-
)?.compat,
76-
).toMatchObject({
77-
supportsReasoningEffort: false,
78-
supportsTools: false,
79-
});
8072
});
8173

8274
it("keeps direct Arcee auth env candidates separate from OpenRouter", () => {
@@ -100,12 +92,6 @@ describe("arcee provider plugin", () => {
10092
"trinity-large-preview",
10193
"trinity-large-thinking",
10294
]);
103-
expect(
104-
catalogProvider.models?.find((model) => model.id === "trinity-large-thinking")?.compat,
105-
).toMatchObject({
106-
supportsReasoningEffort: false,
107-
supportsTools: false,
108-
});
10995
});
11096

11197
it("builds the OpenRouter-backed Arcee AI model catalog", async () => {
@@ -126,12 +112,6 @@ describe("arcee provider plugin", () => {
126112
"arcee/trinity-large-preview",
127113
"arcee/trinity-large-thinking",
128114
]);
129-
expect(
130-
catalogProvider.models?.find((model) => model.id === "arcee/trinity-large-thinking")?.compat,
131-
).toMatchObject({
132-
supportsReasoningEffort: false,
133-
supportsTools: false,
134-
});
135115
});
136116

137117
it("normalizes Arcee OpenRouter models to vendor-prefixed runtime ids", async () => {
@@ -150,10 +130,6 @@ describe("arcee provider plugin", () => {
150130
} as never),
151131
).toMatchObject({
152132
id: "arcee/trinity-large-thinking",
153-
compat: {
154-
supportsReasoningEffort: false,
155-
supportsTools: false,
156-
},
157133
});
158134

159135
expect(
@@ -200,10 +176,6 @@ describe("arcee provider plugin", () => {
200176
).toMatchObject({
201177
id: "arcee/trinity-large-thinking",
202178
baseUrl: "https://openrouter.ai/api/v1",
203-
compat: {
204-
supportsReasoningEffort: false,
205-
supportsTools: false,
206-
},
207179
});
208180

209181
expect(
@@ -217,152 +189,4 @@ describe("arcee provider plugin", () => {
217189
baseUrl: "https://openrouter.ai/api/v1",
218190
});
219191
});
220-
221-
it("repairs stale Trinity tool compat on existing Arcee configs and runtime models", async () => {
222-
const provider = await registerSingleProviderPlugin(arceePlugin);
223-
224-
expect(
225-
provider.normalizeConfig?.({
226-
provider: "arcee",
227-
providerConfig: {
228-
api: "openai-completions",
229-
baseUrl: "https://openrouter.ai/v1/",
230-
models: [
231-
{
232-
id: "arcee/trinity-large-thinking",
233-
name: "Trinity Large Thinking",
234-
reasoning: true,
235-
input: ["text"],
236-
contextWindow: 262144,
237-
maxTokens: 80000,
238-
cost: {
239-
input: 0.25,
240-
output: 0.9,
241-
cacheRead: 0.25,
242-
cacheWrite: 0.25,
243-
},
244-
compat: {
245-
supportsReasoningEffort: false,
246-
supportsStrictMode: true,
247-
},
248-
},
249-
],
250-
},
251-
} as never),
252-
).toMatchObject({
253-
baseUrl: "https://openrouter.ai/api/v1",
254-
models: [
255-
{
256-
id: "arcee/trinity-large-thinking",
257-
compat: {
258-
supportsReasoningEffort: false,
259-
supportsStrictMode: true,
260-
supportsTools: false,
261-
},
262-
},
263-
],
264-
});
265-
266-
expect(
267-
provider.normalizeConfig?.({
268-
provider: "arcee",
269-
providerConfig: {
270-
api: "openai-completions",
271-
baseUrl: "https://api.arcee.ai/api/v1",
272-
models: [
273-
{
274-
id: "trinity-large-thinking",
275-
name: "Trinity Large Thinking",
276-
reasoning: true,
277-
input: ["text"],
278-
contextWindow: 262144,
279-
maxTokens: 80000,
280-
cost: {
281-
input: 0.25,
282-
output: 0.9,
283-
cacheRead: 0.25,
284-
cacheWrite: 0.25,
285-
},
286-
compat: {
287-
supportsReasoningEffort: false,
288-
},
289-
},
290-
],
291-
},
292-
} as never),
293-
).toMatchObject({
294-
baseUrl: "https://api.arcee.ai/api/v1",
295-
models: [
296-
{
297-
id: "trinity-large-thinking",
298-
compat: {
299-
supportsReasoningEffort: false,
300-
supportsTools: false,
301-
},
302-
},
303-
],
304-
});
305-
306-
const trinityRuntimeModel = {
307-
name: "Trinity Large Thinking",
308-
api: "openai-completions",
309-
reasoning: true,
310-
input: ["text"],
311-
contextWindow: 262144,
312-
maxTokens: 80000,
313-
cost: {
314-
input: 0.25,
315-
output: 0.9,
316-
cacheRead: 0.25,
317-
cacheWrite: 0.25,
318-
},
319-
compat: {
320-
supportsReasoningEffort: false,
321-
},
322-
};
323-
324-
const trinityCompat = {
325-
supportsReasoningEffort: false,
326-
supportsTools: false,
327-
};
328-
329-
expect(
330-
provider.contributeResolvedModelCompat?.({
331-
provider: "arcee",
332-
modelId: "arcee/trinity-large-thinking",
333-
model: {
334-
...trinityRuntimeModel,
335-
provider: "arcee",
336-
id: "arcee/trinity-large-thinking",
337-
baseUrl: "https://openrouter.ai/api/v1",
338-
},
339-
} as never),
340-
).toEqual(trinityCompat);
341-
342-
expect(
343-
provider.contributeResolvedModelCompat?.({
344-
provider: "arcee",
345-
modelId: "trinity-large-thinking",
346-
model: {
347-
...trinityRuntimeModel,
348-
provider: "arcee",
349-
id: "trinity-large-thinking",
350-
baseUrl: "https://api.arcee.ai/api/v1",
351-
},
352-
} as never),
353-
).toEqual(trinityCompat);
354-
355-
expect(
356-
provider.contributeResolvedModelCompat?.({
357-
provider: "openrouter",
358-
modelId: "trinity-large-thinking",
359-
model: {
360-
...trinityRuntimeModel,
361-
provider: "openrouter",
362-
id: "trinity-large-thinking",
363-
baseUrl: "https://openrouter.ai/api/v1",
364-
},
365-
} as never),
366-
).toBeUndefined();
367-
});
368192
});

extensions/arcee/index.ts

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,6 @@ import {
1717
normalizeArceeOpenRouterBaseUrl,
1818
toArceeOpenRouterModelId,
1919
} from "./provider-catalog.js";
20-
import {
21-
ARCEE_TRINITY_LARGE_THINKING_COMPAT,
22-
applyArceeTrinityLargeThinkingCompat,
23-
normalizeArceeProviderConfig,
24-
shouldContributeArceeTrinityLargeThinkingCompat,
25-
} from "./provider-policy.js";
2620

2721
const PROVIDER_ID = "arcee";
2822
const ARCEE_WIZARD_GROUP = {
@@ -101,7 +95,7 @@ function normalizeArceeResolvedModel<T extends { baseUrl?: string; id: string }>
10195
return undefined;
10296
}
10397
return {
104-
...applyArceeTrinityLargeThinkingCompat(model),
98+
...model,
10599
id: normalizedId,
106100
baseUrl: normalizedBaseUrl,
107101
};
@@ -126,12 +120,13 @@ export default definePluginEntry({
126120
config,
127121
providerId: PROVIDER_ID,
128122
}),
129-
normalizeConfig: ({ providerConfig }) => normalizeArceeProviderConfig(providerConfig),
123+
normalizeConfig: ({ providerConfig }) => {
124+
const normalizedBaseUrl = normalizeArceeOpenRouterBaseUrl(providerConfig.baseUrl);
125+
return normalizedBaseUrl && normalizedBaseUrl !== providerConfig.baseUrl
126+
? { ...providerConfig, baseUrl: normalizedBaseUrl }
127+
: undefined;
128+
},
130129
normalizeResolvedModel: ({ model }) => normalizeArceeResolvedModel(model),
131-
contributeResolvedModelCompat: (ctx) =>
132-
shouldContributeArceeTrinityLargeThinkingCompat(ctx)
133-
? ARCEE_TRINITY_LARGE_THINKING_COMPAT
134-
: undefined,
135130
normalizeTransport: ({ api, baseUrl }) => {
136131
const normalizedBaseUrl = normalizeArceeOpenRouterBaseUrl(baseUrl);
137132
return normalizedBaseUrl && normalizedBaseUrl !== baseUrl

extensions/arcee/models.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
import type { ModelDefinitionConfig } from "openclaw/plugin-sdk/provider-model-types";
2-
import { ARCEE_BASE_URL, ARCEE_TRINITY_LARGE_THINKING_COMPAT } from "./provider-policy.js";
1+
import type { ModelDefinitionConfig } from "openclaw/plugin-sdk/provider-model-shared";
32

4-
export { ARCEE_BASE_URL, ARCEE_TRINITY_LARGE_THINKING_COMPAT };
3+
export const ARCEE_BASE_URL = "https://api.arcee.ai/api/v1";
54

65
export const ARCEE_MODEL_CATALOG: ModelDefinitionConfig[] = [
76
{
@@ -45,7 +44,9 @@ export const ARCEE_MODEL_CATALOG: ModelDefinitionConfig[] = [
4544
cacheRead: 0.25,
4645
cacheWrite: 0.25,
4746
},
48-
compat: ARCEE_TRINITY_LARGE_THINKING_COMPAT,
47+
compat: {
48+
supportsReasoningEffort: false,
49+
},
4950
},
5051
];
5152

0 commit comments

Comments
 (0)