Commit Graph
29 Commits
Author SHA1 Message Date
Adrià Arrufat 4a49030af6 chore: trim narrative comments 2026-08-04 22:36:56 +02:00
Adrià Arrufat b85fb7c6be simplify: recorder rewind, Tool.waitsForReadiness, single goto default
Post-review cleanup of the goto-waituntil branch:
- Recorder: emit the plain goto immediately and keep only a rewind offset +
  pre-scrubbed dcl line; a readiness wait recorded next rewinds and swaps the
  line. bytes() is infallible again, isEmpty() is gone, and the Agent.zig
  save-path changes revert. Shared renderCall() replaces the duplicated
  await/page. prologue; ObjectMap.clone + @tagName replace the hand-rolled
  clone loop and string literal.
- tools: Tool.waitsForReadiness as an exhaustive predicate (waitForScript was
  silently missing from the recorder's inline set); GotoParams.waitUntil is
  non-optional with the default resolved once; performGoto takes an options
  struct instead of two trailing nullables; the goto schema enum comes from
  Config.tagJsonArray like waitForState's.
- actions: comment states the invariant (polling needs a parsed document)
  rather than the symptom.
2026-08-04 22:26:13 +02:00
Adrià Arrufat a73cd97502 agent: record goto as domcontentloaded when a wait follows
The recorder holds each goto one step: if the next recorded call is
waitForSelector/waitForState, the emitted line carries
waitUntil=domcontentloaded (the follow-up wait covers readiness); anything
else, including extract and comments, flushes the plain load-wait form. An
explicit waitUntil is never rewritten. bytes() flushes a trailing held goto
and became fallible; isEmpty() replaces the len check in synthesizeSave (#3138).
2026-08-04 22:06:46 +02:00
Karl Seguin 8e42d63c1c zig: Zig 0.16
Built against https://github.com/lightpanda-io/zig-v8-fork/tree/zig-0.16 but
it doesn't require a new v8 build.

Built against https://github.com/lightpanda-io/boringssl-zig/tree/zig-0.16
since the current fork we point to isn't updated.

A global std.Io instance, lp.io. Way easier this way and requires 0 changes to
our libcurl integration / event loop.

Network code uses a new layer that does what Zig 0.15's posix package used to
do. Again, quicker migration that way. But, as long as we have the global IO,
and given the half-baked nature of networking in std.Io 0.16, this just makes
sense. Things can be migrated as needed.

The std.time.* -> std.Io.Timestamp/Clock/Duration resulted in _a lot_ of
changes. ArrayList = .{} -> ArrayList -> .empty also resulted in a lot of
changes, but that's obviously superficial. As is the trimLeft/trimRight ->
trimStart/trimEnd rename.

Locking adopt the `Uncancelable` variants, e.g. mutex.lockUncancelable() to
preserve the error-free signature (and, because cancellation would be something
we'd have to put more thought into).

std.json.ObjectMap is now unmanaged, so the allocator had to be passed along.
However, there's still a deprecated managed variant of MemoryPool, so I switched
to it (we can do a small follow up PR to move to the unmanaged after).

I tried use_llvm = false, but it locks my computer, consuming RAM until MacOS
gives me a popup I've never seen before, begging me to start killing processes.

Agent and the networking stuff saw the most significant changes.
2026-07-22 13:26:03 +08:00
Adrià Arrufat 21477b39c9 script: introduce Page API with async goto
Replaces global blocking functions with a `Page` class. `page.goto` is
now asynchronous and must be awaited, while other methods remain
synchronous. Scripts are wrapped in an async IIFE to support top-level
await, and output is returned via `return <value>`.
2026-06-15 22:48:12 +02:00
Adrià Arrufat ac82551e66 agent: replace -i flag with /save and /load commands
Removes the `-i`/`--interactive` CLI flag and live file-based
recording. Instead, the REPL now supports a `/load <path>` command
to run scripts from disk, and `/save` to export the in-memory
session recording.

The `Recorder` is simplified to be purely in-memory, and the script
runtime is moved to `src/script/Runtime.zig`.

BREAKING CHANGE: The `-i`/`--interactive` flag has been removed. Use
the `/save` and `/load` commands within the REPL instead.
2026-06-03 16:21:10 +02:00
Adrià Arrufat 3bbe20735f agent: record raw REPL input
- Add `recordRaw` to record raw JS lines in the REPL.
- Only record commands if they succeed without error.
- Fix kitty terminal cursor keys by forcing legacy arrow encoding.
2026-06-02 12:54:37 +02:00
Francis Bouvier 8824174afa agent: run recorded scripts as JavaScript
Replace recorded agent replay with a standalone JavaScript script runtime.
Install synchronous agent primitives in an isolated V8 context, add console
output, return structured extract values as JS objects/arrays, and route script
execution through the new runtime.

Update recording to emit .js function calls, default /save filenames to .js,
drop script-level extract save support, and refresh agent docs/tutorials for
the new format.

Self-healing is disabled for now.
2026-06-01 15:43:37 +02:00
Adrià Arrufat fb7dfc1410 refactor: clean up comments and remove redundant ping test 2026-05-30 19:53:43 +02:00
Adrià Arrufat f51bda4d5a refactor: deduplicate comment writing and remove unused code 2026-05-30 19:32:59 +02:00
Francis Bouvier 142c940b21 agent: add REPL /save command for recorded sessions
Add a REPL-only `/save [filename.lp]` command that persists the current
  interactive session as PandaScript without requiring the user to start the
  agent with `-i <script>`. The command records the same replayable actions as
  the existing script recorder, but keeps them in memory until the user chooses
  to save.

  Functional behavior:

  - During a REPL session, record replayable browser actions into an in-memory
    script buffer.
  - Manual slash commands are recorded through the same PandaScript formatting
    and filtering rules used by file recording.
  - Natural-language turns record their prompt as a `# ...` comment only when
    the LLM turn produces at least one successful replayable tool call.
  - Failed LLM tool calls are skipped, and repeated successful `/extract` calls
    keep only the last successful extract, matching the existing recorder logic.
  - `/save filename.lp` writes the current in-memory recording to that file.
  - Bare `/save` creates a random `session-<hex>.lp` file on first save.
  - If the first save targets an existing file, prompt with the existing numbered
    TTY picker and ask whether to replace or append.
  - After the first successful save, the REPL session is locked to that filename:
    later `/save` or `/save same-file.lp` appends to the same file without
    prompting, while `/save other-file.lp` is rejected.
  - After each successful save, reset the in-memory recorder so future saves only
    append actions entered since the previous save.
  - On any `/save` error or cancellation, keep the in-memory recorder intact so
    the user can retry without losing captured actions.
  - Restrict `/save` filenames to local file names, not paths, to keep behavior
    scoped to the current directory.

  Code changes:

  - Add `Recorder.Memory`, an in-memory recorder that shares the existing
    `Command.isRecorded`, `Command.format`, comment formatting, and `LP_*`
    reverse-substitution behavior with file recording.
  - Add `Recorder.Memory.reset()` so `/save` can clear only successfully saved
    deltas.
  - Add `/save` to the REPL meta command table and help/completion surface.
  - Add `save_buffer` and `save_path` state to `Agent`.
  - Feed manual REPL tool calls into `save_buffer` alongside the existing optional
    file recorder.
  - Extend LLM turn recording with a `capture_for_save` flag so natural-language
    REPL turns can be captured without affecting non-REPL script/self-heal paths.
  - Implement `/save` handling in `Agent`:
    - parse and validate the optional filename,
    - choose replace/append for existing first-save targets,
    - remember the first successful save path,
    - enforce the single-destination rule for the rest of the session,
    - append later deltas by default,
    - commit remembered path state only after a successful file write.
  - Add focused coverage for the memory recorder’s filtering and reset behavior.
2026-05-29 13:32:53 +02:00
Adrià Arrufat f7a4d1f6e2 terminal: unify tool and action result printing
Replaces `printActionResult` and `printToolResult` with a single
`printToolOutcome` function. This unifies the output path and adds
red/green color coding for tool failures and successes.
2026-05-22 19:05:46 +02:00
Adrià Arrufat 8fb3c7baed tools: restructure browser tools and script schemas
- Replace `Action` enum with `Tool` enum using exhaustive switches
- Extract `ScriptIterator` to `Iterator.zig`
- Refactor `schema.zig` into `Schema.zig`
- Move string substitution logic into `tools.zig`
- Clean up `SlashCommand.zig` to only handle REPL meta-commands
2026-05-22 13:41:04 +02:00
Adrià Arrufat e9943a3976 command: use Action enum for ToolCall name
Replaces the string `name` in `ToolCall` with `action: browser_tools.Action`.
This avoids repeated string-to-enum parsing and simplifies schema lookups.
Also updates `zenai` dependency to use its JSON duplication helper.
2026-05-21 22:51:52 +02:00
Adrià Arrufat 13ebb9e802 agent: simplify command, schema, and repl logic
- Remove `input_schema_raw` from `SchemaInfo` and use `parameters` directly
- Simplify JSON field extraction in `Verifier` to avoid `parseArgs`
- Refactor REPL command splitting and environment substitution
- Clean up and condense comments across the codebase
2026-05-21 22:39:58 +02:00
Adrià Arrufat d16f8a032e script: support triple-quoted strings and optimize lookups
- Add support for triple-quoted strings (''' and """) in the tokenizer
  and quote stripper.
- Avoid allocations in `stripQuotes` by returning slices.
- Optimize schema lookups using O(1) enum-based canonical resolution.
- Skip env var substitution if "$LP_" is not present in the string.
2026-05-21 21:33:05 +02:00
Adrià Arrufat 459893f414 script: unify PandaScript to slash commands
Unifies PandaScript syntax by replacing uppercase keywords with slash
commands (e.g., `/goto`, `/click`, `/fill`). Refactors the `Command`
representation to use a generic tool call structure backed by schemas.

BREAKING CHANGE: Uppercase PandaScript keywords (GOTO, CLICK, TYPE, etc.)
are no longer supported. All scripts must use slash commands.
2026-05-21 20:38:21 +02:00
Adrià Arrufat 31e20ae261 Recorder: pass directory to init and reuse arena
Update `Recorder.init` to accept a `std.fs.Dir` and relative path,
decoupling it from the current working directory.

Also add a persistent arena allocator to `Recorder` to reuse
allocations when scrubbing environment variables on each write.
2026-05-21 17:45:54 +02:00
Adrià Arrufat 55a50746fa script: move helper functions into Command union
Renames `Command.zig` to `command.zig` and moves associated functions
like `parse`, `toToolCall`, and `canHeal` directly into the `Command`
union. This simplifies usage from `Command.Command` to just `Command`.
2026-05-21 17:13:19 +02:00
Adrià Arrufat 92733763d8 refactor: use explicit type names and encapsulate spinner state
- Replace `Self` with `Recorder` and `Verifier` for improved clarity.
- Add `Spinner.isEnabled()` to encapsulate atomic state access.
- Shorten and refine various comments across the codebase.
2026-05-19 11:13:15 +02:00
Adrià Arrufat 66e7b1aafd script: improve verifier and recorder robustness
- Make `VerifyResult.failed` reason non-optional with an OOM fallback.
- Use `ElementProperty` enum for safer JS property injection.
- Ensure `Recorder` disables on all errors to prevent silent data loss.
- Add `CHECK` command round-trip test.
2026-05-19 10:51:13 +02:00
Adrià Arrufat 061a740dbb script: improve reliability, CRLF support, and formatting
- Prevent UB in `applyReplacements` with pointer assertions.
- Reorder `writeAtomic` to build content before writing backup.
- Strip trailing `\r` in `ScriptIterator` for CRLF compatibility.
- Use shorter representation for `SCROLL` commands.
2026-05-19 10:43:20 +02:00
Adrià Arrufat 2862feb7c4 agent: improve recording quality and tool guidance
- Filter redundant `extract` calls in recordings to reduce noise.
- Scrub literal `LP_*` secrets from recorded commands for portability.
- Throw a descriptive error in `extract` when no selectors match.
- Update tool descriptions and guidance to favor a `tree` ->
  `nodeDetails` -> `extract` workflow for more robust selectors.
2026-05-14 13:36:36 +02:00
Adrià Arrufat 001fbb97d0 Recorder: make init fallible and Agent.recorder optional 2026-05-14 08:59:47 +02:00
Adrià Arrufat 635546bf85 Recorder: simplify initialization and file opening 2026-05-13 14:03:18 +02:00
Adrià Arrufat b2454a9190 chore: add license headers to source files 2026-05-13 13:20:36 +02:00
Adrià Arrufat f35c4219c9 refactor: improve error handling and rename Self to Agent
- Replace `unreachable` with explicit error returns in Agent and Executor.
- Add `EvalResult.err` helper in `tools.zig` to simplify error paths.
- Improve error propagation in `SlashCommand` and `Recorder`.
- Rename `Self` to `Agent` for better clarity in function signatures.
2026-05-13 13:15:44 +02:00
Adrià Arrufat 884b9cccfd agent: implement schema-driven EXTRACT command
Replaces simple selector extraction with JSON schema-driven extraction.
Supports multi-line triple-quoted blocks for complex schemas.
2026-05-12 19:38:40 +02:00
Adrià Arrufat c41955ade3 script: move script module to root src directory 2026-05-11 19:56:36 +02:00