Skip to content

feat(code): local in-process code mode + agentic-loop fixes - #409

Open
rschardosin wants to merge 11 commits into
mainfrom
feat/code-mode
Open

feat(code): local in-process code mode + agentic-loop fixes#409
rschardosin wants to merge 11 commits into
mainfrom
feat/code-mode

Conversation

@rschardosin

Copy link
Copy Markdown
Contributor

Summary

Adds astonish code — a fully local, in-process coding tool (in the spirit of Claude Code / opencode / grok CLI). The single binary runs the wired ChatAgent in the same process and calls its built-in tools directly against the host filesystem in the working directory. There is no daemon, no HTTP, and no login. The sandbox is forced off; safety comes from the per-tool approval prompt (--auto-approve / --yolo to bypass).

It reuses the existing agent runtime, TUI, providers, and event pipeline — this is plumbing, not a second agent implementation. Platform chat (astonish chat) is unchanged.

What's included

Code mode

  • RunCodeTUI + localAgentBackend drive the ADK runner in-process, reusing the shared (type,data)events.Event translator (mapSSEToEvents) so the TUI renders identically to platform mode with no new mapping code.
  • Opens without a model configured: a placeholder LLM lets the agent build; the in-TUI /model and new /provider overlays configure the model/providers and persist to the local config file only (~/.config/astonish/config.yaml) — never a database. Provider management is exposed via an optional ProviderAdminBackend, so the platform backend is untouched.
  • AGENTS.md support: loads AGENTS.md (fallback CLAUDE.md) following the agents.md convention — walking up from the working dir to the git root, nearest-wins, plus a global file — into the system prompt.
  • Log isolation: redirects stdlib log/slog away from the alt-screen so background log lines (e.g. ADK "unknown agent" warnings) can't corrupt the UI; --debug sends them to code-debug.log.

Agentic-loop performance & correctness fixes

Informed by analyzing real slow/failing sessions:

  • Tree-sitter navigation on the main thread (repo_map, code_definition, code_references) plus a conditional "code navigation MUST" prompt rule. grep_search stays domain-agnostic (no code advice in its description).
  • read_file soft-cap on large unbounded reads to curb context bloat (the dominant cause of slow inference).
  • Must-read-before-edit fix: read/edit/write all count as "seen", so consecutive edits no longer trigger false "you must read this file" errors and needless re-reads.
  • Context header fix: estimate tokens locally when the provider reports no usage, so the header shows real utilization instead of Context 0 (estimates update context occupancy but are not accumulated into cumulative usage).

Also included

  • Checkpoint/rollback groundwork and plan-mode enforcement.
  • Docs and tests updated across affected packages.

Testing

  • go build ./... passes.
  • golangci-lint run — 0 issues (also enforced by the pre-commit hook).
  • Unit tests added/updated for code mode, provider admin, AGENTS.md loading, the read_file soft-cap, the edit guard, context estimation, and the system-prompt contracts/golden.

Notes for reviewers

  • This branch bundles the code-mode feature with pre-existing rollback / plan-mode / api / web changes that were in the working tree. Happy to split if preferred.
  • Docs: see docs/architecture/terminal-app.md (code mode) and docs/architecture/code-intelligence.md (tree-sitter main-thread + prompt rule).

Add `astonish code`: a fully local, in-process coding tool (like Claude
Code / opencode) that runs the wired ChatAgent in the same process,
executes built-in tools directly against the host filesystem, and never
contacts a platform. Sandbox is forced off; safety comes from the
per-tool approval prompt.

Highlights:
- RunCodeTUI + localAgentBackend driving the ADK runner in-process,
  reusing the shared SSE->event translator (mapSSEToEvents).
- Opens without a model configured: placeholder LLM + in-TUI /model and
  /provider overlays that persist provider config to the local config
  file only (never a database). ProviderAdminBackend is an optional
  capability, so platform chat is unaffected.
- Loads AGENTS.md/CLAUDE.md project guidance (agents.md convention) into
  the system prompt, discovered by walking up to the git root.
- Redirects stdlib log / slog away from the alt-screen TUI so background
  log lines (e.g. ADK "unknown agent" warnings) don't corrupt the UI.

Agentic-loop performance and correctness fixes (informed by analyzing
real slow sessions):
- Put tree-sitter navigation (repo_map, code_definition, code_references)
  on the main-thread tool allowlist and add a conditional "code
  navigation MUST" prompt rule; grep_search stays domain-agnostic.
- Soft-cap unbounded read_file on large files to curb context bloat.
- Fix must-read-before-edit false positive: read/edit/write all count as
  "seen", so consecutive edits no longer force needless re-reads.
- Estimate context tokens locally when the provider reports no usage, so
  the header shows real utilization instead of "Context 0" (estimates
  update context occupancy but not cumulative usage).

Also includes checkpoint/rollback groundwork and plan-mode enforcement.
Docs and tests updated across affected packages.
- Show '✦ Astonish Code' title, working directory, and accurate
  tool-approval notice (auto-approve aware) in the code-mode welcome card
