Retain terminal and assistant-completion facts when pre-start notifications exceed the bounded buffer. Exclude commentary from replies, accept legacy unphased final messages, and let completed terminal items own the final answer while rejecting interrupted, failed, or non-terminal outcomes.
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
Co-authored-by: Peter Steinberger <58493+steipete@users.noreply.github.com>
* fix(doctor): restore the pre-migration session index when runs share a store
Restore picked the winning archive by manifest iteration order, newest first.
When a legacy writer recreated an empty sessions.json between migrations, the
newest run had archived that empty file, so restore installed it and then
refused the only archive holding real sessions as a conflict.
Resolve one winning archive per destination before writing anything, preferring
the earliest run whose archive is still restorable. Losing archives are never
deleted and stay visible as conflicts.
Closes#116163
* fix(doctor): fail closed on ambiguous session restores
* test(doctor): key restore conflicts by archive
* fix(doctor): narrow restored archive lookup
* fix(doctor): stream restore archive hashing
---------
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
* fix(sessions): adopt concurrent ingress user turns
* chore: leave release notes to release prep
* fix(sessions): rebase stale metadata onto active tail
* fix(plugin-state): treat missing plugin-state table as empty on read-only fresh stores
Startup leases create state/openclaw.sqlite before the first plugin-state
write, so legacy-state migration detection read the file through a read-only
connection and failed on the missing plugin_state_entries table, blocking
fresh-state-dir gateway boots from reaching ready.
Fixes#117249
* test(plugin-state): split fresh-store read coverage into focused file
* fix(plugin-state): distinguish fresh stores from damaged schemas
* docs(gateway): document llamacpp toolSchemaProfile for custom llama.cpp endpoints
Custom openai-completions providers pointed at a llama.cpp / llama-server
endpoint bypass the GBNF-safe schema normalization that built-in llama-cpp,
ollama, and lmstudio providers apply automatically. The toolSchemaProfile:
"llamacpp" switch activates it, but the supported value was never named in
the docs, so users hit llama.cpp 400 'failed to parse grammar' on tools with
large minLength/maxLength (e.g. cron trigger.script, maxLength 65536).
- Expand the toolSchemaProfile row to name the values recognized on a model's
compat (llamacpp, gemini) and when llamacpp must be set explicitly;
cross-reference the unsupportedToolSchemaKeywords escape hatch.
- Add a 'Local models (llama.cpp / llama-server)' provider example showing
the required compat.toolSchemaProfile: "llamacpp" on a custom endpoint.
Related to #117070
Co-Authored-By: iCodeMate <noreply@anthropic.com>
* docs(gateway): describe llamacpp profile as stripping, not clamping
Address review feedback on #117120: the fallback sentence claimed the
llamacpp profile "also clamps" constraints, but cleanSchemaForLlamacppGbnf
only removes `pattern` outright and drops `maxLength` at or above the
2000-repetition threshold (LLAMACPP_GBNF_MAX_REPETITION_THRESHOLD). It
never clamps a value, and small bounds like `minLength: 1` are preserved
(confirmed by the projection test at src/plugin-sdk/provider-tools.test.ts).
Reword the escape-hatch sentence to describe the implemented stripping
behavior and contrast it with the unconditional keyword-list drops, so
operators do not expect a preserved bounded constraint.
No code or behavior changes; documentation only.
* docs(gateway): scope llamacpp profile description to its actual transformations
Address follow-up review on #117120: the toolSchemaProfile table row
described `llamacpp` as "rewriting tool schemas into the JSON Schema
subset llama.cpp can compile to GBNF," which overstates what
cleanSchemaForLlamacppGbnf does. The cleaner only removes `pattern`
outright and drops `maxLength` at or above the 2000-repetition threshold
(LLAMACPP_GBNF_MAX_REPETITION_THRESHOLD); it leaves every other keyword
and `minLength` untouched, and those are not guaranteed GBNF-safe.
Reword the row to state the exact transformation contract and explicitly
note it is a targeted compatibility cleaner, not a blanket GBNF-safety
guarantee. No code or behavior changes; documentation only.
Co-Authored-By: iCodeMate <noreply@anthropic.com>
* docs(gateway): tighten llama.cpp profile contract
* docs(gateway): order local model examples
---------
Co-authored-by: iCodeMate <noreply@anthropic.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
* fix(clickclack): answer thread replies past the server thread-reply window
Thread replies stopped being answered once a ClickClack thread passed 100
replies, with no reply, no transcript entry, no gateway warning, and no
replay after reconnect.
The gateway resolved a thread.reply_created event by fetching the whole
thread and searching its reply list. The ClickClack thread endpoint caps
that response at 100 earliest replies by default, so reply 101 was absent,
the lookup returned null, and the event was treated as handled while the
websocket cursor advanced past it. A DM thread reply was worse: it hit the
DM branch first, and thread replies never appear in the root DM timeline
that branch pages, so every DM thread reply was dropped.
Resolve the event's own message_id through the authoritative single-message
endpoint the plugin already uses, which collapses the three window
heuristics into one canonical path. Transient failures still propagate so
reconnect replays them; only a 404 resolves to unreadable, and that now
records an operator warning instead of counting as a handled event.
* test(clickclack): harden reply recovery proof
---------
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
The drain test asserted "close has not settled" after a fixed 20ms sleep.
Node only exempts a connection from close()'s idle sweep once its parser has
begun a message, so a client-side connect barrier let a loaded machine reach
close() before the server parsed the request: the socket was swept as idle,
the client saw ECONNRESET, and the drain settled inside the window.
Wait on capture admission (onRequestStart) instead, which fires server-side
after the headers are accepted, and move the assertion to just before the
successor is used so it proves the successor was minted mid-drain.
* fix(cli): update plugins doctor clean message (#115073)
Root cause: plugins doctor reports 'No plugin issues detected.' even when a selected context-engine plugin is quarantined at runtime, because doctor is a fast static control-plane check.
Architectural owner: plugins-cli.runtime.ts
Canonical fix: Change clean status message to direct operators to 'openclaw health' for active runtime quarantine/fallback status.
Production LOC delta: +2 LOC
* fix(cli): update plugins doctor clean message wording (#115073)
Root cause: Refine the diagnostic text message to run openclaw health directly.
Architectural owner: src/cli/plugins-cli.runtime.ts
* fix(cli): clarify plugin doctor scope
---------
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
* fix(plugin-sdk): project recorded health facts through computed account status
* fix(plugin-sdk): regenerate API baseline and pin ingressUnavailable literal in test
* fix(qa): update Codex auth fixture protocol version
* test(qa): bind Codex auth fixture to runtime version
* test(qa): load Codex fixture version through public surface