* feat(plugins): support the Agent Plugins bundle format
* docs(plugins): document the Agent Plugins bundle format
* test(agents): preserve agent bundle runtime discovery
* fix(plugins): isolate Agent Plugins data-dir failures and align MCP support reporting
* docs(plugins): list Agent Plugins in the canonical plugin-format guides
* fix(plugins): gate Agent Plugins detection on schema, pure inspection, root-relative cwd
* fix(plugins): record Agent Plugins data-dir ownership explicitly
* docs(plugins): cover Agent Plugins in the CLI install detection guide
* fix(plugins): carry Agent Plugins data-dir and transport contracts through external MCP projections
* [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>
* fix(doctor): restore the pre-migration session index when runs share a store
Restore picked the winning archive by manifest iteration order, newest first.
When a legacy writer recreated an empty sessions.json between migrations, the
newest run had archived that empty file, so restore installed it and then
refused the only archive holding real sessions as a conflict.
Resolve one winning archive per destination before writing anything, preferring
the earliest run whose archive is still restorable. Losing archives are never
deleted and stay visible as conflicts.
Closes#116163
* fix(doctor): fail closed on ambiguous session restores
* test(doctor): key restore conflicts by archive
* fix(doctor): narrow restored archive lookup
* fix(doctor): stream restore archive hashing
---------
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
* fix(cli): update plugins doctor clean message (#115073)
Root cause: plugins doctor reports 'No plugin issues detected.' even when a selected context-engine plugin is quarantined at runtime, because doctor is a fast static control-plane check.
Architectural owner: plugins-cli.runtime.ts
Canonical fix: Change clean status message to direct operators to 'openclaw health' for active runtime quarantine/fallback status.
Production LOC delta: +2 LOC
* fix(cli): update plugins doctor clean message wording (#115073)
Root cause: Refine the diagnostic text message to run openclaw health directly.
Architectural owner: src/cli/plugins-cli.runtime.ts
* fix(cli): clarify plugin doctor scope
---------
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
* fix: gateway service commands refuse a named profile or relocated OPENCLAW_HOME
- Resolve the default install identity against the canonical state directory
for the active OpenClaw home and profile instead of the unprofiled OS
account default.
- `--profile <name>` / `--dev` project `.openclaw-<profile>` state and config
paths, so every named profile was classified as isolated state and refused
`install`, `start`, `stop`, `restart`, `uninstall`, Doctor service repair,
and self-update service handling.
- `OPENCLAW_HOME` relocates all OpenClaw path defaults and is documented for
running as a dedicated service user; a relocated home is now an install
identity. `HOME` alone still is not.
- An `OPENCLAW_STATE_DIR` or `OPENCLAW_CONFIG_PATH` pointing outside those
canonical paths is still treated as isolated state.
- Recovery guidance in the refusal message now names the paths that must match.
Verified: focused vitest shards for the changed suites plus the daemon, CLI,
and doctor suites that consume the identity check; tsgo core and core-test
lanes; oxlint; docs format, MDX, link, and map checks.
* fix(gateway): keep relocated homes isolated
* fix(config): validate service profile identity
* fix(daemon): enforce named-profile service ownership
* fix(update): reject drifted service selectors before probes
* test(windows): prove scheduled task lifecycle
* test(windows): harden scheduled task proof cleanup
* test(windows): bind lifecycle proof to checkout
* test(windows): normalize cleanup exit status
* test(windows): verify effective task privilege
* test(windows): protect scheduled task proof roots
* test(windows): prove listener-owned task lifecycle
* test(windows): fix scheduled task proof contracts
* test(windows): remove redundant mock coercions
* test(windows): measure fallback before task probes
* test(windows): prove scheduled task process origin
* fix(gateway): preserve unmanaged restart fallback
* test(gateway): cover denied restart ownership
* test(gateway): keep restart helper types private
* test(gateway): classify lifecycle helpers as test code
---------
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
* fix(memory-wiki): preserve user edits when rolling back ChatGPT imports
Rollback deleted created pages and overwrote updated pages unconditionally,
destroying content the user added after the import with no recovery copy.
Import runs now record a content hash of each written page after vault
compile, and rollback preserves any page whose current content no longer
matches into the run's recovered directory before deleting or restoring.
Legacy run records without hashes preserve unconditionally.
Fixes#116457
* fix(memory-wiki): move pages aside atomically during rollback
Rollback now renames the live page into the recovery location before
inspecting it, so a concurrent external save cannot land between the
content read and the delete or snapshot restore. Matching pages drop
the moved copy; mismatching pages keep it as the recovery file.
* fix(memory-wiki): make rollback snapshot restore collision-safe
The snapshot restore wrote directly to the page path after the
move-aside, so a page recreated by an editor in that window was
overwritten with no recovery copy. Restore now creates the snapshot
exclusively and on collision moves the recreated page aside and
retries; recovery filenames are uniqued so a second move-aside cannot
clobber an earlier preserved copy.
* fix(memory-wiki): record rollback hashes at write time
* fix(memory-wiki): make ChatGPT rollback retry-safe
* fix(memory-wiki): fence ChatGPT rollback phases
---------
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>