* Add config unset dry-run
Add --dry-run support to config unset, including JSON output and allow-exec validation parity with config set/patch dry-run handling.
* Refresh checks after proof update
* fix(config): address unset dry-run review
Return structured JSON when config unset dry-run misses a path and validate broad secret provider/default unsets against affected SecretRefs.
Fix logs.tail credential-header redaction and JSON-mode gateway transport errors.\n\nFixes #66832.\nFixes #79108.\nSupersedes #67041.\nSupersedes #79233.\n\nCo-authored-by: Mil Wang <mingjwan@microsoft.com>\nCo-authored-by: Andy Ye <35905412+TurboTheTurtle@users.noreply.github.com>
* Fix chat session picker agent switching
Reset the chat session picker to the selected agent main session when switching agents and hide inactive sub-agent sessions from the normal picker options.
* fix(ui): preserve dashboard session on agent switch
Choose the most recent eligible normal/dashboard session for the selected agent while excluding subagent/internal rows; fall back to main only when no eligible session exists.
* fix(ui): avoid mutating session option sort
ClawSweeper R3 flagged that the previous follow-up added the
`cancelDelivered` hook to the public approval-handler runtime interaction
surface but left the channel plugin docs describing `interactions` as
only bind/unbind/clear-action hooks. Extend the bullet so plugin authors
whose `deliverPending` registers in-process or persistent state know
when to implement the cancellation hook.
AI-assisted: drafted with claude code (claude-opus-4-7).
Address the ClawSweeper R2 finding that the pre-bind stopped guard
introduced in this PR drops a delivered entry without any cleanup. The
prior PR comment block was correct only for adapters whose deliverPending
has no in-process side effects; Matrix registers a reaction target in
both an in-memory Map and a persistent store inside deliverPending, so
the entry would leak until the 24h TTL (or process restart) every time
stop() landed between deliverPending and bindPending.
Add an optional cancelDelivered interaction hook on the runtime types,
forward it through both the spec-to-adapter wrapper
(createChannelApprovalNativeRuntimeAdapter) and the lazy adapter wrapper
(createLazyChannelApprovalNativeRuntimeAdapter), and invoke it from the
two stopped guards in deliverTarget: the pre-bind guard always calls it,
and the post-bind guard calls it on the branch where bindPending
returned no handle (so unbindPending cannot run). Matrix implements the
hook by calling unregisterMatrixApprovalReactionTarget on the entry's
roomId + reactionEventId, which is the exact key
registerMatrixApprovalReactionTarget uses inside deliverPending.
The other native runtime adapters (Slack, Discord, Telegram, qqbot)
leave the hook unimplemented because their deliverPending paths only
emit remote messages and keep no in-process state to drop.
Regression coverage:
- invokes cancelDelivered when stop() fires between deliverPending and
bindPending (Deferred-gated deliverPending, asserts bindPending /
unbindPending never run and cancelDelivered receives the entry)
- invokes cancelDelivered when stop() fires after bindPending returned
null (asserts unbindPending stays uncalled while cancelDelivered fires)
AI-assisted: drafted with claude code (claude-opus-4-7).
The previous commit invoked unbindPending in the deliverPending→bindPending
race path before any binding existed; nativeRuntime.interactions.unbindPending
requires a binding, so the dts build failed with TS2345. In production the
race window that PROOF-CAND-040 measured is always after bindPending (3/3
trials had bindPending=1), so dropping the pre-bindPending unbindPending
call does not change observed cleanup behavior: that branch now just nulls
out the in-flight delivery. The post-bindPending branch keeps the
unbindPending call (binding handle present) and remains the only path
required to fix the leak.
The regression test is updated to park bindPending (not deliverPending)
before invoking stop(), matching the production race window.
AI-assisted: drafted with claude code (claude-opus-4-7).
createChannelApprovalHandlerFromCapability shares a closure-scoped
activeEntries Map across deliverTarget / finalizeResolved /
finalizeExpired / onStopped, with no synchronization primitives in the
file. deliverTarget's two awaits (transport.deliverPending then
interactions.bindPending) bracket a read-modify-write on activeEntries;
if onStopped clears the map between those awaits, the wrapped entry is
inserted into an already-cleared map and never reaches unbindPending —
the native side keeps its listener / channel binding open forever.
Production-faithful e2e measured this 3/3 trials: bindPending=1,
unbindPending=0 per request.
Track a closure-scoped `stopped` flag set by onStopped, and have
deliverTarget call unbindPending and bail to null on each await when
stopped becomes true. nativeRuntime contracts (transport / interactions
signatures) are untouched.
AI-assisted: drafted with claude code (claude-opus-4-7).
Fixes #73990.\n\nAdds a transcript-derived token estimate for local/OpenAI-compatible session transcripts that have real content but no provider usage telemetry, preserving provider-reported usage when available and gating estimation on assistant model identity.\n\nVerification:\n- CI run 25965717279: success\n- Real behavior proof run 25965716561: success\n- Azure Crabbox clean-clone proof: pnpm test src/gateway/session-utils.fs.test.ts src/status/status-message.test.ts; pnpm check:changed; pnpm exec tsx /tmp/openclaw-transcript-proof.mts; git diff --check origin/main...HEAD