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.
Slack and Discord routed system events now retain route ownership when they enter literal-global queues, so sibling agents cannot consume them. Genuinely unbound routing remains an explicit error instead of falling back to a default agent.
* 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>
* fix(sessions): isolated gateways no longer inherit HOME external session catalogs
A gateway on isolated state (custom OPENCLAW_STATE_DIR/CONFIG_PATH/OPENCLAW_HOME,
relocated home, or any named profile) listed, read, continued, archived, and
reopened the operator's real Claude Code/Codex/OpenCode/Pi sessions from the
process HOME. External catalogs now require the default install identity for
process-HOME scans: every catalog verb receives the isolation policy and rejects
HOME-fallback local targets, unknown providers fail closed unless they declare
supportsProcessHomeIsolation, and one structured warning records the skip.
Paired-node hosts and explicitly rooted stores (CLAUDE_CONFIG_DIR, CODEX_HOME,
OPENCODE_DB, Pi session dirs) keep working; default-identity gateways are
unchanged.
* fix(sessions): inject catalog HOME-isolation fact at registry construction
* chore(sdk): regenerate plugin API baselines after rebase
* chore(sdk): regenerate plugin API baselines after rebase
* fix(sessions): distinguish transient lookup failures from policy denials
Make spawned-session ownership lookup failures observable and
distinguishable from genuine tree-visibility policy denials, with
retryability classification so permanent credential/configuration
failures are not mis-framed as transient. Addresses issue #114653.
Diagnostic layer only — does not add retry (maintainer product decision)
and does not address the recency-window denial path (#114180).
Core changes:
- session-visibility-internal: add a core-private three-state lookup result
(ok / failed with retryable flag) plus classifyLookupRetryable (transport
closes/timeouts and retryable request errors => retryable; credential/auth/
unknown => non-retryable) and a shared lookupFailedDenialSuffix.
- session-visibility guard: pick the denial text by retryability so transient
failures say "retry" and permanent failures say "check gateway configuration
and credentials"; both still fail closed. Preserves the shipped public
SessionAccessResult, Set<string> checker input, and test-hook contracts.
- sessions-resolution sandbox resolver: propagate the same retryability-
classified denial; pass allowMissing: true on the speculative
sessions.resolve probe and suppress the warn for the expected "No session
found" miss so ordinary non-owned targets do not log as operational lookup
failures (reuses callGatewayResolveSession for older-gateway compatibility).
- Tests: transient + permanent error coverage for both guard and sandbox
resolver paths; classification contract locks; non-owned-miss quietness
regression; redaction through formatErrorMessage.
* fix(lint): remove unnecessary Boolean() conversion in sessions-resolution
oxlint typescript(no-unnecessary-type-conversion) flagged the redundant
Boolean() wrapper around String.includes() which already returns boolean.
Use ?? false to preserve the undefined->false semantics from the optional
chain.
* fix(sessions): keep unknown lookup failures generic
* fix(sessions): narrow ownership lookup diagnostics
* test(sessions): format visibility coverage
* fix(sessions): preserve cross-agent denials on lookup failure
A failed spawned-session ownership lookup override was relabeling the row
checker's deterministic cross-agent policy denial as a retryable lookup
failure. Retrying the spawned ownership lookup cannot make a known
agent:other:* target eligible, so the cross-visibility refusal must be
preserved as-is.
Constrain the lookup-failure override to same-agent targets by mirroring
the row checker's agent resolution: when the target resolves to a
different agent than the requester, keep the original cross-agent denial
rather than overriding it. Add regression coverage for the cross-agent
case under a failed tree-visibility lookup.
ClawSweeper review P2: preserve cross-agent policy denials on lookup
failure.
* fix(lint): avoid implicit boolean coercion in cross-agent check
Replace the `!!id` coercions in `isCrossAgentTarget` with an explicit
empty-check so the return value reads as a plain inequality. The
`no-implicit-coercion` lint rule flags `!!` on already-resolved strings;
both ids are guaranteed non-empty by the resolve-or-return-false guards
above, so the explicit form is equivalent.
* fix(sessions): preserve lookup failures for owned children
* fix(sessions): preserve ownership lookup failures
* fix(sessions): carry ownership proof through tool access
* fix(plugin-sdk): remove stale visibility imports
* fix(sessions): preserve legacy ownership fallback
* fix(sessions): block incognito status access
* fix(sessions): preserve deterministic policy denials
* fix(sessions): block incognito semantic-current status
* test(channels): isolate env preflight contracts
* test(sessions): align ownership checks with access owner
* style(sessions): format rebased access paths
* fix(sessions): require resolved owners at access checks
* test(gateway): supply explicit session owners
* test(sessions): model expected key misses precisely
* fix(sessions): scope delegated access to target owner
---------
Co-authored-by: Dallin Romney <dallinromney@gmail.com>
* 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.
* fix: prompt caching breaks on Claude Opus 5 and Sonnet 5
shouldPreserveThinkingBlocks() matched modern Claude ids with a hardcoded
family list plus /claude-[5-9]/ future-proofing. That regex assumes the
generation follows the prefix (claude-5-x), but shipped generation-5 ids
place the family in between (claude-opus-5, claude-sonnet-5, claude-mythos-5),
so none matched and dropThinkingBlocks flipped to true. Signed thinking blocks
were stripped from replayed history, diverging every request and invalidating
the Anthropic prompt cache on the default opus/sonnet aliases.
Read the generation from the id instead, preserving blocks for generation 4
and newer. This handles both id shapes and matches the idiom already used in
audit-extra.sync.ts and live-model-filter.ts.
Closes#121251
* fix: correct Claude thinking replay contract
Use the canonical Claude model identity and exact Anthropic preservation contract when deciding whether replay may retain prior thinking blocks. Carry deployment metadata through provider-family and fallback paths, and include canonicalModelId in the transcript policy cache key.
Release note: Restore prompt-cache reuse for Claude Opus 5 and Sonnet 5 while continuing to strip unsupported Sonnet and Haiku 4.5 thinking history.
* refactor: remove unused replay compatibility helper
Use the canonical Claude drop predicate at the remaining test call sites and remove the unconsumed preservation export so the dependency/dead-code gate stays clean.
* fix: keep Claude replay contract internal
Use canonical llm-core identity resolution without exporting a new llm-core capability, avoiding unintended Plugin SDK API drift.
---------
Co-authored-by: FullerStackDev <263060202+fuller-stack-dev@users.noreply.github.com>
* 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
* feat(slack): render live session cards as the default Slack progress mode
Slack streaming.mode default flips partial->progress.
Progress mode renders one live Block Kit session card with a status header, narration, plan, activity, diff stat, and elapsed time; it is edited in place and finalized to success or error with an Open in OpenClaw button when gateway.publicOrigin is set.
Final assistant text always delivers separately.
The shared progress compositor gains a success-only additive per-turn diffStat mirroring the task ledger fold.
resolveGatewayPublicOrigin is exported through the plugin SDK.
The diffs viewer URL falls back to publicOrigin.
The old rich/text progress render fork is deleted.
Native task cards remain unchanged and opt-in.
* chore(config): regenerate bundled channel config metadata
* refactor(slack): keep session card state type internal
* refactor(slack): split session-card and diff-stat owners under lint ceilings
* refactor(channels): reuse diff-stat type from its owner module
Import ChannelProgressDraftDiffStat from progress-draft-diffstat instead of
redeclaring it in the compositor, resolving the all-exports deadcode scan.
* chore(plugin-sdk): regenerate api baselines for channel barrels
Baselines drifted after the rebase reconciled them against main; regenerate to
match the branch's actual channel-message/channel-outbound surface.
* fix(slack): drop a session card that cannot terminalize after final delivery
If the final reply is delivered but the terminal card edit fails, the caller
now clears the stale card instead of leaving it stuck in its Working state
(mirrors the pre-card preview cleanup). Adds a transport-failure regression and
corrects three tests that asserted the prior ignore-the-result behavior.
Documents resolveGatewayPublicOrigin as a dependency-light runtime helper on the
config-contracts SDK subpath, which previously described a type-only surface.
Addresses ClawSweeper P2 (unfinalized card) and P1 (runtime SDK contract).
* fix(slack): suppress default tool messages under the default progress card
resolveChannelStreamingSuppressDefaultToolProgressMessages re-derived the stream
mode from config with an "off" default, unlike its sibling resolvers which take
a caller-resolved mode override. After this branch made progress the Slack
default, a default-config channel turn saw mode "off" and left a stray
"Using tool: X" plain message posting alongside the session card. Thread the
caller-resolved mode through (compositor passes params.mode; Slack dispatch passes
slackStreaming.mode), matching resolveChannelStreamingPreviewToolProgress.
Retarget the progress-session-card delivery-trace golden at an EMPTY Slack config
so it proves the real default path; the regenerated golden is byte-identical,
confirming defaults now yield the clean card sequence (one card post, separate
final text, one terminal update with the Open in OpenClaw button, no stray tool
message). Switch the dispatch delivery-mode mock to the real resolver so the card
tests exercise the true channel default (automatic), not a hand-rolled one.
* chore(plugin-sdk): regenerate api baselines for the streaming mode param
resolveChannelStreamingSuppressDefaultToolProgressMessages gained an optional
mode override; the changed signature reflows the surface hash of every barrel
that re-exports it, so regenerate the affected baselines.
* chore(config): regenerate config baselines
* refactor(voice): promote realtime voice policy to talk owner
Move the exact-speech protocol (typed consult-call outcome instead of
string scraping), agent-proxy session instructions, and consult/wake-name/
barge-in policy defaults from the Discord plugin into src/talk, exported
additively via openclaw/plugin-sdk/realtime-voice. Wake-name enablement now
keys on a supportsActivationNameGating provider capability instead of a
hardcoded provider id. Follow-up to the voice split (#122479).
* fix(talk): bind exact-speech bypass to retained session state
Per ClawSweeper review on #122668: the protocol marker is untrusted model
tool-call text; only a parsed answer matching a currently retained
exact-speech text may select the privileged replay path. Unretained
marker calls route to a normal consult.
* refactor: use canonical re-export names
* fix(test): classify suite support as test source
* fix(agents): retarget gateway stub session-entry import
* test(gateway): retarget session-utils mock keys after alias removal
Use the selected harness for isolated title generation, including native Codex auth.
Retry failed dashboard titles from the first user message without overriding manual names.
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* refactor: consolidate coercion ownership
Centralize four canonical coercion helpers, migrate exact core and plugin duplicates through narrow Plugin SDK facades, and enforce declaration and plugin-normalization ownership boundaries.
The sweep adds eight focused SDK exports while deleting more production and tooling code than it adds. User-visible behavior is unchanged except for safer equivalent object and UI parsing at existing boundaries.
* fix: guard integer option ownership
Register resolveIntegerOption with the canonical function owner and extend the declaration-guard fixture so future local duplicates fail validation.
* fix: keep integer helpers on numeric facade
Remove the unshipped duplicate string-coerce exports and route every affected plugin consumer through the existing number-runtime contract.
* fix: point numeric coercion to number runtime
Make boundary and declaration diagnostics recommend the canonical numeric facade, with failing-before coverage for both guidance paths.
* refactor(qqbot): remove bundled extension source
Mechanical deletion half of the #107295 squashed rebase; the catalog
repoint and host integration land in the follow-up commit.
Co-authored-by: sliverp <870080352@qq.com>
* refactor(qqbot): install plugin from Tencent package
Squashed rebase of #107295 onto current main. Repoints the official
external channel catalog at @tencent-connect/openclaw-qqbot@2.0.1 and
adapts onboarding, doctor migrations, secrets, build guards, and tests.
Documents the known limitation that the external package does not
support structured SecretRef clientSecret values; operators move those
to QQBOT_CLIENT_SECRET or clientSecretFile before upgrading.
Co-authored-by: sliverp <870080352@qq.com>
* fix(doctor): reuse shared hasOwnKey record helper
The rebased QQBot migration carried its own hasOwnKey export, colliding
with the one main now ships in legacy-config-record-shared.ts.
Co-authored-by: sliverp <870080352@qq.com>
* fix(plugins): carry catalog integrity through the update bridge
The externalized-bundled-plugin bridge dropped the official catalog's
expectedIntegrity pin, so bundled-user updates installed the external
npm package without integrity verification. The bridge now carries the
pin for the catalog's exact npm spec and both bridge install calls pass
it through; update-channel spec overrides intentionally skip the pin
since it only covers the pinned version.
Co-authored-by: sliverp <870080352@qq.com>
* chore(plugin-sdk): refresh per-entrypoint API baselines
The QQBot compat export and bundled-type removal shift 26 entrypoint
closure hashes in the new split baseline layout.
Co-authored-by: sliverp <870080352@qq.com>
* refactor(qqbot): drop helper reintroduced during rebase
Main's coercion consolidation added this file after the deletion
commit's base; its only consumers were the removed qqbot sources.
Co-authored-by: sliverp <870080352@qq.com>
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(ui): hide unusable models from picker
* refactor(ui): remove stale model availability helper
* refactor(ui): simplify catalog state guards
* style: format provider catalog imports
* chore: refresh plugin SDK API baseline
* refactor(core): break provider catalog type cycle
* chore(protocol): refresh models list Swift output
* chore: refresh plugin SDK API baseline after rebase
* fix(gateway): preserve full catalog preload semantics
* fix(ui): keep model status within startup budget
* fix(ui): preserve provider status within startup budget
* fix(models): scope live catalog outcomes
* test(ui): expect agent-scoped model refresh
* test(ui): align model refresh e2e fixtures