Skip to content

Proxima v1.1.0

Choose a tag to compare

@github-actions github-actions released this 28 Mar 10:55
· 45 commits to main since this release

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 from the LLM response
  • ContextBuilder adds operating rule 8 — instructs the LLM to use "options" array in clarify responses when presenting a fixed choice list

Fixed

  • Plan mode agent offered numbered options menu instead of stopping — system prompt now explicitly forbids presenting options or asking for approval after write_plan; instructs the agent to emit a brief confirmation and stop
  • onFinalResponse printed a spurious blank line during streaming — when streaming was active, text is ''; the renderer now skips writeln('') + _renderMarkdown('') when text is empty, avoiding a double blank line above the closing separator
  • _dispatchPlan silently discarded async results — _runPlan() and _handleExecutePlan() are now wrapped with unawaited() making the fire-and-forget intent explicit
  • Redundant Researching… spinner in _runPlan — removed manual showSpinner/hideSpinner calls; the renderer drives the spinner correctly via onIterationStart
  • Plan approval picker Edit hint was misleading — said "save to .proxima/plan.md" (plan is already saved at that point); corrected to "edit .proxima/plan.md, then run /execute"
  • write_plan called twice by small local models — the agent loop now exits immediately after the first successful write_plan in a plan-mode session (session.planWritten = true); the LLM no longer takes a second turn, eliminating the duplicate write and the "Would you like to: 1. Approve..." options noise that appeared before the picker
  • Status line printed before plan content — onUsageReport was firing (and printing the status line) before _maybeShowPlanPicker displayed the plan text and picker; fixed by suppressNextStatusLine() before the plan agent turn and printStatusLine() explicitly after the picker
  • Shift+Tab verbose print removed — the green Plan mode ON/OFF + dim description line printed above the prompt on every toggle is replaced by the compact tip line rendered below the prompt by ReadLine
  • Shift+Tab detection used exact byte match only — added endsWith('[Z') guard to cover terminal emulators that deliver the sequence with a prefix variation


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.1.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.1.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.