The scoring heuristic for GB18030 vs UTF-8 could false-positive on longer
CJK strings because UTF-8 CJK bytes reinterpreted as GB18030 also produce
CJK characters with comparable scores, defeating the margin.
Since genuine GB18030 CJK byte sequences are almost never valid UTF-8
(strict mode), a successful UTF-8 strict decode means the body IS UTF-8.
Remove the scoring heuristic and always return utf8Decoded when strict
decode passes. GB18030 fallback still applies when UTF-8 decode fails.
When cursor > 0, prefer cursor over forward-match as the continuation
point. forward-match compares local entries sequentially against history
entries, but when the local store has been polluted with duplicates (from
a prior mis-sync), it can return a wildly low firstNewIdx — causing a
burst of already-synced messages to reappear in the UI.
tail-overlap and last-user-anchor remain the primary content-based
strategies for sliding-window detection; forward-match is now only used
for the initial sync (cursor == 0).
Co-Authored-By: Claude Opus 4.6 <[email protected]>
When cursor >= historyEntries.length (no new gateway messages), the
forward-match fallback strategy could produce false positives if the
local store contained duplicate entries from a previous mis-sync.
This caused the same user message to be re-added every 10-second poll
cycle, creating an infinite loop visible as repeated messages in the UI.
Add a cursor-stable guard before forward-match so that when the gateway
history count has not grown, we trust the cursor and return early.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
On Windows, spreading process.env produces a "Path" key (not "PATH").
The cliShimDir logic then created a second "PATH" key referencing the
non-existent env.PATH (undefined), resulting in a PATH containing only
cliShimDir. This caused gateway child processes to fail with ENOENT
when spawning System32 executables like taskkill.
Fix: read env.PATH || env.Path to capture the actual full path before
prepending cliShimDir.
Parse standard 5-field cron expressions into localized descriptions
instead of showing raw expressions like "Cron · 55 11 * * *".
Supported patterns:
- Fixed daily time: "每天 11:55" / "Every day at 11:55"
- Minute intervals: "每 5 分钟" / "Every 5 minutes"
- Hour intervals: "每 2 小时" / "Every 2 hours"
- Single weekday: "每周一 09:30" / "Every Monday at 09:30"
- Weekday range: "工作日 09:30" / "Weekdays at 09:30"
- Monthly day: "每月 15日 08:00" / "Monthly on the 15th at 08:00"
- Complex expressions fall back to "Cron · expr"
All labels use i18n keys (12 new keys added for zh/en) with no
hardcoded language conditionals. Time is displayed in HH:MM format.
- Append local timezone offset to legacy `at` datetime strings that lack
one, preventing the OpenClaw gateway from interpreting them as UTC
(8-hour drift for UTC+8 users).
- Skip one-time `at` tasks whose scheduled time is already in the past,
since the gateway rejects them and they would never fire again.
- Add `agentId: "main"` to migrated tasks so gateway routing works.
- Expand cron session key regex to handle `agent:{id}:cron:{jobId}` format
generated by the gateway when agentId is set.
- Create human-readable cron session titles (e.g. `[定时] 任务名`) using
a job name cache populated during CronJobService polling.
- Introduce `src/main/i18n.ts` as a lightweight main-process i18n module
with direct `t()` export, replacing hardcoded language conditionals in
trayManager and openclawChannelSessionSync.
Migrate the NIM (NetEase IM) channel from a locally-managed native SDK gateway
to running through the OpenClaw runtime plugin (`openclaw-nim`), aligning it
with the existing pattern used by DingTalk, Feishu, Telegram, Discord, QQ, and
WeCom channels.
Key changes:
- **OpenClaw plugin registration**: Add `[email protected]` to `package.json`
channel plugins; register `nim` in channel-platform maps and config sync.
- **Gateway manager**: Remove direct NIM SDK lifecycle management (start/stop,
reconnect, event forwarding, credential hot-reload, probe mutex). NIM now
delegates to OpenClaw gateway via `syncOpenClawConfig` /
`ensureOpenClawGatewayConnected`, matching other OpenClaw channels. Add
`testNimOpenClawConnectivity()` for config-level validation. Expose
`getOpenClawConfigSchema()` to retrieve JSON Schema + uiHints from gateway.
- **NimConfig restructuring**: Replace flat config fields (`accountWhitelist`,
`teamPolicy`, `teamAllowlist`, `qchatEnabled`, `qchatServerIds`, `debug`)
with structured sub-objects (`p2p`, `team`, `qchat`, `advanced`), each using
`{ policy, allowFrom }` pattern. Update `nimGateway.ts` to consume the new
shape.
- **Schema-driven settings UI**: Add `SchemaForm.tsx` — a generic component
that dynamically renders form fields from JSON Schema + uiHints. Replace the
~230 lines of hardcoded NIM settings fields in `IMSettings.tsx` with a
`SchemaForm` instance driven by the OpenClaw config schema at runtime.
- **IPC + service plumbing**: Add `im:openclaw:config-schema` IPC channel,
preload binding, renderer service method, and TypeScript declarations for
the new schema API and restructured NIM types.
Signed-off-by: Dylan <[email protected]>
OpenClaw's gateway requires a non-empty apiKey for every provider,
even local servers (Ollama, vLLM, etc.) that don't enforce auth.
Previously, only Ollama with Anthropic format got a dummy key
('sk-ollama-local'), leaving Ollama with OpenAI format (and any
other no-auth provider) with an empty string. This caused the
gateway to reject requests with "No API key found for provider".
Now both resolveCurrentApiConfig and resolveRawApiConfig supply
'sk-lobsterai-local' as a placeholder whenever the provider does
not require an API key and none is configured.
Closes#452
Co-Authored-By: Claude Opus 4.6 <[email protected]>
Show both Anthropic and OpenAI compatible URL examples side by side,
using SiliconFlow as the example provider.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
The previous fix (#462) stripped multiple endpoint suffixes
(/chat/completions, /messages, /completions, /responses) which was
overly broad. Only /chat/completions has been confirmed to cause
the duplication issue. Narrow the scope to match the detection logic
in buildOpenAIChatCompletionsURL (coworkFormatTransform.ts).
Co-Authored-By: Claude Opus 4.6 <[email protected]>
When users configure a Custom provider with a full endpoint URL like
`https://gw.example.com/v1/chat/completions`, the OpenClaw gateway
appends `/chat/completions` again, producing a doubled path that
returns HTTP 404.
Strip well-known endpoint suffixes (/chat/completions, /messages,
/completions, /responses) from the base URL before writing it to
openclaw.json so the gateway can append its own path correctly.
Closes #xxx
Co-Authored-By: Claude Opus 4.6 <[email protected]>
The readRequestBody scoring heuristic incorrectly chose GB18030 over UTF-8
for short CJK strings like "你好" (score 11 vs 10), causing garbled session
titles (e.g. "浣犲ソ"). Add a score margin of 5 so GB18030 only wins when
the evidence is strong.
Also add diagnostic logging for API request URLs and title generation to
aid future debugging.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
Previously lastSystemPromptBySession was cleared after every turn,
causing the full system prompt to be re-injected into the user message
on each subsequent turn. Persist the cache across turns so the prompt
is only sent on the first message (or when it actually changes).
Closes#453
The 20s probe request blocks message sending and times out when the API
endpoint is slow or behind a proxy, causing a confusing
"Model validation timed out after 20s" error. Let the engine handle API
errors directly instead.
Closes#454
Co-Authored-By: Claude Opus 4.6 <[email protected]>
When a channel turn (e.g. DingTalk) ends with stopReason='error' or a
chat error event, syncChannelAfterTurn was never called. This meant the
AI reply already delivered to the IM user was never pulled into the local
session, so LobsterAI showed an empty error session with no messages.
Save turn.sessionKey before cleanup and call syncChannelAfterTurn in
both handleChatFinal (stoppedByError path) and handleChatError so the
incremental gateway history sync runs regardless of whether the turn
succeeded or failed.
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>