Tracks per-skill usage from the skill.used diagnostic event (trusted-only
delivery, file-scoped identity), sweeps workshop-created skills daily from
gateway maintenance (active -> stale 30d -> archived 90d, pinned bypass,
restore-only unarchive, files never touched), filters archived skills from
snapshots fail-open, reports workspace-scoped overlap candidates, and adds
openclaw skills curator CLI, additive gateway methods, and a warn-only
doctor finding. Zero new config keys; SQLite/Kysely state only.
* fix(imessage): handle stdout/stderr stream errors in the RPC client child
A dead imsg RPC helper can emit an async error on any of its stdio streams.
On a raw stream an unhandled 'error' event throws and surfaces as an
uncaughtException, crashing the gateway. #75438 added this guard for stdin
but left stdout/stderr — on the same long-lived child — unguarded.
Route stdout/stderr stream errors through the existing failAll path via a
shared failFromStreamError helper, mirroring the stdin handler. Add a
regression test that emits 'error' on each stream and asserts the child does
not throw and in-flight requests reject cleanly.
* fix(imessage): terminate failed RPC transports
* test(imessage): exercise real stream failure
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(agent-model): omit synthesized maxTokens fallback (Fixes#98295)
The configured-fallback resolver used DEFAULT_CONTEXT_TOKENS (200_000) as
the last-resort value for model.maxTokens when no configured, provider,
or bundled-catalog value was known. For strict OpenAI-compatible providers,
model.maxTokens is forwarded as max_completion_tokens on every request,
so the synthesized 200k value became a wire-level output cap that exceeded
the provider ceiling and produced HTTP 400 (Param Incorrect) on providers
like Xiaomi MiMo (131_072 max) — reported for mimo-v2.5 / mimo-v2.5-pro
added via 'models add' on a custom provider entry.
The sibling applyConfiguredProviderOverrides path already omits maxTokens
when nothing resolves; align resolveConfiguredFallbackModel with that
behavior. When the transport sees an undefined model.maxTokens, it omits
max_completion_tokens entirely and the provider applies its own default,
which is the correct behavior when the user has configured no output limit.
contextWindow retains DEFAULT_CONTEXT_TOKENS as its local budgeting
fallback (it is not shipped on the wire).
* test(agents): prove unknown output cap omission
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Compaction failure and timeout recovery can no longer leave a session transcript assistant-last and wedged after restoring pre-compaction state.
Transcript-continuation failures now carry a typed agent-core error code and no longer demote the active model through model fallback scoring.
Surface: embedded agent runner compaction recovery, model fallback classification, packages/agent-core.
Refs #100312. Includes regression coverage for #99943.
Stalled sessions could leave a session lane occupied by an orphaned active task after the embedded run handle was gone. Diagnostics repeatedly reported the stuck state, but the lane stayed held and queued turns could remain blocked indefinitely.
Release ownerless active lane tasks after the stale threshold, while guarding against fresh lane work and raised compaction safety windows so recovery does not double-run serialized session work or overlap transcript compaction. Stalled model-call recovery also no longer requires an in-process embedded run handle, so no-handle stalls can be reclaimed by the existing conservative recovery path.
Refs #99847.
Refs #94650.
Wedged model calls could hold an agent session lane for the normalized max timer duration when the user disabled the run timeout. In production that path resolves to MAX_TIMER_TIMEOUT_MS, so a zero-progress model call could block sibling work for about 24.8 days instead of failing through the normal terminal outcome path.
Cap the lane watchdog at the default agent deadline plus the existing grace window while preserving explicit shorter run timeouts. The disabled/max-timeout case now releases the lane at the default 48h deadline instead of inheriting the timer sentinel.
Refs #97588.
Refs #94650.
* fix(chat.abort): pass stored sessionId to match active embedded runs
The chat.abort RPC path was missing sessionId in the abort resolver
call, unlike the /stop path which passes entry?.sessionId. This meant
resolveAuthorizedRunsForSessionKeys could not match active runs
registered by sessionId, causing chat.abort to report aborted:false
while the tool subprocess continued running.
Fix: load the session entry in the chat.abort handler and pass
entry?.sessionId to abortChatRunsForSessionKeyWithPartials, giving
it the same match dimension that /stop already uses.
* test(gateway): tighten chat abort session matching proof
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* test(qa): canonicalize channel routing scenarios
* test(qa): enforce Crabline actor allowlists
* test(qa): preserve canonical flow integration
* test(matrix): use retained topology fixture id
* test(qa): preserve WhatsApp live defaults
* QA: declare channel transport policy in scenarios
* refactor(qa): keep transport policy type local
* refactor(qa): keep transport policy on leaf contract
* test(matrix): use retained routing fixture
* feat(mobile): rename, delete, and create sidebar session groups on iOS and Android
Group section headers on the iOS Command Center sessions screen and the
Android sessions screen gain Rename/New/Delete group actions. Bulk ops
enumerate every member (active + archived, explicit high list limit since
the gateway caps an absent limit at 100 rows) and patch category per
session; delete keeps sessions and moves them to Ungrouped. Known custom
groups persist client-side (iOS UserDefaults, Android SharedPreferences)
so empty groups stay visible as move targets.
* chore(i18n): refresh native string inventory after rebase onto main
* feat(ui): rename, delete, and toggle sidebar session groups
Sidebar group headers gain a kebab + right-click menu with Rename group,
New group, and Delete group. Rename/delete enumerate every member session
(active + archived, across agents) via unbounded sessions.list queries and
patch category per session; delete keeps sessions and moves them to
Ungrouped. Stored-but-empty groups render as sections, and the sidebar
sort popover gains a persisted Group by toggle (Custom groups / None).
* test(ui): capture sidebar group management UI proof shots
* fix(ui): page session-group enumeration past the gateway's 100-row default
sessions.list caps an absent limit at SESSIONS_LIST_DEFAULT_LIMIT (100), so
the rename/delete member enumeration now walks nextOffset pages explicitly;
a silent cap would strand members in the old group on stores >100 sessions.
* chore(i18n): restore fallback-key tracking for new sidebar group strings after rebase
* chore(i18n): translate new sidebar group strings across locales
* fix(memory-core): clamp widen-fallback kNN k to sqlite-vec 4096 limit
The widen fallback in searchVector re-runs the kNN with k = vectorCount
(the full vector table size), unclamped. sqlite-vec hard-caps `k` at 4096,
so once a memory index exceeds 4096 chunks any query that triggers the
fallback fails with "k value in knn query too large".
Clamp the widen re-query to MAX_VECTOR_KNN_K (4096). 4096 oversampled
candidates is far more than enough to feed top-N hybrid ranking, so there
is no meaningful recall loss.
* test(memory-core): cover widen-fallback kNN clamp to sqlite-vec 4096 ceiling
Add two regression tests for the searchVector widen-fallback clamp:
- asserts a >4096-vector widen re-query caps k at 4096 (out-of-range k
would raise sqlite-vec "k out of range" and kill the whole search)
- asserts a sub-ceiling widen (30) passes through untouched, proving the
clamp is a ceiling and not a fixed value
Uses a prepare-mock on the KNN/COUNT statements rather than materializing
4097+ real vectors plus the native extension; the assertion is purely on
the k bind handed to the widen re-query.
* fix(memory-core): preserve filtered recall beyond KNN cap
---------
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
* fix(gateway): don't over-claim a crash on a 1006 abnormal close
A 1006 close code means abnormal closure with no close frame, most often a
connection dropped under a burst of concurrent tool calls or event-loop
saturation — the gateway process typically stays healthy. The troubleshooting
text listed "Gateway crashed or was terminated unexpectedly" as a co-equal
cause, so an operator could pattern-match it and restart a healthy gateway.
Lead with the actionable dropped-under-load cause and demote the
unreachable/terminated case to "rare; confirm it is still running".
Addresses ask 3 of #100941. The connection-pooling and concurrency-cap asks
are architectural and intentionally out of scope, so this does not auto-close
the issue.
Refs #100941
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(gateway): keep 1006 diagnostics cause-neutral
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
* fix(macos): keep onboarding green across gateway plugin restart
Activating the Codex candidate installs the codex plugin and the gateway
restarts itself to load it, dropping the activate RPC socket after the
server already persisted success. Onboarding then claimed every option
failed. Reconcile transport drops against crestodian.setup.detect before
reporting failure, and stop latching the finish-page skills load error
that fires while the pager pre-builds pages before the gateway exists.
* chore(i18n): refresh native inventory line numbers
The shared chat session sheet (iOS chat + macOS webchat) gains server-backed
search (sessions.list search param, 250ms debounce, cancellation-safe local
fallback when offline), an Active/Archived scope, swipe/context actions for
pin/rename/archive with optimistic updates and rollback, pin indicators, and
restore-on-open for archived rows. Archiving the open session switches back
to the main session so the composer never points at a send-rejecting session.
OpenClawChatTransport consolidates the two list requirements into canonical
listSessions(limit:search:archived:) with forwarding sugars (unreleased
internal API; all in-repo conformers updated: iOS gateway transport, demo and
fixture transports, previews, macOS webchat, test fakes). macOS webchat also
implements patchSession, giving the sheet's controls a live transport there.
OpenClawChatSessionEntry becomes var-based and gains pinnedAt/archivedAt;
the two full-init rebuild blocks in ChatViewModel collapse to copy-mutation
(the pattern that silently dropped newly added fields), preserving main's
model-identity/thinking-metadata semantics. A shared session list organizer
mirrors gateway ordering (pinnedAt desc, updatedAt desc, key) for cached and
offline lists, and pinned sessions survive the 24h recency cutoff in the
session picker.
Refs #100712