* test: consolidate OpenClaw test state fixtures
* test(plugin-sdk): expose isolated test state
Promote the isolated OpenClaw test-state lifecycle through a narrow published Plugin SDK subpath so extension tests no longer import private core helpers. This intentional SDK surface addition is maintainer-approved.
* test: use SDK test-state seam in extensions
Route bundled extension suites through the focused repo-local Plugin SDK test-state entrypoint and remove the Codex projector harness exports made stale by fixture consolidation. Keep the seam out of production builds and published package artifacts while auditing its real consumers in the full-tree deadcode scan.
* test(plugins): map test-state in package boundaries
* fix(browser): tab creation steals window focus during agent automation
Agent-created tabs inherited CDP's foreground default: direct CDP
Target.createTarget omitted the background flag, and the extension
relay's createTab defaulted to active:true, so every agent tab open
activated the new tab (and, on the extension driver, focused the
window), interrupting whatever the human was doing in that browser.
Direct CDP tab creation now requests background:true (agent tab
ownership/selection is target-id based and never depended on
activation), and the extension relay defaults an omitted background
to true while preserving an explicit background:false, matching the
Codex/Claude-in-Chrome model the extension driver mirrors.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor(browser): keep focus fix LOC-neutral
Preserve background tab creation while keeping the oversized CDP and relay modules within the current LOC ratchet.\n\nCodex-Session: 019f5e93-780a-7350-88f9-1986cdb64914
* fix(browser): honor explicit CDP focus requests
Keep background-by-default automation while treating Target.createTarget focus=true as an explicit foreground request in the extension relay.
Codex-Session: 019f5e93-780a-7350-88f9-1986cdb64914
* fix(browser): preserve explicit CDP focus semantics
Apply the background-by-default automation policy only when focus is omitted, preserving focus=false foreground-tab requests as well as focus=true.
Codex-Session: 019f5e93-780a-7350-88f9-1986cdb64914
* fix(browser): preserve create target window focus
Carry the resolved CDP focus intent through the extension relay and explicitly focus the containing Chrome window when requested.\n\nCodex-Session: 019f5e93-780a-7350-88f9-1986cdb64914
* style(browser): refresh relay import order
* test(secrets): use secure node exec fixtures
* test(doctor): secure exec secret fixture
* test(doctor): retain narrowed temp path
* test(secrets): secure remaining exec fixtures
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(browser): retire durable tab rows whose browser never returns
Durable cleanup defers whenever ownership cannot be proven, so a browser
that never comes back at the same cdpUrl leaves its rows behind forever:
each sweep re-claims them, fails the identity lookup, warns, and defers
again. Nothing in the subsystem removes a row by age.
The `browser.session-tabs` namespace is opened with a 5000-row cap and
`reject-new`, so once those rows accumulate to the cap, tracking a new tab
throws PLUGIN_STATE_LIMIT_EXCEEDED. That propagates into the compensation
path in browser-tool-session-tabs.ts, which closes the tab the user just
opened and rethrows -- every `browser open` on that profile then opens a
tab, closes it again, and errors, with no self-healing path.
Bound the retry: when a close attempt reports the target unavailable and
the tab has been unused for longer than the retire window, drop the row
instead of deferring again. A browser returning after that long almost
always carries a fresh instance fingerprint, which retires the row through
the ownership-mismatch path anyway.
closeTrackedBrowserTabsForSessions now accepts `now` like the sweep does,
so lifecycle cleanup can be exercised on a coherent clock.
* refactor(browser): split session tab cleanup claim and test harness
check-lint failed on max-lines: session-tab-registry.ts was at 699 of its
700-line budget and the durable registry test at 982 of 1000, so the retire
branch and its regression test pushed both over.
Extract the cleanup claim bookkeeping (claim, ownership match, delete) into
session-tab-cleanup-claim.ts, and the durable registry test shapes into
session-tab-registry.sqlite.test-helpers.ts, matching the existing
*.test-helpers.ts convention in this directory. No behavior change.
* test(browser): protect unreachable retirement races
Co-authored-by: Yigtwxx <yigiterdogan023@gmail.com>
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* feat(browser): send pages to the main session from the Chrome extension
One-click page share in the OpenClaw Chrome extension: toolbar popup with an
optional note, page/selection context menu, and Alt+Shift+S. Capture is
selection-first with a readability heuristic, X/Twitter thread extraction, and
Google Docs plain-text export via the user's session cookies. Payloads ride the
existing paired relay WebSocket as a new pageShare message; the gateway-only
page-share sink wraps page text in the external-content safety boundary, then
enqueues a main-session system event and requests an immediate heartbeat
(hooks/wake semantics). Node-hosted relays report a clear unsupported error.
Capture heuristics adapted from Nat Eliason's MIT-licensed send-to-openclaw.
Co-authored-by: Codex <codex@openai.com>
* fix(browser): keep page-controlled metadata inside the share safety boundary
Review findings: move title/URL inside wrapExternalContent (a hostile <title>
must not become trusted header text), prefer the user's selection over the
full Google Docs export, and pass the context-menu selectionText through so
iframe selections and selections cleared during relay reconnect still win.
* fix(browser): bind context-menu shares to the click-time document
Selection shares from the context menu now send the click snapshot directly
(no recapture), so navigations during relay reconnect cannot mislabel the
source and iframe selections are preserved. The Google Docs selection probe
scans all accessible frames before falling back to the full-document export.
* test(browser): expect the page-share handler in relay server args
* fix(browser): probe only the main frame for Google Docs selections
All-frame injection rejects wholesale when one frame is inaccessible and
returns child frames in nondeterministic order, so the probe now reads the
main frame only. Child-frame selections still share correctly through the
context menu's click-time selectionText; toolbar/shortcut entry sends the
full page for that case (named tradeoff in the code comment).
* fix(browser): satisfy page-share CI gates
---------
Co-authored-by: Codex <codex@openai.com>
* fix(browser): honor abortSignal during Playwright click-and-hold delay
The click-and-hold delay in clickViaPlaywright slept with a bare
setTimeout(resolve, delayMs) where delayMs can reach ACT_MAX_CLICK_DELAY_MS
(5s). The interaction AbortSignal was checked before and after the sleep
but the sleep itself was not abortable, so an abort mid-hold left the
action chain orphaned: the ref'd timer kept the event loop busy for the
full remaining delay and postponed the navigation-guard teardown (grace
recheck and, with an SSRF policy, the page.route interception) until the
orphaned sleep ended.
Swap the bare timer for sleepWithAbort(delayMs, signal) from
openclaw/plugin-sdk/runtime-env so the abort clears the timer and unwinds
the chain immediately. The caller-visible rejection is unchanged: the
abort listener registered before the action still wins the race with the
original signal.reason.
Co-Authored-By: Claude <noreply@anthropic.com>
* refactor(browser): reuse click abort helper
Keep the contributor abort-aware hold delay while deleting duplicate promise and listener lifecycle code in favor of the existing interaction helper.
Co-authored-by: wangmiao0668000666 <wang.miao86@xydigit.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(browser): honor AbortSignal in tab discovery poll
* fix(browser): unify abort-aware tab discovery
Apply the canonical abortable sleep to both discovery loops, reject late backend results after cancellation, and replace timing-sensitive proof with deterministic timer coverage.
Co-authored-by: wangmiao0668000666 <wang.miao86@xydigit.com>
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>