* feat: consume live featured catalog state
* fix(plugins): honor hosted curation for published bundled plugins
* fix(plugins): accept package-less trusted ClawHub provenance
* fix(channels): restart gateway when channels.start fails for unloaded plugin
After channels login, the CLI calls channels.start to reconcile the running
gateway. If the channel plugin was installed or enabled after the gateway
started, the plugin is not in the runtime registry and channels.start rejects
with 'invalid channels.start channel'. Detect this error and fall back to
gateway.restart.request so the gateway reloads plugins and starts the channel
automatically.
Fixes#90296
* ci: retrigger checks (update proof section format)
Co-authored-by: Cursor <cursoragent@cursor.com>
* ci: retrigger proof check (real behavior evidence)
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(channels): narrow unloaded-plugin restart
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(acpx): keep leaked non-openai model out of the Codex ACP thinking slot
Codex ACP spawn mis-routed an inherited non-OpenAI fleet default into the
reasoning-effort slot and aborted (#95780). Replace the splitter with a closed
classifier and make the spawn path provenance-aware: drop an inherited leaked
default so Codex starts on its own default, but fail closed with
ACP_INVALID_RUNTIME_OPTION when a caller explicitly selects an unsupported or
malformed model. Thread a modelExplicit flag from resolveAcpSpawnRuntimeOptions
through the ACP runtime ensure contract; strip it before the acpx delegate.
Dropping the inherited default only at ensureSession was not enough: the manager
still persisted the leaked model in runtimeOptions, and the first turn replayed
it through applyRuntimeControls -> setConfigOption(model), which the new
fail-closed Codex control path then rejected. ensureSession now reports the
effective model it applied on the returned handle (applied | dropped), and the
manager persists that effective model, so a dropped inherited default is never
saved or replayed as a model control before the first turn. Explicit unsupported
selections still fail closed at spawn and never persist.
* test(acp): split runtime config validation tests
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* [AI] fix(daemon): set default --max-old-space-size=8192 in gateway service env
Generate a managed NODE_OPTIONS with --max-old-space-size=8192 for the
gateway systemd service, discarding ambient host NODE_OPTIONS to prevent
--require/--inspect persistence. 8192 MB is chosen as a safe default
that prevents the common OOM crash-loop with the default Node ~4 GB heap
under sustained load.
Related to #96203
* fix(daemon): adapt gateway heap limit to host memory
* fix(daemon): keep gateway heap helpers internal
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* feat(gateway): add sessions.files.reveal to open a session workspace on the gateway host
Resolves the session's workspace root server-side (same resolution as
sessions.files.list), refuses exec-node and cloud-worker placements, and
opens the directory with the platform opener. The opener helpers move out
of config.ts into a shared open-path module that config.openFile now uses.
* feat(ui): always-on chat title bar with inline rename and workspace menu
Every chat pane now renders its header: click the session title to rename
it inline (Enter commits via sessions.patch, Escape cancels), a workspace
chip names the session's checkout with reveal/copy-path/copy-branch
actions, and cloud-worker sessions show a globe. The single-pane floating
toggle cluster and the transcript's reserved titlebar drag band fold into
the header, which keeps native macOS window dragging.
* fix(ui): resolve title-bar session aliases and clear the collapsed-nav overlay
Live verification found two gaps: route aliases like ?session=main showed
the generic key-derived title instead of the session's label (resolve via
hello defaults + key equivalence, matching the pane), and the floating
sidebar-expand pill overlapped the header title on plain web while the nav
was collapsed (52px inset, mirroring the native-shell clearances).
* fix(ui): keep the protocol schema layer out of the Control UI startup bundle
The title-bar change made session-row-badges runtime-import the
gateway-protocol barrel for one classifier, dragging typebox and every
schema into startup JS (395 KiB gzip vs the 370 KiB budget). The placement
vocabulary now lives in a dependency-free session-placement-state module:
the schema union derives from it, the barrel re-exports it, and the UI
deep-imports it (startup back to 361.8 KiB).
* fix(gateway-protocol): keep SessionPlacementStateSchema statically typed
Type.Union over a mapped array loses tuple inference and collapses Static
to never; the literal list stays explicit with a compile-time guard tying
it to the shared SESSION_PLACEMENT_STATES vocabulary.
* chore(protocol): regenerate Swift/Kotlin bindings for sessions.files.reveal
* chore: narrow new exports flagged by the deadcode gate
* chore(i18n): leave generated locale artifacts to the locale-refresh workflow
The generated-artifact isolation gate (067635cb51) landed mid-flight;
source PRs now carry en.ts only.
`taskkill.exe` can hang when the target process is stuck in a kernel wait
state (e.g. a pending I/O request). Add a 5-second timeout so QA scenario
cleanup fails closed instead of hanging the test runner indefinitely.
Co-authored-by: Peter Steinberger <steipete@gmail.com>
`taskkill.exe` can hang when the target process is stuck in a kernel wait
state. Add a 5-second timeout to both taskkill invocations in the gateway
child process tree cleanup so stalled termination fails closed instead of
hanging the QA runner.
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(pr): close the review follow-ups on early gate proof
Three fail-safe gaps from #109331's review that silently disabled the
speedup: a stale terminal failure blocked a gate-proven pending rerun
(now superseded only by a SCHEDULED run whose own gate passed — manual
runs still cannot mask unresolved failures, guard test intact); the
attempt jobs lookup read one page while full-scope runs sit near the
100-job cap (now pages until the gate job is visible); and same-attempt
completion between snapshot and re-read discarded valid gate evidence
(now only an advanced attempt or non-success completion rejects).
* fix(pr): only newer gate-proven reruns supersede a terminal failure
Autoreview P1: without an ordering bound, a stalled older run's passed
gate could mask a newer completed failure. The supersede now requires
the gate-proven scheduled run to be created after the failed run;
regression test covers the stalled-older-run case.
* feat(llama-cpp): add in-process text inference
* test(llama-cpp): narrow setup provider fixture
* fix(llama-cpp): trim public surface and refresh docs map
* fix(llama-cpp): import Context type in inference test
* perf(pr): accept gate-proven in-progress CI runs for landing
verify-pr-hosted-gates required the whole CI run to reach completed,
but openclaw/ci-gate — the branch-protection check — needs every
selected lane and fails on any non-success, so a successful gate bound
to the run's check suite proves the merge-relevant outcome while
post-gate stragglers (timing summaries, artifact uploads) are still
running. prepare-run now accepts an in-progress run whose own gate
check succeeded, cutting one to several minutes per landing. Exact
attempt binding via check_suite id prevents a stale gate from a
previous attempt vouching for a rerun; gate proof only applies to the
exact head under verification, never recent-parent evidence.
* fix(pr): bind gate proof to the run attempt, not the check suite
Autoreview P1: check suites survive reruns, so a prior attempt's
successful gate could vouch for an in-progress rerun that has not
reached its own gate. The proof now goes through the attempt-scoped
jobs listing (filter=latest): the gate job must carry the run's own
run_attempt. Regression tests cover same-run/previous-attempt,
different-run, failed, and missing gate jobs.
* fix(pr): rely on filter=latest for gate attempt scoping
Autoreview P2: the REST list-jobs payload does not expose run_attempt,
so requiring it rejected every genuine rerun gate. loadCiGateJobs
already fetches per run with filter=latest, which scopes to the current
attempt, so a run_id match binds the gate correctly without the absent
field.
* fix(pr): declare ciGateJobs in the hosted-gates evidence typedef
* fix(pr): un-export the internal gate helper and loosen test id typing
hasSuccessfulCiGateJob is only reached through collectHostedGateEvidence,
so exporting it tripped the hard-zero deadcode gate; the new test also
typed workflow ids as number against the unknown-shaped return.
* fix(pr): bind early gate proof to the run attempt
Autoreview P1: workflow attempts share a run id and filter=latest keeps
a not-yet-rerun job's prior-attempt execution, so a partial rerun could
surface an old successful ci-gate. The REST job payload does expose
run_attempt (verified live), so fetch the attempt-specific jobs endpoint
and require the gate job's run_attempt to equal the run's.
* fix(pr): revalidate the run attempt after loading gate jobs
Autoreview P2: a rerun starting between the workflow-run snapshot and the
attempt-specific jobs fetch could return a stale prior-attempt gate. Re-read
the run after fetching its jobs and drop the evidence if the run completed or
its attempt advanced, so early proof only applies while the same attempt is
still pending.
Two Code-section bullets: correctness on real inputs is mandatory but
over-engineering for imagined ones is a defect; extremely unlikely edge
cases are tradable for real simplification when the tradeoff is named.