Skip to content

Releases: jizzel/proxima

Proxima v1.7.1

Choose a tag to compare

@github-actions github-actions released this 30 Aug 21:50

What's changed

Fixed

  • The REPL crashed at startup when the terminal size was unavailable — stdout.terminalColumns throws a StdoutException rather than returning a default whenever the size cannot be read (output piped to a file, some CI runners, a PTY opened without a window size). Three unguarded call sites — the REPL header and two in the renderer — turned that into an unhandled exception before the first prompt, a hard crash over a cosmetic detail. All three now go through guarded terminalWidth() / terminalHeight() helpers that fall back to 80×24


Installation

macOS / Linux — install script

curl -fsSL https://raw.githubusercontent.com/jizzel/proxima/main/install.sh | sh

macOS (Apple Silicon + Intel via Rosetta 2)

curl -fsSL https://github.com/jizzel/proxima/releases/download/v1.7.1/proxima-macos-arm64 \
  -o /usr/local/bin/proxima && chmod +x /usr/local/bin/proxima

Linux (x86_64)

curl -fsSL https://github.com/jizzel/proxima/releases/download/v1.7.1/proxima-linux-x64 \
  -o /usr/local/bin/proxima && chmod +x /usr/local/bin/proxima

Windows (x86_64) — PowerShell

irm https://raw.githubusercontent.com/jizzel/proxima/main/install.ps1 | iex

Or download proxima-windows-x64.exe below and add it to a directory on your PATH.

Proxima v1.7.0

Choose a tag to compare

@github-actions github-actions released this 30 Aug 20:20

What's changed

