The plugin re-resolved its own enable state with supervision.enabled as
enabledByDefault, so an operator who only declared plugins.entries.codex.config
(auto-enabled by core) silently lost every codex setting, including
appServer.homeScope=user, whenever supervision was absent or off.
Use a live plugin config block as the plugin-side default, matching core's
plugin-tool-configured auto-enable and the onepassword convention. Supervision
stays gated in its own tool surface.
* test(ui): align new-session e2e with path routes and startup transcript
* fix(ui): claim new-session handoffs on the direct chat route
Deferred session navigation can route a pane straight onto the created
session key, which never runs the switch path that admits the one-shot
handoffs. The rejected turn then expired unclaimed instead of offering a
retry, and an accepted prompt stayed hidden until the transcript bootstrap
resolved. Mirror chat-state-route's admission on the direct branch.
Fixes#114374
* fix(ui): keep the projected first prompt above the replies it started
A pane that admits the accepted prompt before its transcript bootstrap
resolves leaves that row in chatMessages while the history request is in
flight. The merge then re-placed it as a late optimistic tail, and since
#113266 stable rows keep insertion order, the first turn rendered below the
tool activity it kicked off. The handoff reconciler owns that head row, so
exclude it from the late tail.
* feat(ui): resolve session URLs through the gateway with best-effort slug matching
Session URLs now resolve a stored board face and a display-name slug through
the gateway instead of guessing from the browser's cached session page.
- An uncached open marks only the in-app navigation, never the shareable href,
so the chat loader can re-derive the face and replace the URL.
- A cached row settles the lookup locally, keeping navigation off the wire.
- A one-segment reference resolves short id, then literal key, then display-name
slug; ties reuse the existing disambiguation view.
* refactor(ui): keep slug ties on short disambiguation prefixes
Slug candidates reused the full uuid, which produced 32-character links in the
disambiguation view. uniqueShortIdPrefix already falls back to the full id when
the bounded search was truncated, so reuse it and drop the flag.
* fix(ui): navigate worktree owner links in-app so the face marker survives
The Worktrees owner link was a bare anchor consuming only the shareable href,
so preferenceDerivedFace was a no-op there and an uncached owner always opened
on chat. Match the other session surfaces: keep the clean href and navigate
with the options on plain clicks.
* fix(ui): search the first uuid block so long session references resolve
The gateway matches sessions.list search as a plain substring and stored keys
hold a hyphenated uuid, so any hyphen-stripped reference past the first block
matched nothing and 404'd. This hit disambiguation links, which offer prefixes
longer than eight characters. Send the first block and keep applying the full
prefix per row.
A resolved slug now canonicalizes to that same short reference instead of a
full uuid, so /chat/main/deploy-monitor settles on -6db92d48.
* docs(ui): record why the face marker stays out of shareable hrefs
* fix(telegram): persist update offset only after durable spool write in isolated polling
Closes#113315
In isolated polling ingress, the parent session now persists the Telegram
update offset only after the update has been durably written to the spool.
This prevents an update from being acknowledged (via the offset) before it
has been safely enqueued, which could lead to permanent data loss if the
process crashes between offset persistence and spool write.
Changes:
- Call persistUpdateId(updateId) after writeTelegramSpooledUpdate succeeds.
- Add structured logs for poll-start, update receipt, spool success/failure,
and offset persistence success/failure.
- In isolated mode, stop passing onUpdateId to the bot so the offset is only
persisted once, after the spool write.
- Add regression tests verifying spool-before-offset ordering and the
failure path when spooling fails.
* fix(telegram): persist isolated polling offset only across contiguous spooled updates
Closes#113315
- Track successfully spooled update ids and advance the persisted offset only
through a contiguous boundary. This prevents a later update_id from being
persisted before an earlier one has finished spooling, which would make the
earlier update unrecoverable after a restart.
- Serialize spooling while no durable offset floor exists, so the first
successful spool becomes the persisted floor.
- Coalesce duplicate worker deliveries for the same update_id and ack both
success and failure paths.
- Fix TypeScript cast errors in polling-session.test.ts.
* fix(telegram): serialize durable polling offsets (#113368)
---------
Co-authored-by: Carlos <carlos@example.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
The SenseAudio provider guide linked its Docs row to
https://senseaudio.cn/docs, which returns 404. The SenseAudio platform
homepage itself links to https://docs.senseaudio.cn (200), so point the
Docs row there instead.
* [AI] fix(ui): add min-height:0 to flex scroll containers for Firefox
Firefox enforces the CSS Flexbox automatic minimum size more strictly
than Chromium, preventing .sidebar-content and .md-preview-dialog__body
from shrinking below their content height. This blocked overflow:auto
from creating a scrollable area when Markdown content exceeded the
available panel height.
Add min-height:0 to both flex children so Firefox allows them to shrink
and become the inner scroll container.
Related to #107571
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* [AI] fix(ui): bound Markdown sidebar previews
* [AI] fix(ui): bound file sidebar editors
* test(ui): cover scroll repair in sidebar column layout
---------
Co-authored-by: 杨爱文 <yang.aiwen@xydigit.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Thiago Pontes AI <thiagopontesai@Hefestos-Mac-Mini.local>
* fix(acp): keep ACP turns alive when a backend rejects the requested thinking level
ACP sessions carrying a thinking level the backend cannot express died before
their first turn. Pre-turn control application forwarded the persisted thinking
option to the backend-advertised effort key, and a rejected value aborted the
whole turn with ACP_TURN_FAILED.
The skip path for thinking config rejections only matched the
ACP_BACKEND_UNSUPPORTED_CONTROL code, which OpenClaw mints for keys a backend
does not advertise. Backends that advertise the key but reject the value answer
with a JSON-RPC error instead, so the skip never fired and the optional control
became fatal. Thinking rejections now match the same rejection signals the
sibling timeout control already tolerates, including invalid-value answers.
Required controls and genuine runtime failures still fail the turn.
* fix(acp): narrow thinking rejection fallback
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
A prepared OpenClaw auth handoff was still resolved for connections that
target the operator's native Codex home, so every turn failed with
"Prepared Codex auth requires an isolated app-server home." once a stored
OpenAI profile produced a prepared model route. User-home mode now keeps
its native account for both subscription and Platform routes, matching the
documented contract; the shared-client guard stays as the backstop.
* fix(agents): close symlink-then-.. workspace boundary bypass in assertSandboxPath
resolveSandboxPath builds on path.resolve, which collapses ".." lexically
before any symlink is resolved. When a "<symlink>/.." sequence appears, the
lexical collapse lands on a harmless in-root path while the OS resolves the
same raw string (symlink first, then "..") to a location outside the
workspace, so assertSandboxPath approved inputs whose real resolution escaped
the boundary.
Add assertRawParentWithinRoot: it resolves the raw (non-collapsed) parent
chain via the OS realpath (fs.realpath.native; the JS realpath and
path.resolve both pre-collapse "..") and asserts it stays inside the
workspace root. It runs after assertNoPathAliasEscape so that guard's more
specific messages still win for cases it already catches, adding coverage only
for the residual gap it never sees.
Not currently reachable through shipped tools (read/write/edit route I/O
through fs-safe Root, which opens its own collapsed path; other callers use
the returned .resolved) -- this hardens the validator so the boundary no
longer depends on caller discipline. Regression test included.
* fix(agents): simplify raw sandbox path guard
* fix(agents): preserve symlinked sandbox roots
* fix(agents): validate raw final path aliases
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(ui): back off unavailable session PR polling
The sidebar PR indicators iterated worktree rows sequentially; each ~68ms
UNAVAILABLE reply immediately advanced to the next row, hammering
controlUi.sessionPullRequests at ~14 calls/second forever (the method is
advertised while its runtime dependency is down, so the advertised-method
gate never stopped it).
Route all callers through GatewayBrowserClient.requestSessionPullRequests:
a non-retryable UNAVAILABLE latches until the next socket hello; retryable
failures back off 30s doubling to 5min, honoring longer server retry hints.
A generation counter keeps concurrent rows from multiplying one outage wave.
* fix(ui): satisfy test-types and knip lanes
Type the PR-indicator request mocks with real signatures instead of bare
vi.fn shells, and keep GatewaySessionPullRequestsParams module-local until a
second consumer exists.
* test(ui): route hydration stubs through the PR latch wrapper
* chore(ui): drop unused ControlUiSessionPullRequests re-export
* test(ui): stub the PR latch wrapper in sidebar and clobber fixtures