Summary:
- The PR preserves `failure_alert_disabled === 0` as the enabled-with-defaults failure-alert state and adds focused codec roundtrip tests.
- PR surface: Source +2, Tests +54. Total +56 across 2 files.
- Reproducibility: yes. At source level, current main encodes `failureAlert: {}` with `failure_alert_disabled = 0`, then decodes it as `undefined` when all explicit alert option columns are null.
Automerge notes:
- No ClawSweeper repair was needed after automerge opt-in.
Validation:
- ClawSweeper review passed for head bd9b2a1798.
- Required merge gates passed before the squash merge.
Prepared head SHA: bd9b2a1798
Review: https://github.com/openclaw/openclaw/pull/96615#issuecomment-4794949533
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: liuhao1024 <11816344+liuhao1024@users.noreply.github.com>
Approved-by: takhoffman
* fix(agent): wait for retained session write before releasing held lock on abort
* fix(agent): replace self-wait with deferred release in retained-lock abort cleanup
* fix(test): reject fallback acquire with SessionWriteLockTimeoutError in active-scope cleanup test
* fix(agent): trim retained-lock comments
Signed-off-by: sallyom <somalley@redhat.com>
---------
Signed-off-by: sallyom <somalley@redhat.com>
Co-authored-by: sallyom <somalley@redhat.com>
* fix(plugins): stop ClawHub version install from inheriting latest compatibility
When installing a specific older version of a ClawHub plugin, the
compatibility check fell back to the package-level compatibility
metadata when the version-specific response lacked it. The package-level
field reflects the latest version's requirements, so installing e.g.
version 2026.6.8 would incorrectly require OpenClaw >= 2026.6.10.
Remove the fallback to package-level compatibility in
resolveCompatiblePackageVersion(). If a version's artifact response has
no compatibility data, treat it as having no restrictions rather than
inheriting the latest version's constraints.
Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: IsaiahStapleton <istaplet@redhat.com>
* fix(plugins): narrow compatibility fallback to latest version only
Preserve package-level compatibility enforcement for unpinned/latest
ClawHub installs when the version response omits compatibility data.
Only suppress the fallback for older pinned versions where the
package-level metadata reflects the latest version's requirements.
Add regression test proving unpinned latest installs still reject
incompatible hosts via the package-level compatibility guard.
Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: IsaiahStapleton <istaplet@redhat.com>
* fix(plugins): recover version-specific compatibility from version endpoint
When the artifact endpoint returns sparse metadata (no compatibility)
for a pinned older version, fetch the version endpoint to get the real
version-specific compatibility data. This preserves compatibility
enforcement for pinned versions instead of treating sparse artifact
metadata as unrestricted.
The fallback chain is now: artifact compatibility -> version endpoint
compatibility -> package-level compatibility (latest only).
Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: IsaiahStapleton <istaplet@redhat.com>
* fix(plugins): fail closed when version compatibility recovery fails
When the artifact endpoint returns sparse metadata and the version
endpoint is unavailable (transient 500, network failure, etc.), return
the error instead of proceeding with no compatibility checks. This
prevents incompatible plugins from installing when metadata recovery
fails.
Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: IsaiahStapleton <istaplet@redhat.com>
---------
Signed-off-by: IsaiahStapleton <istaplet@redhat.com>
* fix(providers): bound self-hosted discovery JSON reads
discoverLlamaCppRuntimeContextTokens and discoverOpenAICompatibleLocalModels
parsed their HTTP responses via an unbounded await response.json(). Self-hosted
provider base URLs are user-supplied and untrusted (an endpoint reachable via
SSRF could stream an unbounded JSON body), so a hostile or buggy endpoint could
drive the setup wizard into OOM.
Route both reads through the shared byte-bounded reader (readResponseWithLimit
from @openclaw/media-core) under a single 4 MiB cap before JSON.parse, mirroring
the bound-stream hardening landed for Anthropic error bodies. Overflow cancels
the stream and is swallowed by the existing discovery error handling, so a
capped endpoint degrades gracefully (returns [] / skips the runtime context
probe) instead of buffering the whole body.
* tune self-hosted discovery cap
Signed-off-by: sallyom <somalley@redhat.com>
---------
Signed-off-by: sallyom <somalley@redhat.com>
Co-authored-by: sallyom <somalley@redhat.com>
The model-run maintenance fields (modelRunPruneAfterMs from #88632 base work,
modelRunPruneAfterConfigured from the pressure-gating fix) were required on the
resolved maintenance config exposed to plugins via patchSessionEntry's
maintenanceConfig. External plugin TypeScript callers that construct a
pre-#88632 maintenanceConfig would fail to compile.
Make both fields optional on ResolvedSessionMaintenanceConfig (and the runtime
type), so old-shape plugin configs keep compiling. All internal readers already
treat an absent value as unset: shouldRunModelRunPrune returns false when
modelRunPruneAfterMs == null and modelRunPruneAfterConfigured is falsy, so a
plugin-supplied config without the fields runs no model-run pruning — the
pre-#88632 behavior. The resolver still always populates both fields, so normal
runtime behavior is unchanged. Add an old-shape maintenanceConfig SDK
regression test.
Forced maintenance (sessions cleanup / maintenanceOverride) caps immediately to
maxEntries, but the unset model-run default was high-water gated. In the
(maxEntries, high-water) window stale model-run probes survived while the forced
cap evicted real sessions — the inverse of #88632. shouldRunModelRunPrune now
takes a force flag: when the caller caps immediately, the unset default prunes
once entryCount > maxEntries. Wire force at the two forced call sites
(applyEnforcedMaintenance, previewStoreCleanup). Make the SDK runtime config
field modelRunPruneAfterConfigured optional (additive). Add force-gate unit
test + forced-apply regression test.
The model-run prune predicate fell back to testing the raw sessionKey
when parseAgentSessionKey returned null, so unscoped keys like
`explicit:model-run-<uuid>` and shapes with empty agent ids were
eligible for the new default 24h cleanup. Restrict matching to keys
that successfully parse as agent-scoped with a non-empty agent id,
and add negative tests covering unscoped, empty-agent, extra-segment,
and whitespace-padded keys.
Refs #88632 (review feedback before merge).