Fixed

  • The /model picker corrupted the terminal — it redrew by moving the cursor up by the full list length, so once that exceeded the terminal height the cursor could not reach the top and every arrow keypress appended another copy of the list. With ~90 models the picker filled the scrollback with dozens of duplicates. It now renders a scrolling window sized to the terminal, with a … n more · i/total position hint, and clears exactly what it drew
  • Video-generation models offered in the picker — sora-2 and sora-2-pro appeared in /model but cannot serve /v1/chat/completions, so selecting one failed on first use
  • Model discovery failed silently — every failure path flattened to an empty list: a non-200 returned [], any exception returned [], and the caller added another catchError. An invalid API key, an unreachable host, and a provider that legitimately serves nothing were indistinguishable, so /model showed three Anthropic entries with no hint that OpenAI had returned a 401. Providers now expose discoverModels(), which keeps the reason, and the picker prints it — ⚠ OpenAI: invalid API key (401), ⚠ Ollama: Ollama not detected at …. Run: ollama serve. listModels() is unchanged, so nothing else had to move
  • The active model could be missing from its own picker — the list held only what discovery returned, so when a provider's discovery failed the running model was absent and could not be re-selected after switching away. It is now always included and marked
  • The autocomplete panel left a copy in the scrollback on every keystroke — it drew a separator and suggestion rows below the prompt with real newlines, then restored the cursor with \x1b[s / \x1b[u. Near the bottom of the terminal those newlines scroll the screen, and a cursor restore cannot undo a scroll, so the saved position was stale and each repaint appended another separator and row — the input area walked down the screen as you typed. Replaced with inline completion: the candidate's untyped remainder is shown as dim text after the cursor on the prompt's own line, with a (2/6 · tab) counter when several match. Nothing is ever written below the prompt, so the scroll cannot happen — verified on a PTY at the bottom edge of both a 40-row and a 10-row terminal, where typing now emits zero newlines against the previous 14. Tab accepts the shown completion and cycles on a second press; Enter submits and the arrow keys mean history, since there is no longer a list to highlight
  • A terminal resize while the /model picker was open corrupted the display — each redraw recomputed the window from the current height while the cursor still sat below rows drawn at the previous one, so growing the terminal moved the redraw above the old list and shrinking it left stale rows behind; the Enter/Escape cleanup recalculated the same way. The picker now tracks the rows it actually drew and moves and clears by that
  • The picker overflowed short terminals — the window had a fixed six-row floor, so a six-row terminal or a split pane was drawn eight rows tall, recreating the redraw corruption the scrolling window was added to fix. The window is now derived from the actual height, and below three rows — too short for a header, one row, and the hint — the picker prints the plain list instead. The regression test that missed this restated the formula instead of calling it, and asserted a bound two rows looser than the real one, so it could not fail; it now calls the function and checks every height

Changed

  • The /model picker lists current models instead of the whole catalogue — OpenAI returns every generation ever shipped (69 chat-capable ids, 29 of them dated snapshots identical to the alias they duplicate), which made choosing a model a scrolling exercise. The picker now drops dated snapshots and keeps the three newest versions per family, taking it from 72 entries to 17 — one screen, no scrolling. Discovery is unchanged and still live: a new release appears with no code change. Nothing became unreachable — --model openai/gpt-4o, openai_model in config, /model <id>, and tab completion all still accept any id the endpoint serves. Anthropic's static list and Ollama's locally-pulled tags are already short and are not curated. Two rules keep curation from hiding working models: versions rank within a family rather than across the catalogue (a single ordering compared llama-3.3 against gpt-5.4 and dropped most of a proxy endpoint's catalogue), and an id with no parseable version is kept rather than dropped (o3 and o4-mini are current reasoning models, not old generations)


Installation

macOS / Linux — install script

curl -fsSL https://raw.githubusercontent.com/jizzel/proxima/main/install.sh | sh

macOS (Apple Silicon + Intel via Rosetta 2)

curl -fsSL https://github.com/jizzel/proxima/releases/download/v1.7.0/proxima-macos-arm64 \
  -o /usr/local/bin/proxima && chmod +x /usr/local/bin/proxima

Linux (x86_64)

curl -fsSL https://github.com/jizzel/proxima/releases/download/v1.7.0/proxima-linux-x64 \
  -o /usr/local/bin/proxima && chmod +x /usr/local/bin/proxima

Windows (x86_64) — PowerShell

irm https://raw.githubusercontent.com/jizzel/proxima/main/install.ps1 | iex

Or download proxima-windows-x64.exe below and add it to a directory on your PATH.

Proxima v1.6.1

Choose a tag to compare

@github-actions github-actions released this 30 Aug 18:46

What's changed

Fixed

  • A credential pasted into a prompt was stored verbatim — found by testing the shipped binary. Masking covered tool arguments, so a key typed into a message sat unmasked in ~/.proxima/sessions/*.json. User and assistant content is now masked — with the bare-scheme rule tightened so it needs a credential-shaped token (a digit, base64/URL punctuation, or mid-token case mixing) rather than mere length, which had turned implement basic authentication middleware into implement *** middleware. Tool results are still left alone so --resume replays real file contents rather than making the model re-run tools to recover them. ToolCall.toJson() is masked for the same reason, closing the last unmasked serialisation boundary
  • Invalid --model values reported as internal faults — ⚠ Unexpected error: Invalid argument(s): Unknown provider "gemini". read like a crash for what is a configuration mistake. Now ⚠ Unknown provider "gemini". with a hint about the provider/model form, and the REPL resets the agent loop so the user can switch model and retry
  • The write critic reviewed an empty patch — it read content or a patch argument, but patch_file's schema is old_str/new_str and no tool has ever had a patch argument. Every patch_file review therefore received an empty string and reported "the proposed change is empty or missing" instead of reviewing the edit. The edit is now rendered as a before/after diff
  • git_status failed outside a repository — a freshly scaffolded project has no git repo, and the tool threw a hard error for that ordinary condition. It now reports Not a git repository. so the model can act on it
  • Every Nth message was silently dropped in long sessions — iterationCount is a persisted session field that was reset only after a failed turn, so it accumulated across successful ones. Once it reached the cap the loop guard was already false on entry: the body never ran and the user's message, already appended to history, was never sent to the model. The turn then failed, which reset the counter, so the next message worked — the symptom was an occasional message doing nothing at all. Now reset per turn, matching the documented per-request semantics
  • The iteration cap interrupted normal multi-file work — the default of 10 was tuned for single-file edits, but the system prompt directs the agent to verify every write by reading the file back, so each file costs two iterations. A seven-file scaffold hit the cap four times. Raised to 25, and budget exhaustion is no longer reported as a failure: it returns the new TaskStatus.budgetExhausted and goes through a new AgentCallbacks.onNotice, rendered dim rather than red, so API callers can distinguish a turn that ran out of steps from one that actually failed. The message says how to continue and names both config locations. Runaway loops are still bounded, and the stuck and spinning detectors fire long before this


Installation

macOS / Linux — install script

curl -fsSL https://raw.githubusercontent.com/jizzel/proxima/main/install.sh | sh

macOS (Apple Silicon + Intel via Rosetta 2)

curl -fsSL https://github.com/jizzel/proxima/releases/download/v1.6.1/proxima-macos-arm64 \
  -o /usr/local/bin/proxima && chmod +x /usr/local/bin/proxima

Linux (x86_64)

curl -fsSL https://github.com/jizzel/proxima/releases/download/v1.6.1/proxima-linux-x64 \
  -o /usr/local/bin/proxima && chmod +x /usr/local/bin/proxima

Windows (x86_64) — PowerShell

irm https://raw.githubusercontent.com/jizzel/proxima/main/install.ps1 | iex

Or download proxima-windows-x64.exe below and add it to a directory on your PATH.

Proxima v1.6.0

Choose a tag to compare

@github-actions github-actions released this 30 Aug 14:53

What's changed

Added

  • Official plugin distribution (SPECS §21.1) — proxima plugin list | install <name> | remove <name> | update, and /plugin in the REPL. Plugins install from a published catalogue into ~/.proxima/plugins/, making them available in every project. plugin is intercepted before flag parsing, because the main parser treats the first positional as a natural-language task — without that, proxima plugin install x was silently sent to the model as a prompt
  • Install safety — SHA-256 verified against the catalogue before anything reaches disk, HTTPS-only downloads, and zip-slip rejected: any archive entry resolving outside the plugin directory aborts the install, with validation and extraction sharing one separator-normalised path so Windows-style ..\..\x entries cannot slip past a POSIX check. Plugin names must be a single path component, so plugin remove ../../Documents is refused rather than deleting an arbitrary directory — and both the CLI and /plugin report that rejection instead of propagating it, since an uncaught throw ended the REPL session over a typo. plugin.json's declared executable is contained the same way: a declared ../../victim.sh would otherwise chmod +x a file outside the plugin, which PluginLoader would then follow and register under the malicious descriptor's name and risk level. An update moves the existing install aside and restores it if the staged rename fails, so a transient error cannot uninstall a working plugin. Execute permissions are restored from the descriptor's declared executable — matching only *.sh left an extensionless entry point unrunnable. Zip-slip and the name/permission handling are not in SPECS §21.1 — a checksum proves the archive matches the catalogue, not that its contents are safe
  • ~ expansion in plugin directories (PluginLoader.resolvePluginDir) — config values come from YAML and never pass through a shell, so ~/.proxima/plugins previously resolved to <workingDir>/~/.proxima/plugins
  • scripts/build_catalog.py — packages each plugins/<name>/ into plugin-<name>.zip, computes checksums, and writes catalog.json; wired into release.yml, with a descriptor-validation step added to ci.yml
  • Provider and tool test coverage — AnthropicProvider (33) and OllamaProvider (25) had no HTTP-level tests at all despite Anthropic being the default provider, and transport.dart had none despite existing to prevent a specific bug class. Plus run_tests (23), write_plan (10), delegate (7), the plugin installer (22), and the plugin CLI (15)

Fixed

  • CI pinned to Dart 3.11.1 — both workflows used sdk: stable, so a formatter change between SDK releases failed dart format --set-exit-if-changed on all three platforms for code that formatted cleanly against the SDK named in pubspec.yaml. The pin makes formatting reproducible locally; raise it and the pubspec constraint together
  • Spinning detection interrupted legitimate exploration — isSpinning fired on any six consecutive read-only calls without checking whether they targeted the same thing, so reading six different files on a small project was indistinguishable from looping. Observed twice in one session on a six-file project, blocking the task both times. It now also requires the window to be repetitive (fewer than four distinct call fingerprints). Three existing tests had encoded the wrong behaviour and were corrected
  • /files ignored reads — it matched only write_file/patch_file/delete_file, so it reported "No files accessed this session" after reading a file, while documented as "files read or written"
  • The write critic could not see the file path — it received only the tool name and content, so it reported changes as "lacking a file path" while the confirmation panel displayed that path directly above
  • --resume <missing-id> failed silently — an unknown session id started a blank session with no message, so a typo looked like a successful resume with lost history
  • OpenAI bypassed its own transport helper — a previous change replaced transportPost/transportSend with raw client calls, and the streaming retry had no error guard at all, so a transport failure there escaped as a raw ClientException. This is the exact bug class transport.dart exists to prevent
  • Inconsistent error classification across providers — Anthropic mapped 403 to unknown rather than auth, so a revoked key triggered a pointless fallback to a secondary that would also fail (FallbackProvider treats auth as non-retryable). Ollama mapped everything to unknown, leaving retry logic nothing to act on for a rate-limited remote instance. All three providers now classify identically
  • write_plan threw a raw TypeError on a missing or non-string content, escaping the ToolError taxonomy the agent loop uses to classify failures


Installation

macOS / Linux — install script

curl -fsSL https://raw.githubusercontent.com/jizzel/proxima/main/install.sh | sh

macOS (Apple Silicon + Intel via Rosetta 2)

curl -fsSL https://github.com/jizzel/proxima/releases/download/v1.6.0/proxima-macos-arm64 \
  -o /usr/local/bin/proxima && chmod +x /usr/local/bin/proxima

Linux (x86_64)

curl -fsSL https://github.com/jizzel/proxima/releases/download/v1.6.0/proxima-linux-x64 \
  -o /usr/local/bin/proxima && chmod +x /usr/local/bin/proxima

Windows (x86_64) — PowerShell

irm https://raw.githubusercontent.com/jizzel/proxima/main/install.ps1 | iex

Or download proxima-windows-x64.exe below and add it to a directory on your PATH.

Proxima v1.5.0

Choose a tag to compare

@github-actions github-actions released this 30 Aug 03:35

What's changed

Added

  • LLM summarization of compacted history (SPECS §19.3, the last outstanding spec item) — when the context budget forces Compaction to discard the oldest messages, SubagentRunner.runSummarizer() first condenses them into 3–5 bullets covering files touched, changes made, and decisions reached; the summary is pinned to the front of the retained history so the model keeps the gist of what it can no longer see. Previously that context was simply lost.

    Cost is bounded and opt-out: the call fires only when messages would otherwise be dropped, so an under-budget turn spends nothing. max_tokens is capped at 512, no tools are sent, and the transcript is bounded both per-message (2000 chars) and in total (24000 chars, newest kept). Room for the summary is reserved before truncation so the injected summary cannot push retained history back over its budget — and only when truncation is actually needed, so a history that already fits is never truncated or summarized. Summaries are cached per span (the agent loop rebuilds context each iteration, so an uncached 10-iteration turn paid for 10 near-identical summaries), their token usage is recorded against the session, the transcript carries secret-masked toolInput so files read can be named, and relevance filtering runs before injection so the summary cannot be filtered out. Disable with summarize_on_compact: false.

    Every failure mode degrades to the plain truncation already applied — no provider, a provider error, a non-final response, a blank summary, or a throwing summarizer. A summarization failure costs context, never the turn. Compaction.compact() is now async and takes an optional HistorySummarizer; with none supplied its behaviour is unchanged. 14 new tests

Fixed

  • gpt-5.6-* models printed a debug warning on every turn — the unsupported-parameter retry lived only on the non-streaming path, so a model needing reasoning_effort: none failed the stream, printed ⚠ [debug] Streaming failed, falling back, and recovered silently via complete(). Functionally correct but it read as a broken session. The streaming path now performs the same retry in place


Installation

macOS / Linux — install script

curl -fsSL https://raw.githubusercontent.com/jizzel/proxima/main/install.sh | sh

macOS (Apple Silicon + Intel via Rosetta 2)

curl -fsSL https://github.com/jizzel/proxima/releases/download/v1.5.0/proxima-macos-arm64 \
  -o /usr/local/bin/proxima && chmod +x /usr/local/bin/proxima

Linux (x86_64)

curl -fsSL https://github.com/jizzel/proxima/releases/download/v1.5.0/proxima-linux-x64 \
  -o /usr/local/bin/proxima && chmod +x /usr/local/bin/proxima

Windows (x86_64) — PowerShell

irm https://raw.githubusercontent.com/jizzel/proxima/main/install.ps1 | iex

Or download proxima-windows-x64.exe below and add it to a directory on your PATH.

Proxima v1.4.0

Choose a tag to compare

@github-actions github-actions released this 29 Aug 22:36

What's changed

Added

  • Update check on startup (lib/core/update_checker.dart) — checks GitHub for a newer release at most once per 24 hours, in a fire-and-forget background fetch that never blocks startup and swallows every failure. When an update exists the notice offers the install command, skip-this-version (remembered in ~/.proxima/update_check.json, and a later release is still announced), or remind-me-later. Proxima never installs anything itself — it has filesystem and shell access, so self-replacing its binary would make a compromised release channel equivalent to remote code execution; the user is given the command to run. Disable with check_for_updates: false; source builds (dev) never check. Version comparison is numeric, so 1.10.0 correctly beats 1.9.0. The notice appears at the first prompt rather than before the header — the check is deliberately not awaited at startup, so consuming it there would read a still-pending result and drop the notice under any real network latency
  • Shared ignore matcher (lib/tools/ignore_matcher.dart) — list_files, glob, search, search_symbol, and find_references now all skip the same paths: built-in defaults (VCS, dependency, build directories, generated Dart files), the working directory's .gitignore, and session patterns from /ignore. Previously there were three inconsistent skip lists — two byte-identical copies in the search tools, a different set in project_index, and none at all in list_files, glob, or search, so search walked node_modules and list_files recursed into .git. Full gitignore syntax is supported including negation (!keep.log), anchoring (/build.sh), directory-only rules (cache/), and **. All five walkers consult it for both files and directories, using working-directory-relative paths so path-qualified rules (vendor/lib/) and file rules (lib/excluded.dart) match — a basename alone cannot. The matcher lives on ToolRegistry and is injected as a callback, leaving the ProximaTool.execute contract — which plugins implement — unchanged; the agent loop refreshes it each turn so .gitignore edits and /dir switches apply without a restart. Clears the last unticked box in SPECS §18
  • /ignore <pattern> is now enforced — it previously stored and displayed patterns that no tool ever read, despite the README claiming it excluded them from context
  • Tests for list_files (11) and the ignore matcher (20), covering gitignore precedence, negation ordering, ** semantics, and Windows separators
  • Tests for the four previously untested tools — patch_file (12: first-vs-all replacement, backup creation, the BACKUP_PATH: marker /undo depends on, no-write-on-failure), glob (8), search (9), and run_command (11, including that a blocked command is never executed). SPECS §18 previously ticked these with a "works; no dedicated test file" caveat; every box is now backed by tests

Fixed

  • run_command and run_tests were broken on Windows — both hardcoded bash, which on Windows resolves to the WSL launcher; with no distribution installed it returns UTF-16 help text and exit code 1, so every shell command failed in the shipped proxima-windows-x64.exe. Both now use cmd /c on Windows and bash -c elsewhere, via a single shared RunCommandTool.shellExecutable / shellArgs

  • Timed-out commands are now killed, not orphaned — run_command and run_tests used Process.run(...).timeout(...), which abandons the Future but leaves the child process running: a command Proxima reported as timed out kept executing invisibly, which is unacceptable for a tool behind a permission gate. Verified — a sleep 2 "timed out" at 1s still completed its work 3s later. Both tools now use Process.start and kill the process on timeout. On Windows the whole tree is killed with taskkill /T: cmd /c <command> launches the command as a separate process, so killing the shell alone left it running — holding the working directory open and continuing work the user was told had stopped. On POSIX bash -c execs into the command, so killing the shell suffices

  • glob matched nothing on Windows — glob patterns are written with forward slashes but the tool matched them against native separators, so **/*.dart could never match lib\nested.dart. Paths are now normalised to forward slashes for both matching and output, so results are consistent across platforms

  • Transport errors surface as actionable messages (lib/providers/transport.dart) — OllamaProvider and AnthropicProvider let raw ClientException/SocketException escape, so a stopped Ollama produced six lines of socket dump instead of the message SPECS §22 promises. All three providers now route HTTP through one shared helper: a refused connection to Ollama reads Ollama not detected at <url>. Run: ollama serve, and cloud providers report the unreachable host. Because these are now LLMError(network), a configured fallback_model is finally tried for Ollama and Anthropic too

  • Unreachable servers are no longer retried — agent_loop retried a refused connection up to maxRetriesLlm times and the stream path re-issued each failure via complete(), producing six lines for one problem. A server that cannot be reached now reports once and stops

  • Per-model parameter support is discovered rather than predicted — the provider guessed from model names which models reject an explicit temperature, and was wrong repeatedly (gpt-5 rejects it, gpt-5.1 accepts it). It now sends the request and, on a 400 naming an unsupported parameter, drops that field and retries once. The same mechanism sets reasoning_effort: none when a model refuses function tools while reasoning is active (gpt-5.6-luna, gpt-5.6-sol)

  • /model picker lists only usable models — babbage-002, davinci-002, gpt-3.5-turbo-instruct, the bare chat-latest alias, and all *-codex models are excluded; codex models are deprecated or served only by /v1/responses and 404 on chat. Models are ordered so current families (gpt-5*, gpt-4.1*, o*, gpt-4o*) appear before deprecated ones instead of alphabetically, which previously buried gpt-4o under a dozen gpt-3.5-turbo-* variants



Installation

macOS / Linux — install script

curl -fsSL https://raw.githubusercontent.com/jizzel/proxima/main/install.sh | sh

macOS (Apple Silicon + Intel via Rosetta 2)

curl -fsSL https://github.com/jizzel/proxima/releases/download/v1.4.0/proxima-macos-arm64 \
  -o /usr/local/bin/proxima && chmod +x /usr/local/bin/proxima

Linux (x86_64)

curl -fsSL https://github.com/jizzel/proxima/releases/download/v1.4.0/proxima-linux-x64 \
  -o /usr/local/bin/proxima && chmod +x /usr/local/bin/proxima

Windows (x86_64) — PowerShell

irm https://raw.githubusercontent.com/jizzel/proxima/main/install.ps1 | iex

Or download proxima-windows-x64.exe below and add it to a directory on your PATH.

Proxima v1.3.0

Choose a tag to compare

@github-actions github-actions released this 29 Aug 18:10

What's changed

Added

  • OpenAI provider (lib/providers/openai_provider.dart) — third provider alongside Anthropic and Ollama, with native tool calling and SSE streaming. --model openai/gpt-4o, openai_api_key / OPENAI_API_KEY. Handles the four wire-format divergences from Anthropic: the system prompt is sent as a role: "system" message rather than a top-level field; tool schemas nest under function/parameters; tool results use role: "tool" with tool_call_id; and assistant tool_calls[].function.arguments is a JSON-encoded string, not an object. tool_calls[].id is preserved into ToolCall.callId — OpenAI rejects a tool_call_id it did not issue, and the agent loop replays it on the next request. Omits temperature for the o-series reasoning models, which accept only their default and reject an explicit value — including 0.0 — with a 400. Uses max_completion_tokens (max_tokens is deprecated and rejected by o* models) and sets stream_options.include_usage so streamed responses still report token usage and cost
  • Injectable openai_base_url — the same provider serves any endpoint speaking the OpenAI wire format (Groq, Together, OpenRouter, LM Studio) with no new provider class. Azure OpenAI is not supported: it requires an api-key header, a deployment-specific path, and an api-version query parameter
  • Robust transport handling — a ClientException from an unreachable endpoint is converted to LLMError(LLMErrorKind.network) on both request paths and the mid-stream body, so a configured fallback_model is actually tried during a network outage (FallbackProvider catches only LLMError)
  • Per-model context windows — capabilities.contextWindow is derived from the selected model rather than a flat figure, because ContextBuilder budgets the whole session — including a 10% output allowance — from it. Known OpenAI families match by prefix; anything unrecognised gets a conservative 8K, overridable with openai_context_window
  • Provider-aware credential hints — an auth failure names the active provider's environment variable rather than always ANTHROPIC_API_KEY
  • Single credential source for subagents — the critic_on_write subagent and the agent loop now share one _buildProviderRegistry() helper, so a provider's credentials cannot reach one and not the other
  • OpenAI pricing in CostCalculator for gpt-4o, gpt-4o-mini, gpt-4-turbo, o3, and o3-mini
  • HTTP-level provider tests — first use of package:http/testing.dart's MockClient in this repo, injected through the existing http.Client? constructor seam. 23 tests assert the decoded request body and parsed response, covering all four wire divergences, error-code mapping, SSE streaming, and model discovery. 9 further tests cover ProviderRegistry dispatch

Changed

  • Model lists are no longer hardcoded in the CLI — the /model picker and tab completion now build their list from LLMProvider.listModels(). OpenAIProvider and OllamaProvider discover models live — OpenAI filtering is an exclusion list rather than an allow-list, so chat models from compatible endpoints (Groq, Together, OpenRouter, LM Studio) survive discovery instead of being filtered out; AnthropicProvider returns a static list. New OpenAI and Ollama releases appear with no code change. Removes the duplicated SlashCommandHandler.anthropicModels const and its copy in repl.dart. Live fetches run in the background at REPL start, are re-tried in the picker only when stdout is a TTY, and always degrade to [] — the picker still works offline


Installation

macOS / Linux — install script

curl -fsSL https://raw.githubusercontent.com/jizzel/proxima/main/install.sh | sh

macOS (Apple Silicon + Intel via Rosetta 2)

curl -fsSL https://github.com/jizzel/proxima/releases/download/v1.3.0/proxima-macos-arm64 \
  -o /usr/local/bin/proxima && chmod +x /usr/local/bin/proxima

Linux (x86_64)

curl -fsSL https://github.com/jizzel/proxima/releases/download/v1.3.0/proxima-linux-x64 \
  -o /usr/local/bin/proxima && chmod +x /usr/local/bin/proxima

Windows (x86_64) — PowerShell

irm https://raw.githubusercontent.com/jizzel/proxima/main/install.ps1 | iex

Or download proxima-windows-x64.exe below and add it to a directory on your PATH.

Proxima v1.2.0

Choose a tag to compare

@github-actions github-actions released this 29 Aug 15:02

What's changed

Added

Agent Loop — Spinning Detection

  • StuckDetector.isSpinning(toolLog, {int window = 6}) — second stuck-detection mechanism that fires when the last window calls are all read-only tools (read_file, list_files, glob, search, search_symbol, find_references, get_imports, git_status, git_diff, git_log). Catches models that loop on reads without ever writing — not caught by the identical-call detector.
  • StuckDetector.spinWindow = 6 — public constant exposed for callers that need to slice the log for display.
  • AgentCallbacks.onStuck gains reason parameter ('stuck' | 'spinning', default 'stuck') — lets the renderer show a tailored message for each case; existing callers unaffected.
  • TaskSummaryRenderer.renderStuck differentiated message — spinning shows "Agent is reading without making progress — last N calls were all read-only"; stuck shows the existing "Repeated tool calls detected" message.

Improved

  • plugin_loader.dart — fully async I/O — _loadOne converted from sync to async; existsSync → exists, readAsStringSync → readAsString, statSync → stat, list().toList() → await for; prevents blocking the Dart event loop during plugin discovery at startup.
  • find_references_tool.dart — streaming directory walk — _walk now uses await for instead of dir.list().toList(); processes entries one at a time and exits early when max_results is reached, avoiding loading the full directory listing into memory on large codebases.

Security

  • Secret masking before disk writes (lib/core/secret_masker.dart) — tool arguments are persisted to both ~/.proxima/audit.jsonl and ~/.proxima/sessions/*.json; secret-shaped values are now replaced with *** in both. Detects Anthropic (sk-ant-), OpenAI (sk-proj-, sk-), GitHub (gh[porsu]_), AWS (AKIA…), Slack (xox[baprs]-), and JWTs by pattern. Authorization headers consume the auth scheme (Bearer/Basic/Token/Digest/APIKey) and the credential after it, so opaque credentials with no provider prefix are masked too. Bare schemes (Bearer/Basic/Digest/APIKey/Token) are matched without an Authorization: prefix, covering headers passed structurally as {'headers': {'Authorization': 'Token abc'}}. Argument names are normalised (camelCase split, separators collapsed) and matched per segment, so api-key, x-api-key, apiKey, API_KEY, and Authorization are all recognised while author and tokenizer are not. Masking is applied at the serialisation boundary only (AuditLog.record(), Message.toJson(), TaskRecord.toJson()) — never to the live in-memory session, which AnthropicProvider and Compaction both read. Terminal output and tool results are deliberately left unmasked so the confirm prompt shows the real command and --resume replays correctly. Closes the last unmitigated Critical risk in SPECS §22. 46 new tests

Fixed

  • ollama_base_url ignored by /model — SlashCommandHandler read OLLAMA_BASE_URL from the environment directly, so a base URL set in ~/.proxima/config.yaml was honoured by the agent loop but ignored by the model picker. The handler now takes an injectable ollamaBaseUrl (matching the existing isTty injection pattern) wired from ProximaConfig

Documentation

  • SPECS §15.3 documents config precedence (YAML overrides environment); new §15.3.1 specifies secret masking; §18 MVP checklist reconciled against the test suite, with list_files .gitignore support explicitly marked not implemented; §22 risk register updated
  • README gains a "Secret masking" subsection under Data & Privacy and an explicit config-precedence note


Installation

macOS / Linux — install script

curl -fsSL https://raw.githubusercontent.com/jizzel/proxima/main/install.sh | sh

macOS (Apple Silicon + Intel via Rosetta 2)

curl -fsSL https://github.com/jizzel/proxima/releases/download/v1.2.0/proxima-macos-arm64 \
  -o /usr/local/bin/proxima && chmod +x /usr/local/bin/proxima

Linux (x86_64)

curl -fsSL https://github.com/jizzel/proxima/releases/download/v1.2.0/proxima-linux-x64 \
  -o /usr/local/bin/proxima && chmod +x /usr/local/bin/proxima

Windows (x86_64) — PowerShell

irm https://raw.githubusercontent.com/jizzel/proxima/main/install.ps1 | iex

Or download proxima-windows-x64.exe below and add it to a directory on your PATH.

Proxima v1.1.0

Choose a tag to compare

@github-actions github-actions released this 28 Mar 10:55

What's changed

Added

Terminal UX — Rich Feedback & Activity Indicators

  • AgentCallbacks.onIterationStart(iteration, maxIterations) — called at the start of each loop iteration; Renderer uses it to show ⠋ Thinking… [N/10] spinner with iteration depth between tool calls

  • AgentCallbacks.onToolExecuting(toolCall) — fires immediately before tool execution (after permission grant); Renderer shows ◆ Reading core/session.dart… activity line

  • Animated blink dots on in-progress tool lines — executing line pulses … → …. → ….. → …... every 400ms via Timer.periodic so users see the tool is alive, not stuck

  • Context preserved in result lines — the activity label from onToolExecuting is held in _pendingLabel and reused in onToolResult; result now reads ✓ Reading core/session.dart… (1423 lines) 0.3s instead of ✓ read_file (1423 lines)

  • Per-tool elapsed time — _toolStartTime captured at onToolExecuting, formatted as ms or s and appended dim to every result line

  • Turn elapsed time in usage report — _turnStartTime set on iteration 1 via onIterationStart; appended to the ↑N ↓N total: N line: ↑1240 ↓387 total: 14832 4.2s cost: $0.0042

  • onThinking now shows ⠋ Working… spinner after printing the reasoning line, bridging the gap between reasoning and the first tool executing line

  • Clarify styling — onClarify now renders ? question with cyan ? instead of raw bold text

  • _fmtElapsed(Duration) helper — <1000ms → NNNms, ≥1000ms → N.Ns

  • Static showSpinner('Thinking...') calls removed from repl.dart; spinner is now driven entirely by onIterationStart

  • 342 tests total (up from 237 — 105 new tests across find_references_test.dart, get_imports_test.dart, test/tools/plugin/shell_plugin_test.dart)

  • find_references tool (lib/tools/search/find_references_tool.dart, safe) — finds all usages of a symbol across the codebase using \b<symbol>\b word-boundary matching. Supports path scoping, file_extensions filter, exclude_definition flag (skips definition lines using the same heuristics as search_symbol), and max_results (default 100). Skips .git, node_modules, build, .dart_tool, .pub-cache, and generated files. Output: file:line content lines followed by a Found N references in M files. summary.

  • get_imports tool (lib/tools/search/get_imports_tool.dart, safe) — parses all import/require statements from a single file and categorises them by source type. Supports Dart ([dart]/[package]/[local]), JS/TS ([node]/[local]), Python ([stdlib]/[local]), and Go ([stdlib]/[external]). resolve_paths: true expands local imports to project-relative paths. Throws typed ToolError(notFound) when the file does not exist; ToolError(parseError) for binary files or unsupported extensions.

  • Plugin system — shell/binary drop-in tools via .proxima/plugins/<name>/:

    • ShellPluginTool (lib/tools/plugin/shell_plugin_tool.dart) — wraps any executable as a ProximaTool; passes args as JSON on stdin, reads result from stdout, enforces configurable timeout_seconds; non-zero exit → ToolError
    • PluginLoader (lib/tools/plugin/plugin_loader.dart) — load(dirs, workingDir) discovers plugin directories, validates plugin.json (required fields: name, description, executable, input_schema), checks the executable exists and has the execute bit, parses risk_level string → RiskLevel (defaults to confirm for unrecognised values); logs startup warnings for invalid plugins and never throws
    • ProximaConfig.pluginDirs field — List<String>, default ['.proxima/plugins']; parsed from plugin_dirs YAML key; included in copyWith()
    • _buildToolRegistry() in repl.dart is now async; loads plugins after all built-ins; silently skips any plugin whose name conflicts with a built-in tool (built-in wins)

Plan Mode

  • /plan <task> slash command — runs the agent in SessionMode.safe with isPlanMode: true, produces .proxima/plan.md, then shows an arrow-key picker (Execute / Edit / Skip) before executing
  • /execute slash command — executes the saved plan in .proxima/plan.md without re-running the research phase
  • /cost slash command — shows current session cost and a cost table for the 10 most recent sessions
  • Shift+Tab REPL mode cycle — Shift+Tab now cycles through three modes: normal → plan → accept-edits → normal; each press updates the prompt tag (❯ / ❯ [plan] / ❯ [edits]) and shows a compact tip line below the prompt (⏸ plan mode on / ⏵⏵ accept edits on); the previous verbose green print above the prompt is removed
  • Accept-edits mode (❯ [edits]) — third cycle state; sets _permissionGate.mode to SessionMode.auto transiently so confirm-level tool calls (writes, commands) execute without the Approve/Deny picker; cycling back to normal restores the configured mode; _session.mode and the session file are unchanged (transient only)
  • _ReplMode enum (normal | plan | acceptEdits) in repl.dart replaces the former bool _planMode field
  • _cycleReplMode() replaces _togglePlanMode(); syncs permission gate and calls _renderer.updateStatus(planMode:, acceptEditsMode:) with no print
  • _modeTip() helper returns the tip string for the active mode (null when normal)
  • ReadLine.readLine() gains optional String? statusTip parameter; stored as _statusTip field and folded into _renderPanel() — when no suggestions are visible and a tip is set, one dim tip line is drawn below the prompt; suggestions take priority (tip suppressed while panel is open)
  • ReadLine.readLine() gains onShiftTab callback; detects ESC [ Z (standard terminal Shift+Tab), fires the callback, and returns ReadLine.shiftTabSentinel — no history entry, no completion side-effects
  • ReadLine.shiftTabSentinel public constant (\x00__shift_tab__) — lets the REPL identify the toggle without a magic string literal
  • ProximaSession.isPlanMode runtime-only bool (not persisted) — set by ProximaSession.create(..., isPlanMode: true)
  • ProximaSession.cumulativeCost field — tracks session-total USD cost; persisted to session JSON as cumulative_cost; incremented via recordCost(double)
  • ContextBuilder injects a PLAN MODE system-prompt block when session.isPlanMode is true — instructs the agent to use only read tools, then call write_plan with a structured markdown plan (context, step-by-step changes, tests, risks)
  • WritePlanTool (lib/tools/agent/write_plan_tool.dart) — confirm-risk tool that writes agent-produced plans to .proxima/plan.md; supports dryRun()

Model Persistence

  • /model <name> now persists the selected model as model: in ~/.proxima/config.yaml — future sessions start with the last chosen model
  • ProximaConfig.saveDefaultModel(model) static helper — creates ~/.proxima/config.yaml (and parent directory) if absent; replaces the model: line in-place, leaving all other keys untouched

Cost Tracking

  • CostCalculator.compute(model, usage) — maps known Anthropic model IDs to per-token USD rates; returns 0.0 for unknown/local models
  • CostCalculator.format(cost) — formats USD cost as $0.0000 (4 decimal places)
  • AgentCallbacks.onUsageReport extended with turnCost and sessionCost parameters; Renderer appends cost: $N.NNNN to the usage line when non-zero
  • AgentLoop computes turnCost via CostCalculator.compute and passes it to onUsageReport alongside the session cumulative
  • /status now shows Cost row when session cost is non-zero

Search

  • search_symbol tool (lib/tools/search/search_symbol_tool.dart) — searches for symbol definitions (class, function, variable, any) using regex across the working directory; supports path scoping and kind filtering; rejects path traversal

Interactive UX — Consistent Arrow-Key Pickers & Status Line

  • PickerWidget (lib/renderer/picker_widget.dart) — shared synchronous arrow-key single-choice picker; renders ▶ Option hint rows with inverse-video highlight; used everywhere a fixed choice list is presented; Escape/Ctrl-C returns defaultIndex
  • Permission prompt → picker — PermissionPrompt._confirmPrompt() replaced with PickerWidget.pick(options: ['Approve', 'Deny'], ...) with dim hints; high-risk typed CONFIRM path unchanged
  • Stuck dialog → picker — TaskSummaryRenderer.renderStuck() a/c single-keypress replaced with PickerWidget.pick(options: ['Continue', 'Abort'], defaultIndex: 1) (Abort is the safer default when stuck)
  • /mode no-arg → picker — /mode with no argument opens an arrow-key picker pre-selected to the current mode (safe / confirm / auto with hints); previously showed a dim text line only
  • Plan approval picker simplified — _planApprovalPicker() in repl.dart now delegates to PickerWidget instead of the hand-rolled _renderPlanPicker/_clearPlanPicker helpers (removed)
  • Persistent status line after each turn — Renderer.onUsageReport() calls _printStatusLine() after the token line; shows model: <name> always, mode: <name> when non-default (non-confirm), [plan] when plan mode active, [edits] when accept-edits mode active
  • Renderer.updateStatus({model, mode, planMode, acceptEditsMode}) — push current state into the renderer; called from initialize(), _switchModel(), _switchMode(), and _cycleReplMode()
  • ClarifyResponse gains options field — List<String> options (default empty); when non-empty the agent loop stays alive: onClarifyWithOptions shows a picker, the selected answer is injected as a user message, and the loop continues without exiting
  • AgentCallbacks.onClarifyWithOptions(question, options) — new abstract method; Renderer implements it via PickerWidget.pick(options: options); all mock callbacks in tests return Future.value(0)
  • SchemaValidator._validateClarify() parses optional options array fro...
Read more

Proxima v1.0.1

Choose a tag to compare

@github-actions github-actions released this 26 Mar 14:24

What's changed

Added

  • SessionMode.safe now enforced in PermissionGate — any tool above safe risk level is denied without prompt when the session is in safe mode (step 3a, audited as safe_mode_blocked)
  • ToolErrorCode enum on ToolError (notFound | permissionDenied | pathViolation | timeout | parseError | unknown) — tools can now classify errors for actionable LLM feedback
  • _formatToolError() helper in AgentLoop — tool errors now return structured context (Tool: …\nError: …\nSuggestion: …) instead of bare 'Error: $e' strings
  • AgentCallbacks.onUsageReport(turnUsage, cumulative) — called after every completed turn; Renderer displays ↑N ↓N total: N as a dim line after each response
  • System prompt overhauled in ContextBuilder._buildSystemPrompt(): identity section, 7 ordered operating rules (read-before-write, patch-over-write, verify-after-write, diagnose-before-retry, no-identical-duplicate-calls, safe-mode-tool-list, test-fix-cycle limit), and session state (mode + cumulative token count)
  • 3 new permission gate tests: safe mode blocks confirm tools, safe mode allows safe tools, deny list pre-empts risk classification
  • TestOutputParser — parses raw test runner output into structured TestResult/TestFailure objects; supports Dart, Jest, Pytest, Cargo, Go; graceful fallback to raw string on unparseable output
  • run_tests tool now embeds FRAMEWORK:<name> marker in output and returns a concise structured failure summary (test name, file, message) before the raw output; LLM gets actionable context instead of wall-of-text stdout
  • 15 new parser tests covering all five frameworks and the framework marker extractor
  • Critic subagent (SubagentType.critic) — pre-commit review agent that fires before the permission prompt on write_file/patch_file at confirm risk; returns CriticResult with approve | warn | block_suggestion verdict; advisory only, never a hard gate; silent on approve; displays amber/red note above y/n prompt for warn/block_suggestion
  • CriticResult, CriticVerdict, CriticIssue types added to subagent_runner.dart
  • SubagentRunner.runCritic() — never throws; graceful fallback to approve on LLM error or malformed JSON; strips markdown fences; capped at 1024 tokens
  • PermissionGate.criticCallback — optional CriticCallback? wired in before step 6 (user prompt); only fires for confirm-level write tools, never in auto mode
  • PromptCallback extended with {CriticResult? criticResult} named parameter
  • critic_on_write: true config field in .proxima/config.yaml (default: true)
  • ProximaConfig.criticOnWrite field with yaml key critic_on_write
  • Critic wired in repl.dart using the active model and a fresh provider per invocation
  • 7 new critic tests (approve/warn/block_suggestion/malformed JSON/LLM error/system prompt/markdown fence)
  • ProximaSession.fileCache — Map<String, String> keyed by canonical path; populated by the agent loop after every successful read_file call
  • Compaction.deduplicateFileReads() — new Pass 0 that replaces all but the most recent read_file tool result for any given path with [File already in context]; integrated into Compaction.compact() via optional fileCache param; estimated 15–30% token savings in file-heavy sessions
  • ContextBuilder passes session.fileCache to Compaction.compact()
  • 11 new compaction tests covering deduplication, pruning, and truncation
  • FallbackProvider (lib/providers/fallback_provider.dart) — wraps a primary LLMProvider with a secondary; transparently retries on non-auth LLMError; auth errors rethrown immediately; listModels() also falls back; no changes to AgentLoop
  • ProviderRegistry.create() accepts optional fallbackModel string; wraps primary in FallbackProvider when set; best-effort (ignores fallback config errors)
  • ProximaConfig.fallbackModel field with yaml key fallback_model (default: null)
  • Fallback model wired in repl.dart _getAgentLoop()
  • 5 new fallback provider tests

Fixed

  • FallbackProvider.stream() never reached secondary — on primary stream failure the old implementation yielded a done chunk with hasToolUse: false, which the agent loop treated as a successful empty response; secondary was never tried. Fixed by rethrowing the non-auth LLMError so _streamResponse's catch block falls back to complete(), which correctly retries on the secondary
  • Token usage display appeared before response separator — onUsageReport was called before onFinalResponse/onClarify, so the dim token line printed above the separator instead of below it. Swapped order: text → separator → usage
  • Compaction.deduplicateFileReads() missing role guard — assumed messages[i-1] was always an assistant message; added explicit if (assistantMsg.role != MessageRole.assistant) continue guard for malformed history
  • Critic diffOrContent fallback was too verbose — toolCall.args.toString() sent the full serialized args map to the critic LLM when neither content nor patch was present; replaced with '' to avoid wasting tokens on irrelevant data
  • 4 additional tests (streaming fallback, auth rethrow, fallback path, orphaned tool message)
  • 237 tests total


Installation

macOS / Linux — install script

curl -fsSL https://raw.githubusercontent.com/jizzel/proxima/main/install.sh | sh

macOS (Apple Silicon + Intel via Rosetta 2)

curl -fsSL https://github.com/jizzel/proxima/releases/download/v1.0.1/proxima-macos-arm64 \
  -o /usr/local/bin/proxima && chmod +x /usr/local/bin/proxima

Linux (x86_64)

curl -fsSL https://github.com/jizzel/proxima/releases/download/v1.0.1/proxima-linux-x64 \
  -o /usr/local/bin/proxima && chmod +x /usr/local/bin/proxima

Windows (x86_64) — PowerShell

irm https://raw.githubusercontent.com/jizzel/proxima/main/install.ps1 | iex

Or download proxima-windows-x64.exe below and add it to a directory on your PATH.