* fix(startup): keep legacy state repair in doctor
Separate current-state startup readiness from explicit Doctor migrations.
Preserve current config recovery, quarantine, device identity checks,
lease fencing and updater completion ownership.
Remove automatic startup migration/checkpoint and node-host import paths.
Keep shipped read-only context and roster projections unchanged.
* test(doctor): align proof callers with repair ownership
Keep survivor fixture setup in caller order and select the shared Doctor
flow separately from the channel-specific proof. Include the complete
isolated diagnostics dependency closure.
Prove ordinary startup preserves legacy directories before explicit
Doctor repair, and await SQLite worker closure before test cleanup.
* test(startup): verify index repair through gateway maintenance
* test(doctor): align cutover fixtures with state owners
Reacquire the database after Doctor retires its generation, retain real session history for startup refusal, and keep pending reads at their actual owners. Distinguish the reused readonly reader from independent snapshot-token children and verify every child settles.
* fix(doctor): preserve image activation and published migration receipts
Run the shared noninteractive Doctor owner before default and Compose Gateway
activation so retained Docker volumes keep their published upgrade path while
ordinary Gateway startup stays readiness-only. Preserve root selectors and
settle interrupted repair before executing the original command.
Retain the path-wide tombstones emitted by the published restart-sentinel
importer, including consumed notices, and validate completed source decisions
before retiring recreated inputs. Add the root-image activation lane and causal
receipt coverage without introducing a schema, option, or second importer.
* test(docker): preserve release state pairs in upgrade proof
Use the existing synthetic v2026.9.2 corpus instead of combining a July shared database with a later agent schema. Preserve its deletion journal, registry, both transcript payloads, paired backups, and unsafe-state controls. Register the two shell-launched helpers with Knip.
* test(docker): normalize persisted schema snapshot rows
Compare node:sqlite schema rows using the same plain-object representation as the saved JSON preimage. Keep strict schema, row, and value-type assertions intact.
* fix(models): retain discovered models after refresh failures
Record successful legacy catalog results at the producer boundary so unavailable refreshes retain the accepted inventory. Preserve explicit outcomes, advisory SDK fallback behavior, and first-discovery starter policy.
* fix(models): preserve skipped catalog outcome semantics
Mark bundled static, configured, and advisory catalog projections with
explicit empty outcomes so legacy success inference cannot promote them
to observed account inventory. Preserve live outcomes and helper types.
Keep exact auth provenance histories and move existing fixture/policy
code into focused owners where required by the line-cap ratchet.
Validation: 447 producer and sibling cases, 56 shared self-hosted cases,
95 auth/policy cases, causal missing-outcome failures, maintained checks,
and independent review.
* test(plugin-sdk): keep discovery loader types acyclic
Move the shared loader type into a leaf consumed by both discovery
contract helpers. Preserve its public provider-test-contracts export
without a child-to-parent type import cycle.
Validation: maintained Madge check reports zero cycles; core, all core
test graphs, extension test types, lint, formatting and independent
review pass. Runtime behavior and previous catalog proof are unchanged.
* fix(plugin-sdk): mark generated static catalogs explicitly
Keep the generated non-live, non-strict catalog adapter from claiming
successful acquisition for manifest or configured rows. Preserve null,
errors, strict and custom callbacks, static catalogs, and public types.
Validation: three existing controls fail before the correction; all49
owner and sibling cases pass afterward, with types, lint, line caps and
fresh independent review clean.
* test(gateway): cover restart import during state retirement
Exercise canonical database close while a legacy notice read is paused. Verify admission rejection, retained canonical and source bytes, no migration receipt, and joined maintenance custody without changing the accepted sidecar-stop drain contract.
* fix(test): drain sharing fixtures before removing state
* test: bind retirement regression to its own worker
* docs(docker): clarify automatic Doctor activation
* test(doctor): keep readiness fixture runtime private
* test: stabilize shared skill watcher fixture roots
(cherry picked from commit 15606be10e)
* perf(tooling): share indexed scope parsing for artifact scans
(cherry picked from commit 6e6eef9f5b)
* fix(team-reports): use source owners in scheduler tests
The source barrel retired in #157819, but the scheduler tests still imported
it, breaking the extension test typecheck on main. Import the Discord and
GitHub owners directly, matching the production caller.
Validated the original TS2307/TS7006 failure, the corrected extension type
graph, all 36 scheduler tests, changed checks, and independent P2 review.
(cherry picked from commit 7c4866c73b)
* test(install): isolate global npm configuration in version fixtures
Use a controlled absent global config inside the fixture home so the
release helper does not query the deliberately narrow npm stub. Keep
predecessor selection, fresh-install behavior, and expected exits intact.
(cherry picked from commit 0a9eefc76c)
* test(docker): verify the complete image activation entrypoint
Closes#152540
Credit to @yetval for the report and CLI reproduction.
## What Problem This Solves
Fixes: node approval documentation tells operators to run `openclaw approvals --node`, which the CLI rejects because `--node` belongs to the `get` and `set` subcommands.
## User Impact
User impact: operators can copy working commands to inspect or replace a node's exec approval policy from the Gateway.
## Why This Change Was Made
Both affected passages now use the canonical `approvals get --node` and `approvals set --node` forms, include the required `--file` or `--stdin` input for replacement, and link to the approvals CLI reference. The CLI contract is unchanged.
## Evidence
Head SHA: `a565f41297f4eb917d6bd7b8b941406642042d14`
- Red before: `pnpm openclaw approvals --node abc123` exited 1 with `OpenClaw does not recognize option "--node"`.
- `pnpm openclaw approvals get --help`: passed and exposed `--node <node>`.
- `pnpm openclaw approvals set --help`: passed and exposed `--node <node>`, `--file <path>`, and `--stdin`.
- `pnpm docs:check-mdx`: passed for 1,313 files.
- `pnpm docs:check-links`: passed with 14,114 internal links checked and 0 broken.
- `node scripts/check-changed.mjs -- docs/cli/node.md docs/tools/exec-approvals.md`: passed the docs-only lane.
- Changed-file `oxfmt --check` and `git diff --check`: passed.
`pnpm format:docs:check` could not run repository-wide on Windows because the generated formatter command exceeded the operating system command-line length. The same formatter passed both changed files, and the changed-file gate passed. No live paired node was used because this docs-only repair changes command spelling, which was verified against the real CLI command tree.
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* feat(node): update headless runtimes automatically when idle
Stage private packaged node runtimes and activate only after all owned work
and cleanup settle. Preserve pairing and launch options, keep automatic
activations at least 12 hours apart, and leave shared-state migration and
Gateway installation ownership with the normal updater.
Document defaults, opt-outs, runtime version discovery, and recovery.
Support recoverable Windows selector replacement across service restarts.
Refs #151462
* fix(node): complete auto-update integration and settings defaults
Capture the launcher in compiled test runtimes and trusted wrapper inventories, register the package update proof, and use the canonical Kysely read path. Preserve the inherited enabled state in the config UI and align caller tests with the node shutdown lifecycle.
* fix(node): preserve retained plugin work during automatic updates
Require an explicit idle result from plugin commands, preserve older-plugin work, and connect bundled lifetime owners to automatic node updates. Join canceled inference and failed terminal cleanup before command settlement; keep historical state repair with Doctor. Document compatibility and extend installed-package proof for retained legacy plugin work.
* test(browser): align idle-work fixtures with runtime exports
* test(browser): extract proxy request fixtures
* test(node): retain idle assertions across native cleanup
* fix(gateway): support pinned daemon runtime paths
* fix(node): preserve pinned runtime on reinstall
* test(cli): update daemon coverage runtime mock
* refactor(daemon): keep runtime pin value type private
Remove the unused export; the type remains owned by pin state.
* refactor(daemon): isolate runtime pin type contracts
Break the service contract cycle with a dependency-free type leaf.
Preserve runtime behavior and all original contributor ancestry.
* fix(daemon): preserve runtime pin intent across setup callers
Carry inspected pin revisions through configure, onboarding and Doctor installs.
Keep automatic runtime choices unpinned and explicit runtime resets intentional.
* perf(ui): keep attachment caching out of startup vendor code
Let the lazy text-attachment consumer own the Lit cache directive chunk.
Preserve existing compression and startup budgets.
* fix(ci): restore native fixture qualification
* test: adopt canonical fixture repairs
* fix(ci): align Slack contracts and cold fixture inputs
Adopt the published Slack lifecycle type and approval receiver repair.
Include the tracked plugin-source helper in cold preparation fixtures.
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* refactor(slack): adopt canonical ingress lifecycle type
* test(qwen): control elapsed time in request fixture
* test(ui): keep archive failure roster reads unavailable
---------
Co-authored-by: Jason (Json) <263060202+fuller-stack-dev@users.noreply.github.com>
Co-authored-by: Tuyen <6668014+darkamenosa@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* feat(node-host): advertise an explicit node command allowlist
Persist exact node command selection and restrict ancillary publication and hosting. Preserve the unchanged assertion baseline under the work-order stop rule; check:changed requests removing the obsolete runtime.ts count (2 to 0).
* feat(plugin-sdk): session transcript catalog reader
Expose bounded read-only native display pages and portable attribution through the existing runtime subpath. Keep pagination scoped to the original active transcript branch and allow an explicit bounded native cursor length.
* feat(session-share): read-only OpenClaw session catalog across paired gateways
Publish explicitly selected native session groups through two paired node commands. Validate the closed wire contract, reject remote profile claims, and keep receiver identity binding opt-in and display-only.
* fix(gateway): show published session catalogs to view-scoped roles
Let publication consent satisfy catalog read visibility for roles allowed to view others, while owner-only and unprofiled callers stay hidden. Preserve published attribution without accepting a remote local-session adoption claim. Regression tests reproduce four pre-fix failures; final validation stopped at the work-order baseline gate.
* docs: session sharing across gateways
Document sessions-only node setup, explicit publication groups, receiver attribution, view-scoped catalog access, and read-only limits. Add the bundled plugin inventory and generated reference entry. Live proof runbook remains outside the repository; build and rig execution are blocked by the work-order baseline restriction.
* fix(session-share): preserve source storage and paired reconnects
Respect configured stores through listing, paging, and revocation. Keep cold listings available and bound raw transcript reads. Prefer the established paired node credential on service restart, suppress unrelated host metrics, and refresh the approved plugin configuration docs.
* refactor(gateway): separate authorized catalog reads
Keep the catalog dispatcher within its owned scope and preserve post-read role checks and sender projection. Align the rebased tests with their shared setup and imports.
Related: #128446, #98045 (both remain closed)
## What Problem This Solves
Several manual node setup paths stopped after device approval. A node paused for pairing could remain disconnected; after reconnecting, its initial command surface could still be unapproved. Troubleshooting omitted that separate gate, while bootstrap instructions incorrectly said enrollment never approved the first declared surface.
## Why This Change Was Made
The current CLI, node-host, pairing/status, Windows, and troubleshooting pages now lead through device approval, restarting or rerunning a paused node, inspecting the separate surface request, and approving its distinct request ID. Initial empty surfaces are distinguished from pending expansions that retain approved, still-declared commands. SSH-verified and administrator bootstrap enrollment are distinguished from trusted-network device-only approval.
The join-code reference shows the exact `npx openclaw connect <url>` output and current core ownership. It distinguishes reachable TLS endpoints, explicitly configured loopback URLs, default-loopback URL-discovery refusal, and direct plaintext-LAN setup codes. The invoke reference retains the existing 30,000 ms transport default and explains its invoke-plus-10,000 grace. The current configuration example uses `tools.exec.mode`.
The old nodes index remains a routing page with its stable anchors. Already-landed identity, revocation and re-pair guidance is preserved in the current owning pages.
## User Impact
Operators can complete manual enrollment and identify the correct missing approval before changing shell policy. Bootstrap consent, later expansion approval, Gateway command policy, node-local exec approvals and platform permissions remain distinct. This changes documentation only; runtime, configuration schema, storage, protocol and security policy are unchanged.
## Evidence
- Complete source-to-document audit against pinned main `6d8281b385d0990d6245c2ca9e9cef083514c760`, including reconnect, surface reconciliation, bootstrap/trusted-network controls, core join routes, timeout registration/resolver and focused tests. Windows reconnect wording was checked against the separately maintained client source.
- Fresh source reviews resolved the three initial documentation findings; the final revision has no actionable source-to-document finding.
- Native docs inventory, changed-file checks, scoped MDX, Markdown lint, configuration examples, glossary, formatting and whitespace checks passed. The real publishing-parser anchor audit validates the changed documents. Its full run retains three unchanged maturity-page fragment errors also reproduced on pinned main with the same actual ClawHub documentation source.
- Full before/after Markdown and shared-parser HTML artifacts are retained. No node, Gateway, pairing, token, platform-permission or provider command was executed; tests were read as source evidence rather than reported as fresh runtime passes.
Thanks @yetval for the original cross-page correction and follow-up fixes. The contributor commits and previous review history are preserved; the older plugin prerequisite and 10-second invoke-command default are superseded by the current source facts above.
Independent artifact acceptance checked every affected manual entry path, including the standalone headless recipe and Windows troubleshooting. Its initial timeout-condition finding was corrected and freshly reviewed; final artifact results are 15 pass, 0 fail and 3 source/delivery limitations. Separate source evidence covers those source-only facts. The signed candidate passed the normal native commit hook with matching complete local/native indexed trees.
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* docs(nodes): split the nodes overview by reader job
docs/nodes/index.md was 68,029 bytes, 8,871 words and 35 headings mixing
pairing how-tos, node-host setup, session hosting, command-policy reference and
per-platform allowlists in one page. One H2, "Remote node host (system.run)",
parented 17 H3 sections that were not about system.run, and the page ended with
no next-steps list.
The page already lived in a directory with eleven siblings (audio, camera,
computer-use, images, location-command, media-playback, media-understanding,
presence, talk, troubleshooting, voicewake), so this extends that directory
rather than creating a parallel one. index.md becomes a real index at the same
/nodes route: intro, a "Node pages" list that also names the eleven existing
siblings, and the anchor table below.
Children, one per reader job:
- pairing-and-status.md - approve a node, read status and host stats, upgrade
a fleet across the N-1 protocol window.
- node-host.md - foreground, service, SSH-tunnel and headless node hosts,
gateway preconditions, identity state, and the system.* command surface.
- node-exec.md - allowlist commands, point exec at a node, raw node.invoke,
and exec node binding.
- mcp-and-skills.md - node-hosted MCP servers, node-hosted skills, and local
Ollama inference.
- session-hosting.md - nodeHost.workerRuns, device placement and capacity,
and container isolation.
- session-catalogs.md - Codex, Claude, OpenCode and Pi session discovery and
continuation on paired nodes.
- file-transfers.md - terminal uploads and the File Transfer plugin tools.
- command-policy.md - the platform default allowlists, dangerous-command
opt-ins, gateway.nodes/tools.exec config, and the permissions map.
- device-commands.md - widget panel, camera, screen recording, location, SMS
and device data CLI helpers.
Anchor strategy
Per-anchor redirects are not possible: redirectSource() in
scripts/lib/docs-redirects.mjs rejects any source containing [?#]. Every anchor
the old page published is therefore kept alive on the index itself as authored
<a id="..." /> stubs inside a "Where each section moved" list, each pointing at
its new home. Ids were computed with parseDocsDocument, not a slug
approximation, so the thirteen punctuated headings keep both their encoded and
their cleaned id (for example pairing-%2B-status and pairing-+-status). All 48
ids the pre-split page published resolve on the new index; the index publishes
only two ids of its own, node-pages and where-each-section-moved, so no stub
collides with a heading the index still owns. parseDocsDocument reports zero
collisions on the index and on every child.
Losslessness
Reassembling the 35 section bodies reproduces the original body byte for byte,
apart from the two declared link retargets below. Counts, original body vs
children:
- words 8,634 -> 8,634
- characters 66,320 -> 66,358 (+38, the two retargets)
- code fences 29 -> 29, identical fence for fence as a multiset
- markdown links 30 -> 30
- table rows 16 -> 16, all three tables byte-identical
- the per-platform default-allowlist table is byte-identical: 8 rows, with
iOS 11, watchOS 3, Android 19, macOS 14, Windows 6 and Linux 2 commands
No prose was rewritten. Two intra-page fragment links whose target moved to a
different child, both `](#command-policy)` in device-commands.md, became links
to /nodes/command-policy#command-policy; that is the whole +38 characters.
Sections keep their original relative order within each child, so the five
directional cross-references the page carried ("the environment fallback
above", "see above", "see below", "the static platform-default table above")
all still resolve on their own page.
Also updated: the "Nodes and media" nav group in docs/docs.json, eight in-repo
deep links repointed at the new pages (docs/releases/2026.9.2.md left
untouched, its anchor still resolves through the stubs), fourteen zh-CN
glossary entries for the new titles and index link labels, and
src/docs/config-path-docs.test.ts, which asserts on the `openclaw config`
bracket-path examples that now live in node-exec.md.
Closes audit findings: r3-0443, r3-0445, r3-0447
* docs(nodes): link the relocated device command examples from the exec page
The exec page's "(camera, screen, location, below)" pointed at helpers the
split moved to /nodes/device-commands. Replace the directional word with a
link. Found by ClawSweeper; the orphan-reference scanner's patterns do not
match a bare trailing "below" with no noun phrase in front of it.
Resolve the primary cryptographic device identity through the read-only owner API, retaining successful reads while retrying misses and errors. Compare authenticated device IDs for same-install version admission, keeping independent state directories separate and preserving locality, released-version, rejection-ordering, and reapproval guards.
Cover signed WebSocket list/describe and version admission with real SQLite pairing. Document instance IDs versus routed device identity.
Fixes#136593
* fix(daemon): stop reporting failed runtime probes as unsupported runtimes
The daemon runtime probe wrapped its exec in a bare catch that returned
`supported: false`, so any failure to *run* the probe was laundered into a
verdict that the runtime itself was unsupported. Operators on a perfectly
good Node install were told to install a Node version they already had.
Observed on Ubuntu 26.04 with Node 26.8.1: `openclaw node install` failed
with "No supported Node runtime was selected for the daemon" whenever the
process cwd was not readable by the service user (e.g. `runuser -u openclaw`
inheriting root's 0700 home over SSH), because every child spawn then fails
EACCES. The version logic was never wrong -- resolveSystemNodeInfo returned
supported:true and resolvePreferredNodePath returned /usr/bin/node when
probed directly on the affected host.
Node and Bun probes now share one resolver returning a closed
supported | unsupported | probe-failed union. A failed probe retains its
cause, executable, and cwd, and selection propagates that instead of falling
through to Node-upgrade advice.
Also:
- Derive the supported-version wording from NODE_RELEASE_FLOORS via a new
exported SUPPORTED_NODE_VERSIONS, replacing six hand-copied spellings that
omitted the >=25.9.0 line and told operators to downgrade.
- Forward OPENCLAW_WRAPPER through the node-host install path; the documented
escape hatch was previously gateway-daemon-only.
Production LOC net +6 (+156/-150); consolidating the duplicated Node/Bun
probes paid for the new failure handling.
* docs(cli): drop machine-local path from node probe-failure guidance
ClawSweeper P3: docs/AGENTS.md requires generic docs content with no local
paths. The probe-failure recovery example prescribed a specific directory;
state the readability requirement instead.
* refactor(state): fold singleton tables into config_machine_state at schema v11
Eight singleton tables (skill_curator_state, update_check_state,
clawhub_promotions_feed_state, model_catalog_remote, voicewake_triggers,
voicewake_routing_config, voicewake_routing_routes,
onboarding_recommendations) were each one logical JSON value behind a
fixed key; their bespoke schemas, lazy ensures, and per-table accessors
collapse onto the shared config_machine_state KV under namespaced keys.
cron_store_epochs retires outright: it was born write-only in #114388
and no reader ever existed in any language. Durable values (update
check state, voicewake triggers and routing, per-workspace onboarding
answers) migrate insert-if-absent during the v10->v11 migration; cache
class contents rebuild on next use. Deferred with named reasons:
exec_approvals_config (macOS direct-SQL contract), installed_plugin_index
(same-tx lease fence), node_host_config and web_push_vapid_keys
(secret-table git-backup redaction).
# Conflicts:
# src/skills/workshop/collection-review-state.ts
# src/skills/workshop/collection-review.gateway-admission.test.ts
* test: register v11 guard carve-outs and suppression pin
The v11 migration module joins the raw-SQLite allowlist (migrations are
the named guardrail exception), the lint-suppression allowlist records
the second type-parameter suppression in config-machine-state, and the
identity module keeps only externally consumed exports.
* test: surface CLI stderr when migration-diagnostic assertion fails
* test: expect migration diagnostics on stderr for models plain commands
The #129037 pending-migration cases asserted that aliases/fallbacks
lists never open the state database, but config-health observation
(observeConfigSnapshot -> readConfigHealthStateFromStore) full-opens it
on any config read whose file exists — reproduced identically on clean
main with a main-built dist. The protected contract is exact stdout;
the diagnostic legitimately lands on stderr for every case.
* test: drop unused defaults import from CLI stdout e2e
* test: split session path derivation out of oversized session-files suite
#130016 pushed session-files.test.ts to 1008 lines, over the 1000-line
lint cap and red for every PR's check-lint. The sessionPathForFile
describe moves to a self-contained sibling following the existing
session-files.*.test.ts split pattern; no assertions change.
* refactor(state): fold four more singleton tables into schema v12
tui_last_sessions (cache-class, regenerates on next session switch),
sidebar_sections (persistent section order, migrated as one JSON array),
node_host_config, and web_push_vapid_keys join the v12 fold-in, taking
the retirement to thirteen tables at the same version. The two secret
singletons were blocked on table-granular git-backup redaction; backups
now exclude config_machine_state rows by secret key prefix (nodeHost.*,
webPush.vapidKeys) with a fail-closed row filter and regression proof,
so STATE_SECRET_TABLE_NAMES sheds both tables. The sidebar fold also
retires its lazy-ensure WeakSet and inline DDL; sidebar edits stay
inside the existing session-group write transaction via direct Kysely.
* fix(node-host): omit absent Cloudflare Access config like the column reader
The KV rewrite returned gateway.cloudflareAccess as an own undefined
property where the retired column reader omitted the key; toStrictEqual
consumers (state-migrations doctor-repair test) caught the shape drift.
Mirror the column reader's conditional spread at both construction
sites.
* fix(backup): disclose redacted machine-state prefixes after restore
The prefix-granular secret redaction recorded omitted key prefixes in
the backup manifest but the restore result exposed only excludedTables,
so a redacted restore looked complete while nodeHost.* and
webPush.vapidKeys configuration were intentionally absent. The restore
result and CLI output now disclose the omitted prefixes (JSON mode
carries them via the result shape), with restore-side regression
coverage.
* fix(tui): compare-and-delete retired session pointers
Doctor cleanup read matching pointer keys then deleted them
unconditionally, so a replacement pointer written between the scan and
the delete was erased. The delete now re-checks the stored value inside
the write transaction and only removes pointers that still name a
retired session; a live replacement survives (regression covered).
Also corrects the stale schema-version line in database-first.md.
* fix(cli): fail uninstalled service mutations
Treat Gateway and Node start/restart as failures when no managed service is installed, while preserving absent-service stop as an idempotent success.
* docs(cli): clarify gateway restart recovery
* [AI] fix(node-cli): warn when systemd user lingering is disabled after install
openclaw node install now detects when systemd user lingering is off and
warns the operator (text + JSON) to run 'sudo loginctl enable-linger <user>'.
Without lingering, the user-level node service is torn down when the last SSH
session ends, so the node silently goes offline after logout.
The check is read-only and never auto-enables lingering, matching the
operator-consent policy used elsewhere. It runs only on the verified-success
path: an optional onVerified hook is added to installDaemonServiceAndEmit
that fires after service.isLoaded() confirms the service is loaded and before
the success payload is emitted. The linger diagnostic runs there, so a failed
install or verification failure never carries a linger warning (avoids
misdirecting the operator to fix lingering for a service that was not
successfully installed). The already-installed short-circuit warns separately.
Skipped on non-Linux and when systemd user service is unavailable.
Adds unit tests for both paths, the linger=yes no-op, the install-failure
isolation, the verification-failure no-warn regression, and the
systemd-unavailable skip, plus response.test.ts cases covering onVerified
running on success and failing safely when it throws. The
readSystemdUserLingerStatus mock is typed with the full linger union to
satisfy tsgo. Documents the linger step in docs/cli/node.md and
docs/nodes/troubleshooting.md.
Real-behavior evidence captured on a Linux host by toggling
loginctl disable-linger/enable-linger and running the real install flow:
linger=no emits the warning on successful install (text + JSON) and on the
already-installed path; linger=yes emits nothing; a failed install or
verification failure emits no warning.
Fixes#107033
Co-Authored-By: deepseek-v4-flash <noreply@anthropic.com>
* fix(node-cli): align linger user with service owner
* docs(node): narrow crash-loop claim to gateway units
The duplicate-scope guard that raises on two managers running the same unit
name is enforced for gateway units (two supervisors on the same port SIGTERM
each other in a restart loop); assertNoSystemGatewayOwnership returns early
for node services, so claiming node services crash-loop misattributes gateway
behavior. Qualify the troubleshooting note accordingly.
Addresses ClawSweeper P3 finding on PR #118430.
* fix(systemd): align linger checks with service owner
* test(doctor): align linger status mock contract
* style(doctor): format linger mock
* test(wizard): mock systemd service account
---------
Co-authored-by: deepseek-v4-flash <noreply@anthropic.com>
Co-authored-by: Patrick Erichsen <patrick.a.erichsen@gmail.com>
* refactor(infra): move exec approvals into the shared SQLite state DB
Delete the file-runtime exec-approvals store (exec-approvals.json + .lock
sidecar machinery) on both runtimes and make the reserved
exec_approvals_config singleton row canonical. Doctor owns the one-time
import with claim/verify/receipt discipline; runtime fails closed with a
doctor instruction while un-migrated legacy state exists. The wire CAS
contract, socket semantics, and gateway auth-token derivations are
unchanged. Kills the #113929 lock-contention bug class structurally and
nets around -2.9k lines.
* fix(infra): green CI gates and retire file-era exec approvals tests
Break the migration-type import cycle with a leaf contract, regenerate the
plugin-SDK API and native i18n baselines for the intentional surface change,
drop unused exports, and replace the macOS file-era approvals test suite with
SQLite-backed behavior coverage per the obsolete-internals test policy.
* chore: green max-lines ratchet, native i18n baseline, and unused-export scan
* feat: node-hosted plugins — dynamic tools, MCP servers, and skills
Nodes become declarative plugin hosts:
- node.pluginTools.update: node hosts publish plugin-registered agent tool
descriptors; gateway materializes them as agent tools executing via
node.invoke under the node command allowlist, with tools.effective
invalidation and node online/offline removal.
- Trusted paired-node descriptors: no gateway-side plugin registration
required; gateway.nodes.pluginTools.enabled off-switch (default on);
description/count caps; deterministic node-prefixed collision names.
- Declarative node-hosted MCP: nodeHost.mcp.servers (McpServerConfig shape)
starts MCP clients on the node host, publishes tools as pluginId node-mcp,
executes via built-in mcp.tools.call.v1 with per-layer timeouts, failure
isolation, and orphan-safe shutdown. No re-pairing when servers change.
- Node-hosted skills: node.skills.update publishes ~/.openclaw/skills
content (64 skills/64KB/512KB caps both sides); gateway merges them into
the skills snapshot while connected and exec host=node is available, with
node:// locators, node-prefixed collisions, disabled command dispatch,
and gateway.nodes.skills.enabled + nodeHost.skills.enabled switches.
- Security: node-supplied pluginIds cannot satisfy pluginId-scoped tool
allowlists unless gateway-registered; reserved node-mcp id requires the
core MCP descriptor shape; protocol registry kept out of public
plugin-sdk dts.
- E2E: pond harness proves publication, MCP round-trip, skills locator, and
disconnect/reconnect for all three surfaces.
* style: format node-plugin-tools test
* fix(skills): keep status loader unfiltered when eligibility is passed
skills.status started passing eligibility for the node-skill merge, which
flipped loadWorkspaceSkillEntries into filtered mode and dropped disabled
skills from status reports (QA plugin-lifecycle-hot-reload timeout). Status
now merges node skills explicitly around an unfiltered load. Also: regen
docs_map for new node docs sections; add the intentional node-host MCP
onclose suppression to the lint-suppression allowlist.