Move llama.cpp chat and local embeddings onto a verified externally managed llama-server runtime. Remove the in-process native runtime, forked embedding workers, and node-llama-cpp dependency while preserving guided setup, local GGUF models, tool-capable agent runs, diagnostics, and operator docs.
* ci: run push compacts without tooling lanes and stripe giants three-way
* fix(ci): keep CompactNodeTestPlanMode internal
knip --production flags the exported type as unused; no external module
imports it.
* ci: shard hosted lint and node tests
Split core oxlint across hosted runners and use backend-specific compact Node admission caps while preserving Blacksmith behavior.
* test(ci): guard plugin prerelease planner isolation
* fix(docker): harden runtime image dependencies
* chore(deps): update container security dependencies
* docs(docker): explain image security contents
* test(browser): align file-chooser and install tests with #114506 contract
* test(browser): restore extension install test isolation
* test(browser): add temporary CI diagnostics for pre-registration refusal
* test(browser): make install fixture interpreter hermetic
The suite passed process.execPath as the native-host interpreter; on
GitHub-hosted runners the hostedtoolcache node binary is group/world-
writable, which installChromeExtensionBootstrap correctly refuses, so
every registration test failed CI-only. The fixture now provides an
owned 0700 interpreter; only the launcher-exec test keeps the real
node it must spawn.
* fix(qa-lab): stop re-polling after a probe consumes the discovery deadline
The Matrix health-probe loop re-entered when the probe timeout fired
marginally before Date.now() crossed the deadline, starting a doomed
extra probe. Flaked on contended CI runners as 'expected 1 fetch, got
2'. A timed-out probe now ends discovery.
* test(ui): poll the callout inset invariant in device-scope E2E
One-shot boundingBox reads raced the nav-collapse transition and
intermittently measured a 20px stale offset on CI.
* fix(ui): make active visibility pill distinct from its hover state
* test(ui): advertise sessions.create in the control-ui mock harness
* ci: refresh pull_request run after zombie-queued Blacksmith jobs
Preserve ANSI output for reusable validation job-log reads while retaining a bounded fallback for GitHub CLI versions that do not support the raw-output flag.
The discord extension shard's silent transform/import startup (~210s measured
on a loaded macOS host) exceeded the run-vitest wrapper's 120s default
no-output timeout, so the watchdog killed healthy runs (#123025). The config
now gets the extra-long budget like the other large shards.
Three discord tests mocked openclaw/plugin-sdk/runtime-env with bare factories
missing most exports; under isolate=false they poison the shared worker module
cache and break later files that bind logVerbose/sleepWithAbort through the
shared process test harness. They now spread importOriginal and override only
their stubs.
Fixes#123025
* refactor(plugin-sdk): replace API baselines with diffs
* perf(plugin-sdk): bound API diff resources
* fix(plugin-sdk): isolate API diff dependencies
* fix(release): forward Plugin SDK acknowledgement
* fix(release): enforce SDK acknowledgement on publish
* chore: preserve generated-doc ignore policy
* fix(release): freeze SDK API evidence before publish
* fix(ci): satisfy SDK evidence guards
* fix(release): bind complete SDK evidence
* fix(release): authenticate plugin SDK evidence
* fix(plugin-sdk): abort interrupted API diffs
* test(ui): freeze page clock in background-tasks rail e2e
The rail transcript is compared byte-for-byte across the detail-panel
round-trip while it renders live relative ages; on slow CI runners the
second boundary ticks between the two reads (11s -> 12s) and fails the
equality assertion. Fix the page Date with Playwright setFixedTime while
keeping timers running so the tasks.list polling assertions still hold.
Repro: a 1.5s stall between the reads fails pre-fix with the exact CI
diff and passes post-fix.
* fix(scripts): drop unused export on dependency-evidence CLI main
Knip's workflow scan re-roots script references after an actions/checkout
step that sets path:, so the new trusted-tooling checkout in
openclaw-npm-release.yml stops marking this CLI as a workflow entry and
its exported main() surfaces as an unused export in check-dependencies.
Nothing imports main; the module invokes it through its own entry guard,
so the export keyword was dead surface either way.
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* feat(protocol): add portal methods and event
Bump the reviewed protocol owner-module count from 55 to 56.
* feat(gateway): add portal service and reverse proxy
* feat(agents): add portal tool
* fix(gateway): refine portal URL and proxy auth
* refactor(gateway): keep portal helper types private
* fix(gateway): declare portal transport service
* test(gateway): satisfy portal proxy lint
* test(gateway): narrow websocket payload types
* refactor(protocol): compact portal schema exports
* fix(gateway): export portal protocol types
* feat(ui): add portals page
* docs(gateway): add portals guide
* fix(gateway): dial portal targets via localhost dual-stack
Vite and other Node >=17 dev servers bind ::1 only for localhost, so a
fixed 127.0.0.1 dial 502s on the default path. Use hostname localhost
with family autoselection and rewrite Host to match.
* fix(gateway): type portal dual-stack connection
* fix: satisfy portal integration gates
* fix(gateway): isolate portal cookie jars per target
Cookies are hostname-scoped, not port-scoped, so the per-port origin
split alone let Gateway plugin-auth cookies reach agent-run targets.
Forward only cookies carrying this portal's own name prefix (stripped),
rewrite target Set-Cookie names to the prefixed form incl. the WS 101
handshake, and drop Domain attributes.
* fix(ui): detect unreachable portals behind proxied gateways
Probe the portal origin from the browser (no-cors, 4s timeout) and show
a recovery notice with the gateway-host URL instead of a dead iframe
when only the gateway port is exposed (Serve/Funnel/reverse proxy).
Docs: cookie isolation + reachability; zh-CN glossary entry.
* test(ui): satisfy portal reachability lint
* test(gateway): provide control UI request hosts
* chore(protocol): regenerate after rebase
* fix(gateway): namespace portal auth cookies by listener
* fix(gateway): scope portal token URLs to write-capable clients
The portal bearer token rides in the summary url/tokenQuery; portal.list
is operator.read and portal.changed fans out to read subscribers, so a
read-only client could harvest an openable URL. Make those fields
optional, redact them from read-scope list responses, and drop them from
every portal.changed broadcast; write/admin clients still receive them
and the UI refetches the list on change.
* docs(web): list the portals route
* fix(gateway): type portal open credentials
* docs(gateway): clarify portals PORT/PUBLIC_URL are agent-set
Opening a portal creates only the proxy listener; the agent sets PORT
and PUBLIC_URL in its own exec command, matching the portal tool
contract. Removes the implication of an automatic env handoff.
* chore(protocol): regenerate portal models
* style(gateway): format portal method-order assertions
Rebase union-merge left the portal.list assertion wrapped; oxfmt fits it
on one line.
* chore(plugin-sdk): refresh API baseline after rebase
* chore(plugin-sdk): refresh API baseline after rebase
* chore(protocol): refresh portal event order after rebase
* chore(plugin-sdk): refresh API baseline after rebase
* fix(gateway): pin portal referrer policy to no-referrer
The portal URL carries its bearer token in the query, and upstream
response headers are copied verbatim, so a target answering with
Referrer-Policy: unsafe-url could leak that URL to every third-party
origin it references. Force no-referrer after the copy and drop any
inbound Referer that still carries the token before forwarding.
Three defects proven by running the shipped configs:
Kubernetes probes checked only the status code against /startupz, but the
pinned image predates that route and the Control UI answers unknown paths
with a catch-all 200. A wedged pod was therefore marked Ready forever.
Probes now assert the JSON probe contract and target routes the pinned
image actually serves; verified in a kind cluster where the old command
exits 0 on the missing route and the new one exits 1.
render.yaml set no dockerCommand, so the image CMD ran without
--allow-unconfigured and a fresh Render disk exited 78 with 'Missing
config' before binding. Reproduced locally with Render's exact env.
The Cloudflare Container readiness poll had the same route mismatch
against operator-supplied official image digests; it now polls /healthz,
which every published image serves.
Also replaces an R2 verification step that could never fail: wrangler
cannot list object keys, so the documented command 404'd into || true.
* refactor(config): retire dead streaming.progress.render key
The key had zero runtime consumers after #122552. Core doctor now strips it via stripRetiredTuningKnobs, and production LOC is net -13.
* fix(tooling): pin plugin SDK surface counts to the reduced export set
The retired progress-draft render reader counted twice via channel-outbound and channel-message's wildcard re-export.
* feat(ui): open subagent details in chat sidebar
* chore: drop changelog edit (release generation owns it)
* refactor(ui): drop duplicate close in subagent detail panel
The sidebar region header already owns a Close Details control in both
wide and narrow layouts; the panel-local X duplicated it 40px away.
* fix(ui): stop subagent transcript loader when pane presentation retires
Pane retention wipes sidebarContent directly, so the detail slot's
render-time reset can never run again; a pending refresh timer plus
incoming task events kept refetching chat.history for a hidden panel.
* docs(ui): note close-control ownership in subagent detail header
* fix(ui): break transcript renderer import cycle
* fix(ui): use shared action cursor for subagent rows
The template shipped without the operator-facing half: no way to prove the
restore path works before depending on it, no cost signal for the always-on
versus webhook-only choice, no log surfaces, and no failure-mode index.
Adds an architecture diagram, a restore rehearsal, measured recovery numbers
from the real-R2 proof, provisioned-resource cost guidance, wrangler tail
observability, and a troubleshooting index covering image architecture,
Litestream S3 credentials, the startupz/readyz split, and ephemeral-disk
config loss.
The PR-changed test planner fails safe to the compact full-suite plan for any diff touching packages/**, but that compact plan excludes all extension test configs, so mixed package+extension PRs landed with zero extension test execution (escapes: PR #120534 breaking extensions/codex run-attempt.native-hook-relay.test.ts, PRs #122163/#121522 and cd7b7f639d breaking media-understanding-provider.test.ts and thread-lifecycle.test.ts on main full runs). The preflight now appends whole-config shards for the diff's touched extensions whenever the precise plan fails safe; whole configs (not precise targets) because the fail-safe cause leaves the non-extension diff's extension impact unbounded.
* feat(deploy): add experimental Cloudflare template
* fix(deploy): keep container SSH debug access opt-in
* fix(deploy): satisfy scripts tsgo lane and model wrangler entrypoint in knip
* fix(deploy): model wrangler-consumed exports and isolated dependency in knip
The Worker default export and Durable Object class are instantiated by
wrangler from wrangler.jsonc, and @cloudflare/containers lives in the
template's isolated package.json — modeled per the deadcode checks' own
guidance rather than root-manifest changes.
* docs(deploy): align SSH bootstrap flow with the disabled-by-default policy
* refactor(plugins): retire deactivate hook alias
* refactor(plugin-sdk): prune retired facade exports
* test(logging): isolate logger test controls
* refactor(logging): internalize file transport controls
* test(plugin-sdk): preserve retired facade coverage
* test(auto-reply): remove stale diagnostic imports
* refactor(logging): delete dead config-read guard
shouldSkipMutatingLoggingConfigRead had no production caller even on main;
it survived the dead-export scan only via logger's testApi re-export. The
test-isolation commit removed that mask, exposing the fossil. Delete the
guard, its test-only re-export, its mock entry, and its dedicated test file.
* refactor(plugin-sdk): retire due compatibility subpaths
* test(plugin-sdk): type group policy predicates
* refactor(plugin-sdk): split removed subpath records
* refactor(secrets): remove retired collector barrel
* test(plugin-sdk): tighten wildcard surface pin
* refactor(plugin-sdk): retire matrix facade metadata
* style(plugin-sdk): format facade metadata
* fix(ci): load channel setup contracts from source
Repair the main-owned regression from 99d662473c (Peter Steinberger): the new env-contract test could consume stale ignored dist metadata instead of the checked-in plugin declaration.
* test(plugin-sdk): refresh API baseline after rebase