Zig 0.17 removes @cImport in favor of translate-c steps in the build
system. Each library's link function now creates an addTranslateC step
exposed as a module import: curl and isocline translate their headers
straight from the dependency tree, while sqlite3 uses the artifact's
emitted include tree since the amalgamation lives in a sub-dependency.
Terminal.zig and prompt_assist.zig previously instantiated two
independent @cImport namespaces for isocline; they now share one
translated module, so its types are identical across both files.
Zig 0.16 changed clocks/timestamps. Our migration to Zig 0.16 took the path of
least resistance. For example, we kept 'monotonic' and 'real' even though Zig
0.16 renamed them to 'boot' and 'clock'
This commit tries to standardize all timestamp usage to: lp.datetime.timestamp
and lp.datetime.milliTimestamp using the new zig 0.16 names ('boot' and 'clock')
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.
Each redraw issued one unbuffered stderr write per row plus cursor
moves, which can tear visually mid-frame. Build the frame in a stack
buffer and emit it with a single locked write, like Spinner's
renderLocked.
- 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.
addPathCompletions and ghostPathFirstMatch were ~20-line twins (split
dir/base, openDir, iterate, prefix-match, '/' suffix for directories).
A fix landing in one copy — hidden-file filtering, the shared
symlink-to-directory suffix gap — would make Tab completion and the
ghost hint disagree. Both now consume one PathMatchIterator.
ic_style_def registration lived in setup while the Kind->style switch
lived 600 lines away in IcSink: adding a js_highlight.Kind
compile-errored the sink but silently skipped registration, rendering
the new kind unstyled with no test able to catch it (isocline is
live-only). One table now drives both; an unmapped or doubly-mapped
kind is a compile error.
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.
Terminal.columns() had exactly one caller (Spinner's line renderer) and
Terminal itself never used it. Moving it breaks the Spinner<->Terminal
import cycle and stops Spinner type-checking against the isocline
@cImport for one ioctl.
- 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.
Adds top and bottom borders around code blocks, displaying the language
tag if present. Also changes horizontal rules to dashed lines to
distinguish them from code block borders.
- Extract ANSI escape codes to a dedicated `ansi.zig` module.
- Consolidate styled output rendering in `Terminal.zig`.
- Simplify dollar variable highlighting and table measurement.
Extracts the JavaScript highlighting logic from `Terminal.zig` into a
reusable `js_highlight.zig` module. Uses this module to syntax-highlight
markdown fenced code blocks in `md_term.zig` using ANSI escape codes.
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.
Introduces a stack-threaded `Style` chain to track and re-apply
parent styles upon nested resets. This fixes styling loss in
deeply nested markdown spans.
- Use a 256-ramp gray for `ansi.dim` to improve readability.
- Track and re-apply active styles in nested markdown spans.
- Collapse extra spaces after list markers.
- Links now emit OSC 8 hyperlinks (clickable where supported) with a dim
url fallback; a bare link (label == url) drops the redundant suffix.
- Blockquote lines (`> `) render with a dim bar.
- Horizontal rules (`---`/`***`/`___`) render as a dim line.
- Backslash escapes unescape only markdown-special chars, leaving e.g.
C:\\Users intact.
Style the assistant's answer in the interactive agent REPL with ANSI
escapes: headings, bullet/numbered lists, bold/italic/inline-code/strike,
and links. Rendering is gated to the REPL on a real terminal, so
`--task`/piped output stays verbatim for programmatic consumption.
Adds src/agent/md_term.zig (markdown -> ANSI renderer) and wires it into
Terminal.printAssistant.
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.