Skip to content

feat(agent): add browser self-healing and durable overrides - #292

Open
IRONICBo wants to merge 2 commits into
socai-io:mainfrom
IRONICBo:feat/local-browser-script-overrides
Open

feat(agent): add browser self-healing and durable overrides#292
IRONICBo wants to merge 2 commits into
socai-io:mainfrom
IRONICBo:feat/local-browser-script-overrides

Conversation

@IRONICBo

@IRONICBo IRONICBo commented Sep 5, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • add a generic browser_script recovery tool for browser-backed site-tool failures
  • let the agent inspect and operate the current logged-in tab with arbitrary DOM JavaScript plus trusted CDP click/type/press helpers
  • persist a successfully validated repair under ~/.socai/tool-overrides/<site>/<tool>/ and transparently reuse it on later calls
  • migrate older local overrides safely across Socai releases: native canary first, then stale, re-certified active, or quarantined
  • support both function and function-body string arguments in socai.evaluate / socai.waitFor
  • keep Rust site tools as the preferred path and trigger repair only for explicit DOM, selector, extraction, CDP, or page-transition failures

Production evidence

The original v0.5.5 XHS failure observed the requested query in the search input while the browser remained on /explore. The run saw 32 homepage cards, returned zero search results, and reported Search did not transition to a valid Xiaohongshu result page. PR #291 fixes that concrete composer transition on main; this PR supplies a generic local recovery path for the next site DOM/protocol change without requiring a Rust toolchain on the user's machine.

A logged-in XHS livestream task then exposed a bridge compatibility bug. The agent correctly called socai.evaluate(() => ({ ... })), but JSON serialization omitted the function-valued script member and Rust returned browser-script operation requires 'script'. After normalizing function arguments to source, four consecutive calls completed, entered /livelist, and returned the live categories/cards.

Root cause

The existing high-level tools encode selectors, page transitions, and extraction contracts in Rust. When a site changes those assumptions, the running binary can provide good failure evidence but previously had no safe way to probe the live DOM, validate a replacement, retain it locally, and continue the original task.

The first bridge implementation also documented function arguments but only transported strings. JSON.stringify drops function-valued object properties, so an otherwise valid socai.evaluate(() => ...) request arrived without args.script.

The initial persistence design retained overrides indefinitely across upgrades. That could hide a repaired native implementation, reuse an incompatible local contract, or make rollback fail if an older binary encountered a newer manifest schema.

Implementation

  • wrap supported site tools with LocalOverrideTool; successful built-ins are unchanged, while repairable browser failures return an explicit recovery.action:"browser_script"
  • run the agent control program in a short-lived background sibling tab so navigation of the target page does not destroy pending bridge promises
  • accept function or string page scripts; function source is normalized before the bridge request is serialized
  • evaluate target-page DOM scripts in a fresh CDP isolated world so page code cannot replace serialization, error, or size-check intrinsics
  • terminate an in-flight live-page evaluation on timeout or task cancellation, and close partially created/control targets through RAII cleanup
  • bind type() to a unique marker on the exact visible selector match and refuse to type if click focus moved elsewhere
  • write immutable script candidates with private permissions, execute the exact bytes read back from disk, and atomically activate an immutable manifest containing SHA-256, source version, sanitized URL, timestamp, and timeout
  • require explicit ok:true, matching query/author/note IDs, non-empty activation evidence, and usable per-item identity/content/handoff fields before activation; validate the same contract again on every reuse
  • validate native canary/fallback output against the same per-tool structure before retiring an override; malformed JSON or a bare ok:true is not success
  • check login, captcha, security verification, and rate-limit state before and after persistent script execution; XHS and Douyin have explicit blockers, and Douyin also blocks blank/throttled pages
  • serialize lifecycle decisions with one process-wide async lock and compare the expected latest manifest before appending a state transition
  • surface _socai_local_override and an artifact path on reused results; when a failed upgrade canary may have produced partial page/artifact/history/enrichment effects, report that explicitly

Persistent overrides are intentionally limited to XHS search, get_notes, and author_scan, plus Douyin search. Login, captcha, security verification, rate limits, permissions, true empty results, provider errors, media failures, and OCR failures are not repair triggers. Browser JavaScript has page authority only and receives no host shell or filesystem API.

Persistence, upgrades, and rollback

~/.socai/tool-overrides/<site>/<tool>/
├── active-<timestamp>-<id>.json      # v2, readable by the previous binary
├── state-<timestamp>-<id>.json       # v3 active/stale/quarantined successor
└── versions/
    └── <tool>.<timestamp>.<id>.js

The agent first probes without save_as. Once the complete replacement returns contract-valid, non-empty evidence, it calls browser_script with save_as.tool. The runtime performs blocker preflight, writes the candidate, reads and executes the exact disk bytes, performs blocker postflight and contract validation, then atomically publishes the activation manifest. The next ordinary tool call verifies the latest manifest and script hash and reuses it automatically.

The manifest records source_version, validated_with_version, tool_contract_version, tool_impl_revision, timestamps, status, and reason. On a release or implementation revision change, the new built-in runs once. A contract-valid native success appends stale; a repairable native failure allows the old script to run and be re-certified; a contract change appends quarantined. Activation records stay schema v2 so an older binary can still read them, while newer lifecycle-only records use a separate state-* v3 namespace that the old reader ignores.

Ad-hoc live-page inspection without save_as is recorded in the run but is not persisted as an override.

Validation

  • cargo test -p socai-core --lib — 99 passed, 0 failed, 2 ignored
  • cargo test -p socai-cli — 6 passed, 0 failed
  • cargo check --workspace --all-targets
  • node --check core/src/sites/xhs/page_scripts.js
  • git diff --check
  • real logged-in XHS livestream run: four function-form socai.evaluate calls completed and the task entered /livelist
  • real Chrome override smoke: disk-backed save, automatic reuse, infinite-loop timeout, bounded exception/result handling, bridge request limit, and cancellation cleanup
  • managed Chrome hostile-page smoke: after the main world replaced JSON.stringify and TextEncoder, the isolated world still read the same DOM and returned the expected JSON and byte count
  • two Codex CLI review passes covered blocker pre/postflight, strict native success contracts, canary side effects, concurrency, compatibility, cancellation, exact typing targets, and persistence safety; every reported finding was addressed

No new Rust test function was added, following the repository engineering rule; browser-only behavior was exercised against real Chrome and existing suites cover the shared Rust build.

@vercel

vercel Bot commented Sep 5, 2026

Copy link
Copy Markdown

@IRONICBo is attempting to deploy a commit to the socai Team on Vercel.

A member of the Team first needs to authorize it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant