* test(qa): add managed-worktrees CLI lifecycle scenario coverage
Managed worktrees had zero QA scenario-pack coverage despite being a
headline feature. Mint agent-runtime.managed-worktrees-lifecycle in the
taxonomy, add a runtime scenario, and prove the real child CLI through
create with .worktreeinclude provisioning and the .openclaw setup hook,
dirty removal pinning a snapshot ref, restore rebuilding tracked,
untracked, and provisioned files with their modes, and gc preserving
manual worktrees.
* fix(qa): align model-switch catalog assertion with expectedAlternate flow
qa/scenarios/models/model-switch-follow-up.yaml switched to
expectedAlternate.model in 5a795f4dda but the catalog test still greps
for the retired alternate?.model literal; the test is outside the PR
change-classification lanes, so the break only surfaces on direct runs.
* test(qa): narrow managed-worktrees taxonomy description to proven manual-owner gc
ClawSweeper P2 on #120335: the scenario proves manual-owner gc retention
only; session and Workboard cleanup lifecycles are not exercised, so the
coverage description must not claim them.
Prevent retained plugin closures from recreating, adopting, or stopping successor runtimes across shutdown and restart.
Add deterministic lifecycle regressions for pending startup, exact-owner stop, retained tools, and generation restart.
* fix(discord): thread archive/delete closes sessions in each agent's store
closeDiscordThreadSessions resolved the sessions store with the Discord
account id as agentId ('default' on the default path), which points at a
nonexistent agent's store — archiving or deleting a thread silently closed
nothing. The store now resolves per routed agent via listAgentIds and every
agent's matching sessions are deleted.
* fix(discord): type thread session cleanup across agent stores
* fix(discord): scope thread-session scan per agent and keep it read-only
* test(discord): cover thread deletion across agent stores
* fix(imessage): text echo matching honors message-id conflicts; own sends stop feeding the loop limiter
Two silent-drop paths in the iMessage monitor:
1. The persisted 12h echo cache matched inbound user text against old
outbound text even when the GUIDs conflicted — a user sending 'ok' within
12h of the agent sending 'ok' was dropped as an echo. The text branch now
applies the same hasConflictingMessageIds guard the media branch has;
genuine reconnect echoes still match by id or id-less entries.
2. Every from-me row fed the echo loop rate limiter, so a normal outbound
burst (agent replies, multi-chunk sends, operator phone traffic) could trip
the limiter and silently suppress the next legitimate inbound message.
'from me' is no longer counted as a loop signal, and a tripped limiter now
logs a default-level warning once per conversation instead of verbose-only.
* fix(imessage): preserve delayed echo guards
* refactor(test): exercise real session rewind policy
* refactor(test): use real Telegram media policy
* refactor(test): exercise real HTTP cancellation lifecycle
* test(gateway): prove mutations clear queued session work
* feat(status): render /status as native rich tables on channels that support them
* feat(status): tighten rich /status layout into a titled native table card
* feat(status): trim rich /status tail to one clock-and-uptime context line
* feat(status): context meter, hot-window warning, and default-noise trim in rich card
* test(status): satisfy SessionEntry sessionId in meter fixture
* fix(telegram): gate rich table islands off legacy HTML sends and cover payload sends
* test(telegram): split outbound-adapter presentation tests to satisfy max-lines
* fix(outbound): make presentation capability resolution formatting-aware
* fix(telegram): defer presentation canonicalization on rich accounts until send
* feat(status): one fact per line in the plain status body
* feat(status): group the plain status body into blank-line sections
* feat(status): lead the rich status card with the version title
---------
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* [AI] fix(diagnostics-otel): keep telemetry exporting across in-process restarts
Owned diagnostics-otel generations previously registered their OpenTelemetry
providers globally and only shut the providers down on stop. The pinned
sdk-node registers globals without override and never unregisters them, so a
second in-process generation (config-watcher reload, OPENCLAW_NO_RESPAWN=1,
containers) kept exporting through the first, already-shutdown providers and
telemetry silently stopped.
Switch owned mode to private BasicTracerProvider + MeterProvider instances
whose handles are injected directly into the existing recorder runtime, and
keep the preloaded (OPENCLAW_OTEL_PRELOADED=1) mode on the host's global
providers without ever registering or replacing globals. Resource detection
honors the pinned NodeSDK OTEL_NODE_RESOURCE_DETECTORS contract (unset
defaults to env+process+host; none/subset/all are respected), and the unused
@opentelemetry/sdk-node dependency is removed with a minimal lockfile update.
Adds a real-SDK two-generation restart regression, real-SDK resource-detector
selection coverage, migrates the unit mocks to the provider lifecycle, and
updates the OpenTelemetry docs.
Fixes#119997
Co-Authored-By: glm-5.2 <noreply@anthropic.com>
* [AI] test(diagnostics-otel): restore OpenTelemetry globals after restart test
The restart regression disables global context, metrics, propagation, and
trace APIs and forces OPENCLAW_OTEL_PRELOADED=0, but its cleanup only reset
diagnostic events, so a later test in the same worker could inherit no-op
providers. Snapshot the prior global registrations and preloaded env at
module load, and re-register/restore them in afterEach, matching the
existing exporter-health integration test pattern.
Co-Authored-By: glm-5.2 <noreply@anthropic.com>
* [AI] fix(diagnostics-otel): reconcile private providers with OTEL_SDK_DISABLED
Rebase onto main after #119961 (honor OTEL_SDK_DISABLED) changed the
disabled admission path to return before SDK construction. Restore the
removed getBooleanFromEnv import, drop the stale ownedNodeSdkDisabled
guard, and update disabled-mode tests to the new semantics (all routes
off, disabled runtime registered) plus the private-provider signal
path for the integration suite.
Related to #119997
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(diagnostics-otel): isolate restartable provider generations
* test(diagnostics-otel): prove same-pid generation routing
* test(diagnostics-otel): use supported watcher reload mode
* test(diagnostics-otel): satisfy ownership proof gates
* test(diagnostics-otel): use managed proof cleanup
* test(diagnostics-otel): verify injected trace ancestry
---------
Co-authored-by: glm-5.2 <noreply@anthropic.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
* fix(slack): scope thread-starter cache per workspace and persist channel-id migration durably
Thread-starter cache keys now always include accountId+teamId so multi-workspace
installs cannot cross-read cached thread starters. channel_id_changed migration
previews against the persisted config snapshot and only mutates the in-memory
monitor config after the durable write succeeds; new-channel ingress traffic
serializes behind the migration lane via new_channel_id.
* chore: re-fire CI
* chore: re-fire CI against fixed main baseline
* fix(acpx): stop/start races cannot resurrect a stopped runtime backend
The deferred acpx runtime service published its lazily started backend without
lifecycle ownership checks: a stop during in-flight activation let the stale
service become the active runtime, and stop cleared shared state while startup
still used it. Start/stop now use a lifecycle revision; only the current
revision may publish a runtime, stop invalidates every deferred proxy before
awaiting in-flight startup, and restart waits for the previous stop to settle.
* chore: re-fire CI
* fix(acpx): fence deferred runtime ownership
* refactor(acpx): centralize backend lifecycle ownership
* fix(acpx): annotate deferred runtime cycle
* fix(qqbot): recover credentials from backup deterministically at startup
Credential recovery previously keyed off a loose truthiness check that
misclassified partially configured accounts; recovery state is now a closed
discriminated union (configured/recoverable/partial/missing) so a live-config
wipe restores from the per-account backup only when the account is actually
recoverable, and partial configs surface a clear diagnostic instead of a
silent skip.
* chore: re-fire CI
* refactor(test): exercise real Teams monitor lifecycle
* refactor(test): use real gateway lifecycle state
* refactor(test): use real restart delivery queue
* test: type Teams SDK boundary fixture