* fix(cloud-workers): honor Crabbox SSH fallback ports
Select a currently reachable advertised SSH endpoint before returning a Crabbox worker lease, while preserving fail-closed teardown. Allow deletion of failed cloud sessions only after the environment record proves the provider lease is gone, and align the Cloud Workers docs with the shipped configuration and Control UI flow.
* fix(cloud-workers): retry advertised SSH endpoints
Persist provider-advertised SSH fallback ports and retry them through the shared pinned transport used by bootstrap, tunnels, reconnects, and workspace transfers. Keep candidate selection provider-neutral, preserve identity and host-key fencing, and add the same-version SQLite column through the existing additive migration path.
* fix(crabbox): recheck SSH readiness after setup
Treat provider setup as a mutating lifecycle phase: refresh authoritative inspect state and wait for SSH readiness again before returning the lease. This prevents bootstrap from racing setup-triggered SSH restarts on fresh and replayed workers while preserving security attestation and fail-closed teardown.
* fix(cloud-workers): retry bootstrap transfers across ports
Use the shared advertised-port candidate runner for bundle transfer, install, and best-effort upload cleanup as well as preflight. This keeps fresh bootstrap connections on pinned identity and host-key semantics when a previously selected SSH port disappears between phases.
Proof passed: 517 focused tests (1 skipped), final autoreview/TruffleHog clean, targeted lint/format. Full check-changed reached the unrelated max-lines baseline drift on current origin/main: this branch's merge base contains ui/src/pages/chat/components/chat-model-controls.ts while current origin/main removed that baseline entry; do not touch the unrelated baseline.
* fix(cloud-workers): persist fallback ports compatibly
Create the fallback-port companion table lazily as an additive surface, preserving downgrade tolerance for binaries that still expect the older shared-state schema.
* fix(cloud-workers): preserve bundle artifact narrowing
Bind the immutable discriminated artifact before the SSH retry callbacks so bundle-only fields remain narrowed across fresh candidate invocations. Use the shared temp-directory owner in the tunnel fallback test for deterministic cleanup.
* fix(cloud-workers): retire deleted placements
Terminal placement rows retire by exact CAS after session deletion, and managed AWS docs now pin the Gateway SSH CIDR.
* fix(crabbox): await pending AWS attestation
missing authoritative metadata is pending only while the lease is non-ready; ready leases still require explicit false.
* fix(cloud-workers): fence remote command retries
require explicit idempotent/never classification; remote worker launch never retries after ambiguous SSH loss.
* fix(cloud-workers): bound SSH fallback operations
* perf(plugins): declare doctor contract surfaces
* perf(doctor): slim migration import closures
* perf(plugins): narrow doctor declaration record surface and wire owner-test lane
Registry records carry only the doctorContract declaration instead of the whole
parsed manifest, and check:changed now selects the src/plugins-owned declaration
honesty and closure-guard tests for extension module/manifest changes so
cross-lane drift cannot pass PR classification.
* fix(doctor): keep control-plane dist imports require-safe
Keep doctor and channel control-plane chunks off exec-class dependencies, and enforce native require(esm) loading during postbuild.
* chore(plugin-sdk): regenerate API baseline
* chore(plugin-sdk): sync export ordering
* fix(plugins): satisfy doctor contract CI boundaries
* perf(doctor): make qqbot doctor closure dependency-light
qqbot was the last plugin above 5s in doctor state-migration enumeration
(~8s under tsx/jiti). The cost was not the state-key builder (already a
leaf): its doctor closure value-imported the runtime-doctor SDK barrel,
whose plugin-state-store/state-db re-exports pull kysely (~330 modules),
plus security-runtime for one fileExists (~200 modules), all resolved
per-module by jiti during enumeration.
Split the migration-define helpers and light re-exports into a new
private-local plugin-sdk/runtime-doctor-migrations subpath; runtime-doctor
re-exports it so its public surface is byte-identical (API baseline hash
unchanged). qqbot's doctor-contract and state-migrations now import only
the light subpath, swapping fileExists for the equivalent async
legacyStateFileExists already in the closure.
qqbot enumeration: ~8.0s/531 modules -> ~0.25s/18 modules.
* chore(plugin-sdk): drop private-local subpath from API baseline
runtime-doctor-migrations is private-local-only; the baseline tracks public
modules, and the earlier line was generated before the classification.
* fix(plugins): register runtime-doctor-migrations boundary paths
The private-local subpath list feeds the extension package boundary map;
the shared paths config and xai's derived overrides must carry the same
entry or the boundary contract test fails.
* fix(plugin-sdk): stabilize session catalog baseline
* test(doctor): isolate bind persistence inventory
* fix(ci): prepare max-lines base during checkout
* test(sessions): align freshness regression fixtures
* style(tests): format context usage fixture
* fix(ci): preserve token provenance and isolate fork credentials
Finish the canonical token-provenance fixtures while preserving the
latest maintainer-owned unavailable-usage ordering. Limit the diff-base
GitHub token to manual non-release comparisons and guard that fork
isolation invariant in the existing workflow regression test.
Co-authored-by: Sarah Fortune <sarah.fortune@gmail.com>
* test(ui): identify actual managed image eviction
* fix(ci): reuse the validated protocol comparison base
Consume the immutable diff base already resolved and validated by
preflight for every CI event. Remove the dead duplicate manual
GitHub lookup and unreachable fallbacks, and guard the single
authoritative path without changing fork credential isolation.
* test(sessions): preserve stale total expectation
* test(ui): arm reconnect deferral before disconnect
---------
Co-authored-by: Sarah Fortune <sarah.fortune@gmail.com>
* 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
* fix(plugins): preserve multi-entry pack identity and safe install compensation
Multi-entry plugin packs registered every entry under the single manifest id,
so only one entry survived registry normalization; discovery now records an
entry-scoped effectivePluginId that the manifest registry honors. Managed
install compensation no longer silences runtime cache invalidation on the
success path, and local linked installs never delete operator-owned source
directories on persistence failure.
* fix(plugins): reject colliding derived entry ids in multi-entry packs
ClawSweeper P1: ./a/index.ts and ./b/index.ts both derived pack/index and one
entry silently lost same-root dedupe. Colliding entries now emit an error
diagnostic naming the sources and register no candidate; docs note the
entry-scoped plugins.entries key for multi-entry packs.
* [AI] fix(plugins): warn when registerHook uses a typed hook event name
api.registerHook registers into the legacy internal-hook path, while typed
lifecycle events (before_tool_call, message_received, ...) are dispatched
exclusively by the typed hook runner. Registrations under typed names
silently never fired since #117372 moved the write to legacyInternalHooks.
Emit a registration diagnostic pointing to the public api.on(...) API so
plugin authors see the no-op at load time instead of trusting a false
"loaded".
Related to #116965
Co-Authored-By: deepseek-v4-flash <noreply@anthropic.com>
* [AI] fix(plugins): point registerHook comment at public api.on API
Condense the inline comment in createToolHookRegistrars and replace the
internal registerTypedHook migration reference with the public api.on(...)
API, matching the emitted warning and hooks docs. Behavior and tests
unchanged.
---------
Co-authored-by: deepseek-v4-flash <noreply@anthropic.com>
Co-authored-by: Patrick Erichsen <patrick.a.erichsen@gmail.com>
Emit usage once at the shared plugin completion boundary and expose host-derived plugin identity only on OTel usage spans. Preserve aggregate metric labels and cover the redacted Gateway path end to end.
Refs #98968
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
Co-authored-by: ZengWen-DT <ceng.wen@xydigit.com>
Deletes the deterministic regex capture path that templated raw chat text into skill proposals (junk like a proposal whose whole procedure was one slugified user message). All autonomous learning now flows through the isolated experience reviewer: it sees a bounded workspace skill list, prefers revising pending proposals or updating the governing skill over creating new ones, and treats durable user corrections as first-class evidence. Update proposals are reviewer-only (explicit opt-in) and never auto-apply, since the reviewer drafts them without the live skill body. Removes the producerless pending-suggestion session machinery. Regression test proves the junk path is gone; real-Telegram E2E verdict in the PR body.
* fix(diagnostics-otel): harness.run root span adopts diagnostic trace context
Fall back to internalOrTrustedParentContext when there is no active
tracked parent, so the openclaw.harness.run root span (and its whole
OTel subtree) adopts the diagnostic traceId instead of minting a new
OTel root. This lets spans propagated via the diagnostic traceparent
(e.g. a2a-gateway a2a.call / downstream a2a.task) share one trace with
the run internals.
* fix(diagnostics-otel): propagate exported span context
Co-authored-by: HughTang <44694613+HughTang@users.noreply.github.com>
* fix(diagnostics): break propagation import cycle
* fix(diagnostics): preserve trace propagation contracts
---------
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>