* ci: raise cdp bench MAX_VmHWM to 29000
The agent branch grew .text by ~2.8MB (mostly std monomorphization from
the new script/agent modules); none of it executes in serve mode and
anonymous/heap memory is byte-identical to main (716-724KB Private_Dirty
across main, agent-dev, and a build with the agent code compiled out).
The VmHWM delta is file-backed pages of the binary: the kernel maps them
in large readahead/THP chunks around each touched page, so peak RSS
shifts with link layout on every relink. The agent-less test build
measured 4.2MB *below* main while running the identical serve path,
purely from folio/window boundaries moving. At 28000 the gate was
failing on layout noise (28028-28064), not a regression.
* agent: improve docs agent_script
* Revert "ci: raise cdp bench MAX_VmHWM to 29000"
This reverts commit b6b704d43e.
---------
Co-authored-by: Adrià Arrufat <adria.arrufat@gmail.com>
Experiment to see if this shrinks the build by any significant amount. Some
investigation did surface this as some of the code we control directly which
did stand out.
This commit is in the same vein as 0d9482ccbf. A
dynamic async script which completes during an evalute() does not evaluate if
`is_evaluating == true`. That's good, but we need to make sure that it _does_
eventually get evaluated. As-is, we rely on a done callback to call evaluate()
again which works for all but the last script.
This commit creates a more robust reentrancy flow. is_evaluating still guards
against reentrancy, but a new flag, `evaluate_pending` is set to true. Whenever
`is_evaluating` is set to false if `evaluate_pending` is true, we evaluate().
Similalry, during evaluate(), we better handle completed scripts during
evaluate() and process them immediately (via an outer loop).
- DataTransferItem/DataTransferItemList/Iterator forward acquireRef/releaseRef
to the owning DataTransfer, so its pooled arena outlives any JS-held wrapper
(fixes a potential use-after-free when JS keeps an item/list/iterator alive
past the DataTransfer).
- addItem reads strings straight into the persistent arena via
toStringSliceWithAlloc, removing the intermediate call_arena copy.
- normalizeFormat uses std.ascii.allocLowerString instead of dupe + lowerString.
- setData: rename dup -> owned_data for clarity.
Move console message listener registration out of Session initialization
into an explicit `enableConsoleCapture` method. This avoids buffering
console logs when they are not needed.
This was driven by various WPT tests in the /shadow-dom/ category. There are
4 distinct changes.
1. Template hooks into "cloned" to include the _content. This change required
passing `deep: bool` which is why TextArea and Input are also changed (they
ignore that new parameter)
2. Node.getRootNode and Node.ownerDocument will now traverse through the
ShadowRoot to find the root/document
3. DOM events won't gain the Window when triggered from within a ShadowRoot
4. attachShadow now takes a full option, not just a string mode. This also
touched a few different places since it's called internally too.
* Revert "agent: enhance /save progress and verification feedback"
This reverts commit 54f467d1fa.
* Revert "agent: verify synthesized scripts during /save"
This reverts commit b141da30ca.
* agent: extract save helpers to save.zig
Moves pure helper functions related to the `/save` command (parsing,
validation, file writing, prompt shaping) from `Agent.zig` to a
dedicated `save.zig` module.
Introduces a multi-step synthesis process for `/save` that derives a
logical JSON output schema and uses a dry-run runtime to verify
candidate scripts. The LLM can now run and self-correct its scripts
using a new `run_script` tool before finalizing the save.
Rewrite:
- Section headings now describe tasks ("Log in to Hacker News" vs "Driving the browser by hand").
- Section 3 leads with the five login commands; selector-hunting workflow moved to optional subsection at the end.
- Trimmed prereqs (dropped fish variant, $-escape edge cases, long LP_ prefix explanation).
Every working command preserved. Load-bearing callouts in section 3 kept (/waitForSelector doing two jobs, CSS-only selectors).
The agent.md rewrite dropped documentation for several features that still
exist in the code. Restore them and clean up the markdown:
- Re-add /clear and /reset to the meta-commands table
- Re-add waitForState to the browser-tools list and the /save
state-mutating command list
- Re-add --system-prompt and --verbosity (with the --task stderr-pipe
escalation) to the providers section
- Strip trailing whitespace on blank lines; add blank lines before
headings and between lists and following paragraphs
async_scripts is used to block the "load" event, and preload scripts (a)
shouldn't block this and (b) might never be used. The `preloaded_scripts` map
itself can be cleaned up on shutdown/reset.
- **New opening.** Direct framing: "lets you drive a headless browser by talking to it."
- **New "How to think about it" section.** Explains the three-layer stack and the one-sentence pitch ("pay for the LLM once, then replay deterministically").
- **New "The REPL is where you build scripts" section.** Positions the REPL as a sandbox with a full sandbox-to-saved-script example.
- **New tips section.** Practical guidance for prompts that produce useful saved scripts (be specific about extracted data, name the page, check the file).
- **Updated Quick start.**
Swap `_previous_states: Map(*Element, Bool)` to `_tracked: Set(*Element)` since
we only care about membership (the value was previous always bool). This better
captures the intent.
`checkIntersection` can be called a lot...on every domChanged. And it can call
a known expensive API: `getBoundingClientRect`.
This commit reworks the code to be able to exit from checkIntersection early.
`_previous_states` is now seeded with the target so that, once reported, the
lack of entry can be used to quickly exit.
When profiling airbnb, `checkIntersection` was reported 841 times. With this
change, it was reported twice.
Implement <link rel=preload as=script href=...>. The implementation is similar
to preloadImport / waitForImport. Consider a website that does something like:
```
// pseudo html
<head>
<link preload script1.js>
<link preload script2.js>
<link preload script3.js>
<link preload script4.js>
<script script1.js></script>
<script script2.js></script>
<script script3.js></script>
<script script4.js></script>
```
Then, without preloading, we hit <script script1.js></script> and block while we
load it + execute it. Repeat for 2, 3, 4 ...
With preloading, by the time we block on <script script1.js></script> all the
scripts are already being downloaded in the background.
I opted to remove the script on first use. If a script happens to be used twice
(we have seen this happen for imports, but I guess it's more rare on blocking
scripts), then it'll get re-downloaded the 2nd time, just like before (and just
like before, the http cache is a better mechanism to rely on here).
airbnb preloads 41 scripts.
Extracts chat history and arena management into `Conversation.zig`,
the startup banner into `welcome.zig`, and model/config resolution
helpers into `settings.zig` to simplify `Agent.zig`.