Navigácia
ELOWEN · Dokumentácia

Posledná aktualizácia: 20. októbra 2026

Prechádzať dokumentáciu · browser implementation

Tento článok zatiaľ nie je preložený. Je dostupný v angličtine.

Developer reference

browser implementation

Problem audiences

Page preparation and live-page reads preserve the core typed instance-condition cause. Browser command deadlines are local outcomes for the user-selected page. Display process exits from SIGTERM, SIGINT or SIGKILL stay local; unexpected exit codes and crash signals remain reportable. VirtualDisplayPool.failure now returns the typed Error, and the registry preserves its message and audience when recycling sessions. Owner log messages, teardown behavior and scheduling are unchanged. This requires the paired core candidate's exported elowen-plugin-shared/errors seam.

Migration entry

The manifest entry exports migrate(db: PluginDb<never>): Promise<void> beside register. The daemon loader always awaits it before registration for this reads: ['db'] plugin; the DB CLI calls the same export without registering tools, routes, timers or services. The export runs only the existing transactional plugin SQL through db.migrate. A failed step rolls back its SQL and ledger entry and prevents registration. Repeated calls skip applied versions. Runtime store construction never invokes migrations. Tests that bypass the loader explicitly await this export before using their fixture.

Asynchronous profile I/O

BrowserPool.profileSize(userId) returns a promise and walks sequentially through filesystem promises, retaining the real-directory check, symlink exclusions and existing 100,000-directory limit. Only ENOENT races while walking or measuring files are skipped; permission and I/O failures propagate to the profile API's existing error response. There is no size cache or new timeout.

BrowserPool.clearProfile(userId) checks active launches, sessions and closings, validates the real directory and renames it synchronously before its first await. It then awaits recursive deletion of only the renamed directory, so a newly launched browser's replacement profile survives. SessionRegistry awaits clear and account deletion; deleteUser removes database records only after profile cleanup succeeds. GET and DELETE profile await the same methods. Filesystem failures retain the existing sanitized API response. No deletion queue is added; interruption may leave the detached directory.

Bounded session cleanup

The host awaits the entry export migrate(db) before registration publishes any service. The store uses the async PluginDb in the explicitly quoted plugin_browser schema. Migration 8 is the baseline that creates the sessions, processes and displays tables. Installed ledgers already record it, so it is never renumbered and later steps start at version 9. Session rows have a generated bigint row_id for bounded retention. Store methods resolve after their SQL completes; generated identity values never appear in HTTP DTOs.

Closed-history retention and account deletion use drainDbRows from elowen-plugin-shared/dbBatches with separate schema, table and identity names. The helper locks matching identities in order and measures UTF-8 id, state and nullable close reason plus 256 bytes of row overhead. Each transaction removes at most 64 rows / 256 KiB, processes one oversized row alone and yields after commit. The partial sessions_expiry index covers closed-history selection. Boot reconciliation retains its 64-row UPDATE batches and uses the same module's yieldDbBatch. Production rows normally contain only a few KiB, without transcripts or images. SessionRegistry awaits each operation before acknowledging completion or proceeding to orphan-process cleanup. Interrupted cleanup can resume by repeating the same predicate. Process and display records are removed only after the account's session drain completes. An empty deletion predicate returns without yielding.

Initial account page

BrowserPool captures Chrome's sole page target at launch only when it is about:blank. The first openPage consumes that target before awaiting CDP, reuses its still-open blank page and installs the same proxy authentication and primary-tab ownership as a newly created page. Concurrent and later opens create distinct pages. A launch with other pages, or an initial page already navigated or closed, is left alone; no live blank-tab scan or cleanup runs. Agent/user-created blank tabs are never reused or closed by this rule. Managed-Project conversations use this same account browser, not the Sandbox desktop's Chrome. Headless Site captures own disposable browsers and have no shared session tabs.

Developer reference

Browser tool ownership awaits the host's currentIdentity() before resolving the linked account and private conversation. Both BrowserOpen and existing-session tools await this authorization; shared rooms and delegated child agents retain their existing refusals.

The provider implements the host's BrowserCaptureRequest, BrowserCaptureResult and BrowserCaptureControl types. Its local dispose() stops active captures on plugin shutdown. The account launcher and capture launcher share only Puppeteer loading and common network/background arguments in src/puppeteer.ts. Account sessions stay headed with a persistent profile and private display; captures stay headless with an isolated disposable profile and an origin-pinned proxy. Both retain Chrome's sandbox defaults. Without a registered capture provider, Sites reports capture unavailable; registration allocates no Chrome process and the asynchronous availability probe only loads dependencies.

Browser host waits use withTimeout from elowen-plugin-shared/withTimeout; src/deadline.ts only defines BrowserDeadlineError. Call withTimeout(work, milliseconds, new BrowserDeadlineError(message)) with an existing promise or a callback. A callback runs once after the timer is armed; synchronous throws retain their original identity. The helper clears and unreferences its timer on settlement and rejects on expiry with the exact supplied error. It bounds the wait without cancelling the browser command. Favicon lookup is one consumer: it waits at most five seconds and publishes no icon on failure. BrowserSession, tab reads and tracing use their existing per-command budgets; the operation safety handler recycles Chrome only for its own 45-second deadline, and tracing recycles Chrome when its state becomes unknown. Site capture keeps its separate resolve-on-expiry and abort races. Importing the helper allocates no timer or browser; each call allocates one timer and requires a core package that actually ships this shared export. Closing aborts waiting agent work once with the actual close reason, then abandons tracing, removes listeners, detaches CDP and releases the page.

src/queue.ts::SerialQueue serializes each BrowserSession's operations and each SessionRegistry's creation requests through separate queue instances. Its promise tail continues after a rejection; it introduces no global scheduler or work while idle. src/process-identity.ts::processIdentityMatches(snapshot, startedAtTicks, executablePath, argToken) compares one freshly inspected process with its recorded start tick, executable and exact command-line token. BrowserPool close, registry boot reconciliation and virtual-display orphan cleanup share this predicate. Registry escalation inspects again immediately before delayed SIGKILL, preserving PID-reuse refusal and each caller's reporting policy. These are plugin-local helpers, with no new host export or persistent record shape.

web-src/useBrowserSessions.ts owns the signed-in account read, account/session thumbnail key and request, sessions listing and close mutation for BrowserAccount and BrowserChatDock. useBrowserThumbnail delegates cadence and visibility to host useDesktopStill; the dock additionally requires a known account, collapsed view, no held lease and an open stream. useCloseBrowserSession posts the selected session id and invalidates both sessions and profile after success. Callers retain their error toasts and confirmation state; a refusal invalidates neither cache. Importing the hooks issues no request until a surface mounts.

Session patches use one UPDATE containing only named fields, so disjoint concurrent patches preserve each other. BrowserSession orders persistence through one promise tail. Synchronous controller and viewer-activity notifications enqueue their captured state; failures are logged immediately with the existing plugin.service_failed problem code and remain rejected for the next awaited operation or close. Session close and idle/expiry/recycle callbacks observe rejected cleanup promises. Chrome, its proxy and its display are released even if reading or deleting the process ownership row fails. Startup, shutdown, process/display ownership and orphan reconciliation await their records before continuing. BrowserPool reserves each account's launch before awaiting its old process record, so simultaneous opens share the same launch. Missing and foreign live-session ids both raise BrowserAccessError with status 404 at the registry boundary, which the HTTP API forwards independently of message wording.

Browser UI requires host UI API 55 in both its manifest and registration. Its runtime uses the published ElowenUiRuntime['api'] and ElowenUiRuntime['utils']['interpolate'] types without a local fallback. BrowserAccount uses host interpolation for the running-session count. Without an installed host runtime, the plugin throws instead of rendering a substitute; an unsupported host is rejected by the UI compatibility gate. These runtime declarations do not allocate requests or browser sessions. JSON request serialization belongs to the host API boundary; BrowserChatDock sends navigation as { method: 'POST', json: { leaseId, action } } and close as { method: 'POST' }.

Next: Scheduling Plugin