Commit Graph
9664 Commits
Author SHA1 Message Date
Adrià Arrufat 9c583f1fb0 js: hand --locale to ICU instead of LC_ALL
A BCP 47 tag in LC_ALL is not a POSIX locale, so setlocale(LC_ALL, "")
failed for the rest of the process and for any child. In the REPL,
isocline took the terminal for non-UTF-8 and dropped every non-ASCII
keystroke. Set ICU's default locale through the new
v8__V8__SetDefaultLocale binding and leave the C library alone.

Pins zig-v8-fork to lightpanda-io/zig-v8-fork#209; CI links once that is
tagged and action.yml's zig-v8 is bumped.
2026-09-23 11:18:44 +02:00
Adrià Arrufat 1df5838bdb agent: record slash commands that address a node by id
/save kept a model's backendNodeId call by swapping in the selector the
tool layer resolved, but a typed `/scroll backendNodeId=3` never asked
for one and was dropped from the script. Ask for it on this path too.
2026-09-23 11:04:22 +02:00
Adrià Arrufat a099fb580b tools: let scroll target an element by selector
Recording swaps a call's backendNodeId for the selector the tool layer
resolved, but scroll had no selector parameter: the replayed
scroll({ selector, y }) dropped the field and scrolled the window. Take
a selector as click and hover do.
2026-09-23 11:03:25 +02:00
Adrià Arrufat 7289281784 agent: record the --url navigation for a REPL /save too
It was recorded only for a one-shot --save, so a REPL session opened
with --url saved a script that started on a blank page.
2026-09-23 11:00:36 +02:00
Adrià Arrufat 3ab936ebbb agent: report a --url that fails to load
`call` reports a failed navigation in-band, as an is_error result, so
the catch in gotoStart never saw it: a start page that could not
resolve left the agent on a blank page with no message.
2026-09-23 11:00:16 +02:00
Adrià Arrufat 3514d8d46b deps: bump isocline for the strdup_from_utf8 overflow
isocline copied the submitted line into a buffer one byte short whenever
the locale is not UTF-8. A line whose length the allocator serves
exactly, such as the 24-byte `/screenshot path=out.png`, corrupted the
heap on Enter.
2026-09-23 10:59:30 +02:00
Adrià Arrufat 2a9abd880f deps: bump zenai for the reasoning-token floor
Picks up the per-effort `max_tokens` floor, so a tight budget no longer
buys mostly thinking and a truncated answer, and the search clients
logging a non-2xx like the model clients already did.

The bump also carries zenai's `ErrorDetail.body` -> `.message` rename,
which the search failure path reads.
2026-09-22 16:20:24 +02:00
Adrià Arrufat 2f0d037a53 agent: tighten the backported fixes
Review pass over the branch.

`selectorForArgs` ran for any call carrying a `backendNodeId`, before
`isRecorded` was consulted. `SelectorPath.build` is the expensive part of
a tool call -- uncached full-document matches per ancestor -- and the
read-only tools that take an id (tree, markdown, html, nodeDetails) are
exactly the ones the system prompt steers toward ids, so the common case
built a selector and threw it away. `nodeDetails` built it twice.

The recorded selectors were duped into `self.allocator` and never freed:
`clearRetainingCapacity` drops the only pointers. They belong to the
conversation arena, which already owns the args and the `Command` built
from them, so the ownership question disappears rather than being
patched with a free loop.

`searchKeyStatus` returned null for `.auto`, which is the default -- so
the keyless-endpoint notice never fired in the one configuration that
reaches a keyless endpoint. It now reports the rung the cascade lands on.

`gotoStart` was inserted between `printUsageSummary`'s doc comment and
the function, leaving the `$usage` wire format -- which wrapper scripts
grep -- documenting the wrong function. It also returned `?ToolError`
where the rest of the file uses error unions.

Plus: the three search collectors differed only in a field name, the
failure writer had five `catch return OutOfMemory` in ten lines beside a
neighbour doing it with one, `visitAll` was `pub` with no caller outside
its file, and the tests named themselves after a function that no longer
renders anything.
2026-09-22 16:12:43 +02:00
Adrià Arrufat c839d1fcca agent: refuse to save a script the model did not finish
`/save` synthesis never looked at `finish_reason`, and `stripCodeFence`
accepts a fenced block with no closing fence. A script cut off at
`max_tokens` therefore looked exactly like a complete one: it was written
to disk, the save path was remembered, the buffer was reset and the
terminal said "Saved synthesized script to ...". The truncation only
turned up on replay.

