classifyAgentRunTerminalOutcome computes the timeout classification and the
emit path discarded it: errorKind came only from event metadata the
lifecycle producer never sets, plus text-sniffing an often-undefined error.
The browser projection classifies status timeout only via errorKind, so an
idle/run-budget timeout rendered generic failed with empty error while
sessions.list and the TUI both said timeout. The recorded classification
now flows through.
The live tool stream emits a toolresult block for partial update output
(so streaming stdout renders), but the card extractor treated any result
block as completion — spinner gone mid-execution, and partial text that
looks like an error badged the still-running tool Failed. Live cards'
completion is owned by the stream's resultReceived marker (per the
producer's own contract comment); the result branch now defers to it.
config.get never sends plaintext secrets — stored values arrive as the
__OPENCLAW_REDACTED__ sentinel. The reveal eye had no sentinel awareness:
clicking it showed the literal sentinel in a now-editable input, and any
edit broke the exact-match restore so the mangled text was written over
the real credential with a green Saved. Sentinel values are now never
revealable (input stays readonly) and the disabled eye explains why.
Five-file Telegram jobs finished the first file, then isolate re-imported the next graph in silence until the 300s watchdog killed the worker. Recycle the Vitest process after each file and keep five files per CI job.
* fix(ui): restore interleaved sidebar ordering
Render pinned sessions at their persisted slots among page entries again instead of projecting them into a separate group. This restores custom page/session interleaving regressed by #121712 while preserving drag-to-pin behavior.
* test: consolidate steering authority fixtures
* test: isolate CLI commentary fixture
* fix: dispatch tool-bound turns normally
* fix(macos): reap app-owned child process groups
macOS-owned SSH, Codex, and node-host descendants no longer survive terminal shutdown. Codex retains EOF-first graceful exit before bounded process-group termination and reaping.
* fix(macos): correct managed cleanup wake binding
* fix(macos): preserve Codex shutdown escalation
Keep the app-owned EOF grace window while allowing abortive requests to interrupt it before process-group TERM and KILL.
Reject unbound foreign-channel fallback at shared approval-account selection while preserving recorded bindings and explicit forwarding targets. Cover Telegram and Matrix routing contracts.
Co-authored-by: vatsalgargg <vatsalg80@gmail.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* docs(macos): document cookie sync to a remote computer
Add a Settings > General > Cookie sync section to the macOS platform guide,
alongside the existing browser-login import docs: off-by-default toggle, domain
allowlist, target profile, remote-mode requirement, host-local decryption, and
the DBSC caveat. Cross-links the browser CLI cookie-sync reference.
* docs(macos): clarify cookie sync status
itemId-less preamble segments carry standalone text, but the tracker
treated any itemId-less segment as cumulative run text. A preamble made the
tracker diverge from the real cumulative prefix, the next snapshot failed
startsWith, and every earlier segment's text rendered twice — persisted at
terminal materialization. advanceAccumulatedStreamText now owns the
continuity rule at all four advance sites: only text that extends the
current baseline becomes the new baseline.
The reconciler dropped workboard entries from the canonical persisted list
whenever workboardEnabled was false — but enablement reads as disabled until
the runtime config snapshot loads, so any pin/unpin/drag in that window
permanently deleted every workboard pin from synced prefs. The boards-not-
ready branch already preserved slots for exactly this ambiguity; disabled
now folds into it. Prod net −1.
* fix(doctor): unify legacy auth repair candidate enumeration
The sidecar inline-recovery and flat-store SQLite migration each carried
a near-duplicate listAuthProfileRepairCandidates that diverged: the
sidecar copy ignored PI_CODING_AGENT_DIR (a supported env contract used
by dotenv, gateway env selection, secrets scan, and stale-auth-order)
and the flat copy ignored symlinked state agent dirs. Because repair
sequencing runs sidecar recovery first, any store only visible to the
flat migration had its decryptable sidecar secrets imported as
credential-less 'configured-unavailable' profiles and the user was told
to re-authenticate — while the secrets sat on disk, recoverable.
Move one canonical enumeration (env superset + symlink-tolerant dirent
filter) into doctor-auth-legacy-paths.ts and delete both copies.
* fix(doctor): preserve shared-main precedence in extracted candidate dedupe
The extraction dropped main's undefined-agentDir-wins rule (added with the
state-DB shared store work); an agent-scoped alias resolving to the same
path demoted the shared-main store to a per-agent import, breaking SQLite
migration and archive repair.
Preserve externally scoped Telegram and Matrix test plans instead of expanding each CI shard back into the full extension suite. Keep broad runs bounded and retain external include ownership through directory run specs.
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Quick Chat model changes now settle in target order, the remote probe uses one deadline, and node-owned Codex child shutdown is acknowledged and bounded.
Related: #123420
* feat(browser): sync system cookies to a remote gateway profile
Add `openclaw browser cookie-sync`: decrypt allowlisted macOS Chrome-family
cookies locally and push them into a managed profile on a possibly-remote
Gateway over the existing operator channel. --watch re-syncs on cookie-DB
changes with a single Keychain prompt per session.
- New POST /cookies/set-many batch route (mirrors /cookies/set)
- Extract one canonical readSystemProfileCookies reused by import + sync
- Mandatory domain allowlist (never syncs an unrestricted cookie jar)
- Decryption stays host-local (macOS); no cookie values are logged
* feat(macos): cookie sync checkbox and configuration UI
Add an off-by-default 'Cookie sync' section (Settings > General): a toggle, an
editable domain allowlist editor, and a target-profile field, actionable only in
remote-gateway mode. CookieSyncManager supervises `openclaw browser cookie-sync
--watch` against the connected Gateway when enabled, resolving a LOCAL CLI (never
the SSH-redirect path, since decryption is host-local) and injecting gateway
URL + token/password via environment, never argv. A status row surfaces
running/stopped/error and the last sync summary.
* fix(macos): satisfy cookie sync lint gates
* chore(i18n): register cookie sync native source strings
Regenerate apps/.i18n/native-source.json baseline for the new macOS Cookie
sync settings strings (additive only). Satisfies the native:i18n:verify gate;
generated locale artifacts are refreshed separately by the locale-refresh job.
* fix(apps): open session desktop on its machine
* fix(ui): scope the desktop session lookup to the key's own agent
`sessions.list` has no exact-key filter, so the viewer resolves a `session=`
parameter by searching for the key and matching it exactly in the response. A
key that prefixes longer ones — `agent:main:main` alongside user-named sessions
that start the same way — could push the exact row outside a five-row page and
silently fall back to the picker.
Session keys encode their agent and the list API accepts `agentId`, so scope the
search to that agent and widen the page.
* refactor(ui): stop parking a session key in the desktop environment id
Document-mode inventory failures stashed the requested session key in
`environmentId` purely so the Retry button's non-null guard would pass, even
though document-mode retry refreshes the inventory and never reads the value.
A session key only names a machine once the inventory loads, so it now stays
out of `environmentId`, and the retry branch that ignores it runs before the
guard. Adds E2E coverage for recovering a session-preselected desktop.
* fix(ui): resolve the desktop session with an exact-key lookup
The session-preselect path searched `sessions.list` and scoped the search to
the key's own agent, but a bounded search cannot rule a key out: 25 newer
same-agent sessions sharing the requested key's prefix would push the exact row
off the page, and the viewer would report the source as unavailable for a
session that exists. `sessions.describe` is the exact-key operation and already
projects placement, so the panel calls it directly and the app-root resolver
plumbing goes away with it.
* refactor(ui): move the desktop document keyboard bridge into its own controller
`desktop-panel.ts` crossed the 700-line cap. The mobile keyboard bridge — the
padded sentinel, the value diffing that turns composed input into backspaces
and text, and the field focus/reset helpers — is a self-contained concern, so
it moves into a `DesktopMobileKeyboard` controller alongside the existing
fullscreen controller instead of taking a `max-lines` suppression.
* test(ios): drain text fields instead of assuming a delete burst lands
`testReleaseChatScreenshot` typed a 5-character probe, sent 5 deletes in one
`typeText`, then asserted the field was empty. CI dropped one synthetic
keystroke under simulator load and the assertion failed with a leftover "f".
XCUITest makes no lossless-burst guarantee, so clearing now re-sends against
whatever the field actually still holds, bounded. The two gateway-setup fields
that overtyped through the same burst use the helper for the same reason.
* fix(ui): keep the chat placement owner out of the startup chunk
`desktop-document-mode.ts` is imported by bootstrap, so importing the chat
placement owner from it pulled the chat page's dependency tree into the startup
bundle and pushed startup JS past its gzip budget (331075 B against a 330507 B
allowance). The route module now only parses the URL; resolving a session to its
machine moves next to the lazily loaded desktop panel, which is the only caller.
Startup JS is back to 329710 B.
Background-only Bridge and Mac-node launches keep GUI onboarding and saved Gateway-profile Keychain state cold while preserving environment/config-owned Gateway connectivity.
Bound Telegram extension tests to five files per Vitest process across explicit config, directory, and full-suite routes while preserving serial isolated execution.
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* fix(test): keep shared jsdom window in step with the per-file module reset
The non-isolated runner already resets the module graph after every test file,
so each file evaluates its own component classes. The jsdom window it shares
across the whole worker was never reset with it: every Control UI component
registers with `if (!customElements.get(tag))`, so the first file to import a
component owned that tag for the rest of the run and `document.createElement`
kept building elements closed over that file's module instances. Later files'
singletons, module mocks, and spies were never the ones production reached, so
assertions failed as "expected ... to be called once, but got 0 times" in
whichever files the size-based sequencer happened to place after a warming one.
Drop repo-owned tags with the graph they came from. Dependency packages are
externalized and register once per worker through native ESM, so their
definitions are attributed by define call site and kept.
The same window also carried mounted DOM forward, so helpers reading
`document.body.querySelector(...)` answered an earlier file's leaked dialog and
focus assertions read its stale activeElement. Clear the body with it;
`document.head` stays, since dependency styles cannot be replayed either.
* fix(test): keep the jsdom definition shape local to its module