- Add WorkingDir to tui backend.Info, populated by the local code backend
- Include unrelated in-progress tool/tool-activity and plan-mode changes
Fix pasted images silently failing in local `astonish code` sessions,
plus fit the welcome card on one line.

Image paste pipeline fixes:
- localAgentBackend.RunTurn dropped opts.Attachments and sent a
  text-only user message, so pasted images were collected in the
  composer but never reached the model. Build the user message via
  agent.NewTimestampedUserContentWithAttachments (base64 through the
  new agentAttachmentsFromBackend helper), matching the platform path.
- An empty textarea.pasteMsg (Ctrl+V with an image-only clipboard)
  was rejected by the text-only detector and fell through as a no-op.
  Recognize the paste regardless of emptiness and route empty ones to
  tryPasteImage; swallow truly-empty pastes.
- macOS clipboard reader rewritten from the fragile
  `the clipboard as «class PNGf»` AppleScript coercion to NSPasteboard
  via JXA + AppKit. The old coercion silently failed for some images
  (large, or lacking a public.png representation) — the tell was that
  resizing an image made paste start working. JXA reads public.png
  directly and re-encodes any other representation to PNG.

Welcome card:
- Shorten the code-mode approval notice and bottom hints so neither
  wraps onto a second line.

Tests: add coverage for agentAttachmentsFromBackend and the paste-msg
detector; update welcome-card assertions. Docs updated in
terminal-app.md and pkg/launcher/AGENTS.md.
Add a 'Copy content' option to the EmbeddedFileViewer download dropdown
so users can copy a report's raw markdown source. Shows transient
'Copied' feedback and is only offered for text content (not media).
Includes unit tests for the copy flow and media exclusion.
Code mode runs on the user's own machine rather than a sandbox container, so
the two native prerequisites are now provisioned automatically instead of being
assumed pre-installed.

Tree-sitter: embed the grammar C sources (~1.4 MB gzip) in the binary and
compile libastonish-treesitter on first use, caching it under the user's config
dir. The resolver tries an explicit override, next-to-exe, CWD, the built
cache, and the container path before building. No compiler yields an actionable
error and a grep fallback. `make treesitter-embed` regenerates the tarball.

Ripgrep: new pkg/tools/ripgrep provisions rg — prefer a system rg, else download
the pinned official 14.1.1 release for the host OS/arch, verify its SHA256
against a checked-in manifest, and cache it under the config dir. grep_search
and find_files resolve rg through it; code mode kicks provisioning off in the
background at startup.

grep_search is now ripgrep-only: the pure-Go grep fallback (not gitignore-aware,
no type/multiline/context support) is removed, since rg is guaranteed available.

Also includes code-mode context-usage/resume work: estimated context fallback
when providers report no usage, context-on-resume seeding, and related TUI
plumbing.

Tests cover the tree-sitter build+cache and resolver, ripgrep download/verify/
extract with checksum-mismatch rejection and PATH preference, and the estimated
context transcript behavior. Docs updated (code-intelligence, terminal-app,
tools AGENTS).
- Esc now cancels the streaming turn (Claude Code / OpenCode style) via
  shared cancelInFlightTurn; idle Esc never quits (Ctrl+C still quits idle).
- Streaming footer hint shows 'esc cancel · ctrl+c cancel'.
- /help now documents esc (cancel turn), ctrl+d (quit when empty), the /?
  alias, and stays in sync with the command palette.
- Expand code-mode /provider catalog (SAP AI Core, LiteLLM) with a drift
  guard; add throttled mid-turn context estimate so the header advances
  during long tool loops.
- Add tests (cancel, help sync, provider catalog) and update terminal-app
  docs + pkg/tui/AGENTS.md.
- Add process_read/write/kill/list to mainThreadToolAllowlist so the
  top-level coding agent can drive interactive PTY commands
  (waiting_for_input) directly, matching chat mode.
- Disable the auto-pager in shell_command's PTY (PAGER/GIT_PAGER=cat,
  GIT_TERMINAL_PROMPT=0) so git/less-style CLIs don't hang; PTY
  interactivity is preserved.
- Make shell_command cancellable: waitForShellSession selects on
  ctx.Done() and kills the child on Esc/turn cancel instead of waiting
  out the timeout.
- Keep the TUI event loop responsive under bursts: per-item markdown
  render cache (keyed by width+content, cleared on resize) and bounded
  event coalescing that repaints once per batch.
- Tests + architecture/AGENTS docs updated.
…ter diff, and supporting fixes

Plan mode & planning methodology (Option B):
- Rewrite PlanModeSystemContext with an investigate -> design -> verify
  methodology and a completeness bar; keep the hard-gate rules intact.
- Add first-class per-phase Files ([]PlanFileChange) and Verify fields to
  announce_plan / PlanStepInfo / PlanState, rendered and round-tripped
  losslessly in PLAN.md (plan_document.go).
- Strengthen the delegation "Planning strategy" block (dependency tracing +
  final verification discipline); regenerate the system-prompt golden file
  and bump the prompt-size ceiling accordingly.