It now aborts like any other failed synthesis, leaving the previous file
and the buffer alone.
2026-09-22 15:54:16 +02:00
Adrià Arrufat 182ad25157 NodeRegistry: pin that reset never reuses an id
`reset` clears the lookup maps and deliberately leaves `node_id` where it
is, so a stale id fails closed with a miss instead of resolving to
whatever registers next. Nothing asserted that, and it is the invariant
every id-addressed tool call leans on across a navigation.
2026-09-22 15:53:21 +02:00
Adrià Arrufat b012c44fa0 SemanticTree: walk once, in visitAll
`jsonStringify` and `textStringify` each carried their own copy of the
same six-line preamble -- xpath buffer, listener target map, label index,
walk, error log -- differing only in the visitor and the wording of the
log line. Either one could drift from the other silently, since nothing
compares them.

`visitAll` takes the visitor and owns the walk; both dumpers are now one
line. Their existing tests cover it.
2026-09-22 15:53:00 +02:00
Adrià Arrufat 006af21a6d agent: --url opens a start page before the first turn
A `--task` run that knows where it is going still spent a model turn
navigating there, and the REPL had no way to start anywhere but blank.

`--url` opens the page first, through `browser_tools.call` rather than
around it, so a bad URL fails like any other tool call and the opening
navigation is recorded for `--save` -- which is the first line any
replayable script needs anyway.
2026-09-22 15:52:07 +02:00
Adrià Arrufat 92a6b698fc agent: --search-engine, and say when the engine has no key
The search engine could only be set from the REPL's `/searchEngine`, so a
one-shot run had no way to pin one -- and a benchmark that wants its
results to mean something has to record which API answered.

`/searchEngine` also carried the only warning about key state, which is
the half that matters more. A keyless engine is not an error and starts
fine, so a run that silently falls back to a rate-limited public endpoint
looks exactly like a working one until every search begins failing, and
then it looks like a bad agent. Both messages now fire wherever the
engine is resolved, not just from the command.

`resolveSearchEngine`'s doc comment said there was no CLI flag. There is
one now.
2026-09-22 15:50:15 +02:00
Adrià Arrufat 1c9b1b8bc2 tools: tell the caller why a search failed
A failed search reported only the error name: "keenable search failed:
ApiError". The status code and the provider's own message were logged
once inside `apiSearch` and then died with the client on its deferred
deinit, so nothing downstream could tell a rate limit from a rejected key
-- and a model reading the tool result had nothing to act on.

`Failure` carries the status and body out before the client goes, and the
message now names both. A 429 additionally says it is a rate limit and
that waiting or reading the page instead are the ways out, because that
is the one failure where the right move is not "try another query".

Found by a benchmark run where a keyless search engine quietly hit its
hourly cap; every search failed for twenty minutes and the run just
looked like a bad agent.
2026-09-22 15:47:35 +02:00
Adrià Arrufat 1e081b5ac8 tools: collect search hits once, render them once
The four per-engine markdown writers differed only in which response
field holds the title, the url and the snippet; the numbering, the
newline flattening and the "No results." case were copied four times.

Each engine now contributes a `collect` that maps its response onto
`[]Hit`, and one `renderResults` turns those into the markdown the model
reads. Adding an engine is a field mapping rather than another copy of
the renderer, and the intermediate `SearchResults` is a shape a caller
can use directly instead of re-parsing markdown.

Output is byte-identical -- the existing tests assert the exact strings
and are unchanged apart from calling the new pair.
2026-09-22 15:44:55 +02:00
Adrià Arrufat 4da152e9aa agent: resolve the replay selector in the tool layer
`--save` drops any tool call that names its element by `backendNodeId` and
carries no selector (`Command.ToolCall.isRecorded`), because a registry id
means nothing in a later session. That is a silent gap on the chat path:
whenever the model addresses a node by id, the action runs and is then
quietly omitted from the saved script.

The tool layer is the one place that already resolves the node before
acting, so it is where the selector can be taken while the node still
exists -- a navigation takes it away a moment later. `CallOpts.record`
asks for it and `ToolResult.selector` returns it, for every tool, with no
per-tool knowledge anywhere. Recording then swaps `backendNodeId` for the
selector and keeps every other argument, so `scroll`'s `y` and
`waitForState`'s arguments survive.

