* fix(plugins): keep official plugin config across bundled-to-external moves, harden npm metadata parsing
- doctor --fix and plugin auto-enable treat official-catalog plugin ids as
known configuration even when the external package is not installed yet, so
a bundled-to-external transition (codex) no longer silently drops the
plugins.allow entry and unregisters the agent harness; garbage ids are still
pruned, and the codex-specific diagnostic suppression in the doctor scan is
replaced by the generic official-catalog rule (#107226)
- npm view metadata normalization handles object and multi-version array
output, flat dist.* keys and nested dist objects, absent openclaw blocks,
selects the maximum satisfying semver (publication order is not semver
order), fails closed when a recognized range has no satisfying entry, and
names the missing fields in the error (#107842)
The #107219 register-crash fix (lazy binding-store opening so plugin
registration never touches runtime.state under the base runtime) landed with
#108311 alongside its regression test.
* style: format install-source-utils imports
* fix(plugins): align hook and tool registrations
Reuse gateway-owned plugin registrations for matching hooks and tools while loading only missing tool owners from narrower runtime scopes.\n\nCo-authored-by: w33d <w33d@steadholme.local>
* test(plugins): cover mixed registry tool owners
Verify gateway-pinned and compatible active registrations compose without another plugin load.
Co-authored-by: w33d <w33d@steadholme.local>
* refactor(plugins): keep pinned registry lookup internal
Reuse the existing runtime-state contract so hook and tool ownership does not expand the Plugin SDK surface or treat unpinned active registries as Gateway owners.
* test(plugins): complete hook context fixture
Supply the required tool name in the stateful hook ownership regression context.
* chore: leave contributor release note in PR
Normal contributor PRs do not modify the release-owned changelog; the PR body retains the release note and attribution.
* fix(plugins): align partial registry owners
* fix(plugins): preserve scoped tool diagnostics
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: w33d <w33d@steadholme.local>
* chore(plugins): gate deprecation hygiene in CI and purge internal deprecated usage
* fix(agents): migrate auth-profiles oauth import to OAuthProviderId
* fix(whatsapp): use durability param in delivery-recovery test
* chore(scripts): declare shouldRunDeprecationHygieneChecks in check-changed types
* test(plugins): update runtime-api guard for ssrf policy rename
Adds a Discord Activities integration so an agent can show a self-contained
HTML widget to Discord users, opened as a sandboxed Activity inside the client.
Off by default: routes, the discord_widget tool, and the launch handler register
only when channels.discord.activities is configured. OAuth identifies the user
and gates on the account allowlist; widget lookup is capability- or
instance-validated; token exchange is rate-limited; widget HTML runs in a
no-network sandboxed iframe.
New grouped sessions tool (patch: label/pin/archive/model/thinking; group
CRUD; owner-gated; no reset/delete/compact). sessions_spawn gains visible +
worktree via sessions.create with admission reservation and abort-confirmed
rollback; a started run with no run id is aborted and deleted rather than
left orphaned. subagents tool reads the unified task_runs ledger tree-scoped
(subagent/acp/media/cron) and cancels detached runs, enforcing the
controlScope gate for leaf callers. session_status gains a cost line.
Agent-origin model patches record a fallback marker; runs failing on
auth/billing/model_not_found revert to the last working model (transient
errors never revert), and an independent thinkingLevel change realigns the
marker so revert cannot clobber it.
Refs #107237
* feat(linux): canvas via CLI-node + Tauri app IPC bridge
* refactor: extract gateway helper modules
* build(linux-canvas): register plugin package in lockfile
* fix(linux-canvas): move canvas advertise test out of core, regen docs/protocol/deadcode
* fix(gateway): break node-catalog/registry import cycle via leaf normalize module; add canvas glossary term
* style: oxfmt invoke.ts and runtime.ts after buildNodeEventParams extraction
* fix(linux): load Canvas WebView via dedicated data_directory context
Wry's Linux/WebKitGTK incognito mode discards Tauri's registered
WebContext (wry webkitgtk/mod.rs), so the Canvas window got a fresh
ephemeral context without the openclaw-canvas:// scheme handler — the
bundled A2UI page never committed (stayed about:blank) and every A2UI
command timed out. Use an isolated cache-backed data_directory instead,
which keeps the protocol handler while still isolating Canvas storage
from the dashboard window.
* fix(linux): keep Canvas WebView ephemeral via incognito + data_directory
Autoreview flagged that a dedicated data_directory alone persists Canvas
browser state (cookies, localStorage, IndexedDB, service workers) across
restarts, so an agent that navigates Canvas to a site could leak an
authenticated session into a later session. iOS uses a non-persistent
store; Linux should match.
Add .incognito(true) alongside .data_directory(): the distinct directory
gives Tauri a fresh WebContext key so it still attaches the
openclaw-canvas:// protocol closure, and incognito makes Wry swap in a
fresh *ephemeral* context carrying those protocols. Live-verified on a
Wayland/WebKitGTK box: the bundled page still loads
(location.href=openclaw-canvas://localhost/index.html, openclawA2UI
present, A2UI renders) and the canvas-webview dir holds no persistent
cookie/storage files.
User-facing name is now OpenClaw (the system speaks); internal code name is
system-agent. Gateway methods crestodian.* -> openclaw.chat/openclaw.setup.*,
agent tool -> openclaw, reserved agent ids openclaw + retired crestodian.
openclaw setup routes: onboarding flags -> onboard, -m/--yes -> system agent,
bare configured interactive -> OpenClaw chat, unconfigured -> onboarding.
Hidden crestodian CLI and /crestodian TUI aliases kept; docs moved to
docs/cli/openclaw.md with redirect stub. macOS/Android strings in lockstep.
Refs #107237
Mechanical: manager, combined facade, install path, lifecycle, config
loading, and resolver types move to focused modules; the runtime facade
re-exports its prior surface so importers are unchanged. Deletes dead
tools-barrel re-exports flagged by check-dependencies. No behavior
change.
Plugins can register api.registerMcpServerConnectionResolver({serverName,
resolve}) to bind an MCP server's transport (url/headers) to the trusted
sender of each message. Server identity stays static; only the connection
is per-requester. Runtimes are cached per (sessionId, channel, accountId,
senderId); runs without a trusted requester fail closed (cron, subagent,
heartbeat). Resolved credentials never enter fingerprints or logs. Static
servers keep the existing session-scoped lifecycle byte-for-byte.
Enables per-user hosted MCP sessions (e.g. per-user OAuth email/calendar)
in multi-sender channels like Slack.
Refs #106229
* feat(linux): add node device capabilities
* fix(linux-node): actionable pending-approval error + node-host advertise integration test
* fix(linux-node): map geoclue access-denied to LOCATION_DISABLED; floor camera maxWidth to avoid zero-height scale
* fix(linux-node): clamp small camera maxWidth to 2 instead of default
* docs(linux-node): clarify where-am-i -t is a process timeout, not update throttle
* refactor(gateway): extract legacy-node filter + rejection hint to fit LOC ratchet; docs-map + deadcode baseline
* fix(gateway): drop now-unused DEFAULT_DANGEROUS_NODE_COMMANDS import after hint extraction
* test(node-host): drop imports orphaned by removed error-code test
Catalog session rows (sidebar context menu + click), the built-in viewer
header, and a new "Open Codex/Claude sessions in" preference can launch the
native CLI (codex resume / claude --resume) in the operator terminal on the
machine that owns the session.
- Gateway-local sessions spawn through the existing terminal launch policy
(sandbox/enabled gates preserved) with the resume command in the session cwd.
- Paired-node sessions run through a new seq-ordered node PTY relay: a
duplex node-host command streams PTY output via node.invoke.progress and
receives keystrokes/resize via a new node.invoke.input event, behind the
unchanged terminal.* client protocol (TerminalSessionManager gains a backend
abstraction; node relay reuses the streaming-invoke controller).
- Owner boundary: each plugin owns its resume command and builds argv from a
validated thread id; the gateway routes node opens through the node command
allowlist and plugin invoke policy (no advertisement-only trust), and nodes
re-verify session eligibility before spawning.
- UI setting catalogOpenTarget + canOpenTerminal capability gate every entry
point; capability requires the owning host to actually have the CLI.
Node PATH is normalized before command-availability probes, Windows .cmd/.bat
shims spawn via ComSpec, and catalog terminal opens reattach persisted tabs
before opening the new tab.
* feat(codex): continue paired-node Codex catalog sessions from the Control UI
Codex catalog rows on paired nodes were view-only. Node rows are now
continuable when the node advertises and permits the codex threads.list,
thread.turns.list, and cli.session.resume commands. Continue adopts the
session into a locked codex OpenClaw session (deterministic
harness:codex:node-session key, bounded native-history import, two-phase
initializing marker) and installs a webchat conversation binding
(codex-cli-node-session) through the new generic
bindPluginSessionConversation seam with attempt-ID guarded rollback, so
each web turn runs codex exec resume on the owning node via the existing
inbound_claim path. Local continue/archive semantics are unchanged;
archived rows are no longer offered as continuable.
* fix(plugins): serialize plugin session binds per session
Review P2: the bind rollback checked the attempt id and then awaited the
unbind, so a concurrent Continue could install a newer binding between the
two and lose it to the older attempt's rollback. All session binds for one
session key now queue through an in-process tail, so a failing attempt can
only ever roll back its own binding; the attempt-id guard stays as defense
against out-of-band rebinds.
* refactor(codex): extract node-continue and session-bind modules for the LOC ratchet
Pure movement to satisfy the changed-file TypeScript size ratchet: the
paired-node continue flow moves to session-catalog-node-continue.ts and
session-catalog-node-adoption.ts, catalog paging/parsing helpers to
session-catalog-parsing.ts, the Control UI session-bind seam to
session-conversation-binding.ts (+ session-key helpers), and the internal
conversation-id/channel-prefix helpers to utils. transcript-mirror.ts is
restored to main byte-for-byte; the node history builder constructs a plain
CodexThread (only id is required) instead of widening signatures.
* fix(plugins): satisfy preserve-caught-error in the session-bind rollback
* fix(codex): gate node continue on operator.admin and resume by CLI session id
Second review round: (1) bound node turns pass canMutateCodexHost only for
owners/admins, while sessions.catalog.continue is operator.write — a
write-scoped Control UI client could adopt a node session whose every turn
would then be rejected. The gateway now forwards the caller's scopes to the
provider (SessionCatalogContinueProviderParams.clientScopes) and codex fails
node continues closed without operator.admin, before the dedupe join so a
non-admin cannot ride an admin's in-flight operation. (2) The node binding
stored the app-server threadId, but codex exec resume takes the CLI session
id and forked threads share a session tree where the two differ; the binding
now prefers record.sessionId.
* fix(codex): defer node-session restore until the binding is durable
Third review round: continuing an archived adopted node session unarchived
it before the gateway installed the conversation binding, so a failed bind
left a visible session with no node routing. The unarchive now rides
afterConversationBound; finalizeNodeAdoptedSession already covers both the
initializing and finalized marker states, so the eager restore helper is
deleted.
* refactor: delete dead infra and config exports
* refactor: preserve live infra and config contracts
* refactor(config): remove obsolete file-store lifecycle APIs
* refactor(infra): finish current-main dead export cleanup