fix(bridge): emit empty content delta as heartbeat; preserve user provider fields on reinstall

OpenClaw's LLM idle watchdog (default 120s) fires on lack of *model
progress*, not lack of bytes — an SSE comment frame (": keepalive\n\n")
keeps the TCP socket alive but isn't recognized as progress, so a long
quiet tool-call phase still idles out. When that happens OpenClaw falls
back to re-sending the prior turn's assistant text (pi-embedded:1308
fallbackAnswerText), producing duplicate-Discord-message symptoms.

Heartbeat now emits a real chat.completion.chunk with an empty content
delta every 30s. Clients drop empty deltas; the upstream idle watchdog
should count it as model progress because it's a real event on the
canonical streaming channel.

scripts/install.mjs now spreads the existing provider entry before
overriding script-managed fields, so user-added fields like
timeoutSeconds survive reinstall.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
zhi
2026-05-14 08:53:22 +00:00
parent 1b7cd6b215
commit 0b24330787
2 changed files with 30 additions and 18 deletions

View File

@@ -67,10 +67,15 @@ function install() {
// 3. Update openclaw.json
const cfg = readConfig();
// Add provider
// Add provider — spread existing first so user-added fields
// (e.g. timeoutSeconds, extraHeaders) survive reinstall. Script-managed
// fields (baseUrl/apiKey/api/models) are then overridden authoritatively
// since they're tied to the constants and model catalog above.
cfg.models = cfg.models ?? {};
cfg.models.providers = cfg.models.providers ?? {};
const existingProvider = cfg.models.providers[PLUGIN_ID] ?? {};
cfg.models.providers[PLUGIN_ID] = {
...existingProvider,
baseUrl: `http://127.0.0.1:${BRIDGE_PORT}/v1`,
apiKey: BRIDGE_API_KEY,
api: "openai-completions",