One `save_selectors` entry per call, errors included, so the index lines
up with `RunToolsResult.tool_calls_made`.
2026-09-22 15:42:35 +02:00
Karl Seguin b84e5b7397 Merge pull request #3591 from lightpanda-io/dont-reuse-bad-connections
http: dont' re-use connections which are likely in a bad state.
2026-09-22 15:43:30 +08:00
Karl Seguin 239a7937e0 Merge pull request #3592 from lightpanda-io/element-focus
webapi: element.focus() only on focusable elements
2026-09-22 15:04:28 +08:00
Karl Seguin 82d09c4714 Merge pull request #3593 from lightpanda-io/orderfile-space-symbol-handling
ci: orderfile generation properly handles zig symbols with space
2026-09-22 14:34:33 +08:00
Karl Seguin 92c48a68a7 Merge pull request #3534 from lightpanda-io/wasm-streaming
Implement WebAssembly.compileStreaming and instantiateStreaming
2026-09-22 14:19:52 +08:00
Adrià Arrufat 6209893a5b wasm: don't re-enter V8 when an exception is already pending
TryCatchRethrow, JsException and ExecutionTerminated all mean V8 already
has something pending: creating an error value and aborting the stream
would replace the exception the script is meant to see, or hand a killed
script a catchable Error. Drop the streaming handle instead, matching the
early exit in Caller.handleError.
2026-09-22 07:53:44 +02:00
Karl Seguin 1f19eb31a2 ci: orderfile generation properly handles zig symbols with space
Taken from https://github.com/lightpanda-io/browser/pull/3570
2026-09-22 12:06:57 +08:00
Karl Seguin 0ffca7fa0a webapi: element.focus() only on focusable elements
Element.focus() would "focus" the element even when it shouldn't. We already
have the logic to determine if an element is focusable in `user_input.zig`, so
this was moved to Element and is now used in el.focus().
2026-09-22 11:25:52 +08:00
Karl Seguin 962a13007b Merge pull request #3590 from lightpanda-io/orderfile-regen
orderfile: regenerate the hot-code profile
2026-09-22 10:12:37 +08:00
Karl Seguin 0b66a5ed05 http: dont' re-use connections which are likely in a bad state.
Some status-codes should never have a body except for a single trailing blank
line. If we don't handle these, then we end up with a dirty connection in our
connection pool:

1 - read the header, but not the body
2 - put the connection back in the pool
3 - try to read the header, but actually get the body from #1

WPT /fetch/api/basic/response-null-body.any.html exercises this path and is
flaky (because it depends whether the request goes back out on a keep-alive
connection)..but for a given run,you'll almost always get 1-3 failures.

This commit processes the request, but tells libcurl not to re-use the
connection.
2026-09-22 10:08:41 +08:00
github-actions[bot] 0df1e3da37 orderfile: regenerate the hot-code profile
hot set 15092KB resident at 4KB fault-around: 21580 text, 21388 rodata symbols
2026-09-22 01:04:54 +00:00
Karl Seguin e96c31f157 Merge pull request #3572 from lightpanda-io/nikneym/cdp-isolated-world-origin
`cdp`: move a kept isolated-world context onto the navigated origin
2026-09-22 07:47:46 +08:00
Karl Seguin 709567e2d8 Merge pull request #3585 from lightpanda-io/webdriver-element
webdriver: add element endpoints
2026-09-22 07:47:06 +08:00
Karl Seguin d3262ada54 Merge pull request #3583 from lightpanda-io/improved-cache
http: improve caching
2026-09-21 21:36:47 +08:00
nikneym 238d4a53a3 cdp: move a kept isolated-world context onto the navigated origin 2026-09-21 16:28:00 +03:00
Karl Seguin dba2fdf5fc Merge pull request #3578 from lightpanda-io/response-null-body
webapi: improve fetch/response correctness with null bodies
2026-09-21 18:56:12 +08:00
Karl Seguin 6b28f986e6 Merge pull request #3586 from lightpanda-io/webdriver-http-limit
webdriver: increase http default / max limit
2026-09-21 18:55:53 +08:00
Karl Seguin 7f3cf5793f cache use s-maxcache only to reduce total cache time
Tricky since we act as both the user agent and a shared cache. This is the
safest of the two.
2026-09-21 18:52:13 +08:00
Karl Seguin ee4d54928d webdriver: increase http default / max limit
The http max default was 4K with a 16KB hard limit. The default limit is now 1MB
with an initial default of 4K. This is to accommodate larger WebDriver payloads.
2026-09-21 17:52:41 +08:00
Karl Seguin 735ae57f16 Merge pull request #3577 from lightpanda-io/type-error-helper
chore: move more cases to new typeError helper
2026-09-21 16:48:25 +08:00
Karl Seguin af47d524f2 Merge pull request #3576 from lightpanda-io/xhr-wpt
webapi: correct error on wrong method, guard headers
2026-09-21 16:08:47 +08:00
Karl Seguin 9de057ffa2 Merge pull request #3575 from lightpanda-io/lang-selector
webapi: remove "en" lang fallback
2026-09-21 16:08:33 +08:00
Karl Seguin 9f443e13d2 Merge pull request #3573 from lightpanda-io/iterative-dump
mem: Make dump iterative
2026-09-21 15:57:51 +08:00
Karl Seguin 09a0d580c9 verify js.Value is object before trying to taggedOpaque() 2026-09-21 15:42:49 +08:00
Pierre Tachoire d9bcf1a961 Merge pull request #3492 from lightpanda-io/nikneym/netscape-cookie-format
`cookies`: accept Netscape cookie format in `--cookie` argument
2026-09-21 09:04:52 +02:00
Karl Seguin a8e43777f5 webdriver: add element endpoints
Follows https://github.com/lightpanda-io/browser/pull/3552 to add various
element endpoints. Total of 13 new endpoints were added, including a slightly
fancier router to deal with variables in URLs.

```
$ curl -X POST "http://localhost:9222/session" --data '{}'
{"value":{"sessionId":"248378f2-bc8d-4cb5-8aaf-3ec4193c5a9e","capabilities":{"acceptInsecureCerts":false,"browserName":"Lightpanda","browserVersion":"1.0.0-dev.9605+475ed9ae7","platformName":"linux","setWindowRect":false,"userAgent":"Lightpanda/1.0","proxy":{}}}}⏎                        ~/c/p/browser2]

$ curl -X POST "http://localhost:9222/session/248378f2-bc8d-4cb5-8aaf-3ec4193c5a9e/url" --data '{"url": "https://lightpanda.io"}'
{"value":null}

$ curl -X POST "http://localhost:9222/session/248378f2-bc8d-4cb5-8aaf-3ec4193c5a9e/element" --data '{"using":"css selector","value":"h1"}'
{"value":{"element-6066-11e4-a52e-4f735466cecf":"1"}}

$ curl -X GET "http://localhost:9222/session/248378f2-bc8d-4cb5-8aaf-3ec4193c5a9e/element/1/text"
{"value":"The first browserfor machines, not humans"}
```

First two already existed, last two are new.
2026-09-21 14:57:03 +08:00
Karl Seguin 6f877ccfaa Merge pull request #3581 from lightpanda-io/readme-full-wpt-command
doc: include full argument list for our typical wpt run
2026-09-21 13:38:40 +08:00
Karl Seguin 3213342055 http: improve caching
1 - Centralized cache-awareness into Cache and pulled header details out of
    SqliteCache and HttpClient

2 - Added support for expires header

3 - Support caching more status types (but not all, since HttpClient would need
    to be aware of what caching a 3xx/206 means)

4 - Revalidate cares about  "not specified" vs "no-store" vs "stale"
    (e.g. expires=0 means "stale", not fallthrough the last-modified logic)
2026-09-21 11:38:34 +08:00
Karl Seguin 8bf1d1032e Merge pull request #3582 from lightpanda-io/orderfile-regen
orderfile: regenerate the hot-code profile
2026-09-21 11:22:13 +08:00
github-actions[bot] d23ac8dfac orderfile: regenerate the hot-code profile
hot set 15024KB resident at 4KB fault-around: 21264 text, 21390 rodata symbols
2026-09-21 01:05:55 +00:00
Karl Seguin 9c526708d9 doc: include full argument list for our typical wpt run 2026-09-21 07:21:59 +08:00
Pierre Tachoire 744bf768c8 Merge pull request #3579 from lightpanda-io/orderfile-regen
orderfile: regenerate the hot-code profile
2026-09-20 12:42:18 +02:00
github-actions[bot] b7dd6e5942 orderfile: regenerate the hot-code profile
hot set 14872KB resident at 4KB fault-around: 21291 text, 21390 rodata symbols
2026-09-20 01:04:24 +00:00
Karl Seguin d28d37be92 webapi: improve fetch/response correctness with null bodies
Responses with specific status (e.g. 204) should always have a null body. Also
adds validation to Response constructor (e.g. can't provide an invalid status).

Improves a handful of WPT tests:

fetch/api/response/response-error.any.html
fetch/api/response/response-static-json.any.html
2026-09-19 13:38:12 +08:00
Karl Seguin 856d62183a chore: move more cases to new typeError helper
https://github.com/lightpanda-io/browser/pull/3403 added a typeError helper and
this commits moves a few more callers to it.
2026-09-19 12:18:14 +08:00