* feat: credit linked session participants as co-authors
Authenticated profiles can link GitHub and receive automatic co-author credit in shared coding sessions.
* style: format rebased co-author registries
* fix: mark profile schema DDL boundary
* feat(agents): default to eager delegation in the main session
The delegation prompt section now defaults to "prefer" in each agent's
canonical main session and "suggest" elsewhere; explicit config wins in
both directions. The section is rewritten to be token-leaner and now
covers the hidden-vs-visible spawn distinction (hidden subagents are
invisible and auto-archived; deliverable-bearing work spawns
visible=true and replies with the link) plus the run-end notification
contract. The Messaging spawn-mechanics line is suppressed when the
Delegation section renders, and the stable Tooling visible:true hint is
aligned with the new guidance.
* perf(sessions): coalesce session-state wake bursts
Watched-session change notices woke the watcher's main session with the
generic 250ms heartbeat coalesce, so a burst of changes across several
watched sessions produced one wake per change. Wakes now coalesce for
20s; notices are already queued and deduped, so none are lost.
* test(agents): regenerate prompt snapshots after rebase
* test(sessions,agents): update sibling assertions for coalesced wakes and visible guidance
session-state-events tests advance timers past the new 20s wake
coalesce window; the sessions_spawn schema test tracks the updated
visible description.
* feat(gateway): reach a Gateway behind an identity-aware proxy from the CLI
Operator CLI surfaces (tui, attach, call, probe, onboarding, and configure) can present configured gateway.remote.edgeAuth headers to an identity-aware proxy. Headers are origin-bound, WSS-only, never follow redirects, cannot set transport-owned headers, and are redacted. Identity-proxy upgrade rejections are classified and remediated instead of being reported as an unreachable gateway.
* test(gateway): cover config-aware probe calls
Keep status probe expectations aligned with the resolved configuration forwarded for origin-bound edge-auth resolution.
* fix(gateway): preserve edge auth across wizard saves and enforce wss before secret resolution
Preserve gateway.remote.edgeAuth only when the configured Gateway scope is unchanged, and reject non-WSS targets before resolving any edge-auth SecretInput.
* fix(ui): align session owner filtering terminology
Owner filters now use owner terminology consistently across the Gateway, Control UI, and generated native protocol clients. The old terms were prerelease-only, so no migration or compatibility aliases are needed.
* fix(gateway): preserve creator provenance filtering
Keep creatorId scoped to immutable createdActor provenance while ownerId queries the current projected assignable owner. This preserves independent creator and responsibility filtering after reassignment.
* test(ui): wait for remembered draft before reload
CI exposed an IndexedDB draft-persistence race: the test reloaded before the write completed and then re-filled the message while restoration arrived. Wait on the canonical durable-draft helper before reload so the reload cannot race the write.
* fix(ui): keep session menus clear of hovercards
Horizontal sidebar previews were positioned from the inner link rect instead of the complete session row, allowing the hovercard to overlap trailing actions.
Anchor preview placement to the full row while preserving the link trigger and ownership flow so the session menu remains clickable.
* perf(ui): keep hovercard fix within startup budget
Use the sidebar direct-row invariant to anchor the hovercard outside trailing actions without a selector lookup. This preserves the fix without increasing the startup budget.
* fix(qa): preserve Matrix CLI timeout precedence
Keep the primary timeout operator-visible when descendant settlement also reports secondary cleanup trouble. Preserve the complete AggregateError as the public wrapper cause so settlement evidence remains inspectable.
* test(ui): isolate Markdown table clipboard boundary
With isolate:false, the shared module registry can retain clipboard imports before this test installs its module mock, leaving the spy disconnected from the production call.
Stub navigator.clipboard.writeText instead so the test exercises the real copyToClipboard boundary regardless of module load order, then restore the original descriptor.
Skill Workshop could autonomously rewrite or delete handwritten skills and discarded its own drop rationale. Workshop mutations are now gated on ledger-derived ownership (applied create proposals; unknown provenance fails closed to user-owned read-only), ownership claims end at drop and return on restore with failure-safe ordering, legacy backups stay restorable, every reconcile outcome persists with drop reasons in an additive table, and a bounded skill_workshop history action exposes them. Adopt/disown opt-in: #125711.
Co-authored with @jalehman (original ownership design).
Fixes#125652
* fix: show sessions waiting for concurrency slots
* test: align queued session integration fixtures
* test: distinguish queued and reactivated followups
* fix: preserve queued state in workboard and android
* fix: project queued status through chat history
* test(ui): keep queued sidebar case under line cap
Expose per-path removed and failed outcomes for offline agent deletion so scripted callers can detect manual cleanup. Keep unfinished deletion journals retryable until every path reaches a terminal state.
* fix(auth): preserve WHAM classifications and failure recording
WHAM 401/403 state now drives accurate re-auth guidance, while inline hook failures are contained after persistence so recorded failures cannot escape or be masked.
* docs(plugin-sdk): define auth cooldown classifications
document the additive cooldown diagnostic contract and cover its canonical public-SDK projection.
* fix(auth): keep WHAM diagnostics source-compatible
keep cooldownReason canonical, persist exact WHAM diagnostics in optional cooldownClassification, and preserve operator guidance plus failure-hook containment.
* fix(auth): keep failover on canonical cooldown reasons
ensure optional WHAM diagnostics never drive scheduling and discard mismatched persisted reason/classification pairs.
Keep ambient main-session group notices working when direct messages use an isolated dmScope, while preserving explicit session watches and routed group topology.
Restore direct project sessions regressed by registered-project placement. Git remains required for managed worktrees, while direct sessions validate only that the project directory is available.
Refs #112242
The gateway-routed path already mapped terminal run status to an exit code,
but the local embedded path did not, so a failed turn exited 0 while its own
JSON envelope reported status "error". Route both through the canonical
agent-run terminal outcome, and fail closed on an unrecognized status since
the gateway response carries an open string.
* fix(models): reject unknown providers when setting a default model
`openclaw models set` validated only the provider/model syntax, so a
nonexistent provider was written to config with a success message while
config validate, doctor, models status and gateway startup all stayed silent.
An unknown provider now fails without changing config; an unknown model under
a known provider still saves with a warning, since the local catalog can lag a
newly released or self-hosted model. Doctor reports refs already in config.
* fix(models): avoid shadowing the repair warning and the map spread
* fix(voice-call): survive gateway in-process restart and stop CLI dead-ends
The gateway's in-process restart (SIGUSR1 config reload) reuses the cached
plugin registry, so service stop/start run on the same retained voice-call
registration. Generation fencing from #120289 treated that restart as a stale
actor: stop retired the generation forever, the next start silently bailed,
and every voicecall.* RPC answered UNAVAILABLE "runtime generation is
retired" while the webhook never rebound.
- Registrations now hold a replaceable generation: service start after stop
mints a fresh generation, takes over a running slot owned by a retired
predecessor, and reports start failures to service health instead of
silently returning.
- The voicecall CLI classifies gateway failures with typed guards instead of
message substrings: standalone/store fallback only when the gateway is
genuinely absent; reachable-but-failed (request errors, auth, timeout)
exits with actionable text; a standalone webhook port collision explains
that a running Gateway probably owns the port instead of raw EADDRINUSE.
- Plugin SDK gateway-runtime exports structural isGatewayTransportError /
isGatewayClientRequestError guards (+2 documented surface budget).
- Regression coverage: same-registration stop/start restart, retired-owner
takeover, typed CLI fallback classification, and a real token-auth gateway
server routing voicecall.status through callGatewayFromCli.
* refactor(voice-call): split CLI modules and dedupe gateway fallbacks
Collapse the four duplicated gateway-or-runtime command blocks (speak, dtmf,
end, continue fallback) into one generic runGatewayManagerCommand helper —
the continue command owns its legacy-method fallback and operation polling
via a gatewayCall closure, so the helper carries no per-command policy.
Smoke reuses the shared initiateVoiceCall path instead of a bespoke
fallback.
Split the 988-line cli.ts into concept modules (cli-gateway-call,
cli-call-log, cli-command-io) and drop its grandfathered max-lines
suppression plus the now-stale max-lines and assertion-safety baseline
entries (shrink-only ratchet maintenance).
Behavior-frozen: stdout/exit semantics unchanged; net -2 production LOC.
* fix(voice-call): redact gateway URLs in CLI operational errors
ClawSweeper P1: the operational-error formatter interpolated the raw
connectionDetails.url, so a configured gateway URL with userinfo or query
tokens would print credentials into terminal output. Redact the composed
message once with the canonical net-policy redactor (also covers
remote-controlled close-reason text), exported through the plugin SDK
gateway-runtime subpath (+1 documented surface budget). Regression test
covers a credential-bearing URL in both the URL and message fields.
* feat(control-ui): move the Ask OpenClaw toggle to the sidebar footer
The sidebar bottom now mirrors its top: big agent selector with inline
chevron + small new-session square above, big account identity card with
inline chevron + small lobster toggle below. The account chevron moves
from the row's far right to directly after the name, matching the top
header treatment; the freed edge hosts the toggle. The old top-left
shell-chrome placement is removed along with its four-button width
reservation (which read-scoped clients paid for without seeing the
button). Scope gating, palette entry, and panel wiring are unchanged;
gating regressions moved to the sidebar cases.
* fix(control-ui): keep Ask OpenClaw reachable while the nav is collapsed
Address the ClawSweeper P1: desktop collapse hides the sidebar entirely
(display:none, no rail), which would have removed the toggle's only
persistent surface. The lobster now joins the chrome strip exactly while
the nav is collapsed — the footer stays its home — and the strip's
collapsed width reserves four controls for that state. Regression covers
collapsed-present/expanded-absent/read-scope-absent.
* fix(voice-call): expose realtime/streaming stream paths through tailscale serve/funnel
Tailscale serve/funnel now auto-exposes the realtime and streaming WebSocket stream paths when those audio modes are enabled; previously Twilio <Connect><Stream> could not reach wss://<ts-host><streamPath> and realtime calls dropped after ~1s with no log.
Also: CLI voicecall expose mounts/clears stream paths symmetrically, partial tailscale mounts roll back on failure, and a warning now fires when a call's stream WebSocket never arrives within the token TTL. Reported/observed live by Peter Steinberger.
* fix(voice-call): make tailscale stream exposure atomic
Preserve configured public Tailscale prefixes when mapping realtime and streaming routes, and roll back the full route set when any mount fails. The CLI now uses the same transactional setup path and reports failure instead of accepting a partial exposure.
* fix(voice-call): expose Twilio's configured streaming path
Keep realtime routes under the public webhook prefix they advertise, while mounting streaming.streamPath exactly as Twilio emits it. This preserves the canonical public-to-local route list without expanding the provider API outside the scoped repair.
The panel from #125199 rendered raw wire enums in code tags, used
form-grid/field markup whose styles the agents page never loads (bare
unstyled inputs outside the card), referenced non-existent avatar
classes, and put callouts inside the settings group. Rebuild it on the
canonical settings primitives: status rows with dot status and friendly
source/evidence labels, a System/This Agent segmented scope control,
the settings secret input (gains a disabled prop), a danger status row
for errors, and a quiet-inherit + primary-save action row. Retitle to
"GitHub Identity" and move it below Tool Access and Available Right
Now. Add a .settings-account primitive for the 20px round avatar.
Live-tested on an isolated dev gateway: native-credential verify via
the GitHub API, segmented scope switching, and the empty-token error
row. Controller behavior unchanged.
* feat(ui): preserve composer drafts across restarts
Persist draft text and attachment Blobs in browser-local IndexedDB, scoped to Gateway credentials and session or New Session targets. Clean up durable data after successful send or creation, attachment removal, and session deletion.
* fix(ui): preserve text when draft attachments exceed cap
preserve text-only durable fallback for oversized attachments; serialize visible Incognito transition with its storage fence; document retention and disposal.
* test(ui): make draft retirement proof isolate-safe
The shared isolate:false UI suite exposed an order-dependent module mock; exercise and settle the real IndexedDB boundary instead.
* style(ui): format session mutation imports
* fix(ui): keep incognito drafts memory-only
* perf(ui): lazy-load durable draft storage
* fix(ui): sequence durable draft transitions
* fix(ui): restore text-only new session drafts
Programmatic draft restoration must not advance the user-mutation generation; real user input continues through setMessage.
Move navigation transition ownership into the existing handoff module as a behavior-neutral extraction that keeps the submission owner within max-lines.
* fix(ui): restore text-only drafts without import cycles
Complete the text-only restore fix by moving started-session route transitions into a leaf module. This keeps max-lines ownership clean without reintroducing the Madge cycle.
* fix(ui): reconcile attachment handoffs with durable drafts
Restore programmatic attachment handoffs without recording a user mutation. Cover stale navigation state losing to newer durable drafts across two pages and a fresh page.
* fix(ui): start durable attachment writes before teardown
Start each CAS IndexedDB write and retirement immediately so text and attachment transactions register before page teardown. IndexedDB readwrite ordering and draft revisions keep snapshots serialized.
* fix(ui): persist New Session drafts before teardown
Accept the committed predecessor or a known in-flight local write ID atomically so New Session writes can start before teardown without spurious local-lineage conflicts.
Reset cached lineage when authoritative storage is missing and re-snapshot the still-current edit.
* fix(voice-call): grace realtime stream disconnects
Share reconnect grace by CallSid and stream ID across classic and realtime streams while cleaning realtime bridge resources immediately.
Log terminal call reasons and document Twilio inbound voice and status callback setup.
* test(voice-call): align reconnect grace after rebase
Preserve the newly landed realtime generation and inactivity coverage while updating its terminal expectations for shared delayed finalization.
Remove the redundant replacement cross-product case so the lifecycle suite remains below the max-lines limit.
Realtime call teardown previously depended on an object-identity guard
that silently skipped ending the call record when bridge instances were
replaced, and had no transport-liveness backstop: a WS close that never
propagated left an answered call running forever. Bindings are now
socket-bound with current-generation terminal ownership, predecessor
audio is retired on successor admission (the overlapping-voices bug),
a 30s media-inactivity watchdog with 2s grace ends calls whose
transport dies silently, and every realtime call end is logged with
its cause.
* feat(dashboard): add session:progress board tile rendering the live progress card
Advertise the core-owned widget kind via hello controlUiWidgetKinds at operator.read.
Render it inline without an iframe from the session-progress-cards store.
Pin it with dashboard tool widget_put using pluginKind session:progress and optional props.sessionKey.
Follow up the progress-card unification from #125125.
* fix(dashboard): surface session progress load failures
Record protected progress-card read failures in the shared per-session store.
Render an actionable board-tile error with retry instead of indefinite loading.
Cover the rejected-read and successful-retry flow at the widget boundary.
* fix(dashboard): honor progress tile access and activity
Avoid progress-card reads while a retained board is inactive.
Distinguish sharing denial from transient load failures and show the correct remedy.
Qualify cross-session pinning docs and cover activation plus denial behavior.
* feat(control-ui): persistent Ask OpenClaw companion with global toggle
The custodian surface now behaves like the persistent machine-wide agent it
already is on the Gateway: the session id persists in localStorage so a
reopened surface rebinds to the live engine (wizard and approval state
survive close/reopen), the durable transcript is refetched when a surface
opens or the gateway reconnects (idle-gated so active question/wizard cards
are never clobbered), and the panel toggles from anywhere via the shared
panel-toggle contract, a command-palette action, and an admin-gated lobster
chrome button.
One server-side line: the openclaw.chat owner-mismatch rejection now carries
the existing structured session-invalidated details so persisted clients
re-mint their id from a closed code instead of matching error prose.
No gateway events, no protocol schema changes, no polling. Splits
(session-identity/variant modules, session-lifecycle and panel-toggle test
files) keep the touched files under the max-lines ratchet.
* fix(control-ui): coerce custodian toggle detail without a type assertion
The assertion-safety ratchet holds custodian-panel.ts at zero uncommented
assertions; parse the toggle CustomEvent detail through the canonical
record-coerce guard and literal narrowing instead of casting.
* fix(control-ui): delete unused CustodianPanelToggleDetail export
The record-coerce toggle parsing left the exported type without a
production consumer; the deadcode gate rightly flags it. The palette test
keeps a local shape.
* test(control-ui): select the palette custodian item via keyboard
Async session-search results can reflow the palette list mid-click on slow
CI runners, silently dropping the positional click; keyboard selection of
the asserted-active item is atomic against reflow. Also stage the reopen
wait (panel section, then text) for sharper failure localization.
* fix(control-ui): project live wizard state on rejoin and scope-gate the toggles
Address both ClawSweeper P1 findings. The welcome-only rejoin of an
existing session now routes through engine.decorateRejoinReply (the
existing ChatWizardHost projection), so a reconnecting client re-renders
the live wizard/question controls the session still awaits; the stale
welcome question only fills in when no interaction is live. The chrome
button, palette action, and deferred panel loading now use the
scope-aware canCallGatewayMethod gate (operator.admin) that the session
store already used, so advertised-but-read-scoped clients see nothing.
* test(control-ui): fix the cloud-workers e2e flake at both roots
The mocked config.get stayed frozen at the empty initial config while
patch responses advanced, so a config-store reconciliation refetch could
flap the snapshot to empty and saveProfile silently dropped the next
save; the mock now stays consistent before each patch resolution. Also
give waitForRequest an opt-in after-cursor: it is satisfied by any prior
same-method request and returns the latest match, so a second wait could
assert against the stale earlier request; the cloud-workers waits pin it
(15x green locally, previously failing 1-in-3).
* fix(ci): cover rejoin projection in sibling engine mocks; bump startup baseline
The greeting-welcome and session-ownership suites' engine mocks now
export decorateRejoinReply like the handler requires. The Control UI
startup-JS baseline moves 337511 -> 338920 B via the documented update
command: the shell chrome toggle, palette action, and scope-aware gating
are genuine startup surface (~1.4 KiB gzip, within the committed
ceiling).
* fix(control-ui): settle interrupted structured replies and racing turns on rejoin
Address both ClawSweeper reconnect P1s. A submitted question/wizard reply
with an unknown outcome now triggers a full session rejoin on reconnect
instead of being blocked by its own uncertainty flag: the Gateway projects
whether the answer was consumed and which control is live. A restored
persisted id also arms a one-shot rejoin barrier: the welcome-only request
queues behind any in-flight turn on the Gateway's per-session queue, so a
post-response history refresh deterministically surfaces rows a racing
turn persisted after the initial fetch. The open-agent handoff moved to
custodian-navigation (its owner) to keep the store under the size cap.
Live-Gateway proof (isolated state dir, real gpt-5.6-luna turns): video
and screenshots on the PR.
* test(control-ui): reopen via the chrome toggle in the custodian e2e
The palette click-through composition proved timing-flaky on loaded CI
runners in three different ways while adding no coverage: the palette
action's dispatch is pinned by the palette unit test and the event-opens-
panel path by the chrome-toggle step. Keep the gated palette entry
assertion + screenshot; reopen through the chrome path.
* fix(control-ui): keep the agent-handoff path helper module-local
The store now routes through performCustodianAgentHandoff, leaving the
path builder without external callers; the deadcode gate rightly flags
the export.
* fix(control-ui): run the rejoin barrier even when a live control projects
The racing-history refresh happens before the reply/control message is
appended, so skipping it for projected wizard/question rejoins had no
purpose and lost rows a turn persisted while the page was closed mid-
wizard. Regression covers the live-step rejoin reconciling racing rows.