Add Brave as a second API-backed web search engine: the search tool's
auto mode tries Brave then Tavily (keyed on BRAVE_API_KEY/TAVILY_API_KEY)
before the DuckDuckGo scrape. A new /searchEngine REPL metacommand pins
the engine explicitly (auto|tavily|brave|duckduckgo) — an explicit
engine surfaces its failures instead of silently degrading — and is
persisted to .lp-agent.zon like the other REPL settings.
Bumps zenai to pick up the zenai.search.brave client.
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.
- attachCompleter now takes the CompletionSource: Agent no longer
reaches through terminal.assist to configure what it just attached,
and attach/configure can't be sequenced wrong.
- history_paths flows through one channel: attach() loads the initial
history from the same State field modeCallback swaps from, instead of
setupRepl receiving a second copy that had to agree.
- all_names + closestCommand move to SlashCommand.zig, the command
registry - prompt_assist consumes it like everyone else and
Terminal's closestCommand re-export disappears.
- picker treats a tty that refuses raw mode as NotInteractive and
degrades to the line prompt instead of leaking termios errnos to
callers that can only blanket-catch.
- kitty keyboard flags become named ansi.zig constants shared by
Terminal.readLine and picker's RawTerminal.
- skipWhitespace was std.mem.indexOfNonePos; single-use style consts
inline into the style table; drop the dead CompletionSource default
and doc-comment splice in Spinner.
Completion, ghost hints, and prompt highlighting (~640 lines) were
cohabiting with Terminal's readline lifecycle and output printing. They
now live in prompt_assist.zig, which owns the isocline callbacks, the
ps-* style palette, and the slash-command name table. The C callbacks
read a small prompt_assist.State (js_mode, completion_source,
history_paths) embedded in Terminal, replacing the three loose fields.
Pure code motion otherwise; the valueAt/renderSchemaHint tests move
with their code and stay in the suite via Terminal's test hook.
The numbered/interactive picker (ChoiceState, RawTerminal, render loop)
was ~215 self-contained lines inside Terminal.zig. It runs before - or
without - the isocline REPL (provider selection during setup), so
settings.zig no longer imports the isocline-configured Terminal at all.
Pure code motion; the ChoiceState tests move with it and stay in the
suite via Agent.zig's test hook.
- Add highlighting for JS functions, methods, types, and template
interpolations.
- Introduce teal color and adjust cyan to bright cyan.
- Import `ansi.zig` directly instead of through `Terminal`.
- Add PandaScript semantics note to the agent's system prompt.
- Refactor markdown table rendering and streaming.
Introduces an incremental markdown renderer (`md_term.Stream`) to
format streamed assistant responses in real time. Buffers lines,
maintains code fence state across chunks, and temporarily withholds
tables to render them with proper column alignment once complete.
The /save script documentation lived as a hand-maintained string in
Agent.zig whose primitives table drifted whenever a tool changed. Move
it to src/script/skill.zig and render the reference (signatures, option
lists, enums, defaults, per-parameter descriptions) from Schema.all()
at first use, keeping the curated per-tool notes behind an exhaustive
switch on Tool so a new or renamed tool is a compile error until its
doc entry exists.
The rendered skill is shared by three consumers:
- the /save and revision system prompts (built lazily in Agent.zig)
- a new mcp://skill/pandascript resource
- `zig build skills`, which writes zig-out/skills/<name>/SKILL.md with
Claude Code frontmatter via a registry-based generator exe, so
future Lightpanda skills are one registry entry each
Schema.FieldEntry now retains per-parameter schema descriptions, which
previously existed only in the raw JSON.
`stream_active` relied on every runTools exit path balancing pause with
`endStreamedText`. If one is ever missed, a stale-true flag skips the
spinner pause on the next turn, interleaving frames with streamed text.
Reset it alongside `streamed_text` so the invariant is local.
Streaming defaulted on for every mode, so one-shot `--task` and script
runs interleaved intermediate assistant commentary into stdout, which
wrappers treat as the answer. Gate the stream hook on `Terminal.isRepl`
so non-interactive modes keep the buffered final answer.
Streaming wrote assistant deltas straight to stdout regardless of
`suppress_answer`, so `--save`/capture turns (which set it to hide the
answer) leaked model text past the `runTurn` guard. Pass a null stream
hook when the turn's answer is suppressed.
Instruct the /save synthesis model to annotate the generated script with
short `//` intent comments — one per logical block (navigate, extract,
fan out, aggregate, return) describing what the block accomplishes, so a
later reader (human or agent) grasps the script at a glance.
The save synthesis prompt previously ended with "no commentary", which
discouraged code comments; reword it to forbid only prose/markdown fences
outside the code while explicitly asking for the `//` intent comments. The
same prompt backs the MCP `save` tool, so both surfaces benefit. Also add a
matching best-practice bullet + example to the agent's script-writing skill.
The docs promise triple-quoted values that span multiple lines, but the
REPL dispatched every physical line immediately, so the multi-line
/extract form died with 'unterminated quote' on its first line. A slash
command that leaves a '''...''' block open now keeps reading lines at a
continuation prompt until the block closes; Ctrl-D abandons it.
The one-shot --save flag bypassed save.parseCommand, so '--save test'
wrote a file literally named 'test' while the REPL's /save appended the
extension. Share one ensureJsExtension helper so the paths can't drift.
max http connections 10 -> 40
max http connetions per host 4 -> 6
These are just the defaults and can still be adjusted by the command line
arguments. 6 appears to be both Chrome and FireFox's default per host (which
is probably the more important of the two settings).
The limits are really use-case specific. A use case that it multi-threading
different domains can benefit from a conservative max-host with a very large
max conn. A use case that is multi-threading the same host will need to decide
if it's safe to raise max-host.
Updates the `script_skill` prompt to recommend and demonstrate parallel
page navigation using `Promise.all` and multiple `Page` instances,
rather than serial navigation on a single page.
Updates the zenai dependency and resolves the agent's effort based on
the configured provider's default. Also dynamically updates the active
effort when switching providers.
Respect the user's configured reasoning effort during save synthesis
and user message processing. This prevents API errors on models that
do not support reasoning effort (like Mistral) when `.none` is set.
Remove redundant `detectOllama` and `detectLlamaCpp` wrappers in
settings, calling `detectLocalProvider` directly instead. Also clean
up some logic in Agent.zig.
Allows tools like `/getEnv` with a single optional field to accept
positional arguments. Also adds autocompletion and ghost hints for
live `LP_*` environment variables.
Additionally, fixes a memory leak in `settings.zig` when parsing
invalid ZON configurations.
Generalizes Ollama detection, completion, and model reconciliation
to support llama.cpp. Also improves API error reporting by formatting
and surfacing HTTP status and messages on failures.
Lazily probes and caches Ollama server reachability for `/provider`
autocomplete. Validates Ollama availability when manually setting
the provider. Updates zenai dependency.
Renames `wouldResolve` to `hasDetectableKey` and removes the Ollama
probe from it. Defers printing the welcome banner for Ollama-only
paths to prevent probing the local server twice. Also bumps zenai.
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>`.
Passes the `HF_BILL_TO` environment variable to the ZenAI client
to route Hugging Face billing to an organization. Also updates
the `zenai` dependency.
clearConversation reset the recorded action buffer but left save_path
set, so after /clear or /reset a /save <newfile> was rejected with
"already saving to <oldfile>". Free and clear save_path alongside the
buffer so a cleared session can save to a fresh file.
Introduces an `update` save mode that allows the LLM to revise an
existing script. Distinguishes this from `append`, which now performs
a blind append. Updates the save prompts and system instructions
accordingly.
A second /save with nothing recorded and no prompt sent an empty user
message, which Anthropic rejects (HTTP 400: text content blocks must be
non-empty). Bail out early instead, telling the user to run commands or
give a prompt.
When a /save targets an existing script (a remembered save path, or
choosing append for an existing file), feed the file contents back to
the model and ask for the complete updated script, then overwrite. This
makes '/save fix X' actually fix the saved script — previously the
blind regeneration was appended after the buggy one. The --no-llm dump
path keeps its verbatim append semantics.
Updates isocline to support idle callbacks and registers a hook to
pump the session's curl multi while waiting for user input. This
prevents connection timeouts. Also refactors the idle pumping logic
into `Session.idleSlice` to share it with the MCP server.