- Sync the TUI mirror (planModeSystemContext) and enrich guidanceCodeIntelligence.
- Tests: plan_document_test, plan_state_test, plan_persistence_test,
  plan_tool_test, plan_mode_gate_test, system_prompt_contracts_test.

TUI single-gutter file diff:
- Collapse the dual old/new gutter into one line-number column colored by
  change kind (neutral/red/green); fix prefix/gap/continuation width math.
- Update render tests and all "dual-gutter" doc comments to "single-gutter".

Supporting changes accumulated on this branch:
- Session PLAN.md persistence + compaction survival; sidecar cleanup on delete.
- MCP file-based config, provider settings/local providers, tools cache,
  and related API/launcher wiring + tests.
- Docs: plan-mode-enforcement-summary, terminal-app, smart-compaction, mcp,
  tools, TESTING, mcp-servers; add docs/analysis session review.

Verified: go build ./..., package tests, golangci-lint (0 issues), tsc --noEmit.
Add safe-by-default authorization to `astonish code` (unsandboxed, runs
on the host). Two BeforeToolCallback gates, active only in Normal mode
when EnforceAuthorization is set and bypassed by --auto-approve/--yolo:

- Tool-execution gate: agent.SafeTools is reused as the Normal-mode
  auto-allow baseline; any other tool (write_file, edit_file,
  shell_command, ...) prompts for authorization. Grants: Allow (once) /
  Always Allow (session) / Deny.
- Folder-access gate: tools may only touch the project working dir and
  its subtree; out-of-project paths prompt. Path args extracted
  generically (path/file_path/working_dir/dir/search_path/paths) plus
  heuristic operands from free-form command args via new pkg/pathscope
  (single source of truth for extraction + containment). Grants: Allow
  (once) / Always Allow (session) / Deny.

TUI overlay: cursor-navigable Allow / Always Allow / Deny list (up/down
or j/k, Enter submits the highlighted option, default cursor on the safe
first option; 1/2/3 and y/esc remain accelerators).

Fix: one-shot folder "Allow" grants are now consumed at execution time
via ConsumePathGrants, so "Allow" no longer silently behaves like
"Always Allow" on later accesses to the same out-of-project path.

Enforcement + grant bookkeeping live in pkg/agent/tool_authorization.go;
the launcher threads WorkingDir + EnforceAuthorization and resets
iteration grants per real user turn (IsApprovalResponse flag).

Updates system prompt, terminal-app.md, plan-mode-enforcement-summary.md,
README, and the pkg AGENTS.md notes. Regression tests added.
The folder-access gate mis-flagged commands whose QUOTED literal data
merely contained path characters. splitCommand word-split on whitespace
and shell operators even inside quotes, so a commit message like
`git commit -m "fixes A / B"` was shredded and the bare "/" lifted out
as a spurious out-of-project path operand — producing a false
"wants to access files outside the project directory" prompt (and, via
SetScopeRoot, a false hard reject for non-interactive callers).

Fix: make splitCommand quote-aware. Single/double-quoted spans are one
atomic token and quote marks are consumed; adjacent quoted/unquoted runs
join like a real shell word. A quoted literal (message/prose/pattern) is
now flagged only when its content is itself path-shaped:
  - `git commit -m "fixes A / B"`  -> no path extracted (no prompt)
  - `cat "/etc/passwd"`            -> still flagged (/etc/passwd)

Also: skip the key=value env-assignment split for tokens containing
whitespace (quoted literals may legitimately contain '='), and remove
the now-redundant trimQuotes helper (quotes consumed by the tokenizer).

Because pathscope is the single source of truth, this corrects both the
interactive agent folder-access gate (pkg/agent) and the non-interactive
shell guard (pkg/tools SetScopeRoot) at once. Security is unchanged for
unquoted path shapes; the shell-in-shell (`sh -c "..."`) limitation is
pre-existing and documented.

Adds 9 regression cases to pathscope_test.go and updates terminal-app.md
plus the pkg/agent and pkg/tools AGENTS.md notes.
…ools

Add a full TUI overlay for configuring web search providers (Tavily,
Firecrawl, Brave Search, Perplexity) directly from astonish code mode.

Key changes:
- Fix loadMCPConfig to accept *config.AppConfig so non-platform mode
  properly passes config through to mergeStandardServersWithConfig,
  enabling configured web search MCP servers to be loaded.
- Add WebSearchAdminBackend interface to backend/backend.go with methods
  for listing providers, get/set current provider, clear, and list
  Perplexity models.
- Implement WebSearchAdminBackend on localAgentBackend in tui_code.go
  handling config.yaml persistence and MCP server management.
- Create web_search_picker.go with full TUI overlay (provider list,
  API key input, Perplexity model selection, clear option).
- Wire /websearch slash command into app.go (keyboard dispatch, message
  handlers, overlay rendering, help text).
- Fix MCP tool schema discovery to check ParametersJsonSchema first
  (used by ADK MCP tools) before falling back to Parameters field,
  fixing tools being cached without schemas.
- Add unit tests for backend implementation and picker component.

Users can now configure web search tools from within the TUI and they
become available to the agent after starting a /new session.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant