Commit Graph

176 Commits

Author SHA1 Message Date
mcfn.feng e5299ad485 [packages/cli-v2]fix: delete non-default profile dirs on session close
Restore profile directory cleanup on close, but only for non-default
profiles. The default profile ("actionbook") preserves user state
(cookies, localStorage) across sessions. Test-generated profiles
(e.g. "profile-s-12345-0") are cleaned up automatically.

Profile deletion happens after Chrome has fully exited to avoid
EBUSY from held locks.
2026-03-30 18:00:23 +08:00
mcfn.feng 0f9aab1d70 [packages/cli-v2]fix: use chrome_reaper in launch timeout path, fix misleading doc
- browser.rs: replace inline child.kill() (no wait) with
  chrome_reaper::kill_and_reap() to prevent zombie on launch timeout
- chrome_reaper.rs: remove misleading setsid reference from doc comment
2026-03-30 17:41:52 +08:00
mcfn.feng 894716c00b [packages/cli-v2]fix: prevent Chrome process leak with centralized cleanup
Chrome processes were not being cleaned up in several scenarios:
- SessionEntry had no Drop impl, so dropping it leaked the Chrome process
- CdpSession had no close() method, so background reader/writer tasks leaked
- close/restart/shutdown held the registry lock during slow CDP/Chrome cleanup
- child.wait() in spawn_blocking was fire-and-forget (never awaited)

Changes:
- Add chrome_reaper module with centralized kill_and_reap helpers
- Add Drop impl on SessionEntry as last-resort backstop
- Add CdpSession::close() that drops the writer channel and fails pending requests
- Move all slow I/O (cdp.close(), Chrome kill/reap) outside registry lock
- Use kill_and_reap_async (awaited) instead of fire-and-forget spawn_blocking
- kill_and_reap_option takes ownership (sets to None) to prevent double-cleanup
2026-03-30 17:40:30 +08:00
Asen's Agent ac1772f8ee fix: update argument descriptions to mention @ref alongside CSS/XPath
The clap argument descriptions for <SELECTOR> fields now read
"Selector (CSS, XPath, or @ref)" instead of generic "Target element
selector" or "CSS selector". Updated 19 command structs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 17:01:06 +08:00
mcfn 6cdfa25737 [packages/cli-v2]feat: relax version check to major.minor only (#186)
Replace exact BUILD_VERSION match with major_minor() comparison.
1.0.x-<hash> variants are now compatible as long as major.minor match.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 16:04:23 +08:00
Junliang Feng b419084676 [packages/cli-v2]test: cover version compatibility relaxation (#394) 2026-03-30 16:03:11 +08:00
mcfn 15174e38c8 [packages/cli-v2]test: cover version exact hash edges 2026-03-30 16:00:46 +08:00
Asen's Agent b6cb7776af feat: add snapshot ref (@eN) examples to all selector-based commands
Every command that accepts a <selector> now shows an @eN ref example
and mentions that refs come from snapshot output. Updated 18 commands
across interaction and observation groups.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 15:52:35 +08:00
Asen's Agent 56c49d3467 fix: unify all help entry points to show custom output
All equivalent invocations now show the same custom help:
  actionbook / actionbook help / actionbook --help / actionbook -h
  actionbook browser / actionbook browser help / actionbook browser --help

Pre-parse interception catches these before clap can emit its
auto-generated flat listing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 15:45:52 +08:00
mcfn 65fdfc796f [packages/cli-v2]test: cover version compatibility relaxation 2026-03-30 15:42:19 +08:00
Asen's Agent f2c404b4d7 fix: intercept top-level --help to show custom help instead of clap default
Both `actionbook --help` and `actionbook -h` now show our custom
agent-friendly help instead of clap's auto-generated output.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 15:41:12 +08:00
asensagent 713eea732e fix: unify browser --help with grouped help, add new command docs (#368)
fix: unify browser --help with grouped help and add new command docs

- Intercept `browser --help` to show grouped listing instead of clap's
  flat ungrouped output. Both `browser help` and `browser --help` now
  show the same grouped view.
- Add after_help examples to 5 new observation commands: attrs, box,
  styles, describe, state.
- Update grouped help listing with all 40 browser commands.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 15:35:59 +08:00
Junliang Feng 9109bf2f54 [cli-v2] fix: prevent daemon process accumulation with flock singleton (#390) 2026-03-30 15:34:07 +08:00
mcfn 439a8c1c2f [packages/cli-v2]feat: filter insignificant nodes from describe --nearby output (#138)
Add isSignificant(e) check in the nearby JS: keep a node only if it has
an explicit accessible name (aria-label, labelledby, label[for], closest
label), direct text node children, or is an interactive element
(button/a/input/select/textarea). Structural wrapper divs/spans with no
own text and no name are filtered to null (parent/sibling) or excluded
(children).

Uses a dedicated gna() helper (like gn() but without the innerText
fallback) so descendant text doesn't make empty wrappers appear
significant.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 15:28:44 +08:00
mcfn.feng 15a529ccbe [packages/cli-v2]fix: address review — flock EINTR, idle timeout config, faster E2E
Fixes from dual code review (code-reviewer + codex challenge):
- P0: Invalid ACTIONBOOK_DAEMON_IDLE_TIMEOUT_SECS now falls back to default
  instead of silently disabling timeout
- P1: Replace std::thread::sleep with tokio::time::sleep in async fn
- P1: Extract parse_idle_timeout() pure function for race-free unit testing
- P2: Add ACTIONBOOK_DAEMON_HOUSEKEEPING_INTERVAL_SECS for fast E2E tests
  (idle timeout tests now run in 12s instead of 72s)
- P2: Remove manual stale file cleanup from crash_recovery test
- Minor: Handle EINTR in flock with retry loop
2026-03-30 15:20:32 +08:00
mcfn 1dca8e1d90 [packages/cli-v2]feat: add qualifier enrichment to describe summary (#137)
Summary now appends [disabled], [checked], [selected] qualifiers based
on element state. Multiple qualifiers use comma separation: [disabled, checked].
Default states (enabled, unchecked, unselected) produce no qualifier.

Changes:
- describe.rs gn(): add label lookup via label[for] and closest('label')
  so checkbox/radio names resolve to associated label text
- describe.rs gst(): add checked and selected to state object
- describe.rs summary builder: append qualifiers from state
- output.rs: use pre-computed summary field from response data

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 15:13:40 +08:00
4bmis 521f2055ad Merge branch 'release/1.0.0' of github.com:actionbook/actionbook into release/1.0.0 2026-03-30 15:03:04 +08:00
4bmis 17dc3d219c [packages/cli-v2]fix: print help when call actionbook with no subcommand 2026-03-30 15:02:54 +08:00
mcfn.feng 3336a07258 [packages/cli-v2]fix: prevent daemon process accumulation with flock singleton
Replace the PID file + kill(pid, 0) singleton mechanism with flock-based
locking. The kernel guarantees flock atomicity (no TOCTOU race) and
auto-releases on process exit (including SIGKILL / panic=abort), solving
the root cause of 30+ orphaned daemon processes accumulating over time.

Changes:
- Use flock(LOCK_EX | LOCK_NB) on PID file for daemon singleton
- is_daemon_running() now probes flock instead of PID + kill(pid, 0)
- Losing daemon retries 3 times, checks if winner's socket is ready
- Add idle timeout (default 30min, ACTIONBOOK_DAEMON_IDLE_TIMEOUT_SECS)
- Add has_active_sessions() helper to SessionRegistry
- Add unit tests for flock, idle timeout config, registry
- Add E2E tests for concurrent start, idle timeout, crash recovery
2026-03-30 14:59:09 +08:00
Junliang Feng d29cdb7edd fix(cli-v2): cargo fmt formatting for short tab ID change (#388) 2026-03-30 14:48:40 +08:00
mcfn 9d2ad69507 [packages/cli-v2]chore: cargo fmt + remove dead resolve_tab_ws_url function
- cargo fmt: reformat tracing::warn! in close.rs per rustfmt rules
- Remove resolve_tab_ws_url() from daemon/cdp.rs: function was never
  called and contained a stale tab.id.0 reference (short ID) where
  the WS URL needs the native CDP target ID

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 14:47:28 +08:00
mcfn c5258e7f55 [packages/cli-v2]fix: use native_id for CDP calls in close-tab, cookies, and session reuse
Short tab IDs (t1, t2, ...) are user-facing labels, not CDP target IDs.
Three call sites were incorrectly passing the short ID to CDP methods:

- close.rs: extract native_id from registry before lock release;
  use it for cdp.detach() and Target.closeTarget
- cookies/{list,get,set,delete,clear}.rs: use t.native_id instead of
  t.id.0 when looking up the target for execute_on_tab
- session/start.rs ReuseTarget: add first_native_id field (CDP target
  ID); use it for Page.navigate, /json/list lookup, and cloud registry
  lookup; keep first_tab_id (short ID) for JSON response only

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 14:43:56 +08:00
mcfn c417da59ae [packages/cli-v2]fix: cargo fmt formatting
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 14:37:47 +08:00
Junliang Feng 329d7a18c7 feat(cli-v2): short tab IDs (t1, t2, ...) with native_tab_id (#386) 2026-03-30 14:34:18 +08:00
mcfn c9141e610c [packages/cli-v2]feat: short tab IDs (t1, t2, ...) with native_tab_id
- TabEntry gains native_id (Chrome CDP target ID); id becomes short form
- SessionEntry.push_tab() assigns t1/t2/... sequentially and stores native ID
- get_cdp_and_target resolves short ID → native ID for CDP calls
- start/new-tab/list-tabs/status responses include both tab_id and native_tab_id
- E2E: assert_tab_id validates t\d+ format; tests updated for native_tab_id presence

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 14:00:48 +08:00
mcfn.feng 49830e8434 [packages/cli-v2]fix: replace opaque CDP channel errors with SESSION_CLOSED
When a session is closed while commands are still in flight, the error
was "cdp error: writer channel closed" or "cdp error: connection closed"
which gave no indication of the root cause. Replace with a dedicated
SessionClosed variant that outputs "SESSION_CLOSED: session was closed
while command was pending" with an actionable hint.
2026-03-30 13:59:53 +08:00
4bmis 8b3b1b59f2 Merge branch 'release/1.0.0' of github.com:actionbook/actionbook into release/1.0.0 2026-03-30 13:19:53 +08:00
Junliang Feng efe8a1661f feat(cli-v2): enable parallel E2E tests with profile cleanup (#383) 2026-03-30 13:19:15 +08:00
mcfn.feng 2febbe0f49 [packages/cli-v2]fix: address PR review — scope profile cleanup to local sessions + release lock early
P1: Only delete profile directory for local sessions (those with a
    Chrome child process). Cloud sessions don't own a local profile
    and could share a profile name with an active local session.
P2: Release the registry lock before slow I/O (Chrome kill, recursive
    directory removal) to avoid blocking concurrent session operations.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 12:45:14 +08:00
4bmis 0ae1b83770 Merge branch 'release/1.0.0' of github.com:actionbook/actionbook into release/1.0.0 2026-03-30 12:43:02 +08:00
mcfn.feng 15726ad7b1 [packages/cli-v2]fix: wait navigation race condition + ignore flaky cloud concurrent test
- wait navigation: use two-phase detection (URL change → readyState
  complete) instead of OR logic that false-positives on initial page's
  readyState=complete before navigation fires
- cloud_concurrent_eval_multi_tab: mark #[ignore] — known CDP
  multiplexing issue mis-routes concurrent responses across tabs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 12:42:53 +08:00
mcfn.feng e51ec6ec9d [packages/cli-v2]feat: enable parallel E2E tests with profile cleanup
- browser close now removes the Chrome profile directory after killing
  the process, preventing disk accumulation from test runs
- Remove --test-threads=1 from CI — each test already uses a unique
  session/profile via unique_session(), so parallel execution is safe
- Profile isolation: each test gets its own Chrome user-data-dir,
  no shared state between concurrent tests

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 12:31:54 +08:00
4bmis c59de08a13 Fix cli-v2 global timeout handling 2026-03-30 12:18:19 +08:00
mcfn.feng 21dfd720f0 [packages/cli-v2]refactor: introduce TabContext to eliminate repetitive resolver params
Replace the 6-param pattern (cdp, target_id, selector, registry,
session_id, tab_id) with a TabContext struct created once per command.

Before: element::resolve_node(&cdp, &target_id, &sel, registry, &s, &t)
After:  ctx.resolve_node(&sel)

TabContext bundles (cdp, target_id, registry, session, tab) and exposes
resolve_node, resolve_center, resolve_object, scroll_into_view, and
resolve_object_id. Public fields cdp/target_id for direct CDP calls.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 12:12:26 +08:00
mcfn.feng 148649e8e8 [packages/cli-v2]fix: address PR review — synthetic ref guard + CDP error routing + AX fallback
P1: Skip CDP DOM.resolveNode for synthetic backendNodeIds (< 0, from AX
    nodes without a backing DOM element) — go straight to AX fallback.
P2: Distinguish CDP -32000 (stale node) from other errors (connection
    lost, etc.) — only return REF_STALE for true staleness, propagate
    other CDP errors through the standard error path.
Fallback: When backendNodeId resolution fails or is synthetic, use
    Accessibility.queryAXTree with the stored role + name to find the
    element in the current AX tree.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 11:45:09 +08:00
mcfn.feng 70d2907bb4 [packages/cli-v2]style: cargo fmt after merge with release/1.0.0
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 11:40:05 +08:00
mcfn.feng 4ec3705cc6 Merge remote-tracking branch 'origin/release/1.0.0' into bugfix/click-ref 2026-03-30 11:39:24 +08:00
mcfn.feng 8a7ab9d818 [packages/cli-v2]feat: implement @eN snapshot ref resolution for all selector commands
Wire up snapshot refs (@e1, @e5, etc.) so all commands that accept a
<selector> argument can use them. Previously resolve_ref() was a stub
returning UNSUPPORTED_OPERATION.

- Add reverse lookup (ref_to_id) to RefCache for O(1) ref→backendNodeId
- Add peek_ref_cache() to SessionRegistry for read-only access
- Implement resolve_ref: @eN → backendNodeId → objectId → nodeId via CDP
- Thread registry context through resolve_node, resolve_element_center,
  and resolve_selector_object (22 call sites updated)
- Error codes: REF_NOT_FOUND, REF_STALE, INVALID_ARGUMENT

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 11:36:05 +08:00
mcfn.feng 7a1cc2207a [packages/cli-v2]fix: misc fixes from cookies, logs, wait, and CDP session
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 11:35:46 +08:00
mcfn 1af3161317 [packages/cli-v2]feat: add -v short flag and version subcommand
- Add short alias -v to --version flag
- Add `version` subcommand that calls the same handle_version() logic

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 11:10:16 +08:00
Junliang Feng dfc4e2011c feat(cli-v2): browser storage commands (Batch 9, #167) (#379)
* [packages/cli-v2]feat: implement browser storage commands (Batch 9)

Add browser local-storage and session-storage commands (list/get/set/
delete/clear) via Runtime.evaluate on window.localStorage/sessionStorage.
Tab-level addressing (--session + --tab). Shared StorageKind enum drives
JS object selection and response storage field. clear <key> removes a
specific key (action: "clear", affected: 1), matching spec §14.5.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* [packages/cli-v2]fix: correct CDP null detection in storage get

CDP represents JS null as {type:"object",subtype:"null"}, not type:"null".
Update cold-miss check to test for subtype=="null" when type is "object".

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 10:52:32 +08:00
mcfn 9db064eb86 [packages/cli-v2]fix: correct Runtime.evaluate pointer for hostname in set.rs
CDP Runtime.evaluate response nests the value at result.result.value,
not result.value. Wrong pointer caused implicit-domain derivation to always
fall back to None, triggering "domain/url required" CDP error.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 09:43:47 +08:00
mcfn 28bbac1ca8 [packages/cli-v2]fix: route cookie CDP ops through tab-scoped session (Robin B1)
All Network.getAllCookies, Network.setCookie, Network.deleteCookies calls
now route via execute_on_tab using the first active tab's target_id instead
of execute_browser (browser-scope). Local Chrome does not expose Network.*
at browser scope — fixes CDP error -32601 on real E2E runs.

Also defaults path to "/" in set.rs when --domain is omitted (§13.3).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 09:39:08 +08:00
mcfn 067687375a [packages/cli-v2]fix: cookies set domain derivation and normalize_domain lowercase (B1+B2)
B1: when --domain is omitted, derive domain from window.location.hostname
evaluated on the first active tab; prepend leading dot for subdomain matching.
B2: normalize_domain now lowercases in addition to stripping the leading dot
to handle mixed-case domain comparisons (e.g. Example.COM vs example.com).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 09:32:56 +08:00
mcfn f79d112828 [packages/cli-v2]feat: implement browser cookies commands (Batch 8)
Add browser.cookies.list/get/set/delete/clear commands with CDP-level
Network.getAllCookies, Network.setCookie, Network.deleteCookies support.
Session-level only (no --tab); list/get are observations, set/delete/clear
are actions. Includes domain filtering, field mapping (httpOnly→http_only,
sameSite→same_site, expires=-1→null), and text/JSON output formatting.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 09:28:49 +08:00
Junliang Feng e7979fd226 browser wait commands — element/navigation/network-idle/condition (Batch 7) (#375) 2026-03-30 02:02:26 +08:00
mcfn fc892950b0 [packages/cli-v2]fix: move Network.enable to attach() so pending tracking starts at tab birth
Root cause of B3 false-positive: subscribing to Network events inside
`wait network-idle` only sees requests that start AFTER the wait call.
Requests already in-flight (e.g. fetch/new Image() started before wait)
are permanently missed, so pending stays 0 and the idle quiet window
expires immediately.

Fix — session-level Network tracking:
- `CdpSession` gains `tab_net_pending: Arc<Mutex<HashMap<String, i64>>>`
- `attach()` calls `Network.enable` on the tab immediately after storing
  the session ID, before any user command runs
- `reader_loop` maintains `tab_net_pending` from Network domain events:
  +1 on requestWillBeSent, -1 on loadingFinished/loadingFailed/
  requestServedFromCache (clamped to 0)
- New `network_pending(cdp_session_id) -> i64` returns the live counter
- `detach()` cleans up the counter entry

`network_idle.rs` simplified:
- Removes per-wait event-channel subscription (subscribe-after-request
  race is gone by design)
- Polls `cdp.network_pending()` — reflects all requests since attach
- Keeps JS guard (readyState + img.complete) as secondary check
- Quiet window: pending==0 && js_idle held for 500ms

New unit test: test_network_pending_counter verifies counter increments,
decrements on terminal events, and clamps at 0.
Updated test_attach_detach to expect the new Network.enable exchange.
178/178 unit tests pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 01:57:36 +08:00
mcfn 0c0c2774b2 [packages/cli-v2]fix: implement CDP Network event subscription for network-idle wait
Add event routing infrastructure to CdpSession:
- New `event_subs` field keyed by "{cdp_session_id}:{method}"
- reader_loop now routes CDP events (messages without "id") to subscribers
- `subscribe_events(cdp_session_id, method)` returns mpsc::Receiver<Value>
- `get_cdp_session_id(target_id)` looks up the flat-session ID
- 2 new unit tests: test_event_routing, test_event_not_routed_to_wrong_session

Rewrite network_idle.rs to use CDP Network domain events:
- Subscribe to requestWillBeSent/loadingFinished/loadingFailed/requestServedFromCache
  before calling Network.enable (avoids the subscribe-enable race)
- Track in-flight pending count; decrement on terminal events
- Require pending == 0 AND js_idle (readyState + img.complete) for 500ms quiet
  window before declaring idle
- Fixes B3 false-positive: JS-only heuristics missed non-DOM fetch/XHR/script
  requests; CDP events cover all network traffic

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 01:50:43 +08:00
mcfn 07933ec52b [packages/cli-v2]fix: network-idle B3 — add img.complete + in-flight entry checks (#156)
Performance Resource Timing entries only appear after completion so the
previous responseEnd-only filter missed in-flight requests. Add two more
layers before the Performance API check:

1. document.readyState guard (page must be complete)
2. img.complete check — detects <img> elements still loading
3. responseEnd === 0 entries (W3C Resource Timing Level 2 in-flight)
   + recent entries within 500ms idle window

Any of the three layers returning pending > 0 keeps the wait loop alive.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 01:42:19 +08:00
mcfn 3ad54d122c [packages/cli-v2]fix: 3 wait handler corrections from Robin-Cx review (#156)
B1 wait navigation: use OR instead of AND for url_changed/readyState check.
Same-URL navigations (reload) never change URL — returning on either condition
matches actionbook-rs reference and handles both cases correctly.

B2 wait condition: arrays and objects are always truthy in JS regardless of
whether they are empty. Fix Rust truthiness to use _ => true for both,
matching actionbook-rs reference (waiting.rs:173).

B3 wait network-idle: replace readyState-only check with Performance Resource
Timing API inspection. Count resources with responseEnd < 500ms ago;
network is idle only when that count reaches zero. Matches actionbook-rs
reference (waiting.rs:98-120).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 01:37:50 